From 7f57367aadb0fb7d8445830272745edbf854a71b Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 29 Sep 2023 23:28:12 -0500
Subject: [PATCH 01/20] Initial work on database rewrite

---
 Cargo.toml                    |   3 +-
 src/music_storage/music_db.rs | 332 +++++++++-------------------------
 src/music_storage/playlist.rs |  19 +-
 3 files changed, 91 insertions(+), 263 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 3ee99fa..a96c12a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,6 @@ categories = ["multimedia::audio"]
 [dependencies]
 file-format = { version = "0.17.3", features = ["reader", "serde"] }
 lofty = "0.14.0"
-rusqlite = { version = "0.29.0", features = ["bundled"] }
 serde = { version = "1.0.164", features = ["derive"] }
 time = "0.3.22"
 toml = "0.7.5"
@@ -32,3 +31,5 @@ futures = "0.3.28"
 rubato = "0.12.0"
 arrayvec = "0.7.4"
 discord-presence = "0.5.18"
+chrono = { version = "0.4.31", features = ["serde"] }
+bincode = "1.3.3"
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 093d7c5..77aaf0b 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,38 +1,72 @@
 use file_format::{FileFormat, Kind};
-use serde::Deserialize;
-use lofty::{Accessor, AudioFile, Probe, TaggedFileExt, ItemKey, ItemValue, TagType};
-use rusqlite::{params, Connection};
-use std::fs;
-use std::path::{Path, PathBuf};
+use lofty::{AudioFile, Probe, TaggedFileExt, ItemKey, ItemValue, TagType};
+use std::{error::Error, io::BufReader};
+
 use std::time::Duration;
-use time::Date;
+use chrono::{DateTime, Utc, serde::ts_seconds_option};
 use walkdir::WalkDir;
 
+use std::io::BufWriter;
+use std::fs;
+use serde::{Deserialize, Serialize};
+use std::path::{Path, PathBuf};
+use bincode::{serialize_into, deserialize_from};
+
 use crate::music_controller::config::Config;
 
-#[derive(Debug, Clone)]
-pub struct Song {
-    pub path: URI,
-    pub title:  Option<String>,
-    pub album:  Option<String>,
-    pub tracknum: Option<usize>,
-    pub artist: Option<String>,
-    pub date:   Option<Date>,
-    pub genre:  Option<String>,
-    pub plays:  Option<usize>,
-    pub favorited: Option<bool>,
-    pub format: Option<FileFormat>, // TODO: Make this a proper FileFormat eventually
-    pub duration: Option<Duration>,
-    pub custom_tags: Option<Vec<Tag>>,
+pub struct AlbumArt {
+    pub path: Option<URI>;
 }
 
-#[derive(Clone, Debug)]
+/// Stores information about a single song
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct Song {
+    pub path: URI,
+    pub plays: i32,
+    pub skips: i32,
+    pub favorited: bool,
+    pub rating: u8,
+    pub format: Option<FileFormat>,
+    pub duration: Duration,
+    pub play_time: Duration,
+    #[serde(with = "ts_seconds_option")]
+    pub last_played: Option<DateTime<Utc>>,
+    #[serde(with = "ts_seconds_option")]
+    pub date_added: Option<DateTime<Utc>>,
+    pub tags: Vec<(String, String)>,
+}
+
+impl Song {
+    pub fn get_tag(&self, target_key: String) -> Option<String> {
+        for tag in self.tags {
+            if tag.0 == target_key {
+                return Some(tag.1)
+            }
+        }
+        None
+    }
+
+    pub fn get_tags(&self, target_keys: Vec<String>) -> Vec<Option<String>> {
+        let mut results = Vec::new();
+        for tag in self.tags {
+            for key in target_keys {
+                if tag.0 == key {
+                    results.push(Some(tag.1))
+                }
+            }
+            results.push(None);
+        }
+        results
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
 pub enum URI{
     Local(String),
     Remote(Service, String),
 }
 
-#[derive(Clone, Copy, Debug)]
+#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
 pub enum Service {
     InternetRadio,
     Spotify,
@@ -45,74 +79,40 @@ pub struct Playlist {
     cover_art: Box<Path>,
 }
 
-pub fn create_db() -> Result<(), rusqlite::Error> {
-    let path = "./music_database.db3";
-    let db_connection = Connection::open(path)?;
+/// Initialize the database
+///
+/// If the database file already exists, return the database, otherwise create it first
+/// This needs to be run before anything else to retrieve the library vec
+pub fn init_db(config: &Config) -> Result<Vec<Song>, Box<dyn Error>> {
+    let mut library: Vec<Song> = Vec::new();
 
-    db_connection.pragma_update(None, "synchronous", "0")?;
-    db_connection.pragma_update(None, "journal_mode", "WAL")?;
+    match config.db_path.try_exists() {
+        Ok(_) => {
+            // The database exists, so get it from the file
+            let database = fs::File::open(config.db_path.into_boxed_path())?;
+            let reader = BufReader::new(database);
+            library = deserialize_from(reader)?;
+        },
+        Err(_) => {
+            // Create the database if it does not exist
+            let mut writer = BufWriter::new(
+                fs::File::create(config.db_path.into_boxed_path())?
+            );
+            serialize_into(&mut writer, &library)?;
+        }
+    };
 
-    // Create the important tables
-    db_connection.execute(
-        "CREATE TABLE music_collection (
-            song_path TEXT PRIMARY KEY,
-            title   TEXT,
-            album   TEXT,
-            tracknum INTEGER,
-            artist  TEXT,
-            date    INTEGER,
-            genre   TEXT,
-            plays   INTEGER,
-            favorited BLOB,
-            format  TEXT,
-            duration INTEGER
-        )",
-        (), // empty list of parameters.
-    )?;
-
-    db_connection.execute(
-        "CREATE TABLE playlists (
-            playlist_name TEXT NOT NULL,
-            song_path   TEXT NOT NULL,
-            FOREIGN KEY(song_path) REFERENCES music_collection(song_path)
-        )",
-        (), // empty list of parameters.
-    )?;
-
-    db_connection.execute(
-        "CREATE TABLE custom_tags (
-            song_path TEXT NOT NULL,
-            tag TEXT NOT NULL,
-            tag_value TEXT,
-            FOREIGN KEY(song_path) REFERENCES music_collection(song_path)
-        )",
-        (), // empty list of parameters.
-    )?;
-
-    Ok(())
+    Ok(library)
 }
 
-fn path_in_db(query_path: &Path, connection: &Connection) -> bool {
-    let query_string = format!("SELECT EXISTS(SELECT 1 FROM music_collection WHERE song_path='{}')", query_path.to_string_lossy());
-
-    let mut query_statement = connection.prepare(&query_string).unwrap();
-    let mut rows = query_statement.query([]).unwrap();
-
-    match rows.next().unwrap() {
-        Some(value) => value.get::<usize, bool>(0).unwrap(),
-        None => false
-    }
+fn path_in_db(query_path: &Path, library: &Vec<Song>) -> bool {
+    unimplemented!()
 }
 
-
 pub fn find_all_music(
     config: &Config,
     target_path: &str,
 ) -> Result<(), Box<dyn std::error::Error>> {
-    let db_connection = Connection::open(&*config.db_path)?;
-
-    db_connection.pragma_update(None, "synchronous", "0")?;
-    db_connection.pragma_update(None, "journal_mode", "WAL")?;
 
     let mut current_dir = PathBuf::new();
     for entry in WalkDir::new(target_path).follow_links(true).into_iter().filter_map(|e| e.ok()) {
@@ -134,25 +134,16 @@ pub fn find_all_music(
         // If it's a normal file, add it to the database
         // if it's a cuesheet, do a bunch of fancy stuff
         if format.kind() == Kind::Audio {
-            add_file_to_db(target_file.path(), &db_connection)
+            add_file_to_db(target_file.path())
         } else if extension.to_ascii_lowercase() == "cue" {
             // TODO: implement cuesheet support
         }
     }
 
-    // create the indexes after all the data is inserted
-    db_connection.execute(
-        "CREATE INDEX path_index ON music_collection (song_path)", ()
-    )?;
-
-    db_connection.execute(
-        "CREATE INDEX custom_tags_index ON custom_tags (song_path)", ()
-    )?;
-
     Ok(())
 }
 
-pub fn add_file_to_db(target_file: &Path, connection: &Connection) {
+pub fn add_file_to_db(target_file: &Path) {
     // TODO: Fix error handling here
     let tagged_file = match lofty::read_from_path(target_file) {
         Ok(tagged_file) => tagged_file,
@@ -179,7 +170,7 @@ pub fn add_file_to_db(target_file: &Path, connection: &Connection) {
 
     let mut custom_insert = String::new();
     let mut loops = 0;
-    for item in tag.items() {
+    for (loops, item) in tag.items().enumerate() {
         let mut custom_key = String::new();
         match item.key() {
             ItemKey::TrackArtist |
@@ -203,10 +194,6 @@ pub fn add_file_to_db(target_file: &Path, connection: &Connection) {
         if loops > 0 {
             custom_insert.push_str(", ");
         }
-
-        custom_insert.push_str(&format!(" (?1, '{}', '{}')", custom_key.replace("\'", "''"), custom_value.replace("\'", "''")));
-
-        loops += 1;
     }
     
     // Get the format as a string
@@ -222,69 +209,10 @@ pub fn add_file_to_db(target_file: &Path, connection: &Connection) {
     // TODO: Fix error handling
     let binding = fs::canonicalize(target_file).unwrap();
     let abs_path = binding.to_str().unwrap();
-
-    // Add all the info into the music_collection table
-    connection.execute(
-        "INSERT INTO music_collection (
-            song_path,
-            title,
-            album,
-            tracknum,
-            artist,
-            date,
-            genre,
-            plays,
-            favorited,
-            format,
-            duration
-        ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
-        params![abs_path, tag.title(), tag.album(), tag.track(), tag.artist(), tag.year(), tag.genre(), 0, false, short_format, duration],
-    ).unwrap();
-
-    //TODO: Fix this, it's horrible
-    if custom_insert != "" {
-        connection.execute(
-            &format!("INSERT INTO custom_tags ('song_path', 'tag', 'tag_value') VALUES {}", &custom_insert),
-            params![
-                abs_path,
-            ]
-        ).unwrap();
-    }
 }
 
-#[derive(Debug, Deserialize, Clone)]
-pub enum Tag {
-    SongPath,
-    Title,
-    Album,
-    TrackNum,
-    Artist,
-    Date,
-    Genre,
-    Plays,
-    Favorited,
-    Format,
-    Duration,
-    Custom{tag: String, tag_value: String},
-}
+pub fn add_song_to_db(new_song: Song) {
 
-impl Tag {
-    fn as_str(&self) -> &str {
-        match self {
-            Tag::SongPath => "song_path",
-            Tag::Title  => "title",
-            Tag::Album  => "album",
-            Tag::TrackNum => "tracknum",
-            Tag::Artist => "artist",
-            Tag::Date   => "date",
-            Tag::Genre  => "genre",
-            Tag::Plays  => "plays",
-            Tag::Favorited => "favorited",
-            Tag::Format => "format",
-            Tag::Duration => "duration",
-            Tag::Custom{tag, ..} => tag,
-        }
-    }
 }
 
 #[derive(Debug)]
@@ -305,93 +233,9 @@ impl MusicObject {
 
 /// Query the database, returning a list of items
 pub fn query (
-    config: &Config,
-    text_input: &String,
-    queried_tags: &Vec<&Tag>,
-    order_by_tags: &Vec<&Tag>,
+    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>> {
-    let db_connection = Connection::open(&*config.db_path).unwrap();
-
-    // Set up some database settings
-    db_connection.pragma_update(None, "synchronous", "0").unwrap();
-    db_connection.pragma_update(None, "journal_mode", "WAL").unwrap();
-
-    // Build the "WHERE" part of the SQLite query
-    let mut where_string = String::new();
-    let mut loops = 0;
-    for tag in queried_tags {
-        if loops > 0 {
-            where_string.push_str("OR ");
-        }
-
-        match tag {
-            Tag::Custom{tag, ..} => where_string.push_str(&format!("custom_tags.tag = '{tag}' AND custom_tags.tag_value LIKE '{text_input}' ")),
-            Tag::SongPath => where_string.push_str(&format!("music_collection.{} LIKE '{text_input}' ", tag.as_str())),
-            _ => where_string.push_str(&format!("{} LIKE '{text_input}' ", tag.as_str()))
-        }
-
-        loops += 1;
-    }
-
-    // Build the "ORDER BY" part of the SQLite query
-    let mut order_by_string = String::new();
-    let mut loops = 0;
-    for tag in order_by_tags {
-        match tag {
-            Tag::Custom{..} => continue,
-            _ => ()
-        }
-
-        if loops > 0 {
-            order_by_string.push_str(", ");
-        }
-
-        order_by_string.push_str(tag.as_str());
-
-        loops += 1;
-    }
-
-    // Build the final query string
-    let query_string = format!("
-        SELECT music_collection.*, JSON_GROUP_ARRAY(JSON_OBJECT('Custom',JSON_OBJECT('tag', custom_tags.tag, 'tag_value', custom_tags.tag_value))) AS custom_tags
-        FROM music_collection
-        LEFT JOIN custom_tags ON music_collection.song_path = custom_tags.song_path
-        WHERE {where_string}
-        GROUP BY music_collection.song_path
-        ORDER BY {order_by_string}
-    ");
-    
-    let mut query_statement = db_connection.prepare(&query_string).unwrap();
-    let mut rows = query_statement.query([]).unwrap();
-
-    let mut final_result:Vec<MusicObject> = vec![];
-
-    while let Some(row) = rows.next().unwrap() {
-        let custom_tags: Vec<Tag> = match row.get::<usize, String>(11) {
-            Ok(result) => serde_json::from_str(&result).unwrap_or(vec![]),
-            Err(_) => vec![]
-        };
-
-        let file_format: FileFormat = FileFormat::from(row.get::<usize, String>(9).unwrap().as_bytes());
-
-        let new_song = Song {
-            // TODO: Implement proper errors here
-            path:   URI::Local(String::from("URI")),
-            title:  row.get::<usize, String>(1).ok(),
-            album:  row.get::<usize, String>(2).ok(),
-            tracknum: row.get::<usize, usize>(3).ok(),
-            artist: row.get::<usize, String>(4).ok(),
-            date:   Date::from_calendar_date(row.get::<usize, i32>(5).unwrap_or(0), time::Month::January, 1).ok(), // TODO: Fix this to get the actual date
-            genre:  row.get::<usize, String>(6).ok(),
-            plays:  row.get::<usize, usize>(7).ok(),
-            favorited: row.get::<usize, bool>(8).ok(),
-            format: Some(file_format),
-            duration: Some(Duration::from_secs(row.get::<usize, u64>(10).unwrap_or(0))),
-            custom_tags: Some(custom_tags),
-        };
-
-        final_result.push(MusicObject::Song(new_song));
-    };
-
-    Some(final_result)
+    unimplemented!()
 }
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index f6fc9b7..e61b405 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,27 +1,10 @@
 use std::path::Path;
 use crate::music_controller::config::Config;
-use rusqlite::{params, Connection};
 
 pub fn playlist_add(
     config: &Config,
     playlist_name: &str,
     song_paths: &Vec<&Path>
 ) {
-    let db_connection = Connection::open(&*config.db_path).unwrap();
-
-    for song_path in song_paths {
-        db_connection.execute(
-            "INSERT INTO playlists (
-                playlist_name,
-                song_path
-            ) VALUES (
-                ?1,
-                ?2
-            )",
-            params![
-                playlist_name,
-                song_path.to_str().unwrap()
-            ],
-        ).unwrap();
-    }
+    unimplemented!()
 }

From db53bed1116358e9963983b219e0cd0f7f0f1385 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 30 Sep 2023 23:31:42 -0500
Subject: [PATCH 02/20] Database works

---
 src/music_controller/config.rs           |   2 +-
 src/music_controller/music_controller.rs |   2 -
 src/music_player/music_player.rs         |   2 +-
 src/music_storage/music_db.rs            | 421 ++++++++++++++---------
 src/music_tracker/music_tracker.rs       |  14 +-
 5 files changed, 260 insertions(+), 181 deletions(-)

diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index 034f12d..f6909f5 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -16,7 +16,7 @@ pub struct Config {
 
 impl Default for Config {
     fn default() -> Self {
-        let path = PathBuf::from("./music_database.db3");
+        let path = PathBuf::from("./music_database");
 
         return Config {
             db_path: Box::new(path),
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index da4f676..545d5aa 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -1,8 +1,6 @@
 use std::path::PathBuf;
 use std::sync::{RwLock, Arc};
 
-use rusqlite::Result;
-
 use crate::music_controller::config::Config;
 use crate::music_player::music_player::{MusicPlayer, PlayerStatus, DecoderMessage, DSPMessage};
 use crate::music_storage::music_db::Song;
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
index 5ab0ad9..478fca1 100644
--- a/src/music_player/music_player.rs
+++ b/src/music_player/music_player.rs
@@ -232,7 +232,7 @@ impl MusicPlayer {
                     // Handles message received from MusicPlayer struct
                     match message {
                         Some(DecoderMessage::OpenSong(song)) => {
-                            let song_uri = song.path.clone();
+                            let song_uri = song.location.clone();
                             match SongHandler::new(&song_uri) {
                                 Ok(new_handler) => {
                                     song_handler = Some(new_handler);
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 77aaf0b..cb429c6 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,31 +1,33 @@
 use file_format::{FileFormat, Kind};
-use lofty::{AudioFile, Probe, TaggedFileExt, ItemKey, ItemValue, TagType};
+use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
 use std::{error::Error, io::BufReader};
 
+use chrono::{serde::ts_seconds_option, DateTime, Utc};
 use std::time::Duration;
-use chrono::{DateTime, Utc, serde::ts_seconds_option};
 use walkdir::WalkDir;
 
-use std::io::BufWriter;
-use std::fs;
+use bincode::{deserialize_from, serialize_into};
 use serde::{Deserialize, Serialize};
+use std::fs;
+use std::io::BufWriter;
 use std::path::{Path, PathBuf};
-use bincode::{serialize_into, deserialize_from};
 
 use crate::music_controller::config::Config;
 
+#[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct AlbumArt {
-    pub path: Option<URI>;
+    pub index: u16,
+    pub path: Option<URI>,
 }
 
 /// Stores information about a single song
 #[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct Song {
-    pub path: URI,
+    pub location: URI,
     pub plays: i32,
     pub skips: i32,
     pub favorited: bool,
-    pub rating: u8,
+    pub rating: Option<u8>,
     pub format: Option<FileFormat>,
     pub duration: Duration,
     pub play_time: Duration,
@@ -33,25 +35,26 @@ pub struct Song {
     pub last_played: Option<DateTime<Utc>>,
     #[serde(with = "ts_seconds_option")]
     pub date_added: Option<DateTime<Utc>>,
+    pub album_art: Vec<AlbumArt>,
     pub tags: Vec<(String, String)>,
 }
 
 impl Song {
-    pub fn get_tag(&self, target_key: String) -> Option<String> {
-        for tag in self.tags {
-            if tag.0 == target_key {
-                return Some(tag.1)
-            }
+    pub fn get_tag(&self, target_key: &str) -> Option<&String> {
+        let index = self.tags.iter().position(|r| r.0 == target_key);
+
+        match index {
+            Some(i) => return Some(&self.tags[i].1),
+            None => None,
         }
-        None
     }
 
-    pub fn get_tags(&self, target_keys: Vec<String>) -> Vec<Option<String>> {
+    pub fn get_tags(&self, target_keys: &Vec<String>) -> Vec<Option<String>> {
         let mut results = Vec::new();
-        for tag in self.tags {
+        for tag in &self.tags {
             for key in target_keys {
-                if tag.0 == key {
-                    results.push(Some(tag.1))
+                if &tag.0 == key {
+                    results.push(Some(tag.1.to_owned()))
                 }
             }
             results.push(None);
@@ -60,13 +63,14 @@ impl Song {
     }
 }
 
-#[derive(Clone, Debug, Serialize, Deserialize)]
-pub enum URI{
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
+pub enum URI {
     Local(String),
+    //Cue(String, Duration), TODO: Make cue stuff work
     Remote(Service, String),
 }
 
-#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub enum Service {
     InternetRadio,
     Spotify,
@@ -79,142 +83,6 @@ pub struct Playlist {
     cover_art: Box<Path>,
 }
 
-/// Initialize the database
-///
-/// If the database file already exists, return the database, otherwise create it first
-/// This needs to be run before anything else to retrieve the library vec
-pub fn init_db(config: &Config) -> Result<Vec<Song>, Box<dyn Error>> {
-    let mut library: Vec<Song> = Vec::new();
-
-    match config.db_path.try_exists() {
-        Ok(_) => {
-            // The database exists, so get it from the file
-            let database = fs::File::open(config.db_path.into_boxed_path())?;
-            let reader = BufReader::new(database);
-            library = deserialize_from(reader)?;
-        },
-        Err(_) => {
-            // Create the database if it does not exist
-            let mut writer = BufWriter::new(
-                fs::File::create(config.db_path.into_boxed_path())?
-            );
-            serialize_into(&mut writer, &library)?;
-        }
-    };
-
-    Ok(library)
-}
-
-fn path_in_db(query_path: &Path, library: &Vec<Song>) -> bool {
-    unimplemented!()
-}
-
-pub fn find_all_music(
-    config: &Config,
-    target_path: &str,
-) -> Result<(), Box<dyn std::error::Error>> {
-
-    let mut current_dir = PathBuf::new();
-    for entry in WalkDir::new(target_path).follow_links(true).into_iter().filter_map(|e| e.ok()) {
-        let target_file = entry;
-        let is_file = fs::metadata(target_file.path())?.is_file();
-
-        // 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();
-            continue;
-        }
-
-        let format = FileFormat::from_file(target_file.path())?;
-        let extension = target_file
-            .path()
-            .extension()
-            .expect("Could not find file extension");
-
-        // If it's a normal file, add it to the database
-        // if it's a cuesheet, do a bunch of fancy stuff
-        if format.kind() == Kind::Audio {
-            add_file_to_db(target_file.path())
-        } else if extension.to_ascii_lowercase() == "cue" {
-            // TODO: implement cuesheet support
-        }
-    }
-
-    Ok(())
-}
-
-pub fn add_file_to_db(target_file: &Path) {
-    // TODO: Fix error handling here
-    let tagged_file = match lofty::read_from_path(target_file) {
-        Ok(tagged_file) => tagged_file,
-
-        Err(_) => match Probe::open(target_file)
-            .expect("ERROR: Bad path provided!")
-            .read() {
-                Ok(tagged_file) => tagged_file,
-
-                Err(_) => return
-            }
-    };
-
-    // Ensure the tags exist, if not, insert blank data
-    let blank_tag = &lofty::Tag::new(TagType::Id3v2);
-    let tag = match tagged_file.primary_tag() {
-        Some(primary_tag) => primary_tag,
-
-        None => match tagged_file.first_tag() {
-            Some(first_tag) => first_tag,
-            None => blank_tag
-        },
-    };
-
-    let mut custom_insert = String::new();
-    let mut loops = 0;
-    for (loops, item) in tag.items().enumerate() {
-        let mut custom_key = String::new();
-        match item.key() {
-            ItemKey::TrackArtist |
-            ItemKey::TrackTitle  |
-            ItemKey::AlbumTitle  |
-            ItemKey::Genre       |
-            ItemKey::TrackNumber |
-            ItemKey::Year        |
-            ItemKey::RecordingDate  => continue,
-            ItemKey::Unknown(unknown) => custom_key.push_str(&unknown),
-            custom => custom_key.push_str(&format!("{:?}", custom))
-            // TODO: This is kind of cursed, maybe fix?
-        };
-
-        let custom_value = match item.value() {
-            ItemValue::Text(value) => value,
-            ItemValue::Locator(value) => value,
-            ItemValue::Binary(_) => ""
-        };
-
-        if loops > 0 {
-            custom_insert.push_str(", ");
-        }
-    }
-    
-    // Get the format as a string
-    let short_format: Option<String> = match FileFormat::from_file(target_file) {
-        Ok(fmt) => Some(fmt.to_string()),
-        Err(_) => None
-    };
-
-    println!("{}", short_format.as_ref().unwrap());
-    
-    let duration = tagged_file.properties().duration().as_secs().to_string();
-    
-    // TODO: Fix error handling
-    let binding = fs::canonicalize(target_file).unwrap();
-    let abs_path = binding.to_str().unwrap();
-}
-
-pub fn add_song_to_db(new_song: Song) {
-
-}
-
 #[derive(Debug)]
 pub enum MusicObject {
     Song(Song),
@@ -222,20 +90,231 @@ pub enum MusicObject {
     Playlist(Playlist),
 }
 
-impl MusicObject {
-    pub fn as_song(&self) -> Option<&Song> {
-        match self {
-            MusicObject::Song(data) => Some(data),
-            _ => None
-        }
-    }
+#[derive(Debug)]
+pub struct MusicLibrary {
+    pub library: Vec<Song>,
 }
 
-/// Query the database, returning a list of items
-pub fn query (
-    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!()
+impl MusicLibrary {
+    /// Initialize the database
+    ///
+    /// If the database file already exists, return the Library, 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>> {
+        let mut library: Vec<Song> = Vec::new();
+        let mut backup_path = config.db_path.clone();
+        backup_path.set_extension("bkp");
+
+        match 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 reader = BufReader::new(database);
+                library = deserialize_from(reader)?;
+            }
+            Ok(false) => {
+                // 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 reader = BufReader::new(database);
+                    library = deserialize_from(reader)?;
+                } else {
+                    let mut writer = BufWriter::new(fs::File::create(config.db_path.to_path_buf())?);
+                    serialize_into(&mut writer, &library)?;
+                }
+            },
+            Err(error) => return Err(error.into())
+        };
+
+        Ok(Self { library })
+    }
+
+    pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
+        match config.db_path.try_exists() {
+            Ok(true) => {
+                // The database exists, rename it to `.bkp` and
+                // write the new database
+                let mut backup_name = config.db_path.clone();
+                backup_name.set_extension("bkp");
+                fs::rename(config.db_path.as_path(), backup_name.as_path())?;
+
+                // TODO: Make this save properly like in config.rs
+
+                let mut writer = BufWriter::new(fs::File::create(config.db_path.to_path_buf())?);
+                serialize_into(&mut writer, &self.library)?;
+            }
+            Ok(false) => {
+                // Create the database if it does not exist
+                let mut writer = BufWriter::new(fs::File::create(config.db_path.to_path_buf())?);
+                serialize_into(&mut writer, &self.library)?;
+            },
+            Err(error) => return Err(error.into())
+        }
+
+        Ok(())
+    }
+
+    fn find_by_uri(&self, path: &URI) -> Option<Song> {
+        for track in &self.library {
+            if path == &track.location {
+                return Some(track.clone());
+            }
+        }
+        None
+    }
+
+    pub fn find_all_music(&mut self, target_path: &str) -> Result<(), Box<dyn std::error::Error>> {
+        let mut current_dir = PathBuf::new();
+        for entry in WalkDir::new(target_path)
+            .follow_links(true)
+            .into_iter()
+            .filter_map(|e| e.ok())
+        {
+            let target_file = entry;
+            let is_file = fs::metadata(target_file.path())?.is_file();
+
+            // 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();
+                continue;
+            }
+
+            let format = FileFormat::from_file(target_file.path())?;
+            let extension = target_file
+                .path()
+                .extension()
+                .expect("Could not find file extension");
+
+            // If it's a normal file, add it to the database
+            // if it's a cuesheet, do a bunch of fancy stuff
+            if format.kind() == Kind::Audio {
+                match self.add_file_to_db(target_file.path()) {
+                    Ok(_) => (),
+                    Err(_error) => () //println!("{}, {:?}: {}", format, target_file.file_name(), error)
+                };
+            } else if extension.to_ascii_lowercase() == "cue" {
+                // TODO: implement cuesheet support
+            }
+        }
+
+        Ok(())
+    }
+
+    pub fn add_file_to_db(&mut self, target_file: &Path) -> Result<(), Box<dyn std::error::Error>> {
+        // TODO: Fix error handling here
+        let tagged_file = match lofty::read_from_path(target_file) {
+            Ok(tagged_file) => tagged_file,
+
+            Err(_) => match Probe::open(target_file)?.read() {
+                Ok(tagged_file) => tagged_file,
+
+                Err(error) => return Err(error.into()),
+            },
+        };
+
+        // Ensure the tags exist, if not, insert blank data
+        let blank_tag = &lofty::Tag::new(TagType::Id3v2);
+        let tag = match tagged_file.primary_tag() {
+            Some(primary_tag) => primary_tag,
+
+            None => match tagged_file.first_tag() {
+                Some(first_tag) => first_tag,
+                None => blank_tag,
+            },
+        };
+
+        let mut tags: Vec<(String, 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 value = match item.value() {
+                ItemValue::Text(value) => String::from(value),
+                ItemValue::Locator(value) => String::from(value),
+                ItemValue::Binary(_) => String::from(""),
+            };
+
+            tags.push((key, value))
+        }
+
+        // Get all the album artwork information
+        let mut album_art: Vec<AlbumArt> = Vec::new();
+        for (i, _art) in tag.pictures().iter().enumerate() {
+            let new_art = AlbumArt {
+                index: i as u16,
+                path: None,
+            };
+
+            album_art.push(new_art)
+        }
+
+        // Get the format as a string
+        let format: Option<FileFormat> = match FileFormat::from_file(target_file) {
+            Ok(fmt) => Some(fmt),
+            Err(_) => None,
+        };
+
+        let duration = tagged_file.properties().duration();
+
+        // TODO: Fix error handling
+        let binding = fs::canonicalize(target_file).unwrap();
+        let abs_path = binding.to_str().unwrap();
+
+        let new_song = Song {
+            location: URI::Local(abs_path.to_string()),
+            plays: 0,
+            skips: 0,
+            favorited: false,
+            rating: None,
+            format,
+            duration,
+            play_time: Duration::from_secs(0),
+            last_played: None,
+            date_added: Some(chrono::offset::Utc::now()),
+            tags,
+            album_art,
+        };
+
+        match self.add_song_to_db(new_song) {
+            Ok(_) => (),
+            Err(error) => ()
+        };
+
+        Ok(())
+    }
+
+    pub fn add_song_to_db(&mut self, new_song: Song) -> Result<(), Box<dyn std::error::Error>> {
+        match self.find_by_uri(&new_song.location) {
+            Some(_) => return Err(format!("URI already in database: {:?}", new_song.location).into()),
+            None => ()
+        }
+
+        self.library.push(new_song);
+
+        Ok(())
+    }
+
+    pub fn update_song_tags(&mut self, new_tags: Song) -> Result<(), Box<dyn std::error::Error>> {
+        match self.find_by_uri(&new_tags.location) {
+            Some(_) => (),
+            None => return Err(format!("URI not in database!").into())
+        }
+
+        todo!()
+    }
+
+    /// Query the database, returning a list of items
+    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!()
+    }
 }
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 213a5f1..5c306cc 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -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.artist, song.title) {
+        let (artist, track) = match (song.get_tag("Artist"), song.get_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.artist, song.title) {
+        let (artist, track) = match (song.get_tag("Artist"), song.get_tag("Title")) {
             (Some(artist), Some(track)) => (artist, track),
             _ => return Err(TrackerError::InvalidSong)
         };
@@ -259,11 +259,13 @@ 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.title {
+        let song_name = if let Some(song_name) = song.get_tag("Title") {
             song_name
         } else {
-            String::from("Unknown")
+            &unknown
         };
         
         let _client_thread = self.client.start();
@@ -316,7 +318,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.artist, song.title) {
+        let (artist, track) = match (song.get_tag("Artist"), song.get_tag("Title")) {
             (Some(artist), Some(track)) => (artist, track),
             _ => return Err(TrackerError::InvalidSong)
         };
@@ -342,7 +344,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.artist, song.title) {
+        let (artist, track) = match (song.get_tag("Artist"), song.get_tag("Title")) {
             (Some(artist), Some(track)) => (artist, track),
             _ => return Err(TrackerError::InvalidSong)
         };

From d2d92149f8544cfae45eb9aa1392ea52fbd5f50f Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 2 Oct 2023 01:26:36 -0500
Subject: [PATCH 03/20] preliminary query support, still some bugs to iron out

---
 Cargo.toml                         |   2 +
 src/music_storage/music_db.rs      | 119 +++++++++++++++++++++++++----
 src/music_tracker/music_tracker.rs |  13 ++--
 3 files changed, 114 insertions(+), 20 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index a96c12a..0f98190 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index cb429c6..0292912 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -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,
         };
@@ -311,10 +349,63 @@ impl MusicLibrary {
     /// Query the database, returning a list of items
     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!()
+        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();
+
+        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
+        }
     }
 }
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 5c306cc..8526e89 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -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)
         };

From bde2d194dc898dda6a886d04469660433d513555 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 2 Oct 2023 20:27:59 -0500
Subject: [PATCH 04/20] Finished main query function, added `MusicLibrary` to
 `MusicController`

---
 Cargo.toml                               |  2 +-
 src/music_controller/music_controller.rs | 26 ++++++++-
 src/music_storage/music_db.rs            | 73 +++++++++++++++++-------
 src/music_tracker/music_tracker.rs       | 10 +++-
 4 files changed, 85 insertions(+), 26 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 0f98190..0885526 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 545d5aa..ed3656f 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -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)
+    }
 }
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 0292912..01c12ff 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -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
         }
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 8526e89..12e57e8 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -268,6 +268,13 @@ impl MusicTracker for DiscordRPC {
         } else {
             &unknown
         };
+
+        // Sets album
+        let album = if let Some(album) = song.get_tag(&Tag::Album) {
+            album
+        } else {
+            &unknown
+        };
         
         let _client_thread = self.client.start();
         
@@ -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))
         });

From 140bae703ad96ba2a6ec1c030522622d2d8d4db3 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 3 Oct 2023 20:00:13 -0500
Subject: [PATCH 05/20] Can query by URI now

---
 src/music_controller/music_controller.rs |  3 +-
 src/music_storage/music_db.rs            | 73 ++++++++++++++++--------
 2 files changed, 51 insertions(+), 25 deletions(-)

diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index ed3656f..7ae6e69 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -79,8 +79,9 @@ impl MusicController {
         &self,
         query_string: &String,
         target_tags: Vec<Tag>,
+        search_location: bool,
         sort_by: Vec<Tag>
     ) -> Option<Vec<&Song>> {
-        self.library.query(query_string, &target_tags, &sort_by)
+        self.library.query(query_string, &target_tags, search_location, &sort_by)
     }
 }
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 01c12ff..7810ab4 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,5 +1,6 @@
 use file_format::{FileFormat, Kind};
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
+use std::any::Any;
 use std::{error::Error, io::BufReader};
 
 use chrono::{serde::ts_seconds_option, DateTime, Utc};
@@ -15,7 +16,6 @@ use unidecode::unidecode;
 
 // Fun parallel stuff
 use std::sync::{Arc, Mutex, RwLock};
-use rayon::iter;
 use rayon::prelude::*;
 
 use crate::music_controller::config::Config;
@@ -35,20 +35,22 @@ pub enum Tag {
     Comment,
     Track,
     Disk,
-    Key(String)
+    Key(String),
+    Field(String)
 }
 
 impl ToString for Tag {
     fn to_string(&self) -> String {
         match self {
-            Self::Title => "TrackTitle".into(),
-            Self::Album => "AlbumTitle".into(),
+            Self::Title =>  "TrackTitle".into(),
+            Self::Album =>  "AlbumTitle".into(),
             Self::Artist => "TrackArtist".into(),
-            Self::Genre => "Genre".into(),
+            Self::Genre =>  "Genre".into(),
             Self::Comment => "Comment".into(),
-            Self::Track => "TrackNumber".into(),
-            Self::Disk => "DiscNumber".into(),
-            Self::Key(key) => key.into()
+            Self::Track =>  "TrackNumber".into(),
+            Self::Disk =>   "DiscNumber".into(),
+            Self::Key(key) => key.into(),
+            Self::Field(f) => f.into()
         }
     }
 }
@@ -95,17 +97,12 @@ impl Song {
         }
     }
 
-    pub fn get_tags(&self, target_keys: &Vec<String>) -> Vec<Option<String>> {
-        let mut results = Vec::new();
-        for tag in &self.tags {
-            for key in target_keys {
-                if &tag.0.to_string() == key {
-                    results.push(Some(tag.1.to_owned()))
-                }
-            }
-            results.push(None);
+    pub fn get_field(&self, target_field: &str) -> Box<dyn Any> {
+        match target_field {
+            "location" => Box::new(self.location.clone()),
+            "plays" => Box::new(self.plays.clone()),
+            _ => todo!()
         }
-        results
     }
 }
 
@@ -116,6 +113,15 @@ pub enum URI {
     Remote(Service, String),
 }
 
+impl ToString for URI {
+    fn to_string(&self) -> String {
+        match self {
+            URI::Local(location) => location.to_string(),
+            URI::Remote(_, location) => location.to_string()
+        }
+    }
+}
+
 #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub enum Service {
     InternetRadio,
@@ -142,7 +148,7 @@ pub struct MusicLibrary {
 }
 
 pub fn normalize(input_string: &String) -> String {
-    unidecode(input_string).to_ascii_lowercase().replace(|c: char| !c.is_alphanumeric() && !c.is_ascii_punctuation(), "")
+    unidecode(input_string).to_ascii_lowercase().replace(|c: char| !c.is_alphanumeric(), "")
 }
 
 impl MusicLibrary {
@@ -207,7 +213,9 @@ impl MusicLibrary {
         Ok(())
     }
 
-    fn find_by_uri(&self, path: &URI) -> Option<Song> {
+    /// Queries for a [Song] by its [URI], returning a single Song
+    /// with the URI that matches
+    fn query_by_uri(&self, path: &URI) -> Option<Song> {
         for track in &self.library {
             if path == &track.location {
                 return Some(track.clone());
@@ -348,14 +356,14 @@ impl MusicLibrary {
 
         match self.add_song_to_db(new_song) {
             Ok(_) => (),
-            Err(error) => ()
+            Err(_error) => ()
         };
 
         Ok(())
     }
 
     pub fn add_song_to_db(&mut self, new_song: Song) -> Result<(), Box<dyn std::error::Error>> {
-        match self.find_by_uri(&new_song.location) {
+        match self.query_by_uri(&new_song.location) {
             Some(_) => return Err(format!("URI already in database: {:?}", new_song.location).into()),
             None => ()
         }
@@ -366,7 +374,7 @@ impl MusicLibrary {
     }
 
     pub fn update_song_tags(&mut self, new_tags: Song) -> Result<(), Box<dyn std::error::Error>> {
-        match self.find_by_uri(&new_tags.location) {
+        match self.query_by_uri(&new_tags.location) {
             Some(_) => (),
             None => return Err(format!("URI not in database!").into())
         }
@@ -379,6 +387,7 @@ impl MusicLibrary {
         &self,
         query_string: &String,  // The query itself
         target_tags: &Vec<Tag>, // The tags to search
+        search_location: bool,  // Whether to search the location field or not
         sort_by: &Vec<Tag>,     // Tags to sort the resulting data by
     ) -> Option<Vec<&Song>> {
         let songs = Arc::new(Mutex::new(Vec::new()));
@@ -391,9 +400,25 @@ impl MusicLibrary {
 
                 if normalize(&tag.1).contains(&normalize(&query_string)) {
                     songs.lock().unwrap().push(track);
-                    break
+                    return
                 }
             }
+
+            if !search_location {
+                return
+            }
+
+            // Find a URL in the song
+            match &track.location {
+                URI::Local(path) if normalize(&path).contains(&normalize(&query_string)) => {
+                    songs.lock().unwrap().push(track);
+                    return
+                },
+                URI::Remote(_, path) if normalize(&path).contains(&normalize(&query_string)) => {
+                    songs.lock().unwrap().push(track);
+                    return
+                }, _ => ()
+            };
         });
 
         let lock = Arc::try_unwrap(songs).expect("Lock still has multiple owners!");

From 450750f5a0f6172f629a475b8e890c88f22104e5 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 3 Oct 2023 21:15:56 -0500
Subject: [PATCH 06/20] Can now sort by song fields

---
 src/music_storage/music_db.rs | 51 +++++++++++++++++++++++++----------
 1 file changed, 37 insertions(+), 14 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 7810ab4..25e7373 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -85,7 +85,7 @@ impl Song {
      *
      * let tag = this_song.get_tag(Tag::Title);
      *
-     * assert_eq!(tag, "Title");
+     * assert_eq!(tag, "Some Song Title");
      * ```
      **/
     pub fn get_tag(&self, target_key: &Tag) -> Option<&String> {
@@ -97,11 +97,11 @@ impl Song {
         }
     }
 
-    pub fn get_field(&self, target_field: &str) -> Box<dyn Any> {
+    pub fn get_field(&self, target_field: &str) -> Option<String> {
         match target_field {
-            "location" => Box::new(self.location.clone()),
-            "plays" => Box::new(self.plays.clone()),
-            _ => todo!()
+            "location" => Some(self.location.clone().to_string()),
+            "plays" => Some(self.plays.clone().to_string()),
+            _ => None   // Other field types is not yet supported
         }
     }
 }
@@ -383,6 +383,9 @@ impl MusicLibrary {
     }
 
     /// Query the database, returning a list of [Song]s
+    ///
+    /// The order in which the `sort_by` `Vec` is arranged
+    /// determines the output sorting
     pub fn query(
         &self,
         query_string: &String,  // The query itself
@@ -426,18 +429,38 @@ impl MusicLibrary {
 
         new_songs.par_sort_by(|a, b| {
             for opt in sort_by {
-                let tag_a = match a.get_tag(&opt) {
-                    Some(tag) => tag,
-                    None => continue
+                let tag_a = match opt {
+                    Tag::Field(field_selection) => {
+                        match a.get_field(field_selection) {
+                            Some(field_value) => field_value,
+                            None => continue
+                        }
+                    },
+                    _ => {
+                        match a.get_tag(&opt) {
+                            Some(tag_value) => tag_value.to_owned(),
+                            None => continue
+                        }
+                    }
                 };
 
-                let tag_b = match b.get_tag(&opt) {
-                    Some(tag) => tag,
-                    None => continue
+                let tag_b = match opt {
+                    Tag::Field(field_selection) => {
+                        match b.get_field(field_selection) {
+                            Some(field_value) => field_value,
+                            None => continue
+                        }
+                    },
+                    _ => {
+                        match b.get_tag(&opt) {
+                            Some(tag_value) => tag_value.to_owned(),
+                            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>()) {
+                // Try to parse the tags as f64
+                if let (Ok(num_a), Ok(num_b)) = (tag_a.parse::<i32>(), tag_b.parse::<i32>()) {
                     // If parsing succeeds, compare as numbers
                     if num_a < num_b {
                         return std::cmp::Ordering::Less;
@@ -454,7 +477,7 @@ impl MusicLibrary {
                 }
             }
 
-            // If all tags are equal, sort by some default criteria (e.g., song title)
+            // If all tags are equal, sort by title
             a.get_tag(&Tag::Title).cmp(&b.get_tag(&Tag::Title))
         });
 

From b39c2c006545680684cc0f2c3e4ede0464946204 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 6 Oct 2023 01:22:55 -0500
Subject: [PATCH 07/20] Updated some small things, extra fixes

---
 src/music_controller/music_controller.rs | 2 +-
 src/music_storage/music_db.rs            | 7 ++++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 7ae6e69..7d00b8a 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -74,7 +74,7 @@ impl MusicController {
         self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone()))));
     }
     
-    /// Queries the library for a `Vec<Song>`
+    /// Queries the [MusicLibrary], returning a `Vec<Song>`
     pub fn query_library(
         &self,
         query_string: &String,
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 25e7373..bf7dc23 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -420,7 +420,8 @@ impl MusicLibrary {
                 URI::Remote(_, path) if normalize(&path).contains(&normalize(&query_string)) => {
                     songs.lock().unwrap().push(track);
                     return
-                }, _ => ()
+                },
+                _ => ()
             };
         });
 
@@ -477,8 +478,8 @@ impl MusicLibrary {
                 }
             }
 
-            // If all tags are equal, sort by title
-            a.get_tag(&Tag::Title).cmp(&b.get_tag(&Tag::Title))
+            // If all tags are equal, sort by Track number
+            a.get_tag(&Tag::Track).cmp(&b.get_tag(&Tag::Track))
         });
 
         if new_songs.len() > 0 {

From fa88fd83a99e733ba5910d52aaecb2f0e810027b Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 1 Nov 2023 01:31:58 -0500
Subject: [PATCH 08/20] Large amounts of database work

---
 Cargo.toml                       |   6 +-
 src/music_player/music_player.rs |  13 +-
 src/music_storage/music_db.rs    | 435 +++++++++++++++++++++++--------
 3 files changed, 335 insertions(+), 119 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 0885526..69f5abb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ categories = ["multimedia::audio"]
 
 [dependencies]
 file-format = { version = "0.17.3", features = ["reader", "serde"] }
-lofty = "0.14.0"
+lofty = "0.16.1"
 serde = { version = "1.0.164", features = ["derive"] }
 time = "0.3.22"
 toml = "0.7.5"
@@ -35,3 +35,7 @@ chrono = { version = "0.4.31", features = ["serde"] }
 bincode = "1.3.3"
 unidecode = "0.3.0"
 rayon = "1.8.0"
+log = "0.4"
+pretty_env_logger = "0.4"
+cue = "2.0.0"
+jwalk = "0.8.1"
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
index 478fca1..f12f68c 100644
--- a/src/music_player/music_player.rs
+++ b/src/music_player/music_player.rs
@@ -84,11 +84,12 @@ impl SongHandler {
                 }
             },
             URI::Remote(_, location) => {
-                match RemoteSource::new(location.as_ref(), &config) {
+                match RemoteSource::new(location.to_str().unwrap(), &config) {
                     Ok(remote_source) => Box::new(remote_source),
                     Err(_) => return Err(()),
                 }
             },
+            _ => todo!()
         };
         
         let mss = MediaSourceStream::new(src, Default::default());
@@ -97,11 +98,11 @@ impl SongHandler {
         let meta_opts: MetadataOptions = Default::default();
         let fmt_opts: FormatOptions = Default::default();
 
-        let mut hint = Hint::new();
+        let hint = Hint::new();
         
         let probed = symphonia::default::get_probe().format(&hint, mss, &fmt_opts, &meta_opts).expect("Unsupported format");
         
-        let mut reader  = probed.format;
+        let reader  = probed.format;
         
         let track = reader.tracks()
                     .iter()
@@ -113,7 +114,7 @@ impl SongHandler {
                     
         let dec_opts: DecoderOptions = Default::default();
         
-        let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
+        let decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
                                                     .expect("unsupported codec");
         
         return Ok(SongHandler {reader, decoder, time_base, duration});
@@ -170,7 +171,7 @@ impl MusicPlayer {
                     if local_config != global_config {
                         update_trackers(&mut trackers);
                     }
-    
+
                     let mut results = Vec::new();
                     task::block_on(async {
                         let mut futures = Vec::new();
@@ -183,7 +184,7 @@ impl MusicPlayer {
                         }
                         results = join_all(futures).await;
                     });
-                    
+
                     for result in results {
                         status_sender.send(result).unwrap_or_default()
                     }
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index bf7dc23..3135b34 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,11 +1,13 @@
 use file_format::{FileFormat, Kind};
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
-use std::any::Any;
+use std::ffi::OsStr;
 use std::{error::Error, io::BufReader};
 
 use chrono::{serde::ts_seconds_option, DateTime, Utc};
 use std::time::Duration;
-use walkdir::WalkDir;
+//use walkdir::WalkDir;
+use cue::cd::CD;
+use jwalk::WalkDir;
 
 use bincode::{deserialize_from, serialize_into};
 use serde::{Deserialize, Serialize};
@@ -15,8 +17,8 @@ use std::path::{Path, PathBuf};
 use unidecode::unidecode;
 
 // Fun parallel stuff
-use std::sync::{Arc, Mutex, RwLock};
 use rayon::prelude::*;
+use std::sync::{Arc, Mutex, RwLock};
 
 use crate::music_controller::config::Config;
 
@@ -36,21 +38,21 @@ pub enum Tag {
     Track,
     Disk,
     Key(String),
-    Field(String)
+    Field(String),
 }
 
 impl ToString for Tag {
     fn to_string(&self) -> String {
         match self {
-            Self::Title =>  "TrackTitle".into(),
-            Self::Album =>  "AlbumTitle".into(),
+            Self::Title => "TrackTitle".into(),
+            Self::Album => "AlbumTitle".into(),
             Self::Artist => "TrackArtist".into(),
-            Self::Genre =>  "Genre".into(),
+            Self::Genre => "Genre".into(),
             Self::Comment => "Comment".into(),
-            Self::Track =>  "TrackNumber".into(),
-            Self::Disk =>   "DiscNumber".into(),
+            Self::Track => "TrackNumber".into(),
+            Self::Disk => "DiscNumber".into(),
             Self::Key(key) => key.into(),
-            Self::Field(f) => f.into()
+            Self::Field(f) => f.into(),
         }
     }
 }
@@ -99,27 +101,78 @@ impl Song {
 
     pub fn get_field(&self, target_field: &str) -> Option<String> {
         match target_field {
-            "location" => Some(self.location.clone().to_string()),
+            "location" => Some(self.location.clone().path_string()),
             "plays" => Some(self.plays.clone().to_string()),
-            _ => None   // Other field types is not yet supported
+            _ => None, // Other field types are not yet supported
         }
     }
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
 pub enum URI {
-    Local(String),
-    //Cue(String, Duration), TODO: Make cue stuff work
-    Remote(Service, String),
+    Local(PathBuf),
+    Cue {
+        location: PathBuf,
+        start: Duration,
+        end: Duration,
+    },
+    Remote(Service, PathBuf),
 }
 
-impl ToString for URI {
-    fn to_string(&self) -> String {
+impl URI {
+    /// Returns the start time of a CUEsheet song, or an
+    /// error if the URI is not a Cue variant
+    pub fn start(&self) -> Result<&Duration, Box<dyn Error>> {
         match self {
-            URI::Local(location) => location.to_string(),
-            URI::Remote(_, location) => location.to_string()
+            URI::Local(_) => Err("\"Local\" has no starting time".into()),
+            URI::Remote(_, _) => Err("\"Remote\" has no starting time".into()),
+            URI::Cue {
+                location: _,
+                start,
+                end: _,
+            } => Ok(start),
         }
     }
+
+    /// Returns the end time of a CUEsheet song, or an
+    /// error if the URI is not a Cue variant
+    pub fn end(&self) -> Result<&Duration, Box<dyn Error>> {
+        match self {
+            URI::Local(_) => Err("\"Local\" has no starting time".into()),
+            URI::Remote(_, _) => Err("\"Remote\" has no starting time".into()),
+            URI::Cue {
+                location: _,
+                start: _,
+                end,
+            } => Ok(end),
+        }
+    }
+
+    /// Returns the location as a PathBuf
+    pub fn path(&self) -> &PathBuf {
+        match self {
+            URI::Local(location) => location,
+            URI::Cue {
+                location,
+                start: _,
+                end: _,
+            } => location,
+            URI::Remote(_, location) => location,
+        }
+    }
+
+    fn path_string(&self) -> String {
+        let path_str = match self {
+            URI::Local(location) => location.as_path().to_string_lossy(),
+            URI::Cue {
+                location,
+                start: _,
+                end: _,
+            } => location.as_path().to_string_lossy(),
+            URI::Remote(_, location) => location.as_path().to_string_lossy(),
+        };
+        path_str.to_string()
+    }
 }
 
 #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -129,6 +182,7 @@ pub enum Service {
     Youtube,
 }
 
+/* TODO: Rework this entirely
 #[derive(Debug)]
 pub struct Playlist {
     title: String,
@@ -141,6 +195,7 @@ pub enum MusicObject {
     Album(Playlist),
     Playlist(Playlist),
 }
+*/
 
 #[derive(Debug)]
 pub struct MusicLibrary {
@@ -148,7 +203,9 @@ pub struct MusicLibrary {
 }
 
 pub fn normalize(input_string: &String) -> String {
-    unidecode(input_string).to_ascii_lowercase().replace(|c: char| !c.is_alphanumeric(), "")
+    unidecode(input_string)
+        .to_ascii_lowercase()
+        .replace(|c: char| !c.is_alphanumeric(), "")
 }
 
 impl MusicLibrary {
@@ -178,21 +235,24 @@ impl MusicLibrary {
                     let reader = BufReader::new(database);
                     library = deserialize_from(reader)?;
                 } else {
-                    let mut writer = BufWriter::new(fs::File::create(global_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)?;
                 }
-            },
-            Err(error) => return Err(error.into())
+            }
+            Err(error) => return Err(error.into()),
         };
 
         Ok(Self { library })
     }
 
+    /// Serializes the database out to the file
+    /// specified in the config
     pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
         match config.db_path.try_exists() {
             Ok(true) => {
-                // The database exists, rename it to `.bkp` and
-                // write the new database
+                // The database exists, so rename it to `.bkp` and
+                // write the new database file
                 let mut backup_name = config.db_path.clone();
                 backup_name.set_extension("bkp");
                 fs::rename(config.db_path.as_path(), backup_name.as_path())?;
@@ -206,71 +266,122 @@ impl MusicLibrary {
                 // Create the database if it does not exist
                 let mut writer = BufWriter::new(fs::File::create(config.db_path.to_path_buf())?);
                 serialize_into(&mut writer, &self.library)?;
-            },
-            Err(error) => return Err(error.into())
+            }
+            Err(error) => return Err(error.into()),
         }
 
         Ok(())
     }
 
-    /// Queries for a [Song] by its [URI], returning a single Song
-    /// with the URI that matches
-    fn query_by_uri(&self, path: &URI) -> Option<Song> {
-        for track in &self.library {
-            if path == &track.location {
-                return Some(track.clone());
-            }
-        }
-        None
+    pub fn size(&self) -> usize {
+        self.library.len()
     }
 
-    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 (i, entry) in WalkDir::new(target_path)
+    /// Queries for a [Song] by its [URI], returning a single `Song`
+    /// with the `URI` that matches
+    fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
+        let result = Arc::new(Mutex::new(None));
+        let index = Arc::new(Mutex::new(0));
+        let _ = &self.library.par_iter().enumerate().for_each(|(i, track)| {
+            if path == &track.location {
+                *result.clone().lock().unwrap() = Some(track);
+                *index.clone().lock().unwrap() = i;
+                return;
+            }
+        });
+        let song = Arc::try_unwrap(result).unwrap().into_inner().unwrap();
+        match song {
+            Some(song) => Some((song, Arc::try_unwrap(index).unwrap().into_inner().unwrap())),
+            None => None,
+        }
+    }
+
+    /// Queries for a [Song] by its [PathBuf], returning a `Vec<Song>`
+    /// with matching `PathBuf`s
+    fn query_path(&self, path: &PathBuf) -> Option<Vec<&Song>> {
+        let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
+        let _ = &self.library.par_iter().for_each(|track| {
+            if path == track.location.path() {
+                result.clone().lock().unwrap().push(&track);
+                return;
+            }
+        });
+        if result.lock().unwrap().len() > 0 {
+            Some(Arc::try_unwrap(result).unwrap().into_inner().unwrap())
+        } else {
+            None
+        }
+    }
+
+    /// Finds all the music files within a specified folder
+    pub fn find_all_music(
+        &mut self,
+        target_path: &str,
+        config: &Config,
+    ) -> Result<usize, Box<dyn std::error::Error>> {
+        let mut total = 0;
+        let mut i = 0;
+        for entry in WalkDir::new(target_path)
             .follow_links(true)
             .into_iter()
-            .filter_map(|e| e.ok()).enumerate()
+            .filter_map(|e| e.ok())
         {
             let target_file = entry;
-            let is_file = fs::metadata(target_file.path())?.is_file();
+            let path = target_file.path();
 
-            // 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();
+            // Ensure the target is a file and not a directory,
+            // if it isn't a file, skip this loop
+            if !path.is_file() {
                 continue;
             }
 
-            let format = FileFormat::from_file(target_file.path())?;
-            let extension = target_file
-                .path()
-                .extension()
-                .expect("Could not find file extension");
+            // Check if the file path is already in the db
+            if self.query_uri(&URI::Local(path.to_path_buf())).is_some() {
+                continue;
+            }
+
+            // Save periodically while scanning
+            i += 1;
+            if i % 250 == 0 {
+                self.save(config).unwrap();
+            }
+
+            let format = FileFormat::from_file(&path)?;
+            let extension: &OsStr = match path.extension() {
+                Some(ext) => ext,
+                None => OsStr::new(""),
+            };
 
             // If it's a normal file, add it to the database
             // if it's a cuesheet, do a bunch of fancy stuff
-            if format.kind() == Kind::Audio {
-                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
+            if (format.kind() == Kind::Audio || format.kind() == Kind::Video)
+                && extension.to_ascii_lowercase() != "log"
+                && extension.to_ascii_lowercase() != "vob"
+            {
+                match self.add_file(&target_file.path()) {
+                    Ok(_) => total += 1,
+                    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
+                total += match self.add_cuesheet(&target_file.path()) {
+                    Ok(added) => added,
+                    Err(error) => {
+                        println!("{}", error);
+                        0
+                    }
+                }
             }
         }
 
         // Save the database after scanning finishes
         self.save(&config).unwrap();
 
-        Ok(())
+        Ok(total)
     }
 
-    pub fn add_file_to_db(&mut self, target_file: &Path) -> Result<(), Box<dyn std::error::Error>> {
+    pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> {
         // TODO: Fix error handling here
         let tagged_file = match lofty::read_from_path(target_file) {
             Ok(tagged_file) => tagged_file,
@@ -296,12 +407,12 @@ impl MusicLibrary {
         let mut tags: Vec<(Tag, String)> = Vec::new();
         for item in tag.items() {
             let key = match item.key() {
-                ItemKey::TrackTitle =>  Tag::Title,
+                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::Genre => Tag::Genre,
+                ItemKey::Comment => Tag::Comment,
+                ItemKey::AlbumTitle => Tag::Album,
                 ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()),
                 custom => Tag::Key(format!("{:?}", custom)),
             };
@@ -336,10 +447,9 @@ impl MusicLibrary {
 
         // TODO: Fix error handling
         let binding = fs::canonicalize(target_file).unwrap();
-        let abs_path = binding.to_str().unwrap();
 
         let new_song = Song {
-            location: URI::Local(abs_path.to_string()),
+            location: URI::Local(binding),
             plays: 0,
             skips: 0,
             favorited: false,
@@ -354,18 +464,120 @@ impl MusicLibrary {
             album_art,
         };
 
-        match self.add_song_to_db(new_song) {
+        match self.add_song(new_song) {
             Ok(_) => (),
-            Err(_error) => ()
+            Err(error) => return Err(error),
         };
 
         Ok(())
     }
 
-    pub fn add_song_to_db(&mut self, new_song: Song) -> Result<(), Box<dyn std::error::Error>> {
-        match self.query_by_uri(&new_song.location) {
-            Some(_) => return Err(format!("URI already in database: {:?}", new_song.location).into()),
-            None => ()
+    pub fn add_cuesheet(&mut self, cuesheet: &PathBuf) -> Result<usize, Box<dyn Error>> {
+        let mut tracks_added = 0;
+
+        let cue_data = CD::parse_file(cuesheet.to_owned()).unwrap();
+
+        // Get album level information
+        let album_title = &cue_data.get_cdtext().read(cue::cd_text::PTI::Title).unwrap_or(String::new());
+        let album_artist = &cue_data.get_cdtext().read(cue::cd_text::PTI::Performer).unwrap_or(String::new());
+
+        let parent_dir = cuesheet.parent().expect("The file has no parent path??");
+        for track in cue_data.tracks() {
+            let audio_location = parent_dir.join(track.get_filename());
+
+            if !audio_location.exists() {
+                continue;
+            }
+
+            // Try to remove the original audio file from the db if it exists
+            let _ = self.remove_uri(&URI::Local(audio_location.clone()));
+
+            // Get the track timing information
+            let start = Duration::from_micros((track.get_start() as f32 * 13333.333333).round() as u64);
+            let duration = match track.get_length() {
+                Some(len) =>  Duration::from_micros((len as f32 * 13333.333333).round() as u64),
+                None => {
+                    let tagged_file = match lofty::read_from_path(&audio_location) {
+                        Ok(tagged_file) => tagged_file,
+
+                        Err(_) => match Probe::open(&audio_location)?.read() {
+                            Ok(tagged_file) => tagged_file,
+
+                            Err(error) => return Err(error.into()),
+                        },
+                    };
+
+                    tagged_file.properties().duration() - start
+                }
+            };
+            let end = start + duration;
+
+            // Get the format as a string
+            let format: Option<FileFormat> = match FileFormat::from_file(&audio_location) {
+                Ok(fmt) => Some(fmt),
+                Err(_) => None,
+            };
+
+            let mut tags: Vec<(Tag, String)> = Vec::new();
+            tags.push((Tag::Album, album_title.clone()));
+            tags.push((Tag::Key("AlbumArtist".to_string()), album_artist.clone()));
+            match track.get_cdtext().read(cue::cd_text::PTI::Title) {
+                Some(title) => tags.push((Tag::Title, title)),
+                None => ()
+            };
+            match track.get_cdtext().read(cue::cd_text::PTI::Performer) {
+                Some(artist) => tags.push((Tag::Artist, artist)),
+                None => ()
+            };
+            match track.get_cdtext().read(cue::cd_text::PTI::Genre) {
+                Some(genre) => tags.push((Tag::Genre, genre)),
+                None => ()
+            };
+            match track.get_cdtext().read(cue::cd_text::PTI::Message) {
+                Some(comment) => tags.push((Tag::Comment, comment)),
+                None => ()
+            };
+
+            let album_art = Vec::new();
+
+            let new_song = Song {
+                location: URI::Cue{
+                    location: audio_location,
+                    start,
+                    end,
+                },
+                plays: 0,
+                skips: 0,
+                favorited: false,
+                rating: None,
+                format,
+                duration,
+                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,
+            };
+
+            match self.add_song(new_song) {
+                Ok(_) => tracks_added += 1,
+                Err(_error) => {
+                    //println!("{}", error);
+                    continue
+                },
+            };
+        }
+
+        Ok(tracks_added)
+    }
+
+    pub fn add_song(&mut self, new_song: Song) -> Result<(), Box<dyn Error>> {
+        match self.query_uri(&new_song.location) {
+            Some(_) => {
+                return Err(format!("URI already in database: {:?}", new_song.location).into())
+            }
+            None => (),
         }
 
         self.library.push(new_song);
@@ -373,10 +585,23 @@ impl MusicLibrary {
         Ok(())
     }
 
-    pub fn update_song_tags(&mut self, new_tags: Song) -> Result<(), Box<dyn std::error::Error>> {
-        match self.query_by_uri(&new_tags.location) {
+    /// Removes a song indexed by URI, returning the position removed
+    pub fn remove_uri(&mut self, target_uri: &URI) -> Result<usize, Box<dyn Error>> {
+        let location = match self.query_uri(target_uri) {
+            Some(value) => value.1,
+            None => return Err("URI not in database".into()),
+        };
+
+        self.library.remove(location);
+
+        Ok(location)
+    }
+
+    /// Scan the song by a location and update its tags
+    pub fn update_by_file(&mut self, new_tags: Song) -> Result<(), Box<dyn std::error::Error>> {
+        match self.query_uri(&new_tags.location) {
             Some(_) => (),
-            None => return Err(format!("URI not in database!").into())
+            None => return Err(format!("URI not in database!").into()),
         }
 
         todo!()
@@ -403,61 +628,47 @@ impl MusicLibrary {
 
                 if normalize(&tag.1).contains(&normalize(&query_string)) {
                     songs.lock().unwrap().push(track);
-                    return
+                    return;
                 }
             }
 
             if !search_location {
-                return
+                return;
             }
 
             // Find a URL in the song
-            match &track.location {
-                URI::Local(path) if normalize(&path).contains(&normalize(&query_string)) => {
-                    songs.lock().unwrap().push(track);
-                    return
-                },
-                URI::Remote(_, path) if normalize(&path).contains(&normalize(&query_string)) => {
-                    songs.lock().unwrap().push(track);
-                    return
-                },
-                _ => ()
-            };
+            if normalize(&track.location.path_string()).contains(&normalize(&query_string)) {
+                songs.lock().unwrap().push(track);
+                return;
+            }
         });
 
         let lock = Arc::try_unwrap(songs).expect("Lock still has multiple owners!");
         let mut new_songs = lock.into_inner().expect("Mutex cannot be locked!");
 
+        // Sort the returned list of songs
         new_songs.par_sort_by(|a, b| {
             for opt in sort_by {
                 let tag_a = match opt {
-                    Tag::Field(field_selection) => {
-                        match a.get_field(field_selection) {
-                            Some(field_value) => field_value,
-                            None => continue
-                        }
+                    Tag::Field(field_selection) => match a.get_field(field_selection) {
+                        Some(field_value) => field_value,
+                        None => continue,
+                    },
+                    _ => match a.get_tag(&opt) {
+                        Some(tag_value) => tag_value.to_owned(),
+                        None => continue,
                     },
-                    _ => {
-                        match a.get_tag(&opt) {
-                            Some(tag_value) => tag_value.to_owned(),
-                            None => continue
-                        }
-                    }
                 };
 
                 let tag_b = match opt {
-                    Tag::Field(field_selection) => {
-                        match b.get_field(field_selection) {
-                            Some(field_value) => field_value,
-                            None => continue
-                        }
+                    Tag::Field(field_selection) => match b.get_field(field_selection) {
+                        Some(field_value) => field_value,
+                        None => continue,
+                    },
+                    _ => match b.get_tag(&opt) {
+                        Some(tag_value) => tag_value.to_owned(),
+                        None => continue,
                     },
-                    _ => {
-                        match b.get_tag(&opt) {
-                            Some(tag_value) => tag_value.to_owned(),
-                            None => continue
-                        }
-                    }
                 };
 
                 // Try to parse the tags as f64

From 60add901bde01f5804f191a5d747896fb1bfd2f6 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 1 Nov 2023 10:57:15 -0500
Subject: [PATCH 09/20] Finished CUE file support

---
 src/music_storage/music_db.rs | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 3135b34..7eae5b5 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -493,9 +493,19 @@ impl MusicLibrary {
             let _ = self.remove_uri(&URI::Local(audio_location.clone()));
 
             // Get the track timing information
-            let start = Duration::from_micros((track.get_start() as f32 * 13333.333333).round() as u64);
+            let pregap = match track.get_zero_pre() {
+                Some(pregap) => Duration::from_micros((pregap as f32 * 13333.333333) as u64),
+                None => Duration::from_secs(0)
+            };
+            let postgap = match track.get_zero_post() {
+                Some(postgap) => Duration::from_micros((postgap as f32 * 13333.333333) as u64),
+                None => Duration::from_secs(0)
+            };
+            let mut start = Duration::from_micros((track.get_start() as f32 * 13333.333333) as u64);
+            start -= pregap;
+
             let duration = match track.get_length() {
-                Some(len) =>  Duration::from_micros((len as f32 * 13333.333333).round() as u64),
+                Some(len) =>  Duration::from_micros((len as f32 * 13333.333333) as u64),
                 None => {
                     let tagged_file = match lofty::read_from_path(&audio_location) {
                         Ok(tagged_file) => tagged_file,
@@ -510,7 +520,7 @@ impl MusicLibrary {
                     tagged_file.properties().duration() - start
                 }
             };
-            let end = start + duration;
+            let end = start + duration + postgap;
 
             // Get the format as a string
             let format: Option<FileFormat> = match FileFormat::from_file(&audio_location) {
@@ -563,7 +573,7 @@ impl MusicLibrary {
             match self.add_song(new_song) {
                 Ok(_) => tracks_added += 1,
                 Err(_error) => {
-                    //println!("{}", error);
+                    println!("{}", _error);
                     continue
                 },
             };
@@ -579,6 +589,17 @@ impl MusicLibrary {
             }
             None => (),
         }
+        match self.query_path(&new_song.location.path()) {
+            Some(songs) => {
+                for song in songs {
+                    match &song.location {
+                        URI::Local(location) => return Err(format!("Cuesheet exists: {:?}", new_song.location).into()),
+                        _ => ()
+                    }
+                }
+            }
+            None => (),
+        }
 
         self.library.push(new_song);
 

From c5a631e30f38d8edec11a97b2b57598ab7cbaa18 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 1 Nov 2023 14:13:25 -0500
Subject: [PATCH 10/20] Fixed duplicate entries

---
 src/music_storage/music_db.rs | 48 ++++++++++++++++++++---------------
 1 file changed, 28 insertions(+), 20 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 7eae5b5..e59f51c 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -161,7 +161,7 @@ impl URI {
         }
     }
 
-    fn path_string(&self) -> String {
+    pub fn path_string(&self) -> String {
         let path_str = match self {
             URI::Local(location) => location.as_path().to_string_lossy(),
             URI::Cue {
@@ -359,7 +359,10 @@ impl MusicLibrary {
                 && extension.to_ascii_lowercase() != "vob"
             {
                 match self.add_file(&target_file.path()) {
-                    Ok(_) => total += 1,
+                    Ok(_) => {
+                        //println!("{:?}", target_file.path());
+                        total += 1
+                    },
                     Err(_error) => {
                         //println!("{}, {:?}: {}", format, target_file.file_name(), _error)
                     } // TODO: Handle more of these errors
@@ -482,7 +485,7 @@ impl MusicLibrary {
         let album_artist = &cue_data.get_cdtext().read(cue::cd_text::PTI::Performer).unwrap_or(String::new());
 
         let parent_dir = cuesheet.parent().expect("The file has no parent path??");
-        for track in cue_data.tracks() {
+        for (i, track) in cue_data.tracks().iter().enumerate() {
             let audio_location = parent_dir.join(track.get_filename());
 
             if !audio_location.exists() {
@@ -528,12 +531,21 @@ impl MusicLibrary {
                 Err(_) => None,
             };
 
+            // Get some useful tags
             let mut tags: Vec<(Tag, String)> = Vec::new();
             tags.push((Tag::Album, album_title.clone()));
             tags.push((Tag::Key("AlbumArtist".to_string()), album_artist.clone()));
             match track.get_cdtext().read(cue::cd_text::PTI::Title) {
                 Some(title) => tags.push((Tag::Title, title)),
-                None => ()
+                None => {
+                    match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) {
+                        Some(title) => tags.push((Tag::Title, title)),
+                        None => {
+                            let namestr = format!("{} - {}", i, track.get_filename());
+                            tags.push((Tag::Title, namestr))
+                        }
+                    }
+                }
             };
             match track.get_cdtext().read(cue::cd_text::PTI::Performer) {
                 Some(artist) => tags.push((Tag::Artist, artist)),
@@ -573,7 +585,7 @@ impl MusicLibrary {
             match self.add_song(new_song) {
                 Ok(_) => tracks_added += 1,
                 Err(_error) => {
-                    println!("{}", _error);
+                    //println!("{}", _error);
                     continue
                 },
             };
@@ -589,16 +601,11 @@ impl MusicLibrary {
             }
             None => (),
         }
-        match self.query_path(&new_song.location.path()) {
-            Some(songs) => {
-                for song in songs {
-                    match &song.location {
-                        URI::Local(location) => return Err(format!("Cuesheet exists: {:?}", new_song.location).into()),
-                        _ => ()
-                    }
-                }
-            }
-            None => (),
+        match new_song.location {
+            URI::Local(_) if self.query_path(&new_song.location.path()).is_some() => {
+                return Err(format!("Location exists for {:?}", new_song.location).into())
+            },
+            _ => ()
         }
 
         self.library.push(new_song);
@@ -642,12 +649,13 @@ impl MusicLibrary {
         let songs = Arc::new(Mutex::new(Vec::new()));
 
         self.library.par_iter().for_each(|track| {
-            for tag in &track.tags {
-                if !target_tags.contains(&tag.0) {
-                    continue;
-                }
+            for tag in target_tags {
+                let track_result = match track.get_tag(&tag) {
+                    Some(value) => value,
+                    None => continue
+                };
 
-                if normalize(&tag.1).contains(&normalize(&query_string)) {
+                if normalize(track_result).contains(&normalize(&query_string)) {
                     songs.lock().unwrap().push(track);
                     return;
                 }

From 085927caa16c39968463b8bfacf6eea36f629e8e Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 1 Nov 2023 14:14:28 -0500
Subject: [PATCH 11/20] `cargo fmt`

---
 src/lib.rs                               |   6 +-
 src/music_controller/config.rs           |  30 +--
 src/music_controller/init.rs             |  11 +-
 src/music_controller/music_controller.rs |  45 ++--
 src/music_player/music_output.rs         | 153 ++++++++-----
 src/music_player/music_player.rs         | 264 ++++++++++++++---------
 src/music_player/music_resampler.rs      |  13 +-
 src/music_processor/music_processor.rs   |  20 +-
 src/music_storage/music_db.rs            |  50 +++--
 src/music_storage/playlist.rs            |   8 +-
 src/music_tracker/music_tracker.rs       | 202 ++++++++++-------
 11 files changed, 474 insertions(+), 328 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 3a25b3d..75b98ee 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,13 +12,13 @@ pub mod music_processor {
 }
 
 pub mod music_player {
-    pub mod music_player;
     pub mod music_output;
+    pub mod music_player;
     pub mod music_resampler;
 }
 
 pub mod music_controller {
-    pub mod music_controller;
     pub mod config;
     pub mod init;
-}
\ No newline at end of file
+    pub mod music_controller;
+}
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index f6909f5..82cbd22 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -1,10 +1,10 @@
-use std::path::PathBuf;
-use std::fs::read_to_string;
 use std::fs;
+use std::fs::read_to_string;
+use std::path::PathBuf;
 
 use serde::{Deserialize, Serialize};
 
-use crate::music_tracker::music_tracker::{LastFMConfig, DiscordRPCConfig, ListenBrainzConfig};
+use crate::music_tracker::music_tracker::{DiscordRPCConfig, LastFMConfig, ListenBrainzConfig};
 
 #[derive(Serialize, Deserialize, PartialEq, Eq)]
 pub struct Config {
@@ -22,51 +22,53 @@ impl Default for Config {
             db_path: Box::new(path),
 
             lastfm: None,
-            
+
             discord: Some(DiscordRPCConfig {
                 enabled: true,
                 dango_client_id: 1144475145864499240,
                 dango_icon: String::from("flat"),
             }),
-            
+
             listenbrainz: Some(ListenBrainzConfig {
                 enabled: false,
                 api_url: String::from("https://api.listenbrainz.org"),
                 auth_token: String::from(""),
-            })
+            }),
         };
     }
 }
 
 impl Config {
     /// Creates and saves a new config with default values
-    pub fn new(config_file: &PathBuf) -> std::io::Result<Config> {        
+    pub fn new(config_file: &PathBuf) -> std::io::Result<Config> {
         let config = Config::default();
         config.save(config_file)?;
-        
+
         Ok(config)
     }
 
     /// Loads config from given file path
     pub fn from(config_file: &PathBuf) -> std::result::Result<Config, toml::de::Error> {
-        return toml::from_str(&read_to_string(config_file)
-            .expect("Failed to initalize music config: File not found!"));
+        return toml::from_str(
+            &read_to_string(config_file)
+                .expect("Failed to initalize music config: File not found!"),
+        );
     }
-    
+
     /// Saves config to given path
     /// Saves -> temp file, if successful, removes old config, and renames temp to given path
     pub fn save(&self, config_file: &PathBuf) -> std::io::Result<()> {
         let toml = toml::to_string_pretty(self).unwrap();
-        
+
         let mut temp_file = config_file.clone();
         temp_file.set_extension("tomltemp");
-        
+
         fs::write(&temp_file, toml)?;
 
         // If configuration file already exists, delete it
         match fs::metadata(config_file) {
             Ok(_) => fs::remove_file(config_file)?,
-            Err(_) => {},
+            Err(_) => {}
         }
 
         fs::rename(temp_file, config_file)?;
diff --git a/src/music_controller/init.rs b/src/music_controller/init.rs
index 80b91bf..38b101f 100644
--- a/src/music_controller/init.rs
+++ b/src/music_controller/init.rs
@@ -1,9 +1,7 @@
-use std::path::Path;
 use std::fs::File;
+use std::path::Path;
 
-pub fn init() {
-
-}
+pub fn init() {}
 
 fn init_config() {
     let config_path = "./config.toml";
@@ -13,7 +11,4 @@ fn init_config() {
     }
 }
 
-fn init_db() {  
-
-}
-
+fn init_db() {}
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 7d00b8a..24b8379 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -1,8 +1,8 @@
 use std::path::PathBuf;
-use std::sync::{RwLock, Arc};
+use std::sync::{Arc, RwLock};
 
 use crate::music_controller::config::Config;
-use crate::music_player::music_player::{MusicPlayer, PlayerStatus, DecoderMessage, DSPMessage};
+use crate::music_player::music_player::{DSPMessage, DecoderMessage, MusicPlayer, PlayerStatus};
 use crate::music_storage::music_db::{MusicLibrary, Song, Tag};
 
 pub struct MusicController {
@@ -13,75 +13,78 @@ pub struct MusicController {
 
 impl MusicController {
     /// Creates new MusicController with config at given path
-    pub fn new(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::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)
+            Err(error) => return Err(error),
         };
-        
+
         let controller = MusicController {
             config,
             library,
             music_player,
         };
-        
-        return Ok(controller)
+
+        return Ok(controller);
     }
-        
+
     /// Creates new music controller from a config at given path
     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)
+            Err(error) => return Err(error),
         };
-        
+
         let controller = MusicController {
             config,
             library,
             music_player,
         };
-        
-        return Ok(controller)
+
+        return Ok(controller);
     }
-    
+
     /// Sends given message to control music player
     pub fn song_control(&mut self, message: DecoderMessage) {
         self.music_player.send_message(message);
     }
-    
+
     /// Gets status of the music player
     pub fn player_status(&mut self) -> PlayerStatus {
         return self.music_player.get_status();
     }
-    
+
     /// Gets current song being controlled, if any
     pub fn get_current_song(&self) -> Option<Song> {
         return self.music_player.get_current_song();
     }
-    
+
     /// Gets audio playback volume
     pub fn get_vol(&self) -> f32 {
         return self.music_player.music_processor.audio_volume;
     }
-    
+
     /// Sets audio playback volume on a scale of 0.0 to 1.0
     pub fn set_vol(&mut self, volume: f32) {
         self.music_player.music_processor.audio_volume = volume;
-        self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone()))));
+        self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(
+            self.music_player.music_processor.clone(),
+        ))));
     }
-    
+
     /// Queries the [MusicLibrary], returning a `Vec<Song>`
     pub fn query_library(
         &self,
         query_string: &String,
         target_tags: Vec<Tag>,
         search_location: bool,
-        sort_by: Vec<Tag>
+        sort_by: Vec<Tag>,
     ) -> Option<Vec<&Song>> {
-        self.library.query(query_string, &target_tags, search_location, &sort_by)
+        self.library
+            .query(query_string, &target_tags, search_location, &sort_by)
     }
 }
diff --git a/src/music_player/music_output.rs b/src/music_player/music_output.rs
index 795e9e9..76e6d3b 100644
--- a/src/music_player/music_output.rs
+++ b/src/music_player/music_output.rs
@@ -1,7 +1,7 @@
 use std::{result, thread};
 
-use symphonia::core::audio::{AudioBufferRef, SignalSpec, RawSample, SampleBuffer};
-use symphonia::core::conv::{ConvertibleSample, IntoSample, FromSample};
+use symphonia::core::audio::{AudioBufferRef, RawSample, SampleBuffer, SignalSpec};
+use symphonia::core::conv::{ConvertibleSample, FromSample, IntoSample};
 use symphonia::core::units::Duration;
 
 use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
@@ -25,10 +25,21 @@ pub enum AudioOutputError {
 
 pub type Result<T> = result::Result<T, AudioOutputError>;
 
-pub trait OutputSample: SizedSample + FromSample<f32> + IntoSample<f32> +cpal::Sample + ConvertibleSample + RawSample + std::marker::Send + 'static {}
+pub trait OutputSample:
+    SizedSample
+    + FromSample<f32>
+    + IntoSample<f32>
+    + cpal::Sample
+    + ConvertibleSample
+    + RawSample
+    + std::marker::Send
+    + 'static
+{
+}
 
 pub struct AudioOutput<T>
-where T: OutputSample,
+where
+    T: OutputSample,
 {
     ring_buf_producer: rb::Producer<T>,
     sample_buf: SampleBuffer<T>,
@@ -48,80 +59,112 @@ impl OutputSample for f64 {}
 //create a new trait with functions, then impl that somehow
 
 pub fn open_stream(spec: SignalSpec, duration: Duration) -> Result<Box<dyn AudioStream>> {
-        let host = cpal::default_host();
-        
-        // Uses default audio device
-        let device = match host.default_output_device() {
-            Some(device) => device,
-            _ => return Err(AudioOutputError::OpenStreamError),
-        };
-        
-        let config = match device.default_output_config() {
-            Ok(config) => config,
-            Err(err) => return Err(AudioOutputError::OpenStreamError),
-        };
-        
-        return match config.sample_format(){
-            cpal::SampleFormat::I8 => AudioOutput::<i8>::create_stream(spec, &device, &config.into(), duration),
-            cpal::SampleFormat::I16 => AudioOutput::<i16>::create_stream(spec, &device, &config.into(), duration),
-            cpal::SampleFormat::I32 => AudioOutput::<i32>::create_stream(spec, &device, &config.into(), duration),
-            //cpal::SampleFormat::I64 => AudioOutput::<i64>::create_stream(spec, &device, &config.into(), duration),
-            cpal::SampleFormat::U8 => AudioOutput::<u8>::create_stream(spec, &device, &config.into(), duration),
-            cpal::SampleFormat::U16 => AudioOutput::<u16>::create_stream(spec, &device, &config.into(), duration),
-            cpal::SampleFormat::U32 => AudioOutput::<u32>::create_stream(spec, &device, &config.into(), duration),
-            //cpal::SampleFormat::U64 => AudioOutput::<u64>::create_stream(spec, &device, &config.into(), duration),
-            cpal::SampleFormat::F32 => AudioOutput::<f32>::create_stream(spec, &device, &config.into(), duration),
-            cpal::SampleFormat::F64 => AudioOutput::<f64>::create_stream(spec, &device, &config.into(), duration),
-            _ => todo!(),
-        };
+    let host = cpal::default_host();
+
+    // Uses default audio device
+    let device = match host.default_output_device() {
+        Some(device) => device,
+        _ => return Err(AudioOutputError::OpenStreamError),
+    };
+
+    let config = match device.default_output_config() {
+        Ok(config) => config,
+        Err(err) => return Err(AudioOutputError::OpenStreamError),
+    };
+
+    return match config.sample_format() {
+        cpal::SampleFormat::I8 => {
+            AudioOutput::<i8>::create_stream(spec, &device, &config.into(), duration)
+        }
+        cpal::SampleFormat::I16 => {
+            AudioOutput::<i16>::create_stream(spec, &device, &config.into(), duration)
+        }
+        cpal::SampleFormat::I32 => {
+            AudioOutput::<i32>::create_stream(spec, &device, &config.into(), duration)
+        }
+        //cpal::SampleFormat::I64 => AudioOutput::<i64>::create_stream(spec, &device, &config.into(), duration),
+        cpal::SampleFormat::U8 => {
+            AudioOutput::<u8>::create_stream(spec, &device, &config.into(), duration)
+        }
+        cpal::SampleFormat::U16 => {
+            AudioOutput::<u16>::create_stream(spec, &device, &config.into(), duration)
+        }
+        cpal::SampleFormat::U32 => {
+            AudioOutput::<u32>::create_stream(spec, &device, &config.into(), duration)
+        }
+        //cpal::SampleFormat::U64 => AudioOutput::<u64>::create_stream(spec, &device, &config.into(), duration),
+        cpal::SampleFormat::F32 => {
+            AudioOutput::<f32>::create_stream(spec, &device, &config.into(), duration)
+        }
+        cpal::SampleFormat::F64 => {
+            AudioOutput::<f64>::create_stream(spec, &device, &config.into(), duration)
+        }
+        _ => todo!(),
+    };
 }
 
 impl<T: OutputSample> AudioOutput<T> {
     // Creates the stream (TODO: Merge w/open_stream?)
-    fn create_stream(spec: SignalSpec, device: &cpal::Device, config: &cpal::StreamConfig, duration: Duration) -> Result<Box<dyn AudioStream>> {
+    fn create_stream(
+        spec: SignalSpec,
+        device: &cpal::Device,
+        config: &cpal::StreamConfig,
+        duration: Duration,
+    ) -> Result<Box<dyn AudioStream>> {
         let num_channels = config.channels as usize;
-        
+
         // Ring buffer is created with 200ms audio capacity
         let ring_len = ((50 * config.sample_rate.0 as usize) / 1000) * num_channels;
-        let ring_buf= rb::SpscRb::new(ring_len);
-        
+        let ring_buf = rb::SpscRb::new(ring_len);
+
         let ring_buf_producer = ring_buf.producer();
         let ring_buf_consumer = ring_buf.consumer();
-        
+
         let stream_result = device.build_output_stream(
-            config, 
+            config,
             move |data: &mut [T], _: &cpal::OutputCallbackInfo| {
                 // Writes samples in the ring buffer to the audio output
                 let written = ring_buf_consumer.read(data).unwrap_or(0);
-                
+
                 // Mutes non-written samples
-                data[written..].iter_mut().for_each(|sample| *sample = T::MID);
+                data[written..]
+                    .iter_mut()
+                    .for_each(|sample| *sample = T::MID);
             },
             //TODO: Handle error here properly
             move |err| println!("Yeah we erroring out here"),
-            None
+            None,
         );
-        
+
         if let Err(err) = stream_result {
             return Err(AudioOutputError::OpenStreamError);
         }
-        
+
         let stream = stream_result.unwrap();
-        
+
         //Start output stream
         if let Err(err) = stream.play() {
             return Err(AudioOutputError::PlayStreamError);
         }
-        
+
         let sample_buf = SampleBuffer::<T>::new(duration, spec);
-        
+
         let mut resampler = None;
         if spec.rate != config.sample_rate.0 {
             println!("Resampling enabled");
-            resampler = Some(Resampler::new(spec, config.sample_rate.0 as usize, duration))
+            resampler = Some(Resampler::new(
+                spec,
+                config.sample_rate.0 as usize,
+                duration,
+            ))
         }
-        
-        Ok(Box::new(AudioOutput { ring_buf_producer, sample_buf, stream, resampler}))
+
+        Ok(Box::new(AudioOutput {
+            ring_buf_producer,
+            sample_buf,
+            stream,
+            resampler,
+        }))
     }
 }
 
@@ -131,7 +174,7 @@ impl<T: OutputSample> AudioStream for AudioOutput<T> {
         if decoded.frames() == 0 {
             return Ok(());
         }
-        
+
         let mut samples: &[T] = if let Some(resampler) = &mut self.resampler {
             // Resamples if required
             match resampler.resample(decoded) {
@@ -142,25 +185,25 @@ impl<T: OutputSample> AudioStream for AudioOutput<T> {
             self.sample_buf.copy_interleaved_ref(decoded);
             self.sample_buf.samples()
         };
-        
+
         // Write samples into ring buffer
         while let Some(written) = self.ring_buf_producer.write_blocking(samples) {
             samples = &samples[written..];
         }
-        
+
         Ok(())
     }
-    
+
     // Flushes resampler if needed
     fn flush(&mut self) {
         if let Some(resampler) = &mut self.resampler {
-            let mut stale_samples  = resampler.flush().unwrap_or_default();
-            
+            let mut stale_samples = resampler.flush().unwrap_or_default();
+
             while let Some(written) = self.ring_buf_producer.write_blocking(stale_samples) {
                 stale_samples = &stale_samples[written..];
             }
         }
-        
+
         let _ = self.stream.pause();
     }
-}
\ No newline at end of file
+}
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
index f12f68c..9ed99e5 100644
--- a/src/music_player/music_player.rs
+++ b/src/music_player/music_player.rs
@@ -1,18 +1,18 @@
-use std::sync::mpsc::{self, Sender, Receiver};
+use std::io::SeekFrom;
+use std::sync::mpsc::{self, Receiver, Sender};
 use std::sync::{Arc, RwLock};
 use std::thread;
-use std::io::SeekFrom;
 
 use async_std::io::ReadExt;
 use async_std::task;
 
 use futures::future::join_all;
-use symphonia::core::codecs::{CODEC_TYPE_NULL, DecoderOptions, Decoder};
+use symphonia::core::codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL};
+use symphonia::core::errors::Error;
 use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
-use symphonia::core::io::{MediaSourceStream, MediaSource};
+use symphonia::core::io::{MediaSource, MediaSourceStream};
 use symphonia::core::meta::MetadataOptions;
 use symphonia::core::probe::Hint;
-use symphonia::core::errors::Error;
 use symphonia::core::units::{Time, TimeBase};
 
 use futures::AsyncBufRead;
@@ -20,8 +20,10 @@ use futures::AsyncBufRead;
 use crate::music_controller::config::Config;
 use crate::music_player::music_output::AudioStream;
 use crate::music_processor::music_processor::MusicProcessor;
-use crate::music_storage::music_db::{URI, Song};
-use crate::music_tracker::music_tracker::{MusicTracker, TrackerError, LastFM, DiscordRPC, ListenBrainz};
+use crate::music_storage::music_db::{Song, URI};
+use crate::music_tracker::music_tracker::{
+    DiscordRPC, LastFM, ListenBrainz, MusicTracker, TrackerError,
+};
 
 // Struct that controls playback of music
 pub struct MusicPlayer {
@@ -49,18 +51,18 @@ pub enum DecoderMessage {
     Pause,
     Stop,
     SeekTo(u64),
-    DSP(DSPMessage)
+    DSP(DSPMessage),
 }
 
 #[derive(Clone)]
 pub enum TrackerMessage {
     Track(Song),
-    TrackNow(Song)
+    TrackNow(Song),
 }
 
 #[derive(Debug, Clone)]
 pub enum DSPMessage {
-    UpdateProcessor(Box<MusicProcessor>)
+    UpdateProcessor(Box<MusicProcessor>),
 }
 
 // Holds a song decoder reader, etc
@@ -75,49 +77,59 @@ struct SongHandler {
 impl SongHandler {
     pub fn new(uri: &URI) -> Result<Self, ()> {
         // Opens remote/local source and creates MediaSource for symphonia
-        let config = RemoteOptions {media_buffer_len: 10000, forward_buffer_len: 10000};
+        let config = RemoteOptions {
+            media_buffer_len: 10000,
+            forward_buffer_len: 10000,
+        };
         let src: Box<dyn MediaSource> = match uri {
-            URI::Local(path) => {
-                match std::fs::File::open(path) {
-                    Ok(file) => Box::new(file),
-                    Err(_) => return Err(()),
-                }
+            URI::Local(path) => match std::fs::File::open(path) {
+                Ok(file) => Box::new(file),
+                Err(_) => return Err(()),
             },
             URI::Remote(_, location) => {
                 match RemoteSource::new(location.to_str().unwrap(), &config) {
                     Ok(remote_source) => Box::new(remote_source),
                     Err(_) => return Err(()),
                 }
-            },
-            _ => todo!()
+            }
+            _ => todo!(),
         };
-        
+
         let mss = MediaSourceStream::new(src, Default::default());
-        
+
         // Use default metadata and format options
         let meta_opts: MetadataOptions = Default::default();
         let fmt_opts: FormatOptions = Default::default();
 
         let hint = Hint::new();
-        
-        let probed = symphonia::default::get_probe().format(&hint, mss, &fmt_opts, &meta_opts).expect("Unsupported format");
-        
-        let reader  = probed.format;
-        
-        let track = reader.tracks()
-                    .iter()
-                    .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
-                    .expect("no supported audio tracks");
-        
+
+        let probed = symphonia::default::get_probe()
+            .format(&hint, mss, &fmt_opts, &meta_opts)
+            .expect("Unsupported format");
+
+        let reader = probed.format;
+
+        let track = reader
+            .tracks()
+            .iter()
+            .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
+            .expect("no supported audio tracks");
+
         let time_base = track.codec_params.time_base;
         let duration = track.codec_params.n_frames;
-                    
+
         let dec_opts: DecoderOptions = Default::default();
-        
-        let decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
-                                                    .expect("unsupported codec");
-        
-        return Ok(SongHandler {reader, decoder, time_base, duration});
+
+        let decoder = symphonia::default::get_codecs()
+            .make(&track.codec_params, &dec_opts)
+            .expect("unsupported codec");
+
+        return Ok(SongHandler {
+            reader,
+            decoder,
+            time_base,
+            duration,
+        });
     }
 }
 
@@ -127,9 +139,14 @@ impl MusicPlayer {
         let (message_sender, message_receiver) = mpsc::channel();
         let (status_sender, status_receiver) = mpsc::channel();
         let current_song = Arc::new(RwLock::new(None));
-        
-        MusicPlayer::start_player(message_receiver, status_sender, config.clone(), current_song.clone());
-        
+
+        MusicPlayer::start_player(
+            message_receiver,
+            status_sender,
+            config.clone(),
+            current_song.clone(),
+        );
+
         MusicPlayer {
             music_processor: MusicProcessor::new(),
             music_trackers: Vec::new(),
@@ -140,15 +157,19 @@ impl MusicPlayer {
             config,
         }
     }
-    
-    fn start_tracker(status_sender: Sender<Result<(), TrackerError>>, tracker_receiver: Receiver<TrackerMessage>, config: Arc<RwLock<Config>>) {
+
+    fn start_tracker(
+        status_sender: Sender<Result<(), TrackerError>>,
+        tracker_receiver: Receiver<TrackerMessage>,
+        config: Arc<RwLock<Config>>,
+    ) {
         thread::spawn(move || {
             let global_config = &*config.read().unwrap();
             // Sets local config for trackers to detect changes
             let local_config = global_config.clone();
             let mut trackers: Vec<Box<dyn MusicTracker>> = Vec::new();
             // Updates local trackers to the music controller config // TODO: refactor
-            let update_trackers = |trackers: &mut Vec<Box<dyn MusicTracker>>|{
+            let update_trackers = |trackers: &mut Vec<Box<dyn MusicTracker>>| {
                 if let Some(lastfm_config) = global_config.lastfm.clone() {
                     if lastfm_config.enabled {
                         trackers.push(Box::new(LastFM::new(&lastfm_config)));
@@ -177,9 +198,13 @@ impl MusicPlayer {
                         let mut futures = Vec::new();
                         for tracker in trackers.iter_mut() {
                             match message.clone() {
-                                Ok(TrackerMessage::Track(song)) => futures.push(tracker.track_song(song)),
-                                Ok(TrackerMessage::TrackNow(song)) => futures.push(tracker.track_now(song)),
-                                Err(_) => {},
+                                Ok(TrackerMessage::Track(song)) => {
+                                    futures.push(tracker.track_song(song))
+                                }
+                                Ok(TrackerMessage::TrackNow(song)) => {
+                                    futures.push(tracker.track_now(song))
+                                }
+                                Err(_) => {}
                             }
                         }
                         results = join_all(futures).await;
@@ -192,30 +217,41 @@ impl MusicPlayer {
             }
         });
     }
-    
+
     // Opens and plays song with given path in separate thread
-    fn start_player(message_receiver: Receiver<DecoderMessage>, status_sender: Sender<PlayerStatus>, config: Arc<RwLock<Config>>, current_song: Arc<RwLock<Option<Song>>>) {
+    fn start_player(
+        message_receiver: Receiver<DecoderMessage>,
+        status_sender: Sender<PlayerStatus>,
+        config: Arc<RwLock<Config>>,
+        current_song: Arc<RwLock<Option<Song>>>,
+    ) {
         // Creates thread that audio is decoded in
         thread::spawn(move || {
             let current_song = current_song;
-            
+
             let mut song_handler = None;
-            
+
             let mut seek_time: Option<u64> = None;
-            
+
             let mut audio_output: Option<Box<dyn AudioStream>> = None;
-            
+
             let mut music_processor = MusicProcessor::new();
-            
-            let (tracker_sender, tracker_receiver): (Sender<TrackerMessage>, Receiver<TrackerMessage>) = mpsc::channel();
-            let (tracker_status_sender, tracker_status_receiver): (Sender<Result<(), TrackerError>>, Receiver<Result<(), TrackerError>>) = mpsc::channel();
-            
+
+            let (tracker_sender, tracker_receiver): (
+                Sender<TrackerMessage>,
+                Receiver<TrackerMessage>,
+            ) = mpsc::channel();
+            let (tracker_status_sender, tracker_status_receiver): (
+                Sender<Result<(), TrackerError>>,
+                Receiver<Result<(), TrackerError>>,
+            ) = mpsc::channel();
+
             MusicPlayer::start_tracker(tracker_status_sender, tracker_receiver, config);
-            
+
             let mut song_tracked = false;
             let mut song_time = 0.0;
             let mut paused = true;
-            'main_decode: loop {              
+            'main_decode: loop {
                 'handle_message: loop {
                     let message = if paused {
                         // Pauses playback by blocking on waiting for new player messages
@@ -254,26 +290,34 @@ impl MusicPlayer {
                             status_sender.send(PlayerStatus::Paused).unwrap();
                         }
                         Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time),
-                        Some(DecoderMessage::DSP(dsp_message)) => {
-                            match dsp_message {
-                                DSPMessage::UpdateProcessor(new_processor) => music_processor = *new_processor,
+                        Some(DecoderMessage::DSP(dsp_message)) => match dsp_message {
+                            DSPMessage::UpdateProcessor(new_processor) => {
+                                music_processor = *new_processor
                             }
-                        }
+                        },
                         // Exits main decode loop and subsequently ends thread
                         Some(DecoderMessage::Stop) => {
                             status_sender.send(PlayerStatus::Stopped).unwrap();
-                            break 'main_decode
+                            break 'main_decode;
                         }
-                        None => {},
+                        None => {}
                     }
                     status_sender.send(PlayerStatus::Error).unwrap();
-                }                
+                }
                 // In theory this check should not need to occur?
-                if let (Some(song_handler), current_song) = (&mut song_handler, &*current_song.read().unwrap()) {
+                if let (Some(song_handler), current_song) =
+                    (&mut song_handler, &*current_song.read().unwrap())
+                {
                     match seek_time {
                         Some(time) => {
-                            let seek_to = SeekTo::Time { time: Time::from(time), track_id: Some(0) };
-                            song_handler.reader.seek(SeekMode::Accurate, seek_to).unwrap();
+                            let seek_to = SeekTo::Time {
+                                time: Time::from(time),
+                                track_id: Some(0),
+                            };
+                            song_handler
+                                .reader
+                                .seek(SeekMode::Accurate, seek_to)
+                                .unwrap();
                             seek_time = None;
                         }
                         None => {} //Nothing to do!
@@ -286,60 +330,74 @@ impl MusicPlayer {
                             panic!("{}", err);
                         }
                     };
-                    
+
                     if let (Some(time_base), Some(song)) = (song_handler.time_base, current_song) {
                         let time_units = time_base.calc_time(packet.ts);
                         song_time = time_units.seconds as f64 + time_units.frac;
                         // Tracks song now if song has just started
                         if song_time == 0.0 {
-                            tracker_sender.send(TrackerMessage::TrackNow(song.clone())).unwrap();
+                            tracker_sender
+                                .send(TrackerMessage::TrackNow(song.clone()))
+                                .unwrap();
                         }
-                        
+
                         if let Some(duration) = song_handler.duration {
                             let song_duration = time_base.calc_time(duration);
-                            let song_duration_secs = song_duration.seconds as f64 + song_duration.frac;
+                            let song_duration_secs =
+                                song_duration.seconds as f64 + song_duration.frac;
                             // Tracks song if current time is past half of total song duration or past 4 minutes
-                            if (song_duration_secs / 2.0 < song_time || song_time > 240.0) && !song_tracked {
+                            if (song_duration_secs / 2.0 < song_time || song_time > 240.0)
+                                && !song_tracked
+                            {
                                 song_tracked = true;
-                                tracker_sender.send(TrackerMessage::Track(song.clone())).unwrap();
+                                tracker_sender
+                                    .send(TrackerMessage::Track(song.clone()))
+                                    .unwrap();
                             }
                         }
                     }
-                    
-                    status_sender.send(PlayerStatus::Playing(song_time)).unwrap();
-                    
+
+                    status_sender
+                        .send(PlayerStatus::Playing(song_time))
+                        .unwrap();
+
                     match song_handler.decoder.decode(&packet) {
                         Ok(decoded) => {
                             // Opens audio stream if there is not one
                             if audio_output.is_none() {
                                 let spec = *decoded.spec();
                                 let duration = decoded.capacity() as u64;
-                                
-                                audio_output.replace(crate::music_player::music_output::open_stream(spec, duration).unwrap());
+
+                                audio_output.replace(
+                                    crate::music_player::music_output::open_stream(spec, duration)
+                                        .unwrap(),
+                                );
                             }
                             // Handles audio normally provided there is an audio stream
                             if let Some(ref mut audio_output) = audio_output {
                                 // Changes buffer of the MusicProcessor if the packet has a differing capacity or spec
-                                if music_processor.audio_buffer.capacity() != decoded.capacity() ||music_processor.audio_buffer.spec() != decoded.spec() {
+                                if music_processor.audio_buffer.capacity() != decoded.capacity()
+                                    || music_processor.audio_buffer.spec() != decoded.spec()
+                                {
                                     let spec = *decoded.spec();
                                     let duration = decoded.capacity() as u64;
-                                    
+
                                     music_processor.set_buffer(duration, spec);
                                 }
                                 let transformed_audio = music_processor.process(&decoded);
-                                
+
                                 // Writes transformed packet to audio out
                                 audio_output.write(transformed_audio).unwrap()
                             }
-                        },
+                        }
                         Err(Error::IoError(_)) => {
                             // rest in peace packet
                             continue;
-                        },
+                        }
                         Err(Error::DecodeError(_)) => {
                             // may you one day be decoded
                             continue;
-                        },
+                        }
                         Err(err) => {
                             // Unrecoverable, though shouldn't panic here
                             panic!("{}", err);
@@ -349,28 +407,28 @@ impl MusicPlayer {
             }
         });
     }
-    
+
     // Updates status by checking on messages from spawned thread
     fn update_player(&mut self) {
         for message in self.status_receiver.try_recv() {
             self.player_status = message;
         }
     }
-    
-    pub fn get_current_song(&self) -> Option<Song>{
+
+    pub fn get_current_song(&self) -> Option<Song> {
         match self.current_song.try_read() {
             Ok(song) => return (*song).clone(),
             Err(_) => return None,
         }
     }
-    
+
     // Sends message to spawned thread
     pub fn send_message(&mut self, message: DecoderMessage) {
         self.update_player();
         // Checks that message sender exists before sending a message off
         self.message_sender.send(message).unwrap();
     }
-    
+
     pub fn get_status(&mut self) -> PlayerStatus {
         self.update_player();
         return self.player_status;
@@ -394,7 +452,7 @@ impl Default for RemoteOptions {
             media_buffer_len: 100000,
             forward_buffer_len: 1024,
         }
-    }   
+    }
 }
 
 /// A remote source of media
@@ -408,12 +466,12 @@ struct RemoteSource {
 impl RemoteSource {
     /// Creates a new RemoteSource with given uri and configuration
     pub fn new(uri: &str, config: &RemoteOptions) -> Result<Self, surf::Error> {
-        let mut response = task::block_on(async { 
+        let mut response = task::block_on(async {
             return surf::get(uri).await;
         })?;
-        
+
         let reader = response.take_body().into_reader();
-        
+
         Ok(RemoteSource {
             reader,
             media_buffer: Vec::new(),
@@ -433,16 +491,16 @@ impl std::io::Read for RemoteSource {
                     Ok(_) => {
                         self.media_buffer.extend_from_slice(&buffer);
                         return Ok(());
-                    },
+                    }
                     Err(err) => return Err(err),
                 }
             });
             match read_bytes {
                 Err(err) => return Err(err),
-                _ => {},
+                _ => {}
             }
         }
-        // Reads bytes from the media buffer into the buffer given by 
+        // Reads bytes from the media buffer into the buffer given by
         let mut bytes_read = 0;
         for location in 0..1024 {
             if (location + self.offset as usize) < self.media_buffer.len() {
@@ -450,7 +508,7 @@ impl std::io::Read for RemoteSource {
                 bytes_read += 1;
             }
         }
-        
+
         self.offset += bytes_read;
         return Ok(bytes_read as usize);
     }
@@ -463,13 +521,13 @@ impl std::io::Seek for RemoteSource {
         match pos {
             // Offset is set to given position
             SeekFrom::Start(pos) => {
-                if pos > self.media_buffer.len() as u64{
+                if pos > self.media_buffer.len() as u64 {
                     self.offset = self.media_buffer.len() as u64;
                 } else {
                     self.offset = pos;
                 }
                 return Ok(self.offset);
-            },
+            }
             // Offset is set to length of buffer + given position
             SeekFrom::End(pos) => {
                 if self.media_buffer.len() as u64 + pos as u64 > self.media_buffer.len() as u64 {
@@ -478,16 +536,16 @@ impl std::io::Seek for RemoteSource {
                     self.offset = self.media_buffer.len() as u64 + pos as u64;
                 }
                 return Ok(self.offset);
-            },
+            }
             // Offset is set to current offset + given position
             SeekFrom::Current(pos) => {
-                if self.offset + pos as u64 > self.media_buffer.len() as u64{
+                if self.offset + pos as u64 > self.media_buffer.len() as u64 {
                     self.offset = self.media_buffer.len() as u64;
                 } else {
                     self.offset += pos as u64
                 }
                 return Ok(self.offset);
-            },
+            }
         }
     }
 }
@@ -496,7 +554,7 @@ impl MediaSource for RemoteSource {
     fn is_seekable(&self) -> bool {
         return true;
     }
-    
+
     fn byte_len(&self) -> Option<u64> {
         return None;
     }
diff --git a/src/music_player/music_resampler.rs b/src/music_player/music_resampler.rs
index f654a17..4040835 100644
--- a/src/music_player/music_resampler.rs
+++ b/src/music_player/music_resampler.rs
@@ -48,7 +48,8 @@ where
         // Interleave the planar samples from Rubato.
         let num_channels = self.output.len();
 
-        self.interleaved.resize(num_channels * self.output[0].len(), T::MID);
+        self.interleaved
+            .resize(num_channels * self.output[0].len(), T::MID);
 
         for (i, frame) in self.interleaved.chunks_exact_mut(num_channels).enumerate() {
             for (ch, s) in frame.iter_mut().enumerate() {
@@ -81,7 +82,13 @@ where
 
         let input = vec![Vec::with_capacity(duration); num_channels];
 
-        Self { resampler, input, output, duration, interleaved: Default::default() }
+        Self {
+            resampler,
+            input,
+            output,
+            duration,
+            interleaved: Default::default(),
+        }
     }
 
     /// Resamples a planar/non-interleaved input.
@@ -144,4 +151,4 @@ where
         let src = input.chan(c);
         dst.extend(src.iter().map(|&s| s.into_sample()));
     }
-}
\ No newline at end of file
+}
diff --git a/src/music_processor/music_processor.rs b/src/music_processor/music_processor.rs
index e6d6608..64f4eaa 100644
--- a/src/music_processor/music_processor.rs
+++ b/src/music_processor/music_processor.rs
@@ -1,6 +1,6 @@
 use std::fmt::Debug;
 
-use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, AsAudioBufferRef, SignalSpec};
+use symphonia::core::audio::{AsAudioBufferRef, AudioBuffer, AudioBufferRef, Signal, SignalSpec};
 
 #[derive(Clone)]
 pub struct MusicProcessor {
@@ -10,7 +10,9 @@ pub struct MusicProcessor {
 
 impl Debug for MusicProcessor {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("MusicProcessor").field("audio_volume", &self.audio_volume).finish()
+        f.debug_struct("MusicProcessor")
+            .field("audio_volume", &self.audio_volume)
+            .finish()
     }
 }
 
@@ -22,22 +24,22 @@ impl MusicProcessor {
             audio_volume: 1.0,
         }
     }
-    
+
     /// Processes audio samples
-    /// 
+    ///
     /// Currently only supports transformations of volume
     pub fn process(&mut self, audio_buffer_ref: &AudioBufferRef) -> AudioBufferRef {
         audio_buffer_ref.convert(&mut self.audio_buffer);
-        
+
         let process = |sample| sample * self.audio_volume;
-        
+
         self.audio_buffer.transform(process);
-        
+
         return self.audio_buffer.as_audio_buffer_ref();
     }
-    
+
     /// Sets buffer of the MusicProcessor
     pub fn set_buffer(&mut self, duration: u64, spec: SignalSpec) {
         self.audio_buffer = AudioBuffer::new(duration, spec);
     }
-}
\ No newline at end of file
+}
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index e59f51c..b6457d3 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -362,7 +362,7 @@ impl MusicLibrary {
                     Ok(_) => {
                         //println!("{:?}", target_file.path());
                         total += 1
-                    },
+                    }
                     Err(_error) => {
                         //println!("{}, {:?}: {}", format, target_file.file_name(), _error)
                     } // TODO: Handle more of these errors
@@ -481,8 +481,14 @@ impl MusicLibrary {
         let cue_data = CD::parse_file(cuesheet.to_owned()).unwrap();
 
         // Get album level information
-        let album_title = &cue_data.get_cdtext().read(cue::cd_text::PTI::Title).unwrap_or(String::new());
-        let album_artist = &cue_data.get_cdtext().read(cue::cd_text::PTI::Performer).unwrap_or(String::new());
+        let album_title = &cue_data
+            .get_cdtext()
+            .read(cue::cd_text::PTI::Title)
+            .unwrap_or(String::new());
+        let album_artist = &cue_data
+            .get_cdtext()
+            .read(cue::cd_text::PTI::Performer)
+            .unwrap_or(String::new());
 
         let parent_dir = cuesheet.parent().expect("The file has no parent path??");
         for (i, track) in cue_data.tracks().iter().enumerate() {
@@ -498,17 +504,17 @@ impl MusicLibrary {
             // Get the track timing information
             let pregap = match track.get_zero_pre() {
                 Some(pregap) => Duration::from_micros((pregap as f32 * 13333.333333) as u64),
-                None => Duration::from_secs(0)
+                None => Duration::from_secs(0),
             };
             let postgap = match track.get_zero_post() {
                 Some(postgap) => Duration::from_micros((postgap as f32 * 13333.333333) as u64),
-                None => Duration::from_secs(0)
+                None => Duration::from_secs(0),
             };
             let mut start = Duration::from_micros((track.get_start() as f32 * 13333.333333) as u64);
             start -= pregap;
 
             let duration = match track.get_length() {
-                Some(len) =>  Duration::from_micros((len as f32 * 13333.333333) as u64),
+                Some(len) => Duration::from_micros((len as f32 * 13333.333333) as u64),
                 None => {
                     let tagged_file = match lofty::read_from_path(&audio_location) {
                         Ok(tagged_file) => tagged_file,
@@ -537,33 +543,31 @@ impl MusicLibrary {
             tags.push((Tag::Key("AlbumArtist".to_string()), album_artist.clone()));
             match track.get_cdtext().read(cue::cd_text::PTI::Title) {
                 Some(title) => tags.push((Tag::Title, title)),
-                None => {
-                    match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) {
-                        Some(title) => tags.push((Tag::Title, title)),
-                        None => {
-                            let namestr = format!("{} - {}", i, track.get_filename());
-                            tags.push((Tag::Title, namestr))
-                        }
+                None => match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) {
+                    Some(title) => tags.push((Tag::Title, title)),
+                    None => {
+                        let namestr = format!("{} - {}", i, track.get_filename());
+                        tags.push((Tag::Title, namestr))
                     }
-                }
+                },
             };
             match track.get_cdtext().read(cue::cd_text::PTI::Performer) {
                 Some(artist) => tags.push((Tag::Artist, artist)),
-                None => ()
+                None => (),
             };
             match track.get_cdtext().read(cue::cd_text::PTI::Genre) {
                 Some(genre) => tags.push((Tag::Genre, genre)),
-                None => ()
+                None => (),
             };
             match track.get_cdtext().read(cue::cd_text::PTI::Message) {
                 Some(comment) => tags.push((Tag::Comment, comment)),
-                None => ()
+                None => (),
             };
 
             let album_art = Vec::new();
 
             let new_song = Song {
-                location: URI::Cue{
+                location: URI::Cue {
                     location: audio_location,
                     start,
                     end,
@@ -586,8 +590,8 @@ impl MusicLibrary {
                 Ok(_) => tracks_added += 1,
                 Err(_error) => {
                     //println!("{}", _error);
-                    continue
-                },
+                    continue;
+                }
             };
         }
 
@@ -604,8 +608,8 @@ impl MusicLibrary {
         match new_song.location {
             URI::Local(_) if self.query_path(&new_song.location.path()).is_some() => {
                 return Err(format!("Location exists for {:?}", new_song.location).into())
-            },
-            _ => ()
+            }
+            _ => (),
         }
 
         self.library.push(new_song);
@@ -652,7 +656,7 @@ impl MusicLibrary {
             for tag in target_tags {
                 let track_result = match track.get_tag(&tag) {
                     Some(value) => value,
-                    None => continue
+                    None => continue,
                 };
 
                 if normalize(track_result).contains(&normalize(&query_string)) {
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index e61b405..797d0d2 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,10 +1,6 @@
-use std::path::Path;
 use crate::music_controller::config::Config;
+use std::path::Path;
 
-pub fn playlist_add(
-    config: &Config,
-    playlist_name: &str,
-    song_paths: &Vec<&Path>
-) {
+pub fn playlist_add(config: &Config, playlist_name: &str, song_paths: &Vec<&Path>) {
     unimplemented!()
 }
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 12e57e8..9ffd73d 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -1,11 +1,11 @@
-use std::time::{SystemTime, UNIX_EPOCH};
-use std::collections::BTreeMap;
 use serde_json::json;
+use std::collections::BTreeMap;
+use std::time::{SystemTime, UNIX_EPOCH};
 
 use async_trait::async_trait;
-use serde::{Serialize, Deserialize};
-use md5::{Md5, Digest};
-use discord_presence::{Event};
+use discord_presence::Event;
+use md5::{Digest, Md5};
+use serde::{Deserialize, Serialize};
 use surf::StatusCode;
 
 use crate::music_storage::music_db::{Song, Tag};
@@ -14,13 +14,13 @@ use crate::music_storage::music_db::{Song, Tag};
 pub trait MusicTracker {
     /// Adds one listen to a song halfway through playback
     async fn track_song(&mut self, song: Song) -> Result<(), TrackerError>;
-    
+
     /// Adds a 'listening' status to the music tracker service of choice
     async fn track_now(&mut self, song: Song) -> Result<(), TrackerError>;
-    
+
     /// Reads config files, and attempts authentication with service
     async fn test_tracker(&mut self) -> Result<(), TrackerError>;
-    
+
     /// Returns plays for a given song according to tracker service
     async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError>;
 }
@@ -50,7 +50,7 @@ impl TrackerError {
             StatusCode::ServiceUnavailable => TrackerError::ServiceUnavailable,
             StatusCode::NotFound => TrackerError::ServiceUnavailable,
             _ => TrackerError::Unknown,
-        }
+        };
     }
 }
 
@@ -63,62 +63,66 @@ pub struct LastFMConfig {
 }
 
 pub struct LastFM {
-    config: LastFMConfig
+    config: LastFMConfig,
 }
 
 #[async_trait]
 impl MusicTracker for LastFM {
     async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
         let mut params: BTreeMap<&str, &str> = BTreeMap::new();
-        
+
         // Sets timestamp of song beginning play time
-        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 (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) {
             (Some(artist), Some(track)) => (artist, track),
-            _ => return Err(TrackerError::InvalidSong)
+            _ => return Err(TrackerError::InvalidSong),
         };
-        
+
         params.insert("method", "track.scrobble");
         params.insert("artist", &artist);
         params.insert("track", &track);
         params.insert("timestamp", &string_timestamp);
-        
+
         return match self.api_request(params).await {
             Ok(_) => Ok(()),
             Err(err) => Err(TrackerError::from_surf_error(err)),
-        }
+        };
     }
-    
+
     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(&Tag::Artist), song.get_tag(&Tag::Title)) {
             (Some(artist), Some(track)) => (artist, track),
-            _ => return Err(TrackerError::InvalidSong)
+            _ => return Err(TrackerError::InvalidSong),
         };
-        
+
         params.insert("method", "track.updateNowPlaying");
         params.insert("artist", &artist);
         params.insert("track", &track);
-        
+
         return match self.api_request(params).await {
             Ok(_) => Ok(()),
             Err(err) => Err(TrackerError::from_surf_error(err)),
-        }
+        };
     }
-    
+
     async fn test_tracker(&mut self) -> Result<(), TrackerError> {
         let mut params: BTreeMap<&str, &str> = BTreeMap::new();
         params.insert("method", "chart.getTopArtists");
-        
+
         return match self.api_request(params).await {
             Ok(_) => Ok(()),
             Err(err) => Err(TrackerError::from_surf_error(err)),
-        }
+        };
     }
-    
+
     async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
         todo!();
     }
@@ -126,7 +130,7 @@ impl MusicTracker for LastFM {
 
 #[derive(Deserialize, Serialize)]
 struct AuthToken {
-    token: String
+    token: String,
 }
 
 #[derive(Deserialize, Serialize, Debug)]
@@ -138,72 +142,85 @@ struct SessionResponse {
 
 #[derive(Deserialize, Serialize, Debug)]
 struct Session {
-    session: SessionResponse
+    session: SessionResponse,
 }
 
 impl LastFM {
     /// Returns a url to be approved by the user along with the auth token
     pub async fn get_auth(api_key: &String) -> Result<String, surf::Error> {
         let method = String::from("auth.gettoken");
-        let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&format=json");
-        
+        let api_request_url = format!(
+            "http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&format=json"
+        );
+
         let auth_token: AuthToken = surf::get(api_request_url).await?.body_json().await?;
-        
-        let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token);
-        
+
+        let auth_url = format!(
+            "http://www.last.fm/api/auth/?api_key={api_key}&token={}",
+            auth_token.token
+        );
+
         return Ok(auth_url);
     }
-    
+
     /// Returns a LastFM session key
-    pub async fn get_session_key(api_key: &String, shared_secret: &String, auth_token: &String) -> Result<String, surf::Error> {
+    pub async fn get_session_key(
+        api_key: &String,
+        shared_secret: &String,
+        auth_token: &String,
+    ) -> Result<String, surf::Error> {
         let method = String::from("auth.getSession");
         // Creates api_sig as defined in last.fm documentation
-        let api_sig = format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}");
+        let api_sig =
+            format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}");
 
         // Creates insecure MD5 hash for last.fm api sig
         let mut hasher = Md5::new();
         hasher.update(api_sig);
         let hash_result = hasher.finalize();
         let hex_string_hash = format!("{:#02x}", hash_result);
-        
+
         let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&token={auth_token}&api_sig={hex_string_hash}&format=json");
-        
+
         let response = surf::get(api_request_url).recv_string().await?;
-        
+
         // Sets session key from received response
         let session_response: Session = serde_json::from_str(&response)?;
         return Ok(session_response.session.key);
     }
-    
+
     /// Creates a new LastFM struct with a given config
     pub fn new(config: &LastFMConfig) -> LastFM {
         let last_fm = LastFM {
-            config: config.clone()
+            config: config.clone(),
         };
         return last_fm;
     }
-    
+
     // Creates an api request with the given parameters
-    pub async fn api_request(&self, mut params: BTreeMap<&str, &str>) -> Result<surf::Response, surf::Error> {
+    pub async fn api_request(
+        &self,
+        mut params: BTreeMap<&str, &str>,
+    ) -> Result<surf::Response, surf::Error> {
         params.insert("api_key", &self.config.dango_api_key);
         params.insert("sk", &self.config.session_key);
-        
+
         // Creates and sets api call signature
         let api_sig = LastFM::request_sig(&params, &self.config.shared_secret);
         params.insert("api_sig", &api_sig);
         let mut string_params = String::from("");
-        
+
         // Creates method call string
         // Just iterate over values???
         for key in params.keys() {
             let param_value = params.get(key).unwrap();
             string_params.push_str(&format!("{key}={param_value}&"));
         }
-        
+
         string_params.pop();
-        
+
         let url = "http://ws.audioscrobbler.com/2.0/";
-        
+
         let response = surf::post(url).body_string(string_params).await;
 
         return response;
@@ -218,16 +235,16 @@ impl LastFM {
             sig_string.push_str(&format!("{key}{}", param_value.unwrap()));
         }
         sig_string.push_str(shared_secret);
-        
+
         // Hashes signature using **INSECURE** MD5 (Required by last.fm api)
         let mut md5_hasher = Md5::new();
         md5_hasher.update(sig_string);
         let hash_result = md5_hasher.finalize();
         let hashed_sig = format!("{:#02x}", hash_result);
-    
+
         return hashed_sig;
     }
-    
+
     // Removes last.fm account from dango-music-player
     pub fn reset_account() {
         todo!();
@@ -237,13 +254,13 @@ impl LastFM {
 #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
 pub struct DiscordRPCConfig {
     pub enabled: bool,
-    pub dango_client_id: u64, 
+    pub dango_client_id: u64,
     pub dango_icon: String,
 }
 
 pub struct DiscordRPC {
     config: DiscordRPCConfig,
-    pub client: discord_presence::client::Client
+    pub client: discord_presence::client::Client,
 }
 
 impl DiscordRPC {
@@ -256,7 +273,6 @@ impl DiscordRPC {
     }
 }
 
-
 #[async_trait]
 impl MusicTracker for DiscordRPC {
     async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
@@ -275,16 +291,19 @@ impl MusicTracker for DiscordRPC {
         } else {
             &unknown
         };
-        
+
         let _client_thread = self.client.start();
-        
+
         // Blocks thread execution until it has connected to local discord client
         let ready = self.client.block_until_event(Event::Ready);
         if ready.is_err() {
             return Err(TrackerError::ServiceUnavailable);
         }
-        
-        let start_time = std::time::SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as u64;
+
+        let start_time = std::time::SystemTime::now()
+            .duration_since(UNIX_EPOCH)
+            .unwrap_or_default()
+            .as_millis() as u64;
         // Sets discord account activity to current playing song
         let send_activity = self.client.set_activity(|activity| {
             activity
@@ -293,21 +312,21 @@ impl MusicTracker for DiscordRPC {
                 .assets(|assets| assets.large_image(&self.config.dango_icon))
                 .timestamps(|time| time.start(start_time))
         });
-        
+
         match send_activity {
             Ok(_) => return Ok(()),
             Err(_) => return Err(TrackerError::ServiceUnavailable),
         }
     }
-    
+
     async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
-        return Ok(())
+        return Ok(());
     }
-    
+
     async fn test_tracker(&mut self) -> Result<(), TrackerError> {
-        return Ok(())
+        return Ok(());
     }
-    
+
     async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
         return Ok(0);
     }
@@ -329,7 +348,7 @@ impl MusicTracker for ListenBrainz {
     async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
         let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) {
             (Some(artist), Some(track)) => (artist, track),
-            _ => return Err(TrackerError::InvalidSong)
+            _ => return Err(TrackerError::InvalidSong),
         };
         // Creates a json to submit a single song as defined in the listenbrainz documentation
         let json_req = json!({
@@ -343,21 +362,28 @@ impl MusicTracker for ListenBrainz {
                 }
             ]
         });
-        
-        return match self.api_request(&json_req.to_string(), &String::from("/1/submit-listens")).await {
+
+        return match self
+            .api_request(&json_req.to_string(), &String::from("/1/submit-listens"))
+            .await
+        {
             Ok(_) => Ok(()),
-            Err(err) => Err(TrackerError::from_surf_error(err))
-        }
+            Err(err) => Err(TrackerError::from_surf_error(err)),
+        };
     }
-    
+
     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(&Tag::Artist), song.get_tag(&Tag::Title)) {
             (Some(artist), Some(track)) => (artist, track),
-            _ => return Err(TrackerError::InvalidSong)
+            _ => return Err(TrackerError::InvalidSong),
         };
-        
+
         let json_req = json!({
             "listen_type": "single",
             "payload": [
@@ -370,11 +396,14 @@ impl MusicTracker for ListenBrainz {
                 }
             ]
         });
-        
-        return match self.api_request(&json_req.to_string(), &String::from("/1/submit-listens")).await {
+
+        return match self
+            .api_request(&json_req.to_string(), &String::from("/1/submit-listens"))
+            .await
+        {
             Ok(_) => Ok(()),
-            Err(err) => Err(TrackerError::from_surf_error(err))
-        }
+            Err(err) => Err(TrackerError::from_surf_error(err)),
+        };
     }
     async fn test_tracker(&mut self) -> Result<(), TrackerError> {
         todo!()
@@ -387,12 +416,19 @@ impl MusicTracker for ListenBrainz {
 impl ListenBrainz {
     pub fn new(config: &ListenBrainzConfig) -> Self {
         ListenBrainz {
-            config: config.clone()
+            config: config.clone(),
         }
     }
     // Makes an api request to configured url with given json
-    pub async fn api_request(&self, request: &String, endpoint: &String) -> Result<surf::Response, surf::Error> {
-        let reponse =  surf::post(format!("{}{}", &self.config.api_url, endpoint)).body_string(request.clone()).header("Authorization", format!("Token {}", self.config.auth_token)).await;
-        return reponse
+    pub async fn api_request(
+        &self,
+        request: &String,
+        endpoint: &String,
+    ) -> Result<surf::Response, surf::Error> {
+        let reponse = surf::post(format!("{}{}", &self.config.api_url, endpoint))
+            .body_string(request.clone())
+            .header("Authorization", format!("Token {}", self.config.auth_token))
+            .await;
+        return reponse;
     }
 }

From eeceb27800675913a263c8807b393350ea26992b Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 3 Nov 2023 08:39:19 -0500
Subject: [PATCH 12/20] Added album query function

---
 Cargo.toml                               |   2 +
 src/music_controller/music_controller.rs |   2 +-
 src/music_storage/music_db.rs            | 161 ++++++++++++++++-------
 3 files changed, 113 insertions(+), 52 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 69f5abb..8daf129 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -39,3 +39,5 @@ log = "0.4"
 pretty_env_logger = "0.4"
 cue = "2.0.0"
 jwalk = "0.8.1"
+base64 = "0.21.5"
+zip = "0.6.6"
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 24b8379..cb39ded 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -85,6 +85,6 @@ impl MusicController {
         sort_by: Vec<Tag>,
     ) -> Option<Vec<&Song>> {
         self.library
-            .query(query_string, &target_tags, search_location, &sort_by)
+            .query(query_string, &target_tags, &sort_by)
     }
 }
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index b6457d3..7d325e3 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,15 +1,16 @@
 use file_format::{FileFormat, Kind};
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
 use std::ffi::OsStr;
+use std::collections::{HashMap, HashSet};
 use std::{error::Error, io::BufReader};
 
 use chrono::{serde::ts_seconds_option, DateTime, Utc};
 use std::time::Duration;
-//use walkdir::WalkDir;
 use cue::cd::CD;
 use jwalk::WalkDir;
 
 use bincode::{deserialize_from, serialize_into};
+use base64::{engine::general_purpose, Engine as _};
 use serde::{Deserialize, Serialize};
 use std::fs;
 use std::io::BufWriter;
@@ -33,6 +34,7 @@ pub enum Tag {
     Title,
     Album,
     Artist,
+    AlbumArtist,
     Genre,
     Comment,
     Track,
@@ -47,6 +49,7 @@ impl ToString for Tag {
             Self::Title => "TrackTitle".into(),
             Self::Album => "AlbumTitle".into(),
             Self::Artist => "TrackArtist".into(),
+            Self::AlbumArtist => "AlbumArtist".into(),
             Self::Genre => "Genre".into(),
             Self::Comment => "Comment".into(),
             Self::Track => "TrackNumber".into(),
@@ -103,6 +106,15 @@ impl Song {
         match target_field {
             "location" => Some(self.location.clone().path_string()),
             "plays" => Some(self.plays.clone().to_string()),
+            "format" => {
+                match self.format {
+                    Some(format) => match format.short_name() {
+                        Some(short) => Some(short.to_string()),
+                        None => None
+                    },
+                    None => None
+                }
+            },
             _ => None, // Other field types are not yet supported
         }
     }
@@ -113,6 +125,7 @@ pub enum URI {
     Local(PathBuf),
     Cue {
         location: PathBuf,
+        index: usize,
         start: Duration,
         end: Duration,
     },
@@ -120,6 +133,17 @@ pub enum URI {
 }
 
 impl URI {
+    pub fn index(&self) -> Result<&usize, Box<dyn Error>> {
+        match self {
+            URI::Local(_) => Err("\"Local\" has no stored index".into()),
+            URI::Remote(_, _) => Err("\"Remote\" has no stored index".into()),
+            URI::Cue {
+                index,
+                ..
+            } => Ok(index),
+        }
+    }
+
     /// Returns the start time of a CUEsheet song, or an
     /// error if the URI is not a Cue variant
     pub fn start(&self) -> Result<&Duration, Box<dyn Error>> {
@@ -127,9 +151,8 @@ impl URI {
             URI::Local(_) => Err("\"Local\" has no starting time".into()),
             URI::Remote(_, _) => Err("\"Remote\" has no starting time".into()),
             URI::Cue {
-                location: _,
                 start,
-                end: _,
+                ..
             } => Ok(start),
         }
     }
@@ -141,9 +164,8 @@ impl URI {
             URI::Local(_) => Err("\"Local\" has no starting time".into()),
             URI::Remote(_, _) => Err("\"Remote\" has no starting time".into()),
             URI::Cue {
-                location: _,
-                start: _,
                 end,
+                ..
             } => Ok(end),
         }
     }
@@ -154,8 +176,7 @@ impl URI {
             URI::Local(location) => location,
             URI::Cue {
                 location,
-                start: _,
-                end: _,
+                ..
             } => location,
             URI::Remote(_, location) => location,
         }
@@ -166,8 +187,7 @@ impl URI {
             URI::Local(location) => location.as_path().to_string_lossy(),
             URI::Cue {
                 location,
-                start: _,
-                end: _,
+                ..
             } => location.as_path().to_string_lossy(),
             URI::Remote(_, location) => location.as_path().to_string_lossy(),
         };
@@ -182,30 +202,24 @@ pub enum Service {
     Youtube,
 }
 
-/* TODO: Rework this entirely
 #[derive(Debug)]
-pub struct Playlist {
-    title: String,
-    cover_art: Box<Path>,
+pub struct Album<'a> {
+    pub title: &'a String,
+    pub artist: Option<&'a String>,
+    pub cover: Option<&'a AlbumArt>,
+    pub tracks: Vec<&'a Song>,
 }
 
-#[derive(Debug)]
-pub enum MusicObject {
-    Song(Song),
-    Album(Playlist),
-    Playlist(Playlist),
-}
-*/
-
 #[derive(Debug)]
 pub struct MusicLibrary {
     pub library: Vec<Song>,
 }
 
 pub fn normalize(input_string: &String) -> String {
-    unidecode(input_string)
-        .to_ascii_lowercase()
-        .replace(|c: char| !c.is_alphanumeric(), "")
+    unidecode(input_string).chars().filter_map(|c: char| {
+        let x = c.to_ascii_lowercase();
+        c.is_alphanumeric().then_some(x)
+    }).collect()
 }
 
 impl MusicLibrary {
@@ -218,7 +232,7 @@ impl MusicLibrary {
         let global_config = &*config.read().unwrap();
         let mut library: Vec<Song> = Vec::new();
         let mut backup_path = global_config.db_path.clone();
-        backup_path.set_extension("bkp");
+        backup_path.set_extension("tmp");
 
         match global_config.db_path.try_exists() {
             Ok(true) => {
@@ -253,14 +267,12 @@ impl MusicLibrary {
             Ok(true) => {
                 // The database exists, so rename it to `.bkp` and
                 // write the new database file
-                let mut backup_name = config.db_path.clone();
-                backup_name.set_extension("bkp");
-                fs::rename(config.db_path.as_path(), backup_name.as_path())?;
-
-                // TODO: Make this save properly like in config.rs
-
-                let mut writer = BufWriter::new(fs::File::create(config.db_path.to_path_buf())?);
+                let mut writer_name = config.db_path.clone();
+                writer_name.set_extension("tmp");
+                let mut writer = BufWriter::new(fs::File::create(writer_name.to_path_buf())?);
                 serialize_into(&mut writer, &self.library)?;
+
+                fs::rename(writer_name.as_path(), config.db_path.clone().as_path())?;
             }
             Ok(false) => {
                 // Create the database if it does not exist
@@ -273,6 +285,7 @@ impl MusicLibrary {
         Ok(())
     }
 
+    /// Returns the library size in number of tracks
     pub fn size(&self) -> usize {
         self.library.len()
     }
@@ -314,7 +327,7 @@ impl MusicLibrary {
     }
 
     /// Finds all the music files within a specified folder
-    pub fn find_all_music(
+    pub fn scan_folder(
         &mut self,
         target_path: &str,
         config: &Config,
@@ -413,9 +426,12 @@ impl MusicLibrary {
                 ItemKey::TrackTitle => Tag::Title,
                 ItemKey::TrackNumber => Tag::Track,
                 ItemKey::TrackArtist => Tag::Artist,
+                ItemKey::AlbumArtist => Tag::AlbumArtist,
                 ItemKey::Genre => Tag::Genre,
                 ItemKey::Comment => Tag::Comment,
                 ItemKey::AlbumTitle => Tag::Album,
+                ItemKey::DiscNumber => Tag::Disk,
+                ItemKey::Unknown(unknown) if unknown == "ACOUSTID_FINGERPRINT" => continue,
                 ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()),
                 custom => Tag::Key(format!("{:?}", custom)),
             };
@@ -423,7 +439,7 @@ impl MusicLibrary {
             let value = match item.value() {
                 ItemValue::Text(value) => String::from(value),
                 ItemValue::Locator(value) => String::from(value),
-                ItemValue::Binary(_) => String::from(""),
+                ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)),
             };
 
             tags.push((key, value))
@@ -541,6 +557,7 @@ impl MusicLibrary {
             let mut tags: Vec<(Tag, String)> = Vec::new();
             tags.push((Tag::Album, album_title.clone()));
             tags.push((Tag::Key("AlbumArtist".to_string()), album_artist.clone()));
+            tags.push((Tag::Track, (i + 1).to_string()));
             match track.get_cdtext().read(cue::cd_text::PTI::Title) {
                 Some(title) => tags.push((Tag::Title, title)),
                 None => match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) {
@@ -569,6 +586,7 @@ impl MusicLibrary {
             let new_song = Song {
                 location: URI::Cue {
                     location: audio_location,
+                    index: i,
                     start,
                     end,
                 },
@@ -641,39 +659,53 @@ impl MusicLibrary {
 
     /// Query the database, returning a list of [Song]s
     ///
-    /// The order in which the `sort_by` `Vec` is arranged
-    /// determines the output sorting
+    /// The order in which the sort by Vec is arranged
+    /// determines the output sorting.
+    ///
+    /// Example:
+    /// ```
+    /// query(
+    ///     &String::from("query"),
+    ///     &vec![
+    ///         Tag::Title
+    ///     ],
+    ///     &vec![
+    ///         Tag::Field("location".to_string()),
+    ///         Tag::Album,
+    ///         Tag::Disk,
+    ///         Tag::Track,
+    ///     ],
+    /// )
+    /// ```
+    /// This would find all titles containing the sequence
+    /// "query", and would return the results sorted first
+    /// by path, then album, disk number, and finally track number.
     pub fn query(
         &self,
         query_string: &String,  // The query itself
         target_tags: &Vec<Tag>, // The tags to search
-        search_location: bool,  // Whether to search the location field or not
         sort_by: &Vec<Tag>,     // Tags to sort the resulting data by
     ) -> Option<Vec<&Song>> {
         let songs = Arc::new(Mutex::new(Vec::new()));
 
         self.library.par_iter().for_each(|track| {
             for tag in target_tags {
-                let track_result = match track.get_tag(&tag) {
-                    Some(value) => value,
-                    None => continue,
+                let track_result = match tag {
+                    Tag::Field(target) => match track.get_field(&target) {
+                        Some(value) => value,
+                        None => continue,
+                    },
+                    _ => match track.get_tag(&tag) {
+                        Some(value) => value.to_owned(),
+                        None => continue,
+                    },
                 };
 
-                if normalize(track_result).contains(&normalize(&query_string)) {
+                if normalize(&track_result).contains(&normalize(&query_string)) {
                     songs.lock().unwrap().push(track);
                     return;
                 }
             }
-
-            if !search_location {
-                return;
-            }
-
-            // Find a URL in the song
-            if normalize(&track.location.path_string()).contains(&normalize(&query_string)) {
-                songs.lock().unwrap().push(track);
-                return;
-            }
         });
 
         let lock = Arc::try_unwrap(songs).expect("Lock still has multiple owners!");
@@ -732,4 +764,31 @@ impl MusicLibrary {
             None
         }
     }
+
+    pub fn albums(&self) -> Result<Vec<Album>, Box<dyn Error>> {
+        let mut albums: Vec<Album> = Vec::new();
+        for result in &self.library {
+            let title = match result.get_tag(&Tag::Album){
+                Some(title) => title,
+                None => continue
+            };
+
+            match albums.binary_search_by_key(&normalize(&title), |album| normalize(&album.title.to_owned())) {
+                Ok(pos) => {
+                    albums[pos].tracks.push(result);
+                },
+                Err(pos) => {
+                    let new_album = Album {
+                        title,
+                        artist: result.get_tag(&Tag::AlbumArtist),
+                        tracks: vec![result],
+                        cover: None,
+                    };
+                    albums.insert(pos, new_album);
+                }
+            }
+        }
+
+        Ok(albums)
+    }
 }

From 421ab0c757fb99941738d1528117491269a43aed Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 3 Nov 2023 09:49:53 -0500
Subject: [PATCH 13/20] Added benchmark for testing

---
 src/music_controller/music_controller.rs | 2 +-
 src/music_storage/music_db.rs            | 7 ++++---
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index cb39ded..9455e58 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -85,6 +85,6 @@ impl MusicController {
         sort_by: Vec<Tag>,
     ) -> Option<Vec<&Song>> {
         self.library
-            .query(query_string, &target_tags, &sort_by)
+            .query_tracks(query_string, &target_tags, &sort_by)
     }
 }
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 7d325e3..0e02d49 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -664,7 +664,7 @@ impl MusicLibrary {
     ///
     /// Example:
     /// ```
-    /// query(
+    /// query_tracks(
     ///     &String::from("query"),
     ///     &vec![
     ///         Tag::Title
@@ -680,7 +680,7 @@ impl MusicLibrary {
     /// This would find all titles containing the sequence
     /// "query", and would return the results sorted first
     /// by path, then album, disk number, and finally track number.
-    pub fn query(
+    pub fn query_tracks(
         &self,
         query_string: &String,  // The query itself
         target_tags: &Vec<Tag>, // The tags to search
@@ -773,7 +773,7 @@ impl MusicLibrary {
                 None => continue
             };
 
-            match albums.binary_search_by_key(&normalize(&title), |album| normalize(&album.title.to_owned())) {
+            match albums.binary_search_by_key(&title, |album| album.title) {
                 Ok(pos) => {
                     albums[pos].tracks.push(result);
                 },
@@ -791,4 +791,5 @@ impl MusicLibrary {
 
         Ok(albums)
     }
+
 }

From f890f1a0262d08eff3796db1fb774727b21d3d24 Mon Sep 17 00:00:00 2001
From: G2_Games <ke0bhogsg@gmail.com>
Date: Fri, 3 Nov 2023 10:30:22 -0500
Subject: [PATCH 14/20] Improved performance by reducing reallocations

---
 src/music_storage/music_db.rs | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 0e02d49..a115e53 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -215,11 +215,8 @@ pub struct MusicLibrary {
     pub library: Vec<Song>,
 }
 
-pub fn normalize(input_string: &String) -> String {
-    unidecode(input_string).chars().filter_map(|c: char| {
-        let x = c.to_ascii_lowercase();
-        c.is_alphanumeric().then_some(x)
-    }).collect()
+pub fn normalize(input_string: &String) {
+    unidecode(input_string).retain(|c| !c.is_whitespace());
 }
 
 impl MusicLibrary {
@@ -690,7 +687,7 @@ impl MusicLibrary {
 
         self.library.par_iter().for_each(|track| {
             for tag in target_tags {
-                let track_result = match tag {
+                let mut track_result = match tag {
                     Tag::Field(target) => match track.get_field(&target) {
                         Some(value) => value,
                         None => continue,
@@ -701,7 +698,10 @@ impl MusicLibrary {
                     },
                 };
 
-                if normalize(&track_result).contains(&normalize(&query_string)) {
+                normalize(&mut query_string.to_owned());
+                normalize(&mut track_result);
+
+                if track_result.contains(query_string) {
                     songs.lock().unwrap().push(track);
                     return;
                 }
@@ -772,8 +772,12 @@ impl MusicLibrary {
                 Some(title) => title,
                 None => continue
             };
+            normalize(title);
 
-            match albums.binary_search_by_key(&title, |album| album.title) {
+            match albums.binary_search_by_key(&title, |album| {
+                normalize(&album.title);
+                album.title
+            }) {
                 Ok(pos) => {
                     albums[pos].tracks.push(result);
                 },

From d330c5f0bda45de6477f6758e553b96534fac21e Mon Sep 17 00:00:00 2001
From: G2_Games <ke0bhogsg@gmail.com>
Date: Fri, 3 Nov 2023 12:29:04 -0500
Subject: [PATCH 15/20] Switched album list to BTreeMap, improved perf

---
 src/music_storage/music_db.rs | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index a115e53..5070d97 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,7 +1,7 @@
 use file_format::{FileFormat, Kind};
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
 use std::ffi::OsStr;
-use std::collections::{HashMap, HashSet};
+use std::collections::{HashMap, HashSet, BTreeMap};
 use std::{error::Error, io::BufReader};
 
 use chrono::{serde::ts_seconds_option, DateTime, Utc};
@@ -766,7 +766,7 @@ impl MusicLibrary {
     }
 
     pub fn albums(&self) -> Result<Vec<Album>, Box<dyn Error>> {
-        let mut albums: Vec<Album> = Vec::new();
+        let mut albums: BTreeMap<&String, Album> = BTreeMap::new();
         for result in &self.library {
             let title = match result.get_tag(&Tag::Album){
                 Some(title) => title,
@@ -774,26 +774,21 @@ impl MusicLibrary {
             };
             normalize(title);
 
-            match albums.binary_search_by_key(&title, |album| {
-                normalize(&album.title);
-                album.title
-            }) {
-                Ok(pos) => {
-                    albums[pos].tracks.push(result);
-                },
-                Err(pos) => {
+            match albums.get_mut(&title) {
+                Some(album) => album.tracks.push(result),
+                None => {
                     let new_album = Album {
                         title,
                         artist: result.get_tag(&Tag::AlbumArtist),
                         tracks: vec![result],
                         cover: None,
                     };
-                    albums.insert(pos, new_album);
+                    albums.insert(title, new_album);
                 }
             }
         }
 
-        Ok(albums)
+        Ok(albums.into_par_iter().map(|album| album.1).collect())
     }
 
 }

From e878e72a990728a950469ad8c0a1be71b17545cd Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 4 Nov 2023 04:57:20 -0500
Subject: [PATCH 16/20] Implemented album search, compressed on-disk library

---
 Cargo.toml                    |   4 +-
 src/lib.rs                    |   1 +
 src/music_storage/music_db.rs | 241 +++++++++++++++++++++-------------
 src/music_storage/utils.rs    |  52 ++++++++
 4 files changed, 208 insertions(+), 90 deletions(-)
 create mode 100644 src/music_storage/utils.rs

diff --git a/Cargo.toml b/Cargo.toml
index 8daf129..86ab8a8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,7 +32,7 @@ rubato = "0.12.0"
 arrayvec = "0.7.4"
 discord-presence = "0.5.18"
 chrono = { version = "0.4.31", features = ["serde"] }
-bincode = "1.3.3"
+bincode = { version = "2.0.0-rc.3", features = ["serde"] }
 unidecode = "0.3.0"
 rayon = "1.8.0"
 log = "0.4"
@@ -41,3 +41,5 @@ cue = "2.0.0"
 jwalk = "0.8.1"
 base64 = "0.21.5"
 zip = "0.6.6"
+flate2 = "1.0.28"
+snap = "1.1.0"
diff --git a/src/lib.rs b/src/lib.rs
index 75b98ee..9afe354 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,6 +5,7 @@ pub mod music_tracker {
 pub mod music_storage {
     pub mod music_db;
     pub mod playlist;
+    pub mod utils;
 }
 
 pub mod music_processor {
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 5070d97..4e6e226 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,35 +1,39 @@
+// Crate things
+use crate::music_controller::config::Config;
+use super::utils::{normalize, read_library, write_library};
+
+// Various std things
+use std::collections::BTreeMap;
+use std::error::Error;
+
+// Files
 use file_format::{FileFormat, Kind};
+use jwalk::WalkDir;
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
 use std::ffi::OsStr;
-use std::collections::{HashMap, HashSet, BTreeMap};
-use std::{error::Error, io::BufReader};
+use cue::cd::CD;
+use std::fs;
+use std::path::{Path, PathBuf};
 
+// Time
 use chrono::{serde::ts_seconds_option, DateTime, Utc};
 use std::time::Duration;
-use cue::cd::CD;
-use jwalk::WalkDir;
 
-use bincode::{deserialize_from, serialize_into};
+// Serialization/Compression
 use base64::{engine::general_purpose, Engine as _};
 use serde::{Deserialize, Serialize};
-use std::fs;
-use std::io::BufWriter;
-use std::path::{Path, PathBuf};
-use unidecode::unidecode;
 
 // Fun parallel stuff
 use rayon::prelude::*;
 use std::sync::{Arc, Mutex, RwLock};
 
-use crate::music_controller::config::Config;
-
 #[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct AlbumArt {
     pub index: u16,
     pub path: Option<URI>,
 }
 
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
 pub enum Tag {
     Title,
     Album,
@@ -78,7 +82,7 @@ pub struct Song {
     #[serde(with = "ts_seconds_option")]
     pub date_modified: Option<DateTime<Utc>>,
     pub album_art: Vec<AlbumArt>,
-    pub tags: Vec<(Tag, String)>,
+    pub tags: BTreeMap<Tag, String>,
 }
 
 impl Song {
@@ -94,12 +98,7 @@ impl Song {
      * ```
      **/
     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),
-            None => None,
-        }
+        self.tags.get(target_key)
     }
 
     pub fn get_field(&self, target_field: &str) -> Option<String> {
@@ -202,12 +201,56 @@ pub enum Service {
     Youtube,
 }
 
-#[derive(Debug)]
+#[derive(Clone, Debug)]
 pub struct Album<'a> {
-    pub title: &'a String,
-    pub artist: Option<&'a String>,
-    pub cover: Option<&'a AlbumArt>,
-    pub tracks: Vec<&'a Song>,
+    title: &'a String,
+    artist: Option<&'a String>,
+    cover: Option<&'a AlbumArt>,
+    discs: BTreeMap<usize, Vec<&'a Song>>,
+}
+
+impl Album<'_> {
+    /// Returns the album title
+    pub fn title(&self) -> &String {
+        self.title
+    }
+
+    /// Returns the Album Artist, if they exist
+    pub fn artist(&self) -> Option<&String> {
+        self.artist
+    }
+
+    /// Returns the album cover as an AlbumArt struct, if it exists
+    pub fn cover(&self) -> Option<&AlbumArt> {
+        self.cover
+    }
+
+    pub fn tracks(&self) -> Vec<&Song> {
+        let mut songs = Vec::new();
+        for disc in &self.discs {
+            songs.append(&mut disc.1.clone())
+        }
+        songs
+    }
+
+    pub fn discs(&self) -> &BTreeMap<usize, Vec<&Song>> {
+        &self.discs
+    }
+
+    /// Returns the specified track at `index` from the album, returning
+    /// an error if the track index is out of range
+    pub fn track(&self, disc: usize, index: usize) -> Option<&Song> {
+        Some(self.discs.get(&disc)?[index])
+    }
+
+    /// Returns the number of songs in the album
+    pub fn len(&self) -> usize {
+        let mut total = 0;
+        for disc in &self.discs {
+            total += disc.1.len();
+        }
+        total
+    }
 }
 
 #[derive(Debug)]
@@ -215,10 +258,6 @@ pub struct MusicLibrary {
     pub library: Vec<Song>,
 }
 
-pub fn normalize(input_string: &String) {
-    unidecode(input_string).retain(|c| !c.is_whitespace());
-}
-
 impl MusicLibrary {
     /// Initialize the database
     ///
@@ -229,29 +268,22 @@ impl MusicLibrary {
         let global_config = &*config.read().unwrap();
         let mut library: Vec<Song> = Vec::new();
         let mut backup_path = global_config.db_path.clone();
-        backup_path.set_extension("tmp");
+        backup_path.set_extension("bkp");
 
-        match global_config.db_path.try_exists() {
-            Ok(true) => {
-                // The database exists, so get it from the file
-                let database = fs::File::open(global_config.db_path.to_path_buf())?;
-                let reader = BufReader::new(database);
-                library = deserialize_from(reader)?;
-            }
-            Ok(false) => {
+        match global_config.db_path.exists() {
+            true => {
+                library = read_library(*global_config.db_path.clone())?;
+            },
+            false => {
                 // 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(global_config.db_path.to_path_buf())?;
-                    let reader = BufReader::new(database);
-                    library = deserialize_from(reader)?;
+                if backup_path.exists() {
+                    library = read_library(*backup_path.clone())?;
+                    write_library(&library, global_config.db_path.to_path_buf(), false)?;
                 } else {
-                    let mut writer =
-                        BufWriter::new(fs::File::create(global_config.db_path.to_path_buf())?);
-                    serialize_into(&mut writer, &library)?;
+                    write_library(&library, global_config.db_path.to_path_buf(), false)?;
                 }
             }
-            Err(error) => return Err(error.into()),
         };
 
         Ok(Self { library })
@@ -261,20 +293,8 @@ impl MusicLibrary {
     /// specified in the config
     pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
         match config.db_path.try_exists() {
-            Ok(true) => {
-                // The database exists, so rename it to `.bkp` and
-                // write the new database file
-                let mut writer_name = config.db_path.clone();
-                writer_name.set_extension("tmp");
-                let mut writer = BufWriter::new(fs::File::create(writer_name.to_path_buf())?);
-                serialize_into(&mut writer, &self.library)?;
-
-                fs::rename(writer_name.as_path(), config.db_path.clone().as_path())?;
-            }
-            Ok(false) => {
-                // Create the database if it does not exist
-                let mut writer = BufWriter::new(fs::File::create(config.db_path.to_path_buf())?);
-                serialize_into(&mut writer, &self.library)?;
+            Ok(exists) => {
+                write_library(&self.library, config.db_path.to_path_buf(), exists)?;
             }
             Err(error) => return Err(error.into()),
         }
@@ -352,7 +372,7 @@ impl MusicLibrary {
 
             // Save periodically while scanning
             i += 1;
-            if i % 250 == 0 {
+            if i % 500 == 0 {
                 self.save(config).unwrap();
             }
 
@@ -417,7 +437,7 @@ impl MusicLibrary {
             },
         };
 
-        let mut tags: Vec<(Tag, String)> = Vec::new();
+        let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
         for item in tag.items() {
             let key = match item.key() {
                 ItemKey::TrackTitle => Tag::Title,
@@ -439,7 +459,7 @@ impl MusicLibrary {
                 ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)),
             };
 
-            tags.push((key, value))
+            tags.insert(key, value);
         }
 
         // Get all the album artwork information
@@ -551,31 +571,31 @@ impl MusicLibrary {
             };
 
             // Get some useful tags
-            let mut tags: Vec<(Tag, String)> = Vec::new();
-            tags.push((Tag::Album, album_title.clone()));
-            tags.push((Tag::Key("AlbumArtist".to_string()), album_artist.clone()));
-            tags.push((Tag::Track, (i + 1).to_string()));
+            let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
+            tags.insert(Tag::Album, album_title.clone());
+            tags.insert(Tag::Key("AlbumArtist".to_string()), album_artist.clone());
+            tags.insert(Tag::Track, (i + 1).to_string());
             match track.get_cdtext().read(cue::cd_text::PTI::Title) {
-                Some(title) => tags.push((Tag::Title, title)),
+                Some(title) => tags.insert(Tag::Title, title),
                 None => match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) {
-                    Some(title) => tags.push((Tag::Title, title)),
+                    Some(title) => tags.insert(Tag::Title, title),
                     None => {
                         let namestr = format!("{} - {}", i, track.get_filename());
-                        tags.push((Tag::Title, namestr))
+                        tags.insert(Tag::Title, namestr)
                     }
                 },
             };
             match track.get_cdtext().read(cue::cd_text::PTI::Performer) {
-                Some(artist) => tags.push((Tag::Artist, artist)),
-                None => (),
+                Some(artist) => tags.insert(Tag::Artist, artist),
+                None => None,
             };
             match track.get_cdtext().read(cue::cd_text::PTI::Genre) {
-                Some(genre) => tags.push((Tag::Genre, genre)),
-                None => (),
+                Some(genre) => tags.insert(Tag::Genre, genre),
+                None => None,
             };
             match track.get_cdtext().read(cue::cd_text::PTI::Message) {
-                Some(comment) => tags.push((Tag::Comment, comment)),
-                None => (),
+                Some(comment) => tags.insert(Tag::Comment, comment),
+                None => None,
             };
 
             let album_art = Vec::new();
@@ -687,21 +707,18 @@ impl MusicLibrary {
 
         self.library.par_iter().for_each(|track| {
             for tag in target_tags {
-                let mut track_result = match tag {
+                let track_result = match tag {
                     Tag::Field(target) => match track.get_field(&target) {
                         Some(value) => value,
                         None => continue,
                     },
                     _ => match track.get_tag(&tag) {
-                        Some(value) => value.to_owned(),
+                        Some(value) => value.clone(),
                         None => continue,
                     },
                 };
 
-                normalize(&mut query_string.to_owned());
-                normalize(&mut track_result);
-
-                if track_result.contains(query_string) {
+                if normalize(&track_result.to_string()).contains(&normalize(&query_string.to_owned())) {
                     songs.lock().unwrap().push(track);
                     return;
                 }
@@ -765,30 +782,76 @@ impl MusicLibrary {
         }
     }
 
-    pub fn albums(&self) -> Result<Vec<Album>, Box<dyn Error>> {
-        let mut albums: BTreeMap<&String, Album> = BTreeMap::new();
+    /// Generates all albums from the track list
+    pub fn albums(&self) -> BTreeMap<String, Album> {
+        let mut albums: BTreeMap<String, Album> = BTreeMap::new();
         for result in &self.library {
             let title = match result.get_tag(&Tag::Album){
                 Some(title) => title,
                 None => continue
             };
-            normalize(title);
+            let disc_num = result.get_tag(&Tag::Disk).unwrap_or(&"".to_string()).parse::<usize>().unwrap_or(1);
 
-            match albums.get_mut(&title) {
-                Some(album) => album.tracks.push(result),
+            let norm_title = normalize(title);
+            match albums.get_mut(&norm_title) {
+                Some(album) => {
+                    match album.discs.get_mut(&disc_num) {
+                        Some(disc) => disc.push(result),
+                        None => {
+                            album.discs.insert(disc_num, vec![result]);
+                        }
+                    }
+                },
                 None => {
                     let new_album = Album {
                         title,
                         artist: result.get_tag(&Tag::AlbumArtist),
-                        tracks: vec![result],
+                        discs: BTreeMap::from([(disc_num, vec![result])]),
                         cover: None,
                     };
-                    albums.insert(title, new_album);
+                    albums.insert(norm_title, new_album);
                 }
             }
         }
 
-        Ok(albums.into_par_iter().map(|album| album.1).collect())
+        let blank = String::from("");
+        albums.par_iter_mut().for_each(|album| {
+            for disc in &mut album.1.discs {
+                disc.1.par_sort_by(|a, b| {
+                    if let (Ok(num_a), Ok(num_b)) = (
+                        a.get_tag(&Tag::Title).unwrap_or(&blank).parse::<i32>(),
+                        b.get_tag(&Tag::Title).unwrap_or(&blank).parse::<i32>()
+                    ) {
+                        // If parsing succeeds, compare as numbers
+                        match num_a < num_b {
+                            true => return std::cmp::Ordering::Less,
+                            false => return std::cmp::Ordering::Greater
+                        }
+                    }
+                    match a.get_field("location").unwrap() < b.get_field("location").unwrap() {
+                        true => return std::cmp::Ordering::Less,
+                        false => return std::cmp::Ordering::Greater
+                    }
+                });
+            }
+        });
+        albums
     }
 
+    pub fn query_albums(&self,
+        query_string: &String,  // The query itself
+    ) -> Result<Vec<Album>, Box<dyn Error>> {
+        let all_albums = self.albums();
+
+        let normalized_query = normalize(query_string);
+        let albums: Vec<Album> = all_albums.par_iter().filter_map(|album|
+            if normalize(album.0).contains(&normalized_query) {
+                Some(album.1.clone())
+            } else {
+                None
+            }
+        ).collect();
+
+        Ok(albums)
+    }
 }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
new file mode 100644
index 0000000..1a6d8a4
--- /dev/null
+++ b/src/music_storage/utils.rs
@@ -0,0 +1,52 @@
+use std::io::{BufReader, BufWriter};
+use std::{path::PathBuf, error::Error, fs};
+use flate2::Compression;
+use flate2::write::ZlibEncoder;
+use flate2::read::ZlibDecoder;
+
+use snap;
+
+use unidecode::unidecode;
+use crate::music_storage::music_db::Song;
+
+pub fn normalize(input_string: &String) -> String {
+    let mut normalized = unidecode(input_string);
+
+    // Remove non alphanumeric characters
+    normalized.retain(|c| c.is_alphabetic());
+    normalized = normalized.to_ascii_lowercase();
+
+    normalized
+}
+
+pub fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
+    let database = fs::File::open(path)?;
+    let reader = BufReader::new(database);
+    //let mut d = ZlibDecoder::new(reader);
+
+    let mut d = snap::read::FrameDecoder::new(reader);
+
+    let library: Vec<Song> = bincode::serde::decode_from_std_read(&mut d, bincode::config::standard().with_little_endian().with_variable_int_encoding())?;
+    Ok(library)
+}
+
+pub fn write_library(library: &Vec<Song>, path: PathBuf, take_backup: bool) -> Result<(), Box<dyn Error>> {
+    let mut writer_name = path.clone();
+    writer_name.set_extension("tmp");
+    let mut backup_name = path.clone();
+    backup_name.set_extension("bkp");
+
+    let writer = BufWriter::new(fs::File::create(writer_name.to_path_buf())?);
+    //let mut e = ZlibEncoder::new(writer, Compression::default());
+
+    let mut e = snap::write::FrameEncoder::new(writer);
+
+    bincode::serde::encode_into_std_write(&library, &mut e, bincode::config::standard().with_little_endian().with_variable_int_encoding())?;
+
+    if path.exists() && take_backup {
+        fs::rename(&path, backup_name)?;
+    }
+    fs::rename(writer_name, &path)?;
+
+    Ok(())
+}

From 37b393246ff3db83c711b8fe13d99b0336314d3f Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 4 Nov 2023 18:40:05 -0500
Subject: [PATCH 17/20] Fixed some search issues

---
 src/music_storage/music_db.rs | 68 +++++++++++++++++------------------
 src/music_storage/utils.rs    | 18 +++++-----
 2 files changed, 41 insertions(+), 45 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 4e6e226..e79eae8 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -730,49 +730,43 @@ impl MusicLibrary {
 
         // Sort the returned list of songs
         new_songs.par_sort_by(|a, b| {
-            for opt in sort_by {
-                let tag_a = match opt {
+            for sort_option in sort_by {
+                let tag_a = match sort_option {
                     Tag::Field(field_selection) => match a.get_field(field_selection) {
                         Some(field_value) => field_value,
                         None => continue,
                     },
-                    _ => match a.get_tag(&opt) {
+                    _ => match a.get_tag(&sort_option) {
                         Some(tag_value) => tag_value.to_owned(),
                         None => continue,
                     },
                 };
 
-                let tag_b = match opt {
+                let tag_b = match sort_option {
                     Tag::Field(field_selection) => match b.get_field(field_selection) {
                         Some(field_value) => field_value,
                         None => continue,
                     },
-                    _ => match b.get_tag(&opt) {
+                    _ => match b.get_tag(&sort_option) {
                         Some(tag_value) => tag_value.to_owned(),
                         None => continue,
                     },
                 };
 
-                // Try to parse the tags as f64
                 if let (Ok(num_a), Ok(num_b)) = (tag_a.parse::<i32>(), tag_b.parse::<i32>()) {
                     // 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;
-                    }
+                    return num_a.cmp(&num_b);
                 } 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;
-                    }
+                    return tag_a.cmp(&tag_b);
                 }
             }
 
             // If all tags are equal, sort by Track number
-            a.get_tag(&Tag::Track).cmp(&b.get_tag(&Tag::Track))
+            let path_a = PathBuf::from(a.get_field("location").unwrap());
+            let path_b = PathBuf::from(b.get_field("location").unwrap());
+
+            path_a.file_name().cmp(&path_b.file_name())
         });
 
         if new_songs.len() > 0 {
@@ -786,22 +780,22 @@ impl MusicLibrary {
     pub fn albums(&self) -> BTreeMap<String, Album> {
         let mut albums: BTreeMap<String, Album> = BTreeMap::new();
         for result in &self.library {
-            let title = match result.get_tag(&Tag::Album){
+            let title = match result.get_tag(&Tag::Album) {
                 Some(title) => title,
                 None => continue
             };
-            let disc_num = result.get_tag(&Tag::Disk).unwrap_or(&"".to_string()).parse::<usize>().unwrap_or(1);
-
             let norm_title = normalize(title);
+
+            let disc_num = result.get_tag(&Tag::Disk).unwrap_or(&"".to_string()).parse::<usize>().unwrap_or(1);
             match albums.get_mut(&norm_title) {
+                // If the album is in the list, add the track to the appropriate disc in it
                 Some(album) => {
                     match album.discs.get_mut(&disc_num) {
                         Some(disc) => disc.push(result),
-                        None => {
-                            album.discs.insert(disc_num, vec![result]);
-                        }
+                        None => {album.discs.insert(disc_num, vec![result]);}
                     }
                 },
+                // If the album is not in the list, make a new one and add it
                 None => {
                     let new_album = Album {
                         title,
@@ -814,27 +808,29 @@ impl MusicLibrary {
             }
         }
 
+        // Sort the tracks in each disk in each album
         let blank = String::from("");
         albums.par_iter_mut().for_each(|album| {
             for disc in &mut album.1.discs {
                 disc.1.par_sort_by(|a, b| {
-                    if let (Ok(num_a), Ok(num_b)) = (
-                        a.get_tag(&Tag::Title).unwrap_or(&blank).parse::<i32>(),
-                        b.get_tag(&Tag::Title).unwrap_or(&blank).parse::<i32>()
-                    ) {
-                        // If parsing succeeds, compare as numbers
-                        match num_a < num_b {
-                            true => return std::cmp::Ordering::Less,
-                            false => return std::cmp::Ordering::Greater
-                        }
-                    }
-                    match a.get_field("location").unwrap() < b.get_field("location").unwrap() {
-                        true => return std::cmp::Ordering::Less,
-                        false => return std::cmp::Ordering::Greater
+                    let a_track = a.get_tag(&Tag::Track).unwrap_or(&blank);
+                    let b_track = b.get_tag(&Tag::Track).unwrap_or(&blank);
+
+                    if let (Ok(num_a), Ok(num_b)) = (a_track.parse::<i32>(), b_track.parse::<i32>()) {
+                        // If parsing the track numbers succeeds, compare as numbers
+                        num_a.cmp(&num_b)
+                    } else {
+                        // If parsing doesn't succeed, compare the locations
+                        let path_a = PathBuf::from(a.get_field("location").unwrap());
+                        let path_b = PathBuf::from(b.get_field("location").unwrap());
+
+                        path_a.file_name().cmp(&path_b.file_name())
                     }
                 });
             }
         });
+
+        // Return the albums!
         albums
     }
 
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 1a6d8a4..59cb4d8 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,8 +1,5 @@
 use std::io::{BufReader, BufWriter};
 use std::{path::PathBuf, error::Error, fs};
-use flate2::Compression;
-use flate2::write::ZlibEncoder;
-use flate2::read::ZlibDecoder;
 
 use snap;
 
@@ -10,37 +7,40 @@ use unidecode::unidecode;
 use crate::music_storage::music_db::Song;
 
 pub fn normalize(input_string: &String) -> String {
+    // Normalize the unicode and convert everything to lowercase
     let mut normalized = unidecode(input_string);
 
     // Remove non alphanumeric characters
-    normalized.retain(|c| c.is_alphabetic());
-    normalized = normalized.to_ascii_lowercase();
+    normalized.retain(|c| c.is_alphanumeric());
 
     normalized
 }
 
 pub fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
+
+    // Create a new snap reader over the database file
     let database = fs::File::open(path)?;
     let reader = BufReader::new(database);
-    //let mut d = ZlibDecoder::new(reader);
-
     let mut d = snap::read::FrameDecoder::new(reader);
 
+    // Decode the library from the serialized data into the vec
     let library: Vec<Song> = bincode::serde::decode_from_std_read(&mut d, bincode::config::standard().with_little_endian().with_variable_int_encoding())?;
     Ok(library)
 }
 
 pub fn write_library(library: &Vec<Song>, path: PathBuf, take_backup: bool) -> Result<(), Box<dyn Error>> {
+
+    // Create 2 new names for the file, a temporary one for writing out, and a backup
     let mut writer_name = path.clone();
     writer_name.set_extension("tmp");
     let mut backup_name = path.clone();
     backup_name.set_extension("bkp");
 
+    // Create a new BufWriter on the file and make a snap frame encoer for it too
     let writer = BufWriter::new(fs::File::create(writer_name.to_path_buf())?);
-    //let mut e = ZlibEncoder::new(writer, Compression::default());
-
     let mut e = snap::write::FrameEncoder::new(writer);
 
+    // Write out the data using bincode
     bincode::serde::encode_into_std_write(&library, &mut e, bincode::config::standard().with_little_endian().with_variable_int_encoding())?;
 
     if path.exists() && take_backup {

From 7ddc829dacf672ae67463c47d1fbc05641be0489 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 4 Nov 2023 18:42:21 -0500
Subject: [PATCH 18/20] `cargo fmt`

---
 src/music_storage/music_db.rs | 90 ++++++++++++++++-------------------
 src/music_storage/utils.rs    | 27 ++++++++---
 2 files changed, 62 insertions(+), 55 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index e79eae8..c6a59be 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,17 +1,17 @@
 // Crate things
-use crate::music_controller::config::Config;
 use super::utils::{normalize, read_library, write_library};
+use crate::music_controller::config::Config;
 
 // Various std things
 use std::collections::BTreeMap;
 use std::error::Error;
 
 // Files
+use cue::cd::CD;
 use file_format::{FileFormat, Kind};
 use jwalk::WalkDir;
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
 use std::ffi::OsStr;
-use cue::cd::CD;
 use std::fs;
 use std::path::{Path, PathBuf};
 
@@ -105,14 +105,12 @@ impl Song {
         match target_field {
             "location" => Some(self.location.clone().path_string()),
             "plays" => Some(self.plays.clone().to_string()),
-            "format" => {
-                match self.format {
-                    Some(format) => match format.short_name() {
-                        Some(short) => Some(short.to_string()),
-                        None => None
-                    },
-                    None => None
-                }
+            "format" => match self.format {
+                Some(format) => match format.short_name() {
+                    Some(short) => Some(short.to_string()),
+                    None => None,
+                },
+                None => None,
             },
             _ => None, // Other field types are not yet supported
         }
@@ -136,10 +134,7 @@ impl URI {
         match self {
             URI::Local(_) => Err("\"Local\" has no stored index".into()),
             URI::Remote(_, _) => Err("\"Remote\" has no stored index".into()),
-            URI::Cue {
-                index,
-                ..
-            } => Ok(index),
+            URI::Cue { index, .. } => Ok(index),
         }
     }
 
@@ -149,10 +144,7 @@ impl URI {
         match self {
             URI::Local(_) => Err("\"Local\" has no starting time".into()),
             URI::Remote(_, _) => Err("\"Remote\" has no starting time".into()),
-            URI::Cue {
-                start,
-                ..
-            } => Ok(start),
+            URI::Cue { start, .. } => Ok(start),
         }
     }
 
@@ -162,10 +154,7 @@ impl URI {
         match self {
             URI::Local(_) => Err("\"Local\" has no starting time".into()),
             URI::Remote(_, _) => Err("\"Remote\" has no starting time".into()),
-            URI::Cue {
-                end,
-                ..
-            } => Ok(end),
+            URI::Cue { end, .. } => Ok(end),
         }
     }
 
@@ -173,10 +162,7 @@ impl URI {
     pub fn path(&self) -> &PathBuf {
         match self {
             URI::Local(location) => location,
-            URI::Cue {
-                location,
-                ..
-            } => location,
+            URI::Cue { location, .. } => location,
             URI::Remote(_, location) => location,
         }
     }
@@ -184,10 +170,7 @@ impl URI {
     pub fn path_string(&self) -> String {
         let path_str = match self {
             URI::Local(location) => location.as_path().to_string_lossy(),
-            URI::Cue {
-                location,
-                ..
-            } => location.as_path().to_string_lossy(),
+            URI::Cue { location, .. } => location.as_path().to_string_lossy(),
             URI::Remote(_, location) => location.as_path().to_string_lossy(),
         };
         path_str.to_string()
@@ -273,7 +256,7 @@ impl MusicLibrary {
         match global_config.db_path.exists() {
             true => {
                 library = read_library(*global_config.db_path.clone())?;
-            },
+            }
             false => {
                 // Create the database if it does not exist
                 // possibly from the backup file
@@ -718,7 +701,9 @@ impl MusicLibrary {
                     },
                 };
 
-                if normalize(&track_result.to_string()).contains(&normalize(&query_string.to_owned())) {
+                if normalize(&track_result.to_string())
+                    .contains(&normalize(&query_string.to_owned()))
+                {
                     songs.lock().unwrap().push(track);
                     return;
                 }
@@ -782,17 +767,21 @@ impl MusicLibrary {
         for result in &self.library {
             let title = match result.get_tag(&Tag::Album) {
                 Some(title) => title,
-                None => continue
+                None => continue,
             };
             let norm_title = normalize(title);
 
-            let disc_num = result.get_tag(&Tag::Disk).unwrap_or(&"".to_string()).parse::<usize>().unwrap_or(1);
+            let disc_num = result
+                .get_tag(&Tag::Disk)
+                .unwrap_or(&"".to_string())
+                .parse::<usize>()
+                .unwrap_or(1);
             match albums.get_mut(&norm_title) {
                 // If the album is in the list, add the track to the appropriate disc in it
-                Some(album) => {
-                    match album.discs.get_mut(&disc_num) {
-                        Some(disc) => disc.push(result),
-                        None => {album.discs.insert(disc_num, vec![result]);}
+                Some(album) => match album.discs.get_mut(&disc_num) {
+                    Some(disc) => disc.push(result),
+                    None => {
+                        album.discs.insert(disc_num, vec![result]);
                     }
                 },
                 // If the album is not in the list, make a new one and add it
@@ -816,7 +805,8 @@ impl MusicLibrary {
                     let a_track = a.get_tag(&Tag::Track).unwrap_or(&blank);
                     let b_track = b.get_tag(&Tag::Track).unwrap_or(&blank);
 
-                    if let (Ok(num_a), Ok(num_b)) = (a_track.parse::<i32>(), b_track.parse::<i32>()) {
+                    if let (Ok(num_a), Ok(num_b)) = (a_track.parse::<i32>(), b_track.parse::<i32>())
+                    {
                         // If parsing the track numbers succeeds, compare as numbers
                         num_a.cmp(&num_b)
                     } else {
@@ -834,19 +824,23 @@ impl MusicLibrary {
         albums
     }
 
-    pub fn query_albums(&self,
-        query_string: &String,  // The query itself
+    pub fn query_albums(
+        &self,
+        query_string: &String, // The query itself
     ) -> Result<Vec<Album>, Box<dyn Error>> {
         let all_albums = self.albums();
 
         let normalized_query = normalize(query_string);
-        let albums: Vec<Album> = all_albums.par_iter().filter_map(|album|
-            if normalize(album.0).contains(&normalized_query) {
-                Some(album.1.clone())
-            } else {
-                None
-            }
-        ).collect();
+        let albums: Vec<Album> = all_albums
+            .par_iter()
+            .filter_map(|album| {
+                if normalize(album.0).contains(&normalized_query) {
+                    Some(album.1.clone())
+                } else {
+                    None
+                }
+            })
+            .collect();
 
         Ok(albums)
     }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 59cb4d8..590f8ce 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,10 +1,10 @@
 use std::io::{BufReader, BufWriter};
-use std::{path::PathBuf, error::Error, fs};
+use std::{error::Error, fs, path::PathBuf};
 
 use snap;
 
-use unidecode::unidecode;
 use crate::music_storage::music_db::Song;
+use unidecode::unidecode;
 
 pub fn normalize(input_string: &String) -> String {
     // Normalize the unicode and convert everything to lowercase
@@ -17,19 +17,26 @@ pub fn normalize(input_string: &String) -> String {
 }
 
 pub fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
-
     // Create a new snap reader over the database file
     let database = fs::File::open(path)?;
     let reader = BufReader::new(database);
     let mut d = snap::read::FrameDecoder::new(reader);
 
     // Decode the library from the serialized data into the vec
-    let library: Vec<Song> = bincode::serde::decode_from_std_read(&mut d, bincode::config::standard().with_little_endian().with_variable_int_encoding())?;
+    let library: Vec<Song> = bincode::serde::decode_from_std_read(
+        &mut d,
+        bincode::config::standard()
+            .with_little_endian()
+            .with_variable_int_encoding(),
+    )?;
     Ok(library)
 }
 
-pub fn write_library(library: &Vec<Song>, path: PathBuf, take_backup: bool) -> Result<(), Box<dyn Error>> {
-
+pub fn write_library(
+    library: &Vec<Song>,
+    path: PathBuf,
+    take_backup: bool,
+) -> Result<(), Box<dyn Error>> {
     // Create 2 new names for the file, a temporary one for writing out, and a backup
     let mut writer_name = path.clone();
     writer_name.set_extension("tmp");
@@ -41,7 +48,13 @@ pub fn write_library(library: &Vec<Song>, path: PathBuf, take_backup: bool) -> R
     let mut e = snap::write::FrameEncoder::new(writer);
 
     // Write out the data using bincode
-    bincode::serde::encode_into_std_write(&library, &mut e, bincode::config::standard().with_little_endian().with_variable_int_encoding())?;
+    bincode::serde::encode_into_std_write(
+        &library,
+        &mut e,
+        bincode::config::standard()
+            .with_little_endian()
+            .with_variable_int_encoding(),
+    )?;
 
     if path.exists() && take_backup {
         fs::rename(&path, backup_name)?;

From 2e0ce48506f80a8ef817300f429f1ba1b44d95c7 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 6 Nov 2023 02:39:09 -0600
Subject: [PATCH 19/20] A bunch of improvements

---
 Cargo.toml                               |   1 -
 src/lib.rs                               |   6 +-
 src/music_controller/music_controller.rs |  15 +---
 src/music_player/music_player.rs         |  32 -------
 src/music_processor/music_processor.rs   |  45 ----------
 src/music_storage/music_db.rs            | 110 +++++++++++++----------
 src/music_storage/utils.rs               |  42 +++++++--
 7 files changed, 101 insertions(+), 150 deletions(-)
 delete mode 100644 src/music_processor/music_processor.rs

diff --git a/Cargo.toml b/Cargo.toml
index 86ab8a8..3376e1e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,7 +38,6 @@ rayon = "1.8.0"
 log = "0.4"
 pretty_env_logger = "0.4"
 cue = "2.0.0"
-jwalk = "0.8.1"
 base64 = "0.21.5"
 zip = "0.6.6"
 flate2 = "1.0.28"
diff --git a/src/lib.rs b/src/lib.rs
index 9afe354..7aa37eb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -5,11 +5,7 @@ pub mod music_tracker {
 pub mod music_storage {
     pub mod music_db;
     pub mod playlist;
-    pub mod utils;
-}
-
-pub mod music_processor {
-    pub mod music_processor;
+    mod utils;
 }
 
 pub mod music_player {
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 9455e58..51be432 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -2,7 +2,7 @@ use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 
 use crate::music_controller::config::Config;
-use crate::music_player::music_player::{DSPMessage, DecoderMessage, MusicPlayer, PlayerStatus};
+use crate::music_player::music_player::{DecoderMessage, MusicPlayer, PlayerStatus};
 use crate::music_storage::music_db::{MusicLibrary, Song, Tag};
 
 pub struct MusicController {
@@ -63,19 +63,6 @@ impl MusicController {
         return self.music_player.get_current_song();
     }
 
-    /// Gets audio playback volume
-    pub fn get_vol(&self) -> f32 {
-        return self.music_player.music_processor.audio_volume;
-    }
-
-    /// Sets audio playback volume on a scale of 0.0 to 1.0
-    pub fn set_vol(&mut self, volume: f32) {
-        self.music_player.music_processor.audio_volume = volume;
-        self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(
-            self.music_player.music_processor.clone(),
-        ))));
-    }
-
     /// Queries the [MusicLibrary], returning a `Vec<Song>`
     pub fn query_library(
         &self,
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
index 9ed99e5..e3c43ad 100644
--- a/src/music_player/music_player.rs
+++ b/src/music_player/music_player.rs
@@ -19,7 +19,6 @@ use futures::AsyncBufRead;
 
 use crate::music_controller::config::Config;
 use crate::music_player::music_output::AudioStream;
-use crate::music_processor::music_processor::MusicProcessor;
 use crate::music_storage::music_db::{Song, URI};
 use crate::music_tracker::music_tracker::{
     DiscordRPC, LastFM, ListenBrainz, MusicTracker, TrackerError,
@@ -27,7 +26,6 @@ use crate::music_tracker::music_tracker::{
 
 // Struct that controls playback of music
 pub struct MusicPlayer {
-    pub music_processor: MusicProcessor,
     player_status: PlayerStatus,
     music_trackers: Vec<Box<dyn MusicTracker + Send>>,
     current_song: Arc<RwLock<Option<Song>>>,
@@ -51,7 +49,6 @@ pub enum DecoderMessage {
     Pause,
     Stop,
     SeekTo(u64),
-    DSP(DSPMessage),
 }
 
 #[derive(Clone)]
@@ -60,11 +57,6 @@ pub enum TrackerMessage {
     TrackNow(Song),
 }
 
-#[derive(Debug, Clone)]
-pub enum DSPMessage {
-    UpdateProcessor(Box<MusicProcessor>),
-}
-
 // Holds a song decoder reader, etc
 struct SongHandler {
     pub reader: Box<dyn FormatReader>,
@@ -148,7 +140,6 @@ impl MusicPlayer {
         );
 
         MusicPlayer {
-            music_processor: MusicProcessor::new(),
             music_trackers: Vec::new(),
             player_status: PlayerStatus::Stopped,
             current_song,
@@ -235,8 +226,6 @@ impl MusicPlayer {
 
             let mut audio_output: Option<Box<dyn AudioStream>> = None;
 
-            let mut music_processor = MusicProcessor::new();
-
             let (tracker_sender, tracker_receiver): (
                 Sender<TrackerMessage>,
                 Receiver<TrackerMessage>,
@@ -290,11 +279,6 @@ impl MusicPlayer {
                             status_sender.send(PlayerStatus::Paused).unwrap();
                         }
                         Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time),
-                        Some(DecoderMessage::DSP(dsp_message)) => match dsp_message {
-                            DSPMessage::UpdateProcessor(new_processor) => {
-                                music_processor = *new_processor
-                            }
-                        },
                         // Exits main decode loop and subsequently ends thread
                         Some(DecoderMessage::Stop) => {
                             status_sender.send(PlayerStatus::Stopped).unwrap();
@@ -373,22 +357,6 @@ impl MusicPlayer {
                                         .unwrap(),
                                 );
                             }
-                            // Handles audio normally provided there is an audio stream
-                            if let Some(ref mut audio_output) = audio_output {
-                                // Changes buffer of the MusicProcessor if the packet has a differing capacity or spec
-                                if music_processor.audio_buffer.capacity() != decoded.capacity()
-                                    || music_processor.audio_buffer.spec() != decoded.spec()
-                                {
-                                    let spec = *decoded.spec();
-                                    let duration = decoded.capacity() as u64;
-
-                                    music_processor.set_buffer(duration, spec);
-                                }
-                                let transformed_audio = music_processor.process(&decoded);
-
-                                // Writes transformed packet to audio out
-                                audio_output.write(transformed_audio).unwrap()
-                            }
                         }
                         Err(Error::IoError(_)) => {
                             // rest in peace packet
diff --git a/src/music_processor/music_processor.rs b/src/music_processor/music_processor.rs
deleted file mode 100644
index 64f4eaa..0000000
--- a/src/music_processor/music_processor.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-use std::fmt::Debug;
-
-use symphonia::core::audio::{AsAudioBufferRef, AudioBuffer, AudioBufferRef, Signal, SignalSpec};
-
-#[derive(Clone)]
-pub struct MusicProcessor {
-    pub audio_buffer: AudioBuffer<f32>,
-    pub audio_volume: f32,
-}
-
-impl Debug for MusicProcessor {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("MusicProcessor")
-            .field("audio_volume", &self.audio_volume)
-            .finish()
-    }
-}
-
-impl MusicProcessor {
-    /// Returns new MusicProcessor with blank buffer and 100% volume
-    pub fn new() -> Self {
-        MusicProcessor {
-            audio_buffer: AudioBuffer::unused(),
-            audio_volume: 1.0,
-        }
-    }
-
-    /// Processes audio samples
-    ///
-    /// Currently only supports transformations of volume
-    pub fn process(&mut self, audio_buffer_ref: &AudioBufferRef) -> AudioBufferRef {
-        audio_buffer_ref.convert(&mut self.audio_buffer);
-
-        let process = |sample| sample * self.audio_volume;
-
-        self.audio_buffer.transform(process);
-
-        return self.audio_buffer.as_audio_buffer_ref();
-    }
-
-    /// Sets buffer of the MusicProcessor
-    pub fn set_buffer(&mut self, duration: u64, spec: SignalSpec) {
-        self.audio_buffer = AudioBuffer::new(duration, spec);
-    }
-}
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index c6a59be..9e96e44 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,17 +1,17 @@
 // Crate things
-use super::utils::{normalize, read_library, write_library};
+use super::utils::{normalize, read_library, write_library, find_images};
 use crate::music_controller::config::Config;
 
 // Various std things
 use std::collections::BTreeMap;
 use std::error::Error;
+use std::ops::ControlFlow::{Break, Continue};
 
 // Files
 use cue::cd::CD;
 use file_format::{FileFormat, Kind};
-use jwalk::WalkDir;
+use walkdir::WalkDir;
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
-use std::ffi::OsStr;
 use std::fs;
 use std::path::{Path, PathBuf};
 
@@ -28,9 +28,9 @@ use rayon::prelude::*;
 use std::sync::{Arc, Mutex, RwLock};
 
 #[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct AlbumArt {
-    pub index: u16,
-    pub path: Option<URI>,
+pub enum AlbumArt {
+    Embedded(usize),
+    External(URI),
 }
 
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
@@ -112,7 +112,7 @@ impl Song {
                 },
                 None => None,
             },
-            _ => None, // Other field types are not yet supported
+            _ => todo!(), // Other field types are not yet supported
         }
     }
 }
@@ -182,6 +182,7 @@ pub enum Service {
     InternetRadio,
     Spotify,
     Youtube,
+    None,
 }
 
 #[derive(Clone, Debug)]
@@ -236,6 +237,8 @@ impl Album<'_> {
     }
 }
 
+const BLOCKED_EXTENSIONS: [&str; 3] = ["vob", "log", "txt"];
+
 #[derive(Debug)]
 pub struct MusicLibrary {
     pub library: Vec<Song>,
@@ -272,8 +275,7 @@ impl MusicLibrary {
         Ok(Self { library })
     }
 
-    /// Serializes the database out to the file
-    /// specified in the config
+    /// Serializes the database out to the file specified in the config
     pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
         match config.db_path.try_exists() {
             Ok(exists) => {
@@ -293,19 +295,16 @@ impl MusicLibrary {
     /// Queries for a [Song] by its [URI], returning a single `Song`
     /// with the `URI` that matches
     fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
-        let result = Arc::new(Mutex::new(None));
-        let index = Arc::new(Mutex::new(0));
-        let _ = &self.library.par_iter().enumerate().for_each(|(i, track)| {
+        let result = self.library.par_iter().enumerate().try_for_each(|(i, track)| {
             if path == &track.location {
-                *result.clone().lock().unwrap() = Some(track);
-                *index.clone().lock().unwrap() = i;
-                return;
+                return std::ops::ControlFlow::Break((track, i));
             }
+            Continue(())
         });
-        let song = Arc::try_unwrap(result).unwrap().into_inner().unwrap();
-        match song {
-            Some(song) => Some((song, Arc::try_unwrap(index).unwrap().into_inner().unwrap())),
-            None => None,
+
+        match result {
+            Break(song) => Some(song),
+            Continue(_) => None,
         }
     }
 
@@ -313,7 +312,7 @@ impl MusicLibrary {
     /// with matching `PathBuf`s
     fn query_path(&self, path: &PathBuf) -> Option<Vec<&Song>> {
         let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
-        let _ = &self.library.par_iter().for_each(|track| {
+        let _ = self.library.par_iter().for_each(|track| {
             if path == track.location.path() {
                 result.clone().lock().unwrap().push(&track);
                 return;
@@ -326,20 +325,18 @@ impl MusicLibrary {
         }
     }
 
-    /// Finds all the music files within a specified folder
+    /// Finds all the audio files within a specified folder
     pub fn scan_folder(
         &mut self,
         target_path: &str,
         config: &Config,
     ) -> Result<usize, Box<dyn std::error::Error>> {
         let mut total = 0;
-        let mut i = 0;
-        for entry in WalkDir::new(target_path)
+        for target_file in WalkDir::new(target_path)
             .follow_links(true)
             .into_iter()
             .filter_map(|e| e.ok())
         {
-            let target_file = entry;
             let path = target_file.path();
 
             // Ensure the target is a file and not a directory,
@@ -348,6 +345,7 @@ impl MusicLibrary {
                 continue;
             }
 
+            /* TODO: figure out how to increase the speed of this maybe
             // Check if the file path is already in the db
             if self.query_uri(&URI::Local(path.to_path_buf())).is_some() {
                 continue;
@@ -358,30 +356,27 @@ impl MusicLibrary {
             if i % 500 == 0 {
                 self.save(config).unwrap();
             }
+            */
 
             let format = FileFormat::from_file(&path)?;
-            let extension: &OsStr = match path.extension() {
-                Some(ext) => ext,
-                None => OsStr::new(""),
+            let extension = match path.extension() {
+                Some(ext) => ext.to_string_lossy().to_ascii_lowercase(),
+                None => String::new(),
             };
 
             // If it's a normal file, add it to the database
             // if it's a cuesheet, do a bunch of fancy stuff
             if (format.kind() == Kind::Audio || format.kind() == Kind::Video)
-                && extension.to_ascii_lowercase() != "log"
-                && extension.to_ascii_lowercase() != "vob"
+                && !BLOCKED_EXTENSIONS.contains(&extension.as_str())
             {
                 match self.add_file(&target_file.path()) {
-                    Ok(_) => {
-                        //println!("{:?}", target_file.path());
-                        total += 1
-                    }
+                    Ok(_) => total += 1,
                     Err(_error) => {
-                        //println!("{}, {:?}: {}", format, target_file.file_name(), _error)
+                        println!("{}, {:?}: {}", format, target_file.file_name(), _error)
                     } // TODO: Handle more of these errors
                 };
-            } else if extension.to_ascii_lowercase() == "cue" {
-                total += match self.add_cuesheet(&target_file.path()) {
+            } else if extension == "cue" {
+                total += match self.add_cuesheet(&target_file.path().to_path_buf()) {
                     Ok(added) => added,
                     Err(error) => {
                         println!("{}", error);
@@ -437,25 +432,26 @@ impl MusicLibrary {
             };
 
             let value = match item.value() {
-                ItemValue::Text(value) => String::from(value),
-                ItemValue::Locator(value) => String::from(value),
+                ItemValue::Text(value) => value.clone(),
+                ItemValue::Locator(value) => value.clone(),
                 ItemValue::Binary(bin) => format!("BIN#{}", general_purpose::STANDARD.encode(bin)),
             };
 
             tags.insert(key, value);
         }
 
-        // Get all the album artwork information
+        // Get all the album artwork information from the file
         let mut album_art: Vec<AlbumArt> = Vec::new();
         for (i, _art) in tag.pictures().iter().enumerate() {
-            let new_art = AlbumArt {
-                index: i as u16,
-                path: None,
-            };
+            let new_art = AlbumArt::Embedded(i as usize);
 
             album_art.push(new_art)
         }
 
+        // Find images around the music file that can be used
+        let mut found_images = find_images(&target_file.to_path_buf()).unwrap();
+        album_art.append(&mut found_images);
+
         // Get the format as a string
         let format: Option<FileFormat> = match FileFormat::from_file(target_file) {
             Ok(fmt) => Some(fmt),
@@ -515,7 +511,10 @@ impl MusicLibrary {
             }
 
             // Try to remove the original audio file from the db if it exists
-            let _ = self.remove_uri(&URI::Local(audio_location.clone()));
+            match self.remove_uri(&URI::Local(audio_location.clone())) {
+                Ok(_) => tracks_added -= 1,
+                Err(_) => ()
+            };
 
             // Get the track timing information
             let pregap = match track.get_zero_pre() {
@@ -687,6 +686,7 @@ impl MusicLibrary {
         sort_by: &Vec<Tag>,     // Tags to sort the resulting data by
     ) -> Option<Vec<&Song>> {
         let songs = Arc::new(Mutex::new(Vec::new()));
+        //let matcher = SkimMatcherV2::default();
 
         self.library.par_iter().for_each(|track| {
             for tag in target_tags {
@@ -701,9 +701,19 @@ impl MusicLibrary {
                     },
                 };
 
-                if normalize(&track_result.to_string())
-                    .contains(&normalize(&query_string.to_owned()))
-                {
+                /*
+                let match_level = match matcher.fuzzy_match(&normalize(&track_result), &normalize(query_string)) {
+                    Some(conf) => conf,
+                    None => continue
+                };
+
+                if match_level > 100 {
+                    songs.lock().unwrap().push(track);
+                    return;
+                }
+                */
+
+                if normalize(&track_result.to_string()).contains(&normalize(&query_string.to_owned())) {
                     songs.lock().unwrap().push(track);
                     return;
                 }
@@ -776,6 +786,7 @@ impl MusicLibrary {
                 .unwrap_or(&"".to_string())
                 .parse::<usize>()
                 .unwrap_or(1);
+
             match albums.get_mut(&norm_title) {
                 // If the album is in the list, add the track to the appropriate disc in it
                 Some(album) => match album.discs.get_mut(&disc_num) {
@@ -786,11 +797,13 @@ impl MusicLibrary {
                 },
                 // If the album is not in the list, make a new one and add it
                 None => {
+                    let album_art = result.album_art.get(0);
+
                     let new_album = Album {
                         title,
                         artist: result.get_tag(&Tag::AlbumArtist),
                         discs: BTreeMap::from([(disc_num, vec![result])]),
-                        cover: None,
+                        cover: album_art,
                     };
                     albums.insert(norm_title, new_album);
                 }
@@ -824,6 +837,7 @@ impl MusicLibrary {
         albums
     }
 
+    /// Queries a list of albums by title
     pub fn query_albums(
         &self,
         query_string: &String, // The query itself
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 590f8ce..0847ae0 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,13 +1,14 @@
 use std::io::{BufReader, BufWriter};
 use std::{error::Error, fs, path::PathBuf};
+use walkdir::WalkDir;
+use file_format::{FileFormat, Kind};
 
 use snap;
 
-use crate::music_storage::music_db::Song;
+use super::music_db::{Song, AlbumArt, URI};
 use unidecode::unidecode;
 
-pub fn normalize(input_string: &String) -> String {
-    // Normalize the unicode and convert everything to lowercase
+pub(super) fn normalize(input_string: &String) -> String {
     let mut normalized = unidecode(input_string);
 
     // Remove non alphanumeric characters
@@ -16,7 +17,7 @@ pub fn normalize(input_string: &String) -> String {
     normalized
 }
 
-pub fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
+pub(super) fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
     // Create a new snap reader over the database file
     let database = fs::File::open(path)?;
     let reader = BufReader::new(database);
@@ -32,7 +33,7 @@ pub fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
     Ok(library)
 }
 
-pub fn write_library(
+pub(super) fn write_library(
     library: &Vec<Song>,
     path: PathBuf,
     take_backup: bool,
@@ -63,3 +64,34 @@ pub fn write_library(
 
     Ok(())
 }
+
+pub fn find_images(song_path: &PathBuf) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
+    let mut images: Vec<AlbumArt> = Vec::new();
+
+    let song_dir = song_path.parent().ok_or("")?;
+    for target_file in WalkDir::new(song_dir)
+        .follow_links(true)
+        .into_iter()
+        .filter_map(|e| e.ok())
+    {
+        if target_file.depth() >= 3 { // Don't recurse very deep
+            break
+        }
+
+        let path = target_file.path();
+        if !path.is_file() {
+            continue;
+        }
+
+        let format = FileFormat::from_file(&path)?.kind();
+        if format != Kind::Image {
+            break
+        }
+
+        let image_uri = URI::Local(path.to_path_buf().canonicalize().unwrap());
+
+        images.push(AlbumArt::External(image_uri));
+    }
+
+    Ok(images)
+}

From f00df67844d1571140d0c4134a13961e1a637d46 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 6 Nov 2023 16:41:28 -0600
Subject: [PATCH 20/20] Work in progress optimizations

---
 Cargo.toml                    |  10 +-
 src/music_storage/music_db.rs | 203 ++++++++++++++++++----------------
 2 files changed, 110 insertions(+), 103 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 3376e1e..d30fe73 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,12 +12,12 @@ keywords = ["audio", "music"]
 categories = ["multimedia::audio"]
 
 [dependencies]
-file-format = { version = "0.17.3", features = ["reader", "serde"] }
+file-format = { version = "0.22.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
 lofty = "0.16.1"
-serde = { version = "1.0.164", features = ["derive"] }
+serde = { version = "1.0.191", features = ["derive"] }
 time = "0.3.22"
 toml = "0.7.5"
-walkdir = "2.3.3"
+walkdir = "2.4.0"
 cpal = "0.15.2"
 heapless = "0.7.16"
 rb = "0.4.1"
@@ -37,8 +37,6 @@ unidecode = "0.3.0"
 rayon = "1.8.0"
 log = "0.4"
 pretty_env_logger = "0.4"
-cue = "2.0.0"
 base64 = "0.21.5"
-zip = "0.6.6"
-flate2 = "1.0.28"
 snap = "1.1.0"
+rcue = "0.1.3"
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 9e96e44..3372323 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -8,7 +8,7 @@ use std::error::Error;
 use std::ops::ControlFlow::{Break, Continue};
 
 // Files
-use cue::cd::CD;
+use rcue::parser::parse_from_file;
 use file_format::{FileFormat, Kind};
 use walkdir::WalkDir;
 use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
@@ -33,6 +33,15 @@ pub enum AlbumArt {
     External(URI),
 }
 
+impl AlbumArt {
+    pub fn uri(&self) -> Option<&URI> {
+        match self {
+            Self::Embedded(_) => None,
+            Self::External(uri) => Some(uri),
+        }
+    }
+}
+
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
 pub enum Tag {
     Title,
@@ -490,21 +499,15 @@ impl MusicLibrary {
     pub fn add_cuesheet(&mut self, cuesheet: &PathBuf) -> Result<usize, Box<dyn Error>> {
         let mut tracks_added = 0;
 
-        let cue_data = CD::parse_file(cuesheet.to_owned()).unwrap();
+        let cue_data = parse_from_file(&cuesheet.as_path().to_string_lossy(), false).unwrap();
 
         // Get album level information
-        let album_title = &cue_data
-            .get_cdtext()
-            .read(cue::cd_text::PTI::Title)
-            .unwrap_or(String::new());
-        let album_artist = &cue_data
-            .get_cdtext()
-            .read(cue::cd_text::PTI::Performer)
-            .unwrap_or(String::new());
+        let album_title = &cue_data.title;
+        let album_artist = &cue_data.performer;
 
         let parent_dir = cuesheet.parent().expect("The file has no parent path??");
-        for (i, track) in cue_data.tracks().iter().enumerate() {
-            let audio_location = parent_dir.join(track.get_filename());
+        for file in cue_data.files.iter() {
+            let audio_location = &parent_dir.join(file.file.clone());
 
             if !audio_location.exists() {
                 continue;
@@ -516,100 +519,106 @@ impl MusicLibrary {
                 Err(_) => ()
             };
 
-            // Get the track timing information
-            let pregap = match track.get_zero_pre() {
-                Some(pregap) => Duration::from_micros((pregap as f32 * 13333.333333) as u64),
-                None => Duration::from_secs(0),
-            };
-            let postgap = match track.get_zero_post() {
-                Some(postgap) => Duration::from_micros((postgap as f32 * 13333.333333) as u64),
-                None => Duration::from_secs(0),
-            };
-            let mut start = Duration::from_micros((track.get_start() as f32 * 13333.333333) as u64);
-            start -= pregap;
+            let next_track = file.tracks.clone();
+            let mut next_track = next_track.iter().skip(1);
+            for (i, track) in file.tracks.iter().enumerate() {
+                // Get the track timing information
+                let pregap = match track.pregap {
+                    Some(pregap) => pregap,
+                    None => Duration::from_secs(0),
+                };
+                let postgap = match track.postgap {
+                    Some(postgap) => postgap,
+                    None => Duration::from_secs(0),
+                };
+                let mut start = track.indices[0].1;
+                start -= pregap;
 
-            let duration = match track.get_length() {
-                Some(len) => Duration::from_micros((len as f32 * 13333.333333) as u64),
-                None => {
-                    let tagged_file = match lofty::read_from_path(&audio_location) {
-                        Ok(tagged_file) => tagged_file,
-
-                        Err(_) => match Probe::open(&audio_location)?.read() {
+                let duration = match next_track.next() {
+                    Some(future) => match future.indices.get(0) {
+                        Some(val) => val.1 - start,
+                        None => Duration::from_secs(0)
+                    }
+                    None => {
+                        let tagged_file = match lofty::read_from_path(&audio_location) {
                             Ok(tagged_file) => tagged_file,
 
-                            Err(error) => return Err(error.into()),
-                        },
-                    };
+                            Err(_) => match Probe::open(&audio_location)?.read() {
+                                Ok(tagged_file) => tagged_file,
 
-                    tagged_file.properties().duration() - start
-                }
-            };
-            let end = start + duration + postgap;
+                                Err(error) => return Err(error.into()),
+                            },
+                        };
 
-            // Get the format as a string
-            let format: Option<FileFormat> = match FileFormat::from_file(&audio_location) {
-                Ok(fmt) => Some(fmt),
-                Err(_) => None,
-            };
-
-            // Get some useful tags
-            let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
-            tags.insert(Tag::Album, album_title.clone());
-            tags.insert(Tag::Key("AlbumArtist".to_string()), album_artist.clone());
-            tags.insert(Tag::Track, (i + 1).to_string());
-            match track.get_cdtext().read(cue::cd_text::PTI::Title) {
-                Some(title) => tags.insert(Tag::Title, title),
-                None => match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) {
-                    Some(title) => tags.insert(Tag::Title, title),
-                    None => {
-                        let namestr = format!("{} - {}", i, track.get_filename());
-                        tags.insert(Tag::Title, namestr)
+                        tagged_file.properties().duration() - start
                     }
-                },
-            };
-            match track.get_cdtext().read(cue::cd_text::PTI::Performer) {
-                Some(artist) => tags.insert(Tag::Artist, artist),
-                None => None,
-            };
-            match track.get_cdtext().read(cue::cd_text::PTI::Genre) {
-                Some(genre) => tags.insert(Tag::Genre, genre),
-                None => None,
-            };
-            match track.get_cdtext().read(cue::cd_text::PTI::Message) {
-                Some(comment) => tags.insert(Tag::Comment, comment),
-                None => None,
-            };
+                };
+                let end = start + duration + postgap;
 
-            let album_art = Vec::new();
+                // Get the format as a string
+                let format: Option<FileFormat> = match FileFormat::from_file(&audio_location) {
+                    Ok(fmt) => Some(fmt),
+                    Err(_) => None,
+                };
 
-            let new_song = Song {
-                location: URI::Cue {
-                    location: audio_location,
-                    index: i,
-                    start,
-                    end,
-                },
-                plays: 0,
-                skips: 0,
-                favorited: false,
-                rating: None,
-                format,
-                duration,
-                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,
-            };
-
-            match self.add_song(new_song) {
-                Ok(_) => tracks_added += 1,
-                Err(_error) => {
-                    //println!("{}", _error);
-                    continue;
+                // Get some useful tags
+                let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
+                match album_title {
+                    Some(title) => {tags.insert(Tag::Album, title.clone());},
+                    None => (),
                 }
-            };
+                match album_artist {
+                    Some(artist) => {tags.insert(Tag::Album, artist.clone());},
+                    None => (),
+                }
+                tags.insert(Tag::Track, track.no.parse().unwrap_or((i + 1).to_string()));
+                match track.title.clone() {
+                    Some(title) => tags.insert(Tag::Title, title),
+                    None => match track.isrc.clone() {
+                        Some(title) => tags.insert(Tag::Title, title),
+                        None => {
+                            let namestr = format!("{} - {}", i, file.file.clone());
+                            tags.insert(Tag::Title, namestr)
+                        }
+                    },
+                };
+                match track.performer.clone() {
+                    Some(artist) => tags.insert(Tag::Artist, artist),
+                    None => None,
+                };
+
+                // Find images around the music file that can be used
+                let album_art = find_images(&audio_location.to_path_buf()).unwrap();
+
+                let new_song = Song {
+                    location: URI::Cue {
+                        location: audio_location.clone(),
+                        index: i,
+                        start,
+                        end,
+                    },
+                    plays: 0,
+                    skips: 0,
+                    favorited: false,
+                    rating: None,
+                    format,
+                    duration,
+                    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,
+                };
+
+                match self.add_song(new_song) {
+                    Ok(_) => tracks_added += 1,
+                    Err(_error) => {
+                        //println!("{}", _error);
+                        continue;
+                    }
+                };
+            }
         }
 
         Ok(tracks_added)