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 001/136] 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 002/136] 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 003/136] 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 004/136] 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 005/136] 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 006/136] 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 007/136] 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 008/136] 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 009/136] 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 010/136] 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 011/136] `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 012/136] 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 013/136] 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 014/136] 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 015/136] 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 016/136] 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 017/136] 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 018/136] `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 019/136] 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 020/136] 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)

From 8fec560bcfdf44d520ef2ac3e11b7010941783ba Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 19 Jan 2024 15:37:19 -0600
Subject: [PATCH 021/136] Restructured repository as a workspace

---
 Cargo.toml                               |  34 ++
 src/bus_control.rs                       |   0
 src/lib.rs                               |  24 ++
 src/music_controller/config.rs           |  54 +++
 src/music_controller/init.rs             |  19 +
 src/music_controller/music_controller.rs |  68 ++++
 src/music_player/music_output.rs         | 167 +++++++++
 src/music_player/music_player.rs         | 364 ++++++++++++++++++
 src/music_player/music_resampler.rs      | 147 ++++++++
 src/music_processor/music_processor.rs   |  35 ++
 src/music_storage/music_db.rs            | 456 +++++++++++++++++++++++
 src/music_storage/playlist.rs            |  27 ++
 src/music_tracker/music_tracker.rs       | 186 +++++++++
 13 files changed, 1581 insertions(+)
 create mode 100644 Cargo.toml
 create mode 100644 src/bus_control.rs
 create mode 100644 src/lib.rs
 create mode 100644 src/music_controller/config.rs
 create mode 100644 src/music_controller/init.rs
 create mode 100644 src/music_controller/music_controller.rs
 create mode 100644 src/music_player/music_output.rs
 create mode 100644 src/music_player/music_player.rs
 create mode 100644 src/music_player/music_resampler.rs
 create mode 100644 src/music_processor/music_processor.rs
 create mode 100644 src/music_storage/music_db.rs
 create mode 100644 src/music_storage/playlist.rs
 create mode 100644 src/music_tracker/music_tracker.rs

diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4a63b78
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "dango-core"
+version = "0.1.1"
+edition = "2021"
+license = "AGPL-3.0-only"
+description = "A music backend that manages storage, querying, and playback of remote and local songs."
+homepage = "https://dangoware.com/dango-music-player"
+documentation = "https://docs.rs/dango-core"
+readme = "README.md"
+repository = "https://github.com/DangoWare/dango-music-player"
+keywords = ["audio", "music"]
+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"
+walkdir = "2.3.3"
+cpal = "0.15.2"
+heapless = "0.7.16"
+rb = "0.4.1"
+symphonia = { version = "0.5.3", features = ["all-codecs"] }
+serde_json = "1.0.104"
+cue = "2.0.0"
+async-std = "1.12.0"
+async-trait = "0.1.73"
+md-5 = "0.10.5"
+surf = "2.3.2"
+futures = "0.3.28"
+rubato = "0.12.0"
+arrayvec = "0.7.4"
diff --git a/src/bus_control.rs b/src/bus_control.rs
new file mode 100644
index 0000000..e69de29
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..3a25b3d
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,24 @@
+pub mod music_tracker {
+    pub mod music_tracker;
+}
+
+pub mod music_storage {
+    pub mod music_db;
+    pub mod playlist;
+}
+
+pub mod music_processor {
+    pub mod music_processor;
+}
+
+pub mod music_player {
+    pub mod music_player;
+    pub mod music_output;
+    pub mod music_resampler;
+}
+
+pub mod music_controller {
+    pub mod music_controller;
+    pub mod config;
+    pub mod init;
+}
\ No newline at end of file
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
new file mode 100644
index 0000000..70ee273
--- /dev/null
+++ b/src/music_controller/config.rs
@@ -0,0 +1,54 @@
+use std::path::PathBuf;
+use std::fs::read_to_string;
+use std::fs;
+
+use serde::{Deserialize, Serialize};
+
+use crate::music_tracker::music_tracker::LastFM;
+
+#[derive(Serialize, Deserialize)]
+pub struct Config {
+    pub db_path: Box<PathBuf>,
+    pub lastfm: Option<LastFM>,
+}
+
+impl Config {
+    // Creates and saves a new config with default values
+    pub fn new(config_file: &PathBuf) -> std::io::Result<Config> {
+        let path = PathBuf::from("./music_database.db3");
+        
+        let config = Config {
+            db_path: Box::new(path),
+            lastfm: None,
+        };
+        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!"));
+    }
+    
+    // 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(_) => {},
+        }
+
+        fs::rename(temp_file, config_file)?;
+        Ok(())
+    }
+}
diff --git a/src/music_controller/init.rs b/src/music_controller/init.rs
new file mode 100644
index 0000000..80b91bf
--- /dev/null
+++ b/src/music_controller/init.rs
@@ -0,0 +1,19 @@
+use std::path::Path;
+use std::fs::File;
+
+pub fn init() {
+
+}
+
+fn init_config() {
+    let config_path = "./config.toml";
+
+    if !Path::new(config_path).try_exists().unwrap() {
+        File::create("./config.toml").unwrap();
+    }
+}
+
+fn init_db() {  
+
+}
+
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
new file mode 100644
index 0000000..bb1ab66
--- /dev/null
+++ b/src/music_controller/music_controller.rs
@@ -0,0 +1,68 @@
+use std::path::PathBuf;
+
+use rusqlite::Result;
+
+use crate::music_controller::config::Config;
+use crate::music_player::music_player::{MusicPlayer, PlayerStatus, PlayerMessage, DSPMessage};
+use crate::music_processor::music_processor::MusicProcessor;
+use crate::music_storage::music_db::URI;
+
+pub struct MusicController {
+    pub config: Config,
+    music_player: MusicPlayer,
+}
+
+impl MusicController {
+    /// Creates new MusicController with config at given path
+    pub fn new(config_path: &PathBuf) -> Result<MusicController, std::io::Error>{
+        let config = Config::new(config_path)?;
+        let music_player = MusicPlayer::new();
+        
+        let controller = MusicController {
+            config,
+            music_player,
+        };
+        
+        return Ok(controller)
+    }
+        
+    /// Creates new music controller from a config at given path
+    pub fn from(config_path: &PathBuf) -> std::result::Result<MusicController, toml::de::Error> {
+        let config = Config::from(config_path)?;
+        let music_player = MusicPlayer::new();
+        
+        let controller = MusicController {
+            config,
+            music_player,
+        };
+        
+        return Ok(controller)
+    }
+    
+    /// Opens and plays song at given URI
+    pub fn open_song(&mut self, uri: &URI) {
+        self.music_player.open_song(uri);
+    }
+    
+    /// Sends given message to music player
+    pub fn song_control(&mut self, message: PlayerMessage) {
+        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 audio playback volume
+    pub fn get_vol(&mut 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(PlayerMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone()))));
+    }
+    
+}
diff --git a/src/music_player/music_output.rs b/src/music_player/music_output.rs
new file mode 100644
index 0000000..b774b0f
--- /dev/null
+++ b/src/music_player/music_output.rs
@@ -0,0 +1,167 @@
+use std::{result, thread};
+
+use symphonia::core::audio::{AudioBufferRef, SignalSpec, RawSample, SampleBuffer};
+use symphonia::core::conv::{ConvertibleSample, IntoSample, FromSample};
+use symphonia::core::units::Duration;
+
+use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
+use cpal::{self, SizedSample};
+
+use rb::*;
+
+use crate::music_player::music_resampler::Resampler;
+
+pub trait AudioStream {
+    fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()>;
+    fn flush(&mut self);
+}
+
+#[derive(Debug)]
+pub enum AudioOutputError {
+    OpenStreamError,
+    PlayStreamError,
+    StreamClosedError,
+}
+
+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 struct AudioOutput<T>
+where T: OutputSample,
+{
+    ring_buf_producer: rb::Producer<T>,
+    sample_buf: SampleBuffer<T>,
+    stream: cpal::Stream,
+    resampler: Option<Resampler<T>>,
+}
+impl OutputSample for i8 {}
+impl OutputSample for i16 {}
+impl OutputSample for i32 {}
+//impl OutputSample for i64 {}
+impl OutputSample for u8 {}
+impl OutputSample for u16 {}
+impl OutputSample for u32 {}
+//impl OutputSample for u64 {}
+impl OutputSample for f32 {}
+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!(),
+        };
+    }
+
+
+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>> {
+        let num_channels = config.channels as usize;
+        
+        // Ring buffer is created with 200ms audio capacity
+        let ring_len = ((200 * config.sample_rate.0 as usize) / 1000) * num_channels;
+        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, 
+            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);
+            },
+            //TODO: Handle error here properly
+            move |err| println!("Yeah we erroring out here"),
+            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))
+        }
+        
+        Ok(Box::new(AudioOutput { ring_buf_producer, sample_buf, stream, resampler}))
+    }
+}
+
+impl<T: OutputSample> AudioStream for AudioOutput<T> {
+    // Writes given samples to ring buffer
+    fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()> {
+        if decoded.frames() == 0 {
+            return Ok(());
+        }
+        
+        let mut samples: &[T] = if let Some(resampler) = &mut self.resampler {
+            // Resamples if required
+            match resampler.resample(decoded) {
+                Some(resampled) => resampled,
+                None => return Ok(()),
+            }
+        } else {
+            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();
+            
+            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
new file mode 100644
index 0000000..1996240
--- /dev/null
+++ b/src/music_player/music_player.rs
@@ -0,0 +1,364 @@
+use std::sync::mpsc::{self, Sender, Receiver};
+use std::thread;
+use std::io::SeekFrom;
+
+use async_std::io::ReadExt;
+use async_std::task;
+
+use symphonia::core::codecs::{CODEC_TYPE_NULL, DecoderOptions, Decoder};
+use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
+use symphonia::core::io::{MediaSourceStream, MediaSource};
+use symphonia::core::meta::MetadataOptions;
+use symphonia::core::probe::Hint;
+use symphonia::core::errors::Error;
+use symphonia::core::units::Time;
+
+use futures::AsyncBufRead;
+
+use crate::music_player::music_output::AudioStream;
+use crate::music_processor::music_processor::MusicProcessor;
+use crate::music_storage::music_db::URI;
+
+// Struct that controls playback of music
+pub struct MusicPlayer {
+    pub music_processor: MusicProcessor,
+    player_status: PlayerStatus,
+    message_sender: Option<Sender<PlayerMessage>>,
+    status_receiver: Option<Receiver<PlayerStatus>>,
+}
+
+#[derive(Clone, Copy)]
+pub enum PlayerStatus {
+    Playing,
+    Paused,
+    Stopped,
+    Error,
+}
+
+pub enum PlayerMessage {
+    Play,
+    Pause,
+    Stop,
+    SeekTo(u64),
+    DSP(DSPMessage)
+}
+
+pub enum DSPMessage {
+    UpdateProcessor(Box<MusicProcessor>)
+}
+
+impl MusicPlayer {
+    pub fn new() -> Self {
+        MusicPlayer {
+            music_processor: MusicProcessor::new(),
+            player_status: PlayerStatus::Stopped,
+            message_sender: None,
+            status_receiver: None,
+        }
+    }
+    
+    // Opens and plays song with given path in separate thread
+    pub fn open_song(&mut self, uri: &URI) {
+        // Creates mpsc channels to communicate with thread
+        let (message_sender, message_receiver) = mpsc::channel();
+        let (status_sender, status_receiver) = mpsc::channel();
+        self.message_sender = Some(message_sender);
+        self.status_receiver = Some(status_receiver);
+        
+        let owned_uri = uri.clone();
+
+        // Creates thread that audio is decoded in
+        thread::spawn(move || {
+            let (mut reader, mut decoder) = MusicPlayer::get_reader_and_dec(&owned_uri);
+            
+            let mut seek_time: Option<u64> = None;
+            
+            let mut audio_output: Option<Box<dyn AudioStream>> = None;
+            
+            let mut music_processor = MusicProcessor::new();
+            
+            'main_decode: loop {    
+                // Handles message received from the MusicPlayer if there is one // TODO: Refactor
+                let received_message = message_receiver.try_recv();
+                match received_message {
+                    Ok(PlayerMessage::Pause) => { 
+                        status_sender.send(PlayerStatus::Paused).unwrap();
+                        // Loops on a blocking message receiver to wait for a play/stop message
+                        'inner_pause: loop {
+                            let message = message_receiver.try_recv();
+                            match message {
+                                Ok(PlayerMessage::Play) => {
+                                    status_sender.send(PlayerStatus::Playing).unwrap();
+                                    break 'inner_pause
+                                },
+                                Ok(PlayerMessage::Stop) => {
+                                    status_sender.send(PlayerStatus::Stopped).unwrap();
+                                    break 'main_decode
+                                },
+                                _ => {},
+                            }
+                        }
+                    },
+                    // Exits main decode loop and subsequently ends thread (?)
+                    Ok(PlayerMessage::Stop) => {
+                        status_sender.send(PlayerStatus::Stopped).unwrap();
+                        break 'main_decode
+                    },
+                    Ok(PlayerMessage::SeekTo(time)) => seek_time = Some(time),
+                    Ok(PlayerMessage::DSP(dsp_message)) => {
+                        match dsp_message {
+                            DSPMessage::UpdateProcessor(new_processor) => music_processor = *new_processor,
+                        }
+                    }
+                    _ => {},
+                } 
+                
+                match seek_time {
+                    Some(time) => {
+                        let seek_to = SeekTo::Time { time: Time::from(time), track_id: Some(0) };
+                        reader.seek(SeekMode::Accurate, seek_to).unwrap();
+                        seek_time = None;
+                    }
+                    None => {} //Nothing to do!
+                }
+                
+                let packet = match reader.next_packet() {
+                    Ok(packet) => packet,
+                    Err(Error::ResetRequired) => panic!(), //TODO,
+                    Err(err) => {
+                        //Unrecoverable?
+                        panic!("{}", err);
+                    }
+                };
+                
+                match 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());
+                        }
+                        
+                        // 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
+                        continue;
+                    },
+                    Err(Error::DecodeError(_)) => {
+                        // may you one day be decoded
+                        continue;
+                    },
+                    Err(err) => {
+                        // Unrecoverable, though shouldn't panic here
+                        panic!("{}", err);
+                    }
+                }
+            }
+        });
+    }
+    
+    fn get_reader_and_dec(uri: &URI) -> (Box<dyn FormatReader>, Box<dyn Decoder>) {
+        // Opens remote/local source and creates MediaSource for symphonia
+        let config = RemoteOptions { media_buffer_len: 10000, forward_buffer_len: 10000};
+        let src: Box<dyn MediaSource> = match uri {
+            URI::Local(path) => Box::new(std::fs::File::open(path).expect("Failed to open file")),
+            URI::Remote(_, location) => Box::new(RemoteSource::new(location.as_ref(), &config).unwrap()),
+        };
+        
+        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 mut 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 track = reader.tracks()
+                    .iter()
+                    .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
+                    .expect("no supported audio tracks");
+                    
+        let dec_opts: DecoderOptions = Default::default();
+        
+        let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
+                                                    .expect("unsupported codec");
+        
+        return (reader, decoder);
+    }
+    
+    // Updates status by checking on messages from spawned thread
+    fn update_status(&mut self) {
+        let status = self.status_receiver.as_mut().unwrap().try_recv();
+        if status.is_ok() {
+            self.player_status = status.unwrap();
+            match status.unwrap() {
+                // Removes receiver and sender since spawned thread no longer exists
+                PlayerStatus::Stopped => {
+                    self.status_receiver = None;
+                    self.message_sender = None;
+                }
+                _ => {}
+            }
+        }
+    }
+    
+    // Sends message to spawned thread
+    pub fn send_message(&mut self, message: PlayerMessage) {
+        self.update_status();
+        // Checks that message sender exists before sending a message off
+        if self.message_sender.is_some() {
+            self.message_sender.as_mut().unwrap().send(message).unwrap();
+        }
+    }
+    
+    pub fn get_status(&mut self) -> PlayerStatus {
+        self.update_status();
+        return self.player_status;
+    }
+}
+
+// TODO: Make the buffer length do anything
+/// Options for remote sources
+///
+/// media_buffer_len is how many bytes are to be buffered in totala
+///
+/// forward_buffer is how many bytes can ahead of the seek position without the remote source being read from
+pub struct RemoteOptions {
+    media_buffer_len: u64,
+    forward_buffer_len: u64,
+}
+
+impl Default for RemoteOptions {
+    fn default() -> Self {
+        RemoteOptions {
+            media_buffer_len: 100000,
+            forward_buffer_len: 1024,
+        }
+    }   
+}
+
+/// A remote source of media
+struct RemoteSource {
+    reader: Box<dyn AsyncBufRead + Send + Sync + Unpin>,
+    media_buffer: Vec<u8>,
+    forward_buffer_len: u64,
+    offset: u64,
+}
+
+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 { 
+            return surf::get(uri).await;
+        })?;
+        
+        let reader = response.take_body().into_reader();
+        
+        Ok(RemoteSource {
+            reader,
+            media_buffer: Vec::new(),
+            forward_buffer_len: config.forward_buffer_len,
+            offset: 0,
+        })
+    }
+}
+// TODO: refactor this + buffer into the buffer passed into the function, not a newly allocated one
+impl std::io::Read for RemoteSource {
+    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+        // Reads bytes into the media buffer if the offset is within the specified distance from the end of the buffer
+        if self.media_buffer.len() as u64 - self.offset < self.forward_buffer_len {
+            let mut buffer = [0; 1024];
+            let read_bytes = task::block_on(async {
+                match self.reader.read_exact(&mut buffer).await {
+                    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 
+        let mut bytes_read = 0;
+        for location in 0..1024 {
+            if (location + self.offset as usize) < self.media_buffer.len() {
+                buf[location] = self.media_buffer[location + self.offset as usize];
+                bytes_read += 1;
+            }
+        }
+        
+        self.offset += bytes_read;
+        return Ok(bytes_read as usize);
+    }
+}
+
+impl std::io::Seek for RemoteSource {
+    // Seeks to a given position
+    // Seeking past the internal buffer's length results in the seeking to the end of content
+    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
+        match pos {
+            // Offset is set to given position
+            SeekFrom::Start(pos) => {
+                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 {
+                    self.offset = self.media_buffer.len() as u64;
+                } else {
+                    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{
+                    self.offset = self.media_buffer.len() as u64;
+                } else {
+                    self.offset += pos as u64
+                }
+                return Ok(self.offset);
+            },
+        }
+    }
+}
+
+impl MediaSource for RemoteSource {
+    fn is_seekable(&self) -> bool {
+        return true;
+    }
+    
+    fn byte_len(&self) -> Option<u64> {
+        return None;
+    }
+}
\ No newline at end of file
diff --git a/src/music_player/music_resampler.rs b/src/music_player/music_resampler.rs
new file mode 100644
index 0000000..f654a17
--- /dev/null
+++ b/src/music_player/music_resampler.rs
@@ -0,0 +1,147 @@
+// Symphonia
+// Copyright (c) 2019-2022 The Project Symphonia Developers.
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, SignalSpec};
+use symphonia::core::conv::{FromSample, IntoSample};
+use symphonia::core::sample::Sample;
+
+pub struct Resampler<T> {
+    resampler: rubato::FftFixedIn<f32>,
+    input: Vec<Vec<f32>>,
+    output: Vec<Vec<f32>>,
+    interleaved: Vec<T>,
+    duration: usize,
+}
+
+impl<T> Resampler<T>
+where
+    T: Sample + FromSample<f32> + IntoSample<f32>,
+{
+    fn resample_inner(&mut self) -> &[T] {
+        {
+            //let mut input =  heapless::Vec::<f32, 32>::new();
+            let mut input: arrayvec::ArrayVec<&[f32], 32> = Default::default();
+
+            for channel in self.input.iter() {
+                input.push(&channel[..self.duration]);
+            }
+
+            // Resample.
+            rubato::Resampler::process_into_buffer(
+                &mut self.resampler,
+                &input,
+                &mut self.output,
+                None,
+            )
+            .unwrap();
+        }
+
+        // Remove consumed samples from the input buffer.
+        for channel in self.input.iter_mut() {
+            channel.drain(0..self.duration);
+        }
+
+        // Interleave the planar samples from Rubato.
+        let num_channels = self.output.len();
+
+        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() {
+                *s = self.output[ch][i].into_sample();
+            }
+        }
+
+        &self.interleaved
+    }
+}
+
+impl<T> Resampler<T>
+where
+    T: Sample + FromSample<f32> + IntoSample<f32>,
+{
+    pub fn new(spec: SignalSpec, to_sample_rate: usize, duration: u64) -> Self {
+        let duration = duration as usize;
+        let num_channels = spec.channels.count();
+
+        let resampler = rubato::FftFixedIn::<f32>::new(
+            spec.rate as usize,
+            to_sample_rate,
+            duration,
+            2,
+            num_channels,
+        )
+        .unwrap();
+
+        let output = rubato::Resampler::output_buffer_allocate(&resampler);
+
+        let input = vec![Vec::with_capacity(duration); num_channels];
+
+        Self { resampler, input, output, duration, interleaved: Default::default() }
+    }
+
+    /// Resamples a planar/non-interleaved input.
+    ///
+    /// Returns the resampled samples in an interleaved format.
+    pub fn resample(&mut self, input: AudioBufferRef<'_>) -> Option<&[T]> {
+        // Copy and convert samples into input buffer.
+        convert_samples_any(&input, &mut self.input);
+
+        // Check if more samples are required.
+        if self.input[0].len() < self.duration {
+            return None;
+        }
+
+        Some(self.resample_inner())
+    }
+
+    /// Resample any remaining samples in the resample buffer.
+    pub fn flush(&mut self) -> Option<&[T]> {
+        let len = self.input[0].len();
+
+        if len == 0 {
+            return None;
+        }
+
+        let partial_len = len % self.duration;
+
+        if partial_len != 0 {
+            // Fill each input channel buffer with silence to the next multiple of the resampler
+            // duration.
+            for channel in self.input.iter_mut() {
+                channel.resize(len + (self.duration - partial_len), f32::MID);
+            }
+        }
+
+        Some(self.resample_inner())
+    }
+}
+
+fn convert_samples_any(input: &AudioBufferRef<'_>, output: &mut [Vec<f32>]) {
+    match input {
+        AudioBufferRef::U8(input) => convert_samples(input, output),
+        AudioBufferRef::U16(input) => convert_samples(input, output),
+        AudioBufferRef::U24(input) => convert_samples(input, output),
+        AudioBufferRef::U32(input) => convert_samples(input, output),
+        AudioBufferRef::S8(input) => convert_samples(input, output),
+        AudioBufferRef::S16(input) => convert_samples(input, output),
+        AudioBufferRef::S24(input) => convert_samples(input, output),
+        AudioBufferRef::S32(input) => convert_samples(input, output),
+        AudioBufferRef::F32(input) => convert_samples(input, output),
+        AudioBufferRef::F64(input) => convert_samples(input, output),
+    }
+}
+
+fn convert_samples<S>(input: &AudioBuffer<S>, output: &mut [Vec<f32>])
+where
+    S: Sample + IntoSample<f32>,
+{
+    for (c, dst) in output.iter_mut().enumerate() {
+        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
new file mode 100644
index 0000000..dd7effc
--- /dev/null
+++ b/src/music_processor/music_processor.rs
@@ -0,0 +1,35 @@
+use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, AsAudioBufferRef, SignalSpec};
+
+#[derive(Clone)]
+pub struct MusicProcessor {
+    pub audio_buffer: AudioBuffer<f32>,
+    pub audio_volume: f32,
+}
+
+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);
+    }
+}
\ No newline at end of file
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
new file mode 100644
index 0000000..72d3053
--- /dev/null
+++ b/src/music_storage/music_db.rs
@@ -0,0 +1,456 @@
+use file_format::{FileFormat, Kind};
+use serde::Deserialize;
+use lofty::{Accessor, AudioFile, Probe, TaggedFileExt, ItemKey, ItemValue, TagType};
+use rusqlite::{params, Connection};
+use cue::{cd_text::PTI, cd::CD};
+use std::fs;
+use std::path::{Path, PathBuf};
+use std::time::Duration;
+use time::Date;
+use walkdir::WalkDir;
+
+use crate::music_controller::config::Config;
+
+#[derive(Debug)]
+pub struct Song {
+    pub path: Box<Path>,
+    pub title:  Option<String>,
+    pub album:  Option<String>,
+    tracknum: Option<usize>,
+    pub artist: Option<String>,
+    date:   Option<Date>,
+    genre:  Option<String>,
+    plays:  Option<usize>,
+    favorited: Option<bool>,
+    format: Option<FileFormat>, // TODO: Make this a proper FileFormat eventually
+    duration: Option<Duration>,
+    pub custom_tags: Option<Vec<Tag>>,
+}
+#[derive(Clone)]
+pub enum URI{
+    Local(String),
+    Remote(Service, String),
+}
+
+#[derive(Clone, Copy)]
+pub enum Service {
+    InternetRadio,
+    Spotify,
+    Youtube,
+}
+
+#[derive(Debug)]
+pub struct Playlist {
+    title: String,
+    cover_art: Box<Path>,
+}
+
+pub fn create_db() -> Result<(), rusqlite::Error> {
+    let path = "./music_database.db3";
+    let db_connection = Connection::open(path)?;
+
+    db_connection.pragma_update(None, "synchronous", "0")?;
+    db_connection.pragma_update(None, "journal_mode", "WAL")?;
+
+    // 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(())
+}
+
+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
+    }
+}
+
+/// Parse a cuesheet given a path and a directory it is located in,
+/// returning a Vec of Song objects
+fn parse_cuesheet(
+    cuesheet_path: &Path,
+    current_dir: &PathBuf
+) -> Result<Vec<Song>, Box<dyn std::error::Error>>{
+    let cuesheet = CD::parse_file(cuesheet_path.to_path_buf())?;
+
+    let album = cuesheet.get_cdtext().read(PTI::Title);
+
+    let mut song_list:Vec<Song> = vec![];
+
+    for (index, track) in cuesheet.tracks().iter().enumerate() {
+        let track_string_path = format!("{}/{}", current_dir.to_string_lossy(), track.get_filename());
+        let track_path = Path::new(&track_string_path);
+
+        if !track_path.exists() {continue};
+
+        // Get the format as a string
+        let short_format = match FileFormat::from_file(track_path) {
+            Ok(fmt) => Some(fmt),
+            Err(_) => None
+        };
+
+        let duration = Duration::from_secs(track.get_length().unwrap_or(-1) as u64);
+
+        let custom_index_start = Tag::Custom{
+            tag: String::from("dango_cue_index_start"),
+            tag_value: track.get_index(0).unwrap_or(-1).to_string()
+        };
+        let custom_index_end = Tag::Custom{
+            tag: String::from("dango_cue_index_end"),
+            tag_value: track.get_index(0).unwrap_or(-1).to_string()
+        };
+
+        let custom_tags: Vec<Tag> = vec![custom_index_start, custom_index_end];
+
+        let tags = track.get_cdtext();
+        let cue_song = Song {
+            path: track_path.into(),
+            title: tags.read(PTI::Title),
+            album: album.clone(),
+            tracknum: Some(index + 1),
+            artist: tags.read(PTI::Performer),
+            date: None,
+            genre: tags.read(PTI::Genre),
+            plays: Some(0),
+            favorited: Some(false),
+            format: short_format,
+            duration: Some(duration),
+            custom_tags: Some(custom_tags)
+        };
+
+        song_list.push(cue_song);
+    }
+
+    Ok(song_list)
+}
+
+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()) {
+        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(), &db_connection)
+        } else if extension.to_ascii_lowercase() == "cue" {
+            // TODO: implement cuesheet support
+            parse_cuesheet(target_file.path(), &current_dir);
+        }
+    }
+
+    // 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) {
+    // 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 item in tag.items() {
+        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(", ");
+        }
+
+        custom_insert.push_str(&format!(" (?1, '{}', '{}')", custom_key.replace("\'", "''"), custom_value.replace("\'", "''")));
+
+        loops += 1;
+    }
+    
+    // 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();
+
+    // 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)]
+pub enum Tag {
+    SongPath,
+    Title,
+    Album,
+    TrackNum,
+    Artist,
+    Date,
+    Genre,
+    Plays,
+    Favorited,
+    Format,
+    Duration,
+    Custom{tag: String, tag_value: String},
+}
+
+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)]
+pub enum MusicObject {
+    Song(Song),
+    Album(Playlist),
+    Playlist(Playlist),
+}
+
+impl MusicObject {
+    pub fn as_song(&self) -> Option<&Song> {
+        match self {
+            MusicObject::Song(data) => Some(data),
+            _ => None
+        }
+    }
+}
+
+/// 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>,
+) -> 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:   Path::new(&row.get::<usize, String>(0).unwrap_or("".to_owned())).into(),
+            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)
+}
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
new file mode 100644
index 0000000..f6fc9b7
--- /dev/null
+++ b/src/music_storage/playlist.rs
@@ -0,0 +1,27 @@
+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();
+    }
+}
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
new file mode 100644
index 0000000..93fb5fb
--- /dev/null
+++ b/src/music_tracker/music_tracker.rs
@@ -0,0 +1,186 @@
+use std::time::{SystemTime, UNIX_EPOCH};
+use std::collections::BTreeMap;
+
+use async_trait::async_trait;
+use serde::{Serialize, Deserialize};
+use md5::{Md5, Digest};
+
+#[async_trait]
+pub trait MusicTracker {
+    /// Adds one listen to a song halfway through playback
+    async fn track_song(&self, song: &String) -> Result<(), surf::Error>;
+    
+    /// Adds a 'listening' status to the music tracker service of choice
+    async fn track_now(&self, song: &String) -> Result<(), surf::Error>;
+    
+    /// Reads config files, and attempts authentication with service
+    async fn test_tracker(&self) -> Result<(), surf::Error>;
+    
+    /// Returns plays for a given song according to tracker service
+    async fn get_times_tracked(&self, song: &String) -> Result<u32, surf::Error>;
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct LastFM {
+    dango_api_key: String,
+    auth_token: Option<String>,
+    shared_secret: Option<String>,
+    session_key: Option<String>,
+}
+
+#[async_trait]
+impl MusicTracker for LastFM {
+    async fn track_song(&self, song: &String) -> Result<(), surf::Error> {
+        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 string_timestamp = timestamp.to_string();
+        params.insert("method", "track.scrobble");
+        params.insert("artist", "Kikuo");
+        params.insert("track", "A Happy Death - Again");
+        params.insert("timestamp", &string_timestamp);
+        
+        self.api_request(params).await?;
+        Ok(())
+    }
+    
+    async fn track_now(&self, song: &String) -> Result<(), surf::Error> {
+        let mut params: BTreeMap<&str, &str> = BTreeMap::new();
+        params.insert("method", "track.updateNowPlaying");
+        params.insert("artist", "Kikuo");
+        params.insert("track", "A Happy Death - Again");
+        self.api_request(params).await?;
+        Ok(())
+    }
+    
+    async fn test_tracker(&self) -> Result<(), surf::Error> {
+        let mut params: BTreeMap<&str, &str> = BTreeMap::new();
+        params.insert("method", "chart.getTopArtists");
+        self.api_request(params).await?;
+        Ok(())
+    }
+    
+    async fn get_times_tracked(&self, song: &String) -> Result<u32, surf::Error> {
+        todo!();
+    }
+}
+
+#[derive(Deserialize, Serialize)]
+struct AuthToken {
+    token: String
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+struct SessionResponse {
+    name: String,
+    key: String,
+    subscriber: i32,
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+struct Session {
+    session: SessionResponse
+}
+
+impl LastFM {
+    // Returns a url to be accessed by the user
+    pub async fn get_auth_url(&mut self) -> Result<String, surf::Error> {
+        let method = String::from("auth.gettoken");
+        let api_key = self.dango_api_key.clone();
+        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?;
+        self.auth_token = Some(auth_token.token.clone());
+        
+        let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token);
+        
+        return Ok(auth_url);
+    }
+    
+    pub async fn set_session(&mut self) {
+        let method = String::from("auth.getSession");
+        let api_key = self.dango_api_key.clone();
+        let auth_token = self.auth_token.clone().unwrap();
+        let shared_secret = self.shared_secret.clone().unwrap();
+        
+        // Creates api_sig as defined in last.fm documentation
+        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.unwrap();
+        
+        // Sets session key from received response
+        let session_response: Session = serde_json::from_str(&response).unwrap();
+        self.session_key = Some(session_response.session.key.clone());
+    }
+    
+    // Creates a new LastFM struct
+    pub fn new() -> LastFM {
+        let last_fm = LastFM {
+            // Grab this from config in future
+            dango_api_key: String::from("29a071e3113ab8ed36f069a2d3e20593"),
+            auth_token: None,
+            // Also grab from config in future
+            shared_secret: Some(String::from("5400c554430de5c5002d5e4bcc295b3d")),
+            session_key: None,
+        };
+        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> {
+        params.insert("api_key", &self.dango_api_key);
+        params.insert("sk", &self.session_key.as_ref().unwrap());
+        
+        // Creates and sets api call signature
+        let api_sig = LastFM::request_sig(&params, &self.shared_secret.as_ref().unwrap());
+        params.insert("api_sig", &api_sig);
+        let mut string_params = String::from("");
+        
+        // Creates method call string
+        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;
+    }
+
+    // Returns an api signature as defined in the last.fm api documentation
+    fn request_sig(params: &BTreeMap<&str, &str>, shared_secret: &str) -> String {
+        let mut sig_string = String::new();
+        // Appends keys and values of parameters to the unhashed sig
+        for key in params.keys() {
+            let param_value = params.get(*key);
+            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!();
+    }
+}
\ No newline at end of file

From c8f16f6b3581f8a710a417de7964ced14feb8b72 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 19 Jan 2024 17:49:05 -0600
Subject: [PATCH 022/136] Added preliminary Discord RPC support, updated
 configuration and error handling for music trackers. Closes #2

---
 Cargo.toml                         |   1 +
 src/music_controller/config.rs     |  38 +++++--
 src/music_tracker/music_tracker.rs | 174 +++++++++++++++++++++++------
 3 files changed, 168 insertions(+), 45 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 4a63b78..b312946 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,3 +32,4 @@ surf = "2.3.2"
 futures = "0.3.28"
 rubato = "0.12.0"
 arrayvec = "0.7.4"
+discord-presence = "0.5.18"
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index 70ee273..d018088 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -4,23 +4,43 @@ use std::fs;
 
 use serde::{Deserialize, Serialize};
 
-use crate::music_tracker::music_tracker::LastFM;
+use crate::music_tracker::music_tracker::{LastFM, LastFMConfig, DiscordRPCConfig};
 
 #[derive(Serialize, Deserialize)]
 pub struct Config {
     pub db_path: Box<PathBuf>,
-    pub lastfm: Option<LastFM>,
+    pub lastfm: Option<LastFMConfig>,
+    pub discord: Option<DiscordRPCConfig>,
+}
+
+impl Default for Config {
+    fn default() -> Self {
+        let path = PathBuf::from("./music_database.db3");
+
+        return Config {
+            db_path: Box::new(path),
+
+            lastfm: Some (LastFMConfig {
+                enabled: true,
+                dango_api_key: String::from("29a071e3113ab8ed36f069a2d3e20593"),
+                auth_token: None,
+                shared_secret: Some(String::from("5400c554430de5c5002d5e4bcc295b3d")),
+                session_key: None,
+            }),
+
+            discord: Some(DiscordRPCConfig {
+                enabled: true,
+                dango_client_id: 1144475145864499240,
+                dango_icon: String::from("flat"),
+            }),
+        };
+    }
 }
 
 impl Config {
     // Creates and saves a new config with default values
-    pub fn new(config_file: &PathBuf) -> std::io::Result<Config> {
-        let path = PathBuf::from("./music_database.db3");
-        
-        let config = Config {
-            db_path: Box::new(path),
-            lastfm: None,
-        };
+    pub fn new(config_file: &PathBuf) -> std::io::Result<Config> {        
+        let config = Config::default();
         config.save(config_file)?;
         
         Ok(config)
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 93fb5fb..9568d85 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -2,35 +2,72 @@ use std::time::{SystemTime, UNIX_EPOCH};
 use std::collections::BTreeMap;
 
 use async_trait::async_trait;
-use serde::{Serialize, Deserialize};
+use serde::{Serialize, Deserialize, Serializer};
 use md5::{Md5, Digest};
+use discord_presence::{ Event, DiscordError};
+use surf::StatusCode;
 
 #[async_trait]
 pub trait MusicTracker {
     /// Adds one listen to a song halfway through playback
-    async fn track_song(&self, song: &String) -> Result<(), surf::Error>;
+    async fn track_song(&mut self, song: &String) -> Result<(), TrackerError>;
     
     /// Adds a 'listening' status to the music tracker service of choice
-    async fn track_now(&self, song: &String) -> Result<(), surf::Error>;
+    async fn track_now(&mut self, song: &String) -> Result<(), TrackerError>;
     
     /// Reads config files, and attempts authentication with service
-    async fn test_tracker(&self) -> Result<(), surf::Error>;
+    async fn test_tracker(&mut self) -> Result<(), TrackerError>;
     
     /// Returns plays for a given song according to tracker service
-    async fn get_times_tracked(&self, song: &String) -> Result<u32, surf::Error>;
+    async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError>;
+}
+
+#[derive(Debug)]
+pub enum TrackerError {
+    /// Tracker does not accept the song's format/content
+    InvalidSong,
+    /// Tracker requires authentication
+    InvalidAuth,
+    /// Tracker request was malformed
+    InvalidRequest,
+    /// Tracker is unavailable
+    ServiceUnavailable,
+    /// Unknown tracker error
+    Unknown,
+}
+
+
+impl TrackerError {
+    pub fn from_surf_error(error: surf::Error) -> TrackerError {
+        return match error.status() {
+            StatusCode::Forbidden => TrackerError::InvalidAuth,
+            StatusCode::Unauthorized => TrackerError::InvalidAuth,
+            StatusCode::NetworkAuthenticationRequired => TrackerError::InvalidAuth,
+            StatusCode::BadRequest => TrackerError::InvalidRequest,
+            StatusCode::BadGateway => TrackerError::ServiceUnavailable,
+            StatusCode::ServiceUnavailable => TrackerError::ServiceUnavailable,
+            StatusCode::NotFound => TrackerError::ServiceUnavailable,
+            _ => TrackerError::Unknown,
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct LastFMConfig {
+    pub enabled: bool,
+    pub dango_api_key: String,
+    pub auth_token: Option<String>,
+    pub shared_secret: Option<String>,
+    pub session_key: Option<String>,
 }
 
-#[derive(Serialize, Deserialize)]
 pub struct LastFM {
-    dango_api_key: String,
-    auth_token: Option<String>,
-    shared_secret: Option<String>,
-    session_key: Option<String>,
+    config: LastFMConfig
 }
 
 #[async_trait]
 impl MusicTracker for LastFM {
-    async fn track_song(&self, song: &String) -> Result<(), surf::Error> {
+    async fn track_song(&mut self, song: &String) -> Result<(), TrackerError> {
         let mut params: BTreeMap<&str, &str> = BTreeMap::new();
         
         // Sets timestamp of song beginning play time
@@ -41,27 +78,35 @@ impl MusicTracker for LastFM {
         params.insert("track", "A Happy Death - Again");
         params.insert("timestamp", &string_timestamp);
         
-        self.api_request(params).await?;
-        Ok(())
+        return match self.api_request(params).await {
+            Ok(_) => Ok(()),
+            Err(err) => Err(TrackerError::from_surf_error(err)),
+        }
     }
     
-    async fn track_now(&self, song: &String) -> Result<(), surf::Error> {
+    async fn track_now(&mut self, song: &String) -> Result<(), TrackerError> {
         let mut params: BTreeMap<&str, &str> = BTreeMap::new();
         params.insert("method", "track.updateNowPlaying");
         params.insert("artist", "Kikuo");
         params.insert("track", "A Happy Death - Again");
-        self.api_request(params).await?;
-        Ok(())
+        
+        return match self.api_request(params).await {
+            Ok(_) => Ok(()),
+            Err(err) => Err(TrackerError::from_surf_error(err)),
+        }
     }
     
-    async fn test_tracker(&self) -> Result<(), surf::Error> {
+    async fn test_tracker(&mut self) -> Result<(), TrackerError> {
         let mut params: BTreeMap<&str, &str> = BTreeMap::new();
         params.insert("method", "chart.getTopArtists");
-        self.api_request(params).await?;
-        Ok(())
+        
+        return match self.api_request(params).await {
+            Ok(_) => Ok(()),
+            Err(err) => Err(TrackerError::from_surf_error(err)),
+        }
     }
     
-    async fn get_times_tracked(&self, song: &String) -> Result<u32, surf::Error> {
+    async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError> {
         todo!();
     }
 }
@@ -87,11 +132,11 @@ impl LastFM {
     // Returns a url to be accessed by the user
     pub async fn get_auth_url(&mut self) -> Result<String, surf::Error> {
         let method = String::from("auth.gettoken");
-        let api_key = self.dango_api_key.clone();
+        let api_key = self.config.dango_api_key.clone();
         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?;
-        self.auth_token = Some(auth_token.token.clone());
+        self.config.auth_token = Some(auth_token.token.clone());
         
         let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token);
         
@@ -100,9 +145,9 @@ impl LastFM {
     
     pub async fn set_session(&mut self) {
         let method = String::from("auth.getSession");
-        let api_key = self.dango_api_key.clone();
-        let auth_token = self.auth_token.clone().unwrap();
-        let shared_secret = self.shared_secret.clone().unwrap();
+        let api_key = self.config.dango_api_key.clone();
+        let auth_token = self.config.auth_token.clone().unwrap();
+        let shared_secret = self.config.shared_secret.clone().unwrap();
         
         // Creates api_sig as defined in last.fm documentation
         let api_sig = format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}");
@@ -119,33 +164,29 @@ impl LastFM {
         
         // Sets session key from received response
         let session_response: Session = serde_json::from_str(&response).unwrap();
-        self.session_key = Some(session_response.session.key.clone());
+        self.config.session_key = Some(session_response.session.key.clone());
     }
     
     // Creates a new LastFM struct
-    pub fn new() -> LastFM {
+    pub fn new(config: &LastFMConfig) -> LastFM {
         let last_fm = LastFM {
-            // Grab this from config in future
-            dango_api_key: String::from("29a071e3113ab8ed36f069a2d3e20593"),
-            auth_token: None,
-            // Also grab from config in future
-            shared_secret: Some(String::from("5400c554430de5c5002d5e4bcc295b3d")),
-            session_key: None,
+            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> {
-        params.insert("api_key", &self.dango_api_key);
-        params.insert("sk", &self.session_key.as_ref().unwrap());
+        params.insert("api_key", &self.config.dango_api_key);
+        params.insert("sk", &self.config.session_key.as_ref().unwrap());
         
         // Creates and sets api call signature
-        let api_sig = LastFM::request_sig(&params, &self.shared_secret.as_ref().unwrap());
+        let api_sig = LastFM::request_sig(&params, &self.config.shared_secret.as_ref().unwrap());
         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}&"));
@@ -183,4 +224,65 @@ impl LastFM {
     pub fn reset_account() {
         todo!();
     }
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct DiscordRPCConfig {
+    pub enabled: bool,
+    pub dango_client_id: u64, 
+    pub dango_icon: String,
+}
+
+pub struct DiscordRPC {
+    config: DiscordRPCConfig,
+    pub client: discord_presence::client::Client
+}
+
+impl DiscordRPC {
+    pub fn new(config: &DiscordRPCConfig) -> Self {
+        let rpc = DiscordRPC {
+            client: discord_presence::client::Client::new(config.dango_client_id),
+            config: config.clone(),
+        };
+        return rpc;
+    }
+}
+
+#[async_trait]
+impl MusicTracker for DiscordRPC {
+    async fn track_now(&mut self, song: &String) -> Result<(), TrackerError> {
+        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;
+        // Sets discord account activity to current playing song
+        let send_activity = self.client.set_activity(|activity| {
+            activity
+                .state(song)
+                .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: &String) -> Result<(), TrackerError> {
+        return Ok(())
+    }
+    
+    async fn test_tracker(&mut self) -> Result<(), TrackerError> {
+        return Ok(())
+    }
+    
+    async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError> {
+        return Ok(0);
+    }
 }
\ No newline at end of file

From 14edbe7c465af3d6e64a477f8f1d28b08239ba01 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 19 Jan 2024 18:22:36 -0600
Subject: [PATCH 023/136] Refactored music_player, integrated music_tracker
 into it. Closes #7

---
 src/music_controller/config.rs           |  10 +-
 src/music_controller/music_controller.rs |  33 +-
 src/music_player/music_player.rs         | 443 +++++++++++++++--------
 src/music_processor/music_processor.rs   |   8 +
 src/music_storage/music_db.rs            |  31 +-
 src/music_tracker/music_tracker.rs       |  83 +++--
 6 files changed, 378 insertions(+), 230 deletions(-)

diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index d018088..c77a02a 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
 
 use crate::music_tracker::music_tracker::{LastFM, LastFMConfig, DiscordRPCConfig};
 
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, PartialEq, Eq)]
 pub struct Config {
     pub db_path: Box<PathBuf>,
     pub lastfm: Option<LastFMConfig>,
@@ -20,13 +20,7 @@ impl Default for Config {
         return Config {
             db_path: Box::new(path),
 
-            lastfm: Some (LastFMConfig {
-                enabled: true,
-                dango_api_key: String::from("29a071e3113ab8ed36f069a2d3e20593"),
-                auth_token: None,
-                shared_secret: Some(String::from("5400c554430de5c5002d5e4bcc295b3d")),
-                session_key: None,
-            }),
+            lastfm: None,
 
             discord: Some(DiscordRPCConfig {
                 enabled: true,
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index bb1ab66..29f4fb7 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -1,22 +1,23 @@
 use std::path::PathBuf;
+use std::sync::{RwLock, Arc, Mutex};
 
 use rusqlite::Result;
 
 use crate::music_controller::config::Config;
-use crate::music_player::music_player::{MusicPlayer, PlayerStatus, PlayerMessage, DSPMessage};
+use crate::music_player::music_player::{MusicPlayer, PlayerStatus, DecoderMessage, DSPMessage};
 use crate::music_processor::music_processor::MusicProcessor;
-use crate::music_storage::music_db::URI;
+use crate::music_storage::music_db::{URI, Song};
 
 pub struct MusicController {
-    pub config: Config,
+    pub config: Arc<RwLock<Config>>,
     music_player: MusicPlayer,
 }
 
 impl MusicController {
     /// Creates new MusicController with config at given path
     pub fn new(config_path: &PathBuf) -> Result<MusicController, std::io::Error>{
-        let config = Config::new(config_path)?;
-        let music_player = MusicPlayer::new();
+        let config = Arc::new(RwLock::new(Config::new(config_path)?));
+        let music_player = MusicPlayer::new(config.clone());
         
         let controller = MusicController {
             config,
@@ -28,8 +29,8 @@ 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> {
-        let config = Config::from(config_path)?;
-        let music_player = MusicPlayer::new();
+        let config = Arc::new(RwLock::new(Config::from(config_path)?));
+        let music_player = MusicPlayer::new(config.clone());
         
         let controller = MusicController {
             config,
@@ -39,13 +40,8 @@ impl MusicController {
         return Ok(controller)
     }
     
-    /// Opens and plays song at given URI
-    pub fn open_song(&mut self, uri: &URI) {
-        self.music_player.open_song(uri);
-    }
-    
-    /// Sends given message to music player
-    pub fn song_control(&mut self, message: PlayerMessage) {
+    /// Sends given message to control music player
+    pub fn song_control(&mut self, message: DecoderMessage) {
         self.music_player.send_message(message);
     }
     
@@ -54,15 +50,20 @@ impl MusicController {
         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(&mut self) -> f32 {
+    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(PlayerMessage::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()))));
     }
     
 }
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
index 1996240..2944788 100644
--- a/src/music_player/music_player.rs
+++ b/src/music_player/music_player.rs
@@ -1,41 +1,50 @@
 use std::sync::mpsc::{self, Sender, Receiver};
+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::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
 use symphonia::core::io::{MediaSourceStream, MediaSource};
 use symphonia::core::meta::MetadataOptions;
 use symphonia::core::probe::Hint;
 use symphonia::core::errors::Error;
-use symphonia::core::units::Time;
+use symphonia::core::units::{Time, TimeBase};
 
 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;
+use crate::music_storage::music_db::{URI, Song};
+use crate::music_tracker::music_tracker::{MusicTracker, LastFM, DiscordRPCConfig, DiscordRPC, TrackerError};
 
 // Struct that controls playback of music
 pub struct MusicPlayer {
     pub music_processor: MusicProcessor,
     player_status: PlayerStatus,
-    message_sender: Option<Sender<PlayerMessage>>,
-    status_receiver: Option<Receiver<PlayerStatus>>,
+    music_trackers: Vec<Box<dyn MusicTracker + Send>>,
+    current_song: Arc<RwLock<Option<Song>>>,
+    message_sender: Sender<DecoderMessage>,
+    status_receiver: Receiver<PlayerStatus>,
+    config: Arc<RwLock<Config>>,
 }
 
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
 pub enum PlayerStatus {
-    Playing,
+    Playing(f64),
     Paused,
     Stopped,
     Error,
 }
 
-pub enum PlayerMessage {
+#[derive(Debug, Clone)]
+pub enum DecoderMessage {
+    OpenSong(Song),
     Play,
     Pause,
     Stop,
@@ -43,143 +52,43 @@ pub enum PlayerMessage {
     DSP(DSPMessage)
 }
 
+#[derive(Clone)]
+pub enum TrackerMessage {
+    Track(Song),
+    TrackNow(Song)
+}
+
+#[derive(Debug, Clone)]
 pub enum DSPMessage {
     UpdateProcessor(Box<MusicProcessor>)
 }
 
-impl MusicPlayer {
-    pub fn new() -> Self {
-        MusicPlayer {
-            music_processor: MusicProcessor::new(),
-            player_status: PlayerStatus::Stopped,
-            message_sender: None,
-            status_receiver: None,
-        }
-    }
-    
-    // Opens and plays song with given path in separate thread
-    pub fn open_song(&mut self, uri: &URI) {
-        // Creates mpsc channels to communicate with thread
-        let (message_sender, message_receiver) = mpsc::channel();
-        let (status_sender, status_receiver) = mpsc::channel();
-        self.message_sender = Some(message_sender);
-        self.status_receiver = Some(status_receiver);
-        
-        let owned_uri = uri.clone();
+// Holds a song decoder reader, etc
+struct SongHandler {
+    pub reader: Box<dyn FormatReader>,
+    pub decoder: Box<dyn Decoder>,
+    pub time_base: Option<TimeBase>,
+    pub duration: Option<u64>,
+}
 
-        // Creates thread that audio is decoded in
-        thread::spawn(move || {
-            let (mut reader, mut decoder) = MusicPlayer::get_reader_and_dec(&owned_uri);
-            
-            let mut seek_time: Option<u64> = None;
-            
-            let mut audio_output: Option<Box<dyn AudioStream>> = None;
-            
-            let mut music_processor = MusicProcessor::new();
-            
-            'main_decode: loop {    
-                // Handles message received from the MusicPlayer if there is one // TODO: Refactor
-                let received_message = message_receiver.try_recv();
-                match received_message {
-                    Ok(PlayerMessage::Pause) => { 
-                        status_sender.send(PlayerStatus::Paused).unwrap();
-                        // Loops on a blocking message receiver to wait for a play/stop message
-                        'inner_pause: loop {
-                            let message = message_receiver.try_recv();
-                            match message {
-                                Ok(PlayerMessage::Play) => {
-                                    status_sender.send(PlayerStatus::Playing).unwrap();
-                                    break 'inner_pause
-                                },
-                                Ok(PlayerMessage::Stop) => {
-                                    status_sender.send(PlayerStatus::Stopped).unwrap();
-                                    break 'main_decode
-                                },
-                                _ => {},
-                            }
-                        }
-                    },
-                    // Exits main decode loop and subsequently ends thread (?)
-                    Ok(PlayerMessage::Stop) => {
-                        status_sender.send(PlayerStatus::Stopped).unwrap();
-                        break 'main_decode
-                    },
-                    Ok(PlayerMessage::SeekTo(time)) => seek_time = Some(time),
-                    Ok(PlayerMessage::DSP(dsp_message)) => {
-                        match dsp_message {
-                            DSPMessage::UpdateProcessor(new_processor) => music_processor = *new_processor,
-                        }
-                    }
-                    _ => {},
-                } 
-                
-                match seek_time {
-                    Some(time) => {
-                        let seek_to = SeekTo::Time { time: Time::from(time), track_id: Some(0) };
-                        reader.seek(SeekMode::Accurate, seek_to).unwrap();
-                        seek_time = None;
-                    }
-                    None => {} //Nothing to do!
-                }
-                
-                let packet = match reader.next_packet() {
-                    Ok(packet) => packet,
-                    Err(Error::ResetRequired) => panic!(), //TODO,
-                    Err(err) => {
-                        //Unrecoverable?
-                        panic!("{}", err);
-                    }
-                };
-                
-                match 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());
-                        }
-                        
-                        // 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
-                        continue;
-                    },
-                    Err(Error::DecodeError(_)) => {
-                        // may you one day be decoded
-                        continue;
-                    },
-                    Err(err) => {
-                        // Unrecoverable, though shouldn't panic here
-                        panic!("{}", err);
-                    }
-                }
-            }
-        });
-    }
-    
-    fn get_reader_and_dec(uri: &URI) -> (Box<dyn FormatReader>, Box<dyn Decoder>) {
+// TODO: actual error handling here
+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) => Box::new(std::fs::File::open(path).expect("Failed to open file")),
-            URI::Remote(_, location) => Box::new(RemoteSource::new(location.as_ref(), &config).unwrap()),
+            URI::Local(path) => {
+                match std::fs::File::open(path) {
+                    Ok(file) => Box::new(file),
+                    Err(_) => return Err(()),
+                }
+            },
+            URI::Remote(_, location) => {
+                match RemoteSource::new(location.as_ref(), &config) {
+                    Ok(remote_source) => Box::new(remote_source),
+                    Err(_) => return Err(()),
+                }
+            },
         };
         
         let mss = MediaSourceStream::new(src, Default::default());
@@ -198,42 +107,262 @@ impl MusicPlayer {
                     .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 mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
                                                     .expect("unsupported codec");
         
-        return (reader, decoder);
+        return Ok(SongHandler {reader, decoder, time_base, duration});
+    }
+}
+
+impl MusicPlayer {
+    pub fn new(config: Arc<RwLock<Config>>) -> Self {
+        // Creates mpsc channels to communicate with music player threads
+        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 {
+            music_processor: MusicProcessor::new(),
+            music_trackers: Vec::new(),
+            player_status: PlayerStatus::Stopped,
+            current_song,
+            message_sender,
+            status_receiver,
+            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
+            let update_trackers = |trackers: &mut Vec<Box<dyn MusicTracker>>|{
+                if let Some(lastfm_config) = global_config.lastfm.clone() {
+                    trackers.push(Box::new(LastFM::new(&lastfm_config)));
+                }
+                if let Some(discord_config) = global_config.discord.clone() {
+                    trackers.push(Box::new(DiscordRPC::new(&discord_config)));
+                }
+            };
+            update_trackers(&mut trackers);
+            loop {
+                if let message = tracker_receiver.recv() {
+                    if local_config != global_config {
+                        update_trackers(&mut trackers);
+                    }
+    
+                    let mut results = Vec::new();
+                    task::block_on(async {
+                        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(_) => {},
+                            }
+                        }
+                        results = join_all(futures).await;
+                    });
+                    
+                    for result in results {
+                        status_sender.send(result).unwrap_or_default()
+                    }
+                }
+            }
+        });
+    }
+    
+    // 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>>>) {
+        // 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();
+            
+            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 {              
+                'handle_message: loop {
+                    let message = if paused {
+                        // Pauses playback by blocking on waiting for new player messages
+                        match message_receiver.recv() {
+                            Ok(message) => Some(message),
+                            Err(_) => None,
+                        }
+                    } else {
+                        // Resumes playback by not blocking
+                        match message_receiver.try_recv() {
+                            Ok(message) => Some(message),
+                            Err(_) => break 'handle_message,
+                        }
+                    };
+                    // Handles message received from MusicPlayer struct
+                    match message {
+                        Some(DecoderMessage::OpenSong(song)) => {
+                            let song_uri = song.path.clone();
+                            match SongHandler::new(&song_uri) {
+                                Ok(new_handler) => {
+                                    song_handler = Some(new_handler);
+                                    *current_song.write().unwrap() = Some(song);
+                                    paused = false;
+                                    song_tracked = false;
+                                }
+                                Err(_) => status_sender.send(PlayerStatus::Error).unwrap(),
+                            }
+                        }
+                        Some(DecoderMessage::Play) => {
+                            if song_handler.is_some() {
+                                paused = false;
+                            }
+                        }
+                        Some(DecoderMessage::Pause) => {
+                            paused = true;
+                            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();
+                            break 'main_decode
+                        }
+                        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()) {
+                    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();
+                            seek_time = None;
+                        }
+                        None => {} //Nothing to do!
+                    }
+                    let packet = match song_handler.reader.next_packet() {
+                        Ok(packet) => packet,
+                        Err(Error::ResetRequired) => panic!(), //TODO,
+                        Err(err) => {
+                            // Unrecoverable?
+                            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();
+                        }
+                        
+                        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;
+                            // 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 {
+                                song_tracked = true;
+                                tracker_sender.send(TrackerMessage::Track(song.clone())).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());
+                            }
+                            // 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
+                            continue;
+                        },
+                        Err(Error::DecodeError(_)) => {
+                            // may you one day be decoded
+                            continue;
+                        },
+                        Err(err) => {
+                            // Unrecoverable, though shouldn't panic here
+                            panic!("{}", err);
+                        }
+                    }
+                }
+            }
+        });
     }
     
     // Updates status by checking on messages from spawned thread
-    fn update_status(&mut self) {
-        let status = self.status_receiver.as_mut().unwrap().try_recv();
-        if status.is_ok() {
-            self.player_status = status.unwrap();
-            match status.unwrap() {
-                // Removes receiver and sender since spawned thread no longer exists
-                PlayerStatus::Stopped => {
-                    self.status_receiver = None;
-                    self.message_sender = None;
-                }
-                _ => {}
-            }
+    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>{
+        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: PlayerMessage) {
-        self.update_status();
+    pub fn send_message(&mut self, message: DecoderMessage) {
+        self.update_player();
         // Checks that message sender exists before sending a message off
-        if self.message_sender.is_some() {
-            self.message_sender.as_mut().unwrap().send(message).unwrap();
-        }
+        self.message_sender.send(message).unwrap();
     }
     
     pub fn get_status(&mut self) -> PlayerStatus {
-        self.update_status();
+        self.update_player();
         return self.player_status;
     }
 }
@@ -361,4 +490,4 @@ impl MediaSource for RemoteSource {
     fn byte_len(&self) -> Option<u64> {
         return None;
     }
-}
\ No newline at end of file
+}
diff --git a/src/music_processor/music_processor.rs b/src/music_processor/music_processor.rs
index dd7effc..e6d6608 100644
--- a/src/music_processor/music_processor.rs
+++ b/src/music_processor/music_processor.rs
@@ -1,3 +1,5 @@
+use std::fmt::Debug;
+
 use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, AsAudioBufferRef, SignalSpec};
 
 #[derive(Clone)]
@@ -6,6 +8,12 @@ pub struct MusicProcessor {
     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 {
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 72d3053..7ed59fe 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -11,28 +11,29 @@ use walkdir::WalkDir;
 
 use crate::music_controller::config::Config;
 
-#[derive(Debug)]
+#[derive(Debug, Clone)]
 pub struct Song {
-    pub path: Box<Path>,
+    pub path: URI,
     pub title:  Option<String>,
     pub album:  Option<String>,
-    tracknum: Option<usize>,
+    pub tracknum: Option<usize>,
     pub artist: Option<String>,
-    date:   Option<Date>,
-    genre:  Option<String>,
-    plays:  Option<usize>,
-    favorited: Option<bool>,
-    format: Option<FileFormat>, // TODO: Make this a proper FileFormat eventually
-    duration: Option<Duration>,
+    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>>,
 }
-#[derive(Clone)]
+
+#[derive(Clone, Debug)]
 pub enum URI{
     Local(String),
     Remote(Service, String),
 }
 
-#[derive(Clone, Copy)]
+#[derive(Clone, Copy, Debug)]
 pub enum Service {
     InternetRadio,
     Spotify,
@@ -143,7 +144,7 @@ fn parse_cuesheet(
 
         let tags = track.get_cdtext();
         let cue_song = Song {
-            path: track_path.into(),
+            path: URI::Local(String::from("URI")),
             title: tags.read(PTI::Title),
             album: album.clone(),
             tracknum: Some(index + 1),
@@ -311,7 +312,7 @@ pub fn add_file_to_db(target_file: &Path, connection: &Connection) {
     }
 }
 
-#[derive(Debug, Deserialize)]
+#[derive(Debug, Deserialize, Clone)]
 pub enum Tag {
     SongPath,
     Title,
@@ -363,7 +364,7 @@ impl MusicObject {
 }
 
 /// Query the database, returning a list of items
-pub fn query(
+pub fn query (
     config: &Config,
     text_input: &String,
     queried_tags: &Vec<&Tag>,
@@ -435,7 +436,7 @@ pub fn query(
 
         let new_song = Song {
             // TODO: Implement proper errors here
-            path:   Path::new(&row.get::<usize, String>(0).unwrap_or("".to_owned())).into(),
+            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(),
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 9568d85..7dd1d8a 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -7,19 +7,21 @@ use md5::{Md5, Digest};
 use discord_presence::{ Event, DiscordError};
 use surf::StatusCode;
 
+use crate::music_storage::music_db::Song;
+
 #[async_trait]
 pub trait MusicTracker {
     /// Adds one listen to a song halfway through playback
-    async fn track_song(&mut self, song: &String) -> Result<(), TrackerError>;
+    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: &String) -> Result<(), TrackerError>;
+    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: &String) -> Result<u32, TrackerError>;
+    async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError>;
 }
 
 #[derive(Debug)]
@@ -52,13 +54,12 @@ impl TrackerError {
     }
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
 pub struct LastFMConfig {
     pub enabled: bool,
     pub dango_api_key: String,
-    pub auth_token: Option<String>,
-    pub shared_secret: Option<String>,
-    pub session_key: Option<String>,
+    pub shared_secret: String,
+    pub session_key: String,
 }
 
 pub struct LastFM {
@@ -67,15 +68,21 @@ pub struct LastFM {
 
 #[async_trait]
 impl MusicTracker for LastFM {
-    async fn track_song(&mut self, song: &String) -> Result<(), TrackerError> {
+    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 string_timestamp = timestamp.to_string();
+        
+        let (artist, track) = match (song.artist, song.title) {
+            (Some(artist), Some(track)) => (artist, track),
+            _ => return Err(TrackerError::InvalidSong)
+        };
+        
         params.insert("method", "track.scrobble");
-        params.insert("artist", "Kikuo");
-        params.insert("track", "A Happy Death - Again");
+        params.insert("artist", &artist);
+        params.insert("track", &track);
         params.insert("timestamp", &string_timestamp);
         
         return match self.api_request(params).await {
@@ -84,11 +91,17 @@ impl MusicTracker for LastFM {
         }
     }
     
-    async fn track_now(&mut self, song: &String) -> Result<(), TrackerError> {
+    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) {
+            (Some(artist), Some(track)) => (artist, track),
+            _ => return Err(TrackerError::InvalidSong)
+        };
+        
         params.insert("method", "track.updateNowPlaying");
-        params.insert("artist", "Kikuo");
-        params.insert("track", "A Happy Death - Again");
+        params.insert("artist", &artist);
+        params.insert("track", &track);
         
         return match self.api_request(params).await {
             Ok(_) => Ok(()),
@@ -106,7 +119,7 @@ impl MusicTracker for LastFM {
         }
     }
     
-    async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError> {
+    async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
         todo!();
     }
 }
@@ -129,26 +142,21 @@ struct Session {
 }
 
 impl LastFM {
-    // Returns a url to be accessed by the user
-    pub async fn get_auth_url(&mut self) -> Result<String, surf::Error> {
+    /// 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_key = self.config.dango_api_key.clone();
         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?;
-        self.config.auth_token = Some(auth_token.token.clone());
         
         let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token);
         
         return Ok(auth_url);
     }
     
-    pub async fn set_session(&mut self) {
+    /// Returns a LastFM session key
+    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");
-        let api_key = self.config.dango_api_key.clone();
-        let auth_token = self.config.auth_token.clone().unwrap();
-        let shared_secret = self.config.shared_secret.clone().unwrap();
-        
         // Creates api_sig as defined in last.fm documentation
         let api_sig = format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}");
 
@@ -160,14 +168,14 @@ impl LastFM {
         
         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.unwrap();
+        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).unwrap();
-        self.config.session_key = Some(session_response.session.key.clone());
+        let session_response: Session = serde_json::from_str(&response)?;
+        return Ok(session_response.session.key);
     }
     
-    // Creates a new LastFM struct
+    /// Creates a new LastFM struct with a given config
     pub fn new(config: &LastFMConfig) -> LastFM {
         let last_fm = LastFM {
             config: config.clone()
@@ -178,10 +186,10 @@ impl LastFM {
     // Creates an api request with the given parameters
     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.as_ref().unwrap());
+        params.insert("sk", &self.config.session_key);
         
         // Creates and sets api call signature
-        let api_sig = LastFM::request_sig(&params, &self.config.shared_secret.as_ref().unwrap());
+        let api_sig = LastFM::request_sig(&params, &self.config.shared_secret);
         params.insert("api_sig", &api_sig);
         let mut string_params = String::from("");
         
@@ -226,7 +234,7 @@ impl LastFM {
     }
 }
 
-#[derive(Serialize, Deserialize, Clone)]
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
 pub struct DiscordRPCConfig {
     pub enabled: bool,
     pub dango_client_id: u64, 
@@ -250,7 +258,14 @@ impl DiscordRPC {
 
 #[async_trait]
 impl MusicTracker for DiscordRPC {
-    async fn track_now(&mut self, song: &String) -> Result<(), TrackerError> {
+    async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
+        // Sets song title
+        let song_name = if let Some(song_name) = song.title {
+            song_name
+        } else {
+            String::from("Unknown")
+        };
+        
         let _client_thread = self.client.start();
         
         // Blocks thread execution until it has connected to local discord client
@@ -263,7 +278,7 @@ impl MusicTracker for DiscordRPC {
         // Sets discord account activity to current playing song
         let send_activity = self.client.set_activity(|activity| {
             activity
-                .state(song)
+                .state(format!("Listening to: {}", song_name))
                 .assets(|assets| assets.large_image(&self.config.dango_icon))
                 .timestamps(|time| time.start(start_time))
         });
@@ -274,7 +289,7 @@ impl MusicTracker for DiscordRPC {
         }
     }
     
-    async fn track_song(&mut self, song: &String) -> Result<(), TrackerError> {
+    async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
         return Ok(())
     }
     
@@ -282,7 +297,7 @@ impl MusicTracker for DiscordRPC {
         return Ok(())
     }
     
-    async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError> {
+    async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
         return Ok(0);
     }
 }
\ No newline at end of file

From 7e64c67b46055e88ff9b2b26ba5ed5e0a8b179fc Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 19 Jan 2024 17:45:57 -0600
Subject: [PATCH 024/136] Added preliminary listenbrainz scrobbling, removed
 cue support. Closes #1

---
 Cargo.toml                               |  1 -
 src/music_controller/config.rs           | 19 +++--
 src/music_controller/music_controller.rs |  5 +-
 src/music_player/music_output.rs         |  5 +-
 src/music_player/music_player.rs         | 17 +++--
 src/music_storage/music_db.rs            | 60 ----------------
 src/music_tracker/music_tracker.rs       | 92 ++++++++++++++++++++++--
 7 files changed, 118 insertions(+), 81 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index b312946..3ee99fa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,7 +24,6 @@ heapless = "0.7.16"
 rb = "0.4.1"
 symphonia = { version = "0.5.3", features = ["all-codecs"] }
 serde_json = "1.0.104"
-cue = "2.0.0"
 async-std = "1.12.0"
 async-trait = "0.1.73"
 md-5 = "0.10.5"
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index c77a02a..034f12d 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -4,13 +4,14 @@ use std::fs;
 
 use serde::{Deserialize, Serialize};
 
-use crate::music_tracker::music_tracker::{LastFM, LastFMConfig, DiscordRPCConfig};
+use crate::music_tracker::music_tracker::{LastFMConfig, DiscordRPCConfig, ListenBrainzConfig};
 
 #[derive(Serialize, Deserialize, PartialEq, Eq)]
 pub struct Config {
     pub db_path: Box<PathBuf>,
     pub lastfm: Option<LastFMConfig>,
     pub discord: Option<DiscordRPCConfig>,
+    pub listenbrainz: Option<ListenBrainzConfig>,
 }
 
 impl Default for Config {
@@ -21,18 +22,24 @@ 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
+    /// Creates and saves a new config with default values
     pub fn new(config_file: &PathBuf) -> std::io::Result<Config> {        
         let config = Config::default();
         config.save(config_file)?;
@@ -40,14 +47,14 @@ impl Config {
         Ok(config)
     }
 
-    // Loads config from given file path
+    /// 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!"));
     }
     
-    // Saves config to given path
-    // Saves -> temp file, if successful, removes old config, and renames temp to given path
+    /// 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();
         
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 29f4fb7..da4f676 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -1,12 +1,11 @@
 use std::path::PathBuf;
-use std::sync::{RwLock, Arc, Mutex};
+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_processor::music_processor::MusicProcessor;
-use crate::music_storage::music_db::{URI, Song};
+use crate::music_storage::music_db::Song;
 
 pub struct MusicController {
     pub config: Arc<RwLock<Config>>,
diff --git a/src/music_player/music_output.rs b/src/music_player/music_output.rs
index b774b0f..795e9e9 100644
--- a/src/music_player/music_output.rs
+++ b/src/music_player/music_output.rs
@@ -74,8 +74,7 @@ pub fn open_stream(spec: SignalSpec, duration: Duration) -> Result<Box<dyn Audio
             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?)
@@ -83,7 +82,7 @@ impl<T: OutputSample> AudioOutput<T> {
         let num_channels = config.channels as usize;
         
         // Ring buffer is created with 200ms audio capacity
-        let ring_len = ((200 * config.sample_rate.0 as usize) / 1000) * num_channels;
+        let ring_len = ((50 * config.sample_rate.0 as usize) / 1000) * num_channels;
         let ring_buf= rb::SpscRb::new(ring_len);
         
         let ring_buf_producer = ring_buf.producer();
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
index 2944788..5ab0ad9 100644
--- a/src/music_player/music_player.rs
+++ b/src/music_player/music_player.rs
@@ -21,7 +21,7 @@ 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, LastFM, DiscordRPCConfig, DiscordRPC, TrackerError};
+use crate::music_tracker::music_tracker::{MusicTracker, TrackerError, LastFM, DiscordRPC, ListenBrainz};
 
 // Struct that controls playback of music
 pub struct MusicPlayer {
@@ -146,13 +146,22 @@ impl MusicPlayer {
             // 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
+            // Updates local trackers to the music controller config // TODO: refactor
             let update_trackers = |trackers: &mut Vec<Box<dyn MusicTracker>>|{
                 if let Some(lastfm_config) = global_config.lastfm.clone() {
-                    trackers.push(Box::new(LastFM::new(&lastfm_config)));
+                    if lastfm_config.enabled {
+                        trackers.push(Box::new(LastFM::new(&lastfm_config)));
+                    }
                 }
                 if let Some(discord_config) = global_config.discord.clone() {
-                    trackers.push(Box::new(DiscordRPC::new(&discord_config)));
+                    if discord_config.enabled {
+                        trackers.push(Box::new(DiscordRPC::new(&discord_config)));
+                    }
+                }
+                if let Some(listenbz_config) = global_config.listenbrainz.clone() {
+                    if listenbz_config.enabled {
+                        trackers.push(Box::new(ListenBrainz::new(&listenbz_config)));
+                    }
                 }
             };
             update_trackers(&mut trackers);
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 7ed59fe..093d7c5 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -2,7 +2,6 @@ use file_format::{FileFormat, Kind};
 use serde::Deserialize;
 use lofty::{Accessor, AudioFile, Probe, TaggedFileExt, ItemKey, ItemValue, TagType};
 use rusqlite::{params, Connection};
-use cue::{cd_text::PTI, cd::CD};
 use std::fs;
 use std::path::{Path, PathBuf};
 use std::time::Duration;
@@ -105,64 +104,6 @@ fn path_in_db(query_path: &Path, connection: &Connection) -> bool {
     }
 }
 
-/// Parse a cuesheet given a path and a directory it is located in,
-/// returning a Vec of Song objects
-fn parse_cuesheet(
-    cuesheet_path: &Path,
-    current_dir: &PathBuf
-) -> Result<Vec<Song>, Box<dyn std::error::Error>>{
-    let cuesheet = CD::parse_file(cuesheet_path.to_path_buf())?;
-
-    let album = cuesheet.get_cdtext().read(PTI::Title);
-
-    let mut song_list:Vec<Song> = vec![];
-
-    for (index, track) in cuesheet.tracks().iter().enumerate() {
-        let track_string_path = format!("{}/{}", current_dir.to_string_lossy(), track.get_filename());
-        let track_path = Path::new(&track_string_path);
-
-        if !track_path.exists() {continue};
-
-        // Get the format as a string
-        let short_format = match FileFormat::from_file(track_path) {
-            Ok(fmt) => Some(fmt),
-            Err(_) => None
-        };
-
-        let duration = Duration::from_secs(track.get_length().unwrap_or(-1) as u64);
-
-        let custom_index_start = Tag::Custom{
-            tag: String::from("dango_cue_index_start"),
-            tag_value: track.get_index(0).unwrap_or(-1).to_string()
-        };
-        let custom_index_end = Tag::Custom{
-            tag: String::from("dango_cue_index_end"),
-            tag_value: track.get_index(0).unwrap_or(-1).to_string()
-        };
-
-        let custom_tags: Vec<Tag> = vec![custom_index_start, custom_index_end];
-
-        let tags = track.get_cdtext();
-        let cue_song = Song {
-            path: URI::Local(String::from("URI")),
-            title: tags.read(PTI::Title),
-            album: album.clone(),
-            tracknum: Some(index + 1),
-            artist: tags.read(PTI::Performer),
-            date: None,
-            genre: tags.read(PTI::Genre),
-            plays: Some(0),
-            favorited: Some(false),
-            format: short_format,
-            duration: Some(duration),
-            custom_tags: Some(custom_tags)
-        };
-
-        song_list.push(cue_song);
-    }
-
-    Ok(song_list)
-}
 
 pub fn find_all_music(
     config: &Config,
@@ -196,7 +137,6 @@ pub fn find_all_music(
             add_file_to_db(target_file.path(), &db_connection)
         } else if extension.to_ascii_lowercase() == "cue" {
             // TODO: implement cuesheet support
-            parse_cuesheet(target_file.path(), &current_dir);
         }
     }
 
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 7dd1d8a..213a5f1 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -1,10 +1,11 @@
 use std::time::{SystemTime, UNIX_EPOCH};
 use std::collections::BTreeMap;
+use serde_json::json;
 
 use async_trait::async_trait;
-use serde::{Serialize, Deserialize, Serializer};
+use serde::{Serialize, Deserialize};
 use md5::{Md5, Digest};
-use discord_presence::{ Event, DiscordError};
+use discord_presence::{Event};
 use surf::StatusCode;
 
 use crate::music_storage::music_db::Song;
@@ -38,7 +39,6 @@ pub enum TrackerError {
     Unknown,
 }
 
-
 impl TrackerError {
     pub fn from_surf_error(error: surf::Error) -> TrackerError {
         return match error.status() {
@@ -300,4 +300,88 @@ impl MusicTracker for DiscordRPC {
     async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
         return Ok(0);
     }
-}
\ No newline at end of file
+}
+
+#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
+pub struct ListenBrainzConfig {
+    pub enabled: bool,
+    pub api_url: String,
+    pub auth_token: String,
+}
+
+pub struct ListenBrainz {
+    config: ListenBrainzConfig,
+}
+
+#[async_trait]
+impl MusicTracker for ListenBrainz {
+    async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
+        let (artist, track) = match (song.artist, song.title) {
+            (Some(artist), Some(track)) => (artist, track),
+            _ => return Err(TrackerError::InvalidSong)
+        };
+        // Creates a json to submit a single song as defined in the listenbrainz documentation
+        let json_req = json!({
+            "listen_type": "playing_now",
+            "payload": [
+                {
+                    "track_metadata": {
+                        "artist_name": artist,
+                        "track_name": track,
+                    }
+                }
+            ]
+        });
+        
+        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))
+        }
+    }
+    
+    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) {
+            (Some(artist), Some(track)) => (artist, track),
+            _ => return Err(TrackerError::InvalidSong)
+        };
+        
+        let json_req = json!({
+            "listen_type": "single",
+            "payload": [
+                {
+                    "listened_at": timestamp,
+                    "track_metadata": {
+                        "artist_name": artist,
+                        "track_name": track,
+                    }
+                }
+            ]
+        });
+        
+        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))
+        }
+    }
+    async fn test_tracker(&mut self) -> Result<(), TrackerError> {
+        todo!()
+    }
+    async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
+        todo!()
+    }
+}
+
+impl ListenBrainz {
+    pub fn new(config: &ListenBrainzConfig) -> Self {
+        ListenBrainz {
+            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
+    }
+}

From 12aaab9174b2e1a0b4ff6ff4bac81a50332353e1 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sun, 19 Nov 2023 19:22:11 -0600
Subject: [PATCH 025/136] Updated `lofty` to `0.17.0`, fixing several issues

---
 Cargo.toml                    |  2 +-
 src/music_storage/music_db.rs | 51 ++++++++++++++++++++---------------
 2 files changed, 31 insertions(+), 22 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index d30fe73..cfde222 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ categories = ["multimedia::audio"]
 
 [dependencies]
 file-format = { version = "0.22.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
-lofty = "0.16.1"
+lofty = "0.17.0"
 serde = { version = "1.0.191", features = ["derive"] }
 time = "0.3.22"
 toml = "0.7.5"
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 3372323..093a7c1 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -11,7 +11,7 @@ use std::ops::ControlFlow::{Break, Continue};
 use rcue::parser::parse_from_file;
 use file_format::{FileFormat, Kind};
 use walkdir::WalkDir;
-use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt};
+use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt, ParseOptions};
 use std::fs;
 use std::path::{Path, PathBuf};
 
@@ -246,7 +246,7 @@ impl Album<'_> {
     }
 }
 
-const BLOCKED_EXTENSIONS: [&str; 3] = ["vob", "log", "txt"];
+const BLOCKED_EXTENSIONS: [&str; 5] = ["vob", "log", "txt", "sf2", "mid"];
 
 #[derive(Debug)]
 pub struct MusicLibrary {
@@ -341,6 +341,7 @@ impl MusicLibrary {
         config: &Config,
     ) -> Result<usize, Box<dyn std::error::Error>> {
         let mut total = 0;
+        let mut errors = 0;
         for target_file in WalkDir::new(target_path)
             .follow_links(true)
             .into_iter()
@@ -381,6 +382,7 @@ impl MusicLibrary {
                 match self.add_file(&target_file.path()) {
                     Ok(_) => total += 1,
                     Err(_error) => {
+                        errors += 1;
                         println!("{}, {:?}: {}", format, target_file.file_name(), _error)
                     } // TODO: Handle more of these errors
                 };
@@ -388,6 +390,7 @@ impl MusicLibrary {
                 total += match self.add_cuesheet(&target_file.path().to_path_buf()) {
                     Ok(added) => added,
                     Err(error) => {
+                        errors += 1;
                         println!("{}", error);
                         0
                     }
@@ -398,19 +401,19 @@ impl MusicLibrary {
         // Save the database after scanning finishes
         self.save(&config).unwrap();
 
+        println!("ERRORS: {}", errors);
+
         Ok(total)
     }
 
     pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> {
+        let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
+
         // TODO: Fix error handling here
-        let tagged_file = match lofty::read_from_path(target_file) {
+        let tagged_file = match Probe::open(target_file)?.options(normal_options).read() {
             Ok(tagged_file) => tagged_file,
 
-            Err(_) => match Probe::open(target_file)?.read() {
-                Ok(tagged_file) => tagged_file,
-
-                Err(error) => return Err(error.into()),
-            },
+            Err(error) => return Err(error.into()),
         };
 
         // Ensure the tags exist, if not, insert blank data
@@ -435,7 +438,7 @@ impl MusicLibrary {
                 ItemKey::Comment => Tag::Comment,
                 ItemKey::AlbumTitle => Tag::Album,
                 ItemKey::DiscNumber => Tag::Disk,
-                ItemKey::Unknown(unknown) if unknown == "ACOUSTID_FINGERPRINT" => continue,
+                ItemKey::Unknown(unknown) if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" => continue,
                 ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()),
                 custom => Tag::Key(format!("{:?}", custom)),
             };
@@ -490,7 +493,9 @@ impl MusicLibrary {
 
         match self.add_song(new_song) {
             Ok(_) => (),
-            Err(error) => return Err(error),
+            Err(_) => {
+                //return Err(error)
+            },
         };
 
         Ok(())
@@ -532,7 +537,9 @@ impl MusicLibrary {
                     None => Duration::from_secs(0),
                 };
                 let mut start = track.indices[0].1;
-                start -= pregap;
+                if !start.is_zero() {
+                    start -= pregap;
+                }
 
                 let duration = match next_track.next() {
                     Some(future) => match future.indices.get(0) {
@@ -540,17 +547,15 @@ impl MusicLibrary {
                         None => Duration::from_secs(0)
                     }
                     None => {
-                        let tagged_file = match lofty::read_from_path(&audio_location) {
-                            Ok(tagged_file) => tagged_file,
+                        match lofty::read_from_path(&audio_location) {
+                            Ok(tagged_file) => tagged_file.properties().duration() - start,
 
                             Err(_) => match Probe::open(&audio_location)?.read() {
-                                Ok(tagged_file) => tagged_file,
+                                Ok(tagged_file) => tagged_file.properties().duration() - start,
 
-                                Err(error) => return Err(error.into()),
+                                Err(_) => Duration::from_secs(0),
                             },
-                        };
-
-                        tagged_file.properties().duration() - start
+                        }
                     }
                 };
                 let end = start + duration + postgap;
@@ -656,18 +661,22 @@ impl MusicLibrary {
     }
 
     /// 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) {
+    pub fn update_uri(&mut self, target_uri: &URI, new_tags: Vec<Tag>) -> Result<(), Box<dyn std::error::Error>> {
+        match self.query_uri(target_uri) {
             Some(_) => (),
             None => return Err(format!("URI not in database!").into()),
         }
 
+        for tag in new_tags {
+            println!("{:?}", tag);
+        }
+
         todo!()
     }
 
     /// Query the database, returning a list of [Song]s
     ///
-    /// The order in which the sort by Vec is arranged
+    /// The order in which the `sort by` Vec is arranged
     /// determines the output sorting.
     ///
     /// Example:

From ba4ed346ce018a7dd808ce4837aea8d95891edc2 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 24 Nov 2023 14:22:20 -0600
Subject: [PATCH 026/136] Removed music_player in preparation for GStreamer
 based backend

---
 src/lib.rs                               |   7 -
 src/music_controller/init.rs             |  14 -
 src/music_controller/music_controller.rs |  23 +-
 src/music_player/music_output.rs         | 209 ---------
 src/music_player/music_player.rs         | 529 -----------------------
 src/music_player/music_resampler.rs      | 154 -------
 src/music_storage/playlist.rs            |   2 +-
 src/music_tracker/music_tracker.rs       |   8 +-
 8 files changed, 6 insertions(+), 940 deletions(-)
 delete mode 100644 src/music_controller/init.rs
 delete mode 100644 src/music_player/music_output.rs
 delete mode 100644 src/music_player/music_player.rs
 delete mode 100644 src/music_player/music_resampler.rs

diff --git a/src/lib.rs b/src/lib.rs
index 7aa37eb..a40622d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,14 +8,7 @@ pub mod music_storage {
     mod utils;
 }
 
-pub mod music_player {
-    pub mod music_output;
-    pub mod music_player;
-    pub mod music_resampler;
-}
-
 pub mod music_controller {
     pub mod config;
-    pub mod init;
     pub mod music_controller;
 }
diff --git a/src/music_controller/init.rs b/src/music_controller/init.rs
deleted file mode 100644
index 38b101f..0000000
--- a/src/music_controller/init.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-use std::fs::File;
-use std::path::Path;
-
-pub fn init() {}
-
-fn init_config() {
-    let config_path = "./config.toml";
-
-    if !Path::new(config_path).try_exists().unwrap() {
-        File::create("./config.toml").unwrap();
-    }
-}
-
-fn init_db() {}
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 51be432..8a86205 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -2,20 +2,17 @@ use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 
 use crate::music_controller::config::Config;
-use crate::music_player::music_player::{DecoderMessage, MusicPlayer, PlayerStatus};
 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, 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),
@@ -24,7 +21,6 @@ impl MusicController {
         let controller = MusicController {
             config,
             library,
-            music_player,
         };
 
         return Ok(controller);
@@ -33,7 +29,6 @@ impl MusicController {
     /// 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),
@@ -42,33 +37,17 @@ impl MusicController {
         let controller = MusicController {
             config,
             library,
-            music_player,
         };
 
         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();
-    }
-
     /// Queries the [MusicLibrary], returning a `Vec<Song>`
     pub fn query_library(
         &self,
         query_string: &String,
         target_tags: Vec<Tag>,
-        search_location: bool,
+        _search_location: bool,
         sort_by: Vec<Tag>,
     ) -> Option<Vec<&Song>> {
         self.library
diff --git a/src/music_player/music_output.rs b/src/music_player/music_output.rs
deleted file mode 100644
index 76e6d3b..0000000
--- a/src/music_player/music_output.rs
+++ /dev/null
@@ -1,209 +0,0 @@
-use std::{result, thread};
-
-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};
-use cpal::{self, SizedSample};
-
-use rb::*;
-
-use crate::music_player::music_resampler::Resampler;
-
-pub trait AudioStream {
-    fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()>;
-    fn flush(&mut self);
-}
-
-#[derive(Debug)]
-pub enum AudioOutputError {
-    OpenStreamError,
-    PlayStreamError,
-    StreamClosedError,
-}
-
-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 struct AudioOutput<T>
-where
-    T: OutputSample,
-{
-    ring_buf_producer: rb::Producer<T>,
-    sample_buf: SampleBuffer<T>,
-    stream: cpal::Stream,
-    resampler: Option<Resampler<T>>,
-}
-impl OutputSample for i8 {}
-impl OutputSample for i16 {}
-impl OutputSample for i32 {}
-//impl OutputSample for i64 {}
-impl OutputSample for u8 {}
-impl OutputSample for u16 {}
-impl OutputSample for u32 {}
-//impl OutputSample for u64 {}
-impl OutputSample for f32 {}
-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!(),
-    };
-}
-
-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>> {
-        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_producer = ring_buf.producer();
-        let ring_buf_consumer = ring_buf.consumer();
-
-        let stream_result = device.build_output_stream(
-            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);
-            },
-            //TODO: Handle error here properly
-            move |err| println!("Yeah we erroring out here"),
-            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,
-            ))
-        }
-
-        Ok(Box::new(AudioOutput {
-            ring_buf_producer,
-            sample_buf,
-            stream,
-            resampler,
-        }))
-    }
-}
-
-impl<T: OutputSample> AudioStream for AudioOutput<T> {
-    // Writes given samples to ring buffer
-    fn write(&mut self, decoded: AudioBufferRef<'_>) -> Result<()> {
-        if decoded.frames() == 0 {
-            return Ok(());
-        }
-
-        let mut samples: &[T] = if let Some(resampler) = &mut self.resampler {
-            // Resamples if required
-            match resampler.resample(decoded) {
-                Some(resampled) => resampled,
-                None => return Ok(()),
-            }
-        } else {
-            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();
-
-            while let Some(written) = self.ring_buf_producer.write_blocking(stale_samples) {
-                stale_samples = &stale_samples[written..];
-            }
-        }
-
-        let _ = self.stream.pause();
-    }
-}
diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs
deleted file mode 100644
index e3c43ad..0000000
--- a/src/music_player/music_player.rs
+++ /dev/null
@@ -1,529 +0,0 @@
-use std::io::SeekFrom;
-use std::sync::mpsc::{self, Receiver, Sender};
-use std::sync::{Arc, RwLock};
-use std::thread;
-
-use async_std::io::ReadExt;
-use async_std::task;
-
-use futures::future::join_all;
-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::{MediaSource, MediaSourceStream};
-use symphonia::core::meta::MetadataOptions;
-use symphonia::core::probe::Hint;
-use symphonia::core::units::{Time, TimeBase};
-
-use futures::AsyncBufRead;
-
-use crate::music_controller::config::Config;
-use crate::music_player::music_output::AudioStream;
-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 {
-    player_status: PlayerStatus,
-    music_trackers: Vec<Box<dyn MusicTracker + Send>>,
-    current_song: Arc<RwLock<Option<Song>>>,
-    message_sender: Sender<DecoderMessage>,
-    status_receiver: Receiver<PlayerStatus>,
-    config: Arc<RwLock<Config>>,
-}
-
-#[derive(Clone, Copy, Debug)]
-pub enum PlayerStatus {
-    Playing(f64),
-    Paused,
-    Stopped,
-    Error,
-}
-
-#[derive(Debug, Clone)]
-pub enum DecoderMessage {
-    OpenSong(Song),
-    Play,
-    Pause,
-    Stop,
-    SeekTo(u64),
-}
-
-#[derive(Clone)]
-pub enum TrackerMessage {
-    Track(Song),
-    TrackNow(Song),
-}
-
-// Holds a song decoder reader, etc
-struct SongHandler {
-    pub reader: Box<dyn FormatReader>,
-    pub decoder: Box<dyn Decoder>,
-    pub time_base: Option<TimeBase>,
-    pub duration: Option<u64>,
-}
-
-// TODO: actual error handling here
-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 src: Box<dyn MediaSource> = match uri {
-            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!(),
-        };
-
-        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 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,
-        });
-    }
-}
-
-impl MusicPlayer {
-    pub fn new(config: Arc<RwLock<Config>>) -> Self {
-        // Creates mpsc channels to communicate with music player threads
-        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 {
-            music_trackers: Vec::new(),
-            player_status: PlayerStatus::Stopped,
-            current_song,
-            message_sender,
-            status_receiver,
-            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>>| {
-                if let Some(lastfm_config) = global_config.lastfm.clone() {
-                    if lastfm_config.enabled {
-                        trackers.push(Box::new(LastFM::new(&lastfm_config)));
-                    }
-                }
-                if let Some(discord_config) = global_config.discord.clone() {
-                    if discord_config.enabled {
-                        trackers.push(Box::new(DiscordRPC::new(&discord_config)));
-                    }
-                }
-                if let Some(listenbz_config) = global_config.listenbrainz.clone() {
-                    if listenbz_config.enabled {
-                        trackers.push(Box::new(ListenBrainz::new(&listenbz_config)));
-                    }
-                }
-            };
-            update_trackers(&mut trackers);
-            loop {
-                if let message = tracker_receiver.recv() {
-                    if local_config != global_config {
-                        update_trackers(&mut trackers);
-                    }
-
-                    let mut results = Vec::new();
-                    task::block_on(async {
-                        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(_) => {}
-                            }
-                        }
-                        results = join_all(futures).await;
-                    });
-
-                    for result in results {
-                        status_sender.send(result).unwrap_or_default()
-                    }
-                }
-            }
-        });
-    }
-
-    // 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>>>,
-    ) {
-        // 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 (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 {
-                'handle_message: loop {
-                    let message = if paused {
-                        // Pauses playback by blocking on waiting for new player messages
-                        match message_receiver.recv() {
-                            Ok(message) => Some(message),
-                            Err(_) => None,
-                        }
-                    } else {
-                        // Resumes playback by not blocking
-                        match message_receiver.try_recv() {
-                            Ok(message) => Some(message),
-                            Err(_) => break 'handle_message,
-                        }
-                    };
-                    // Handles message received from MusicPlayer struct
-                    match message {
-                        Some(DecoderMessage::OpenSong(song)) => {
-                            let song_uri = song.location.clone();
-                            match SongHandler::new(&song_uri) {
-                                Ok(new_handler) => {
-                                    song_handler = Some(new_handler);
-                                    *current_song.write().unwrap() = Some(song);
-                                    paused = false;
-                                    song_tracked = false;
-                                }
-                                Err(_) => status_sender.send(PlayerStatus::Error).unwrap(),
-                            }
-                        }
-                        Some(DecoderMessage::Play) => {
-                            if song_handler.is_some() {
-                                paused = false;
-                            }
-                        }
-                        Some(DecoderMessage::Pause) => {
-                            paused = true;
-                            status_sender.send(PlayerStatus::Paused).unwrap();
-                        }
-                        Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time),
-                        // Exits main decode loop and subsequently ends thread
-                        Some(DecoderMessage::Stop) => {
-                            status_sender.send(PlayerStatus::Stopped).unwrap();
-                            break 'main_decode;
-                        }
-                        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())
-                {
-                    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();
-                            seek_time = None;
-                        }
-                        None => {} //Nothing to do!
-                    }
-                    let packet = match song_handler.reader.next_packet() {
-                        Ok(packet) => packet,
-                        Err(Error::ResetRequired) => panic!(), //TODO,
-                        Err(err) => {
-                            // Unrecoverable?
-                            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();
-                        }
-
-                        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;
-                            // 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
-                            {
-                                song_tracked = true;
-                                tracker_sender
-                                    .send(TrackerMessage::Track(song.clone()))
-                                    .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(),
-                                );
-                            }
-                        }
-                        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);
-                        }
-                    }
-                }
-            }
-        });
-    }
-
-    // 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> {
-        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;
-    }
-}
-
-// TODO: Make the buffer length do anything
-/// Options for remote sources
-///
-/// media_buffer_len is how many bytes are to be buffered in totala
-///
-/// forward_buffer is how many bytes can ahead of the seek position without the remote source being read from
-pub struct RemoteOptions {
-    media_buffer_len: u64,
-    forward_buffer_len: u64,
-}
-
-impl Default for RemoteOptions {
-    fn default() -> Self {
-        RemoteOptions {
-            media_buffer_len: 100000,
-            forward_buffer_len: 1024,
-        }
-    }
-}
-
-/// A remote source of media
-struct RemoteSource {
-    reader: Box<dyn AsyncBufRead + Send + Sync + Unpin>,
-    media_buffer: Vec<u8>,
-    forward_buffer_len: u64,
-    offset: u64,
-}
-
-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 {
-            return surf::get(uri).await;
-        })?;
-
-        let reader = response.take_body().into_reader();
-
-        Ok(RemoteSource {
-            reader,
-            media_buffer: Vec::new(),
-            forward_buffer_len: config.forward_buffer_len,
-            offset: 0,
-        })
-    }
-}
-// TODO: refactor this + buffer into the buffer passed into the function, not a newly allocated one
-impl std::io::Read for RemoteSource {
-    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
-        // Reads bytes into the media buffer if the offset is within the specified distance from the end of the buffer
-        if self.media_buffer.len() as u64 - self.offset < self.forward_buffer_len {
-            let mut buffer = [0; 1024];
-            let read_bytes = task::block_on(async {
-                match self.reader.read_exact(&mut buffer).await {
-                    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
-        let mut bytes_read = 0;
-        for location in 0..1024 {
-            if (location + self.offset as usize) < self.media_buffer.len() {
-                buf[location] = self.media_buffer[location + self.offset as usize];
-                bytes_read += 1;
-            }
-        }
-
-        self.offset += bytes_read;
-        return Ok(bytes_read as usize);
-    }
-}
-
-impl std::io::Seek for RemoteSource {
-    // Seeks to a given position
-    // Seeking past the internal buffer's length results in the seeking to the end of content
-    fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
-        match pos {
-            // Offset is set to given position
-            SeekFrom::Start(pos) => {
-                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 {
-                    self.offset = self.media_buffer.len() as u64;
-                } else {
-                    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 {
-                    self.offset = self.media_buffer.len() as u64;
-                } else {
-                    self.offset += pos as u64
-                }
-                return Ok(self.offset);
-            }
-        }
-    }
-}
-
-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
deleted file mode 100644
index 4040835..0000000
--- a/src/music_player/music_resampler.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-// Symphonia
-// Copyright (c) 2019-2022 The Project Symphonia Developers.
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-
-use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, SignalSpec};
-use symphonia::core::conv::{FromSample, IntoSample};
-use symphonia::core::sample::Sample;
-
-pub struct Resampler<T> {
-    resampler: rubato::FftFixedIn<f32>,
-    input: Vec<Vec<f32>>,
-    output: Vec<Vec<f32>>,
-    interleaved: Vec<T>,
-    duration: usize,
-}
-
-impl<T> Resampler<T>
-where
-    T: Sample + FromSample<f32> + IntoSample<f32>,
-{
-    fn resample_inner(&mut self) -> &[T] {
-        {
-            //let mut input =  heapless::Vec::<f32, 32>::new();
-            let mut input: arrayvec::ArrayVec<&[f32], 32> = Default::default();
-
-            for channel in self.input.iter() {
-                input.push(&channel[..self.duration]);
-            }
-
-            // Resample.
-            rubato::Resampler::process_into_buffer(
-                &mut self.resampler,
-                &input,
-                &mut self.output,
-                None,
-            )
-            .unwrap();
-        }
-
-        // Remove consumed samples from the input buffer.
-        for channel in self.input.iter_mut() {
-            channel.drain(0..self.duration);
-        }
-
-        // Interleave the planar samples from Rubato.
-        let num_channels = self.output.len();
-
-        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() {
-                *s = self.output[ch][i].into_sample();
-            }
-        }
-
-        &self.interleaved
-    }
-}
-
-impl<T> Resampler<T>
-where
-    T: Sample + FromSample<f32> + IntoSample<f32>,
-{
-    pub fn new(spec: SignalSpec, to_sample_rate: usize, duration: u64) -> Self {
-        let duration = duration as usize;
-        let num_channels = spec.channels.count();
-
-        let resampler = rubato::FftFixedIn::<f32>::new(
-            spec.rate as usize,
-            to_sample_rate,
-            duration,
-            2,
-            num_channels,
-        )
-        .unwrap();
-
-        let output = rubato::Resampler::output_buffer_allocate(&resampler);
-
-        let input = vec![Vec::with_capacity(duration); num_channels];
-
-        Self {
-            resampler,
-            input,
-            output,
-            duration,
-            interleaved: Default::default(),
-        }
-    }
-
-    /// Resamples a planar/non-interleaved input.
-    ///
-    /// Returns the resampled samples in an interleaved format.
-    pub fn resample(&mut self, input: AudioBufferRef<'_>) -> Option<&[T]> {
-        // Copy and convert samples into input buffer.
-        convert_samples_any(&input, &mut self.input);
-
-        // Check if more samples are required.
-        if self.input[0].len() < self.duration {
-            return None;
-        }
-
-        Some(self.resample_inner())
-    }
-
-    /// Resample any remaining samples in the resample buffer.
-    pub fn flush(&mut self) -> Option<&[T]> {
-        let len = self.input[0].len();
-
-        if len == 0 {
-            return None;
-        }
-
-        let partial_len = len % self.duration;
-
-        if partial_len != 0 {
-            // Fill each input channel buffer with silence to the next multiple of the resampler
-            // duration.
-            for channel in self.input.iter_mut() {
-                channel.resize(len + (self.duration - partial_len), f32::MID);
-            }
-        }
-
-        Some(self.resample_inner())
-    }
-}
-
-fn convert_samples_any(input: &AudioBufferRef<'_>, output: &mut [Vec<f32>]) {
-    match input {
-        AudioBufferRef::U8(input) => convert_samples(input, output),
-        AudioBufferRef::U16(input) => convert_samples(input, output),
-        AudioBufferRef::U24(input) => convert_samples(input, output),
-        AudioBufferRef::U32(input) => convert_samples(input, output),
-        AudioBufferRef::S8(input) => convert_samples(input, output),
-        AudioBufferRef::S16(input) => convert_samples(input, output),
-        AudioBufferRef::S24(input) => convert_samples(input, output),
-        AudioBufferRef::S32(input) => convert_samples(input, output),
-        AudioBufferRef::F32(input) => convert_samples(input, output),
-        AudioBufferRef::F64(input) => convert_samples(input, output),
-    }
-}
-
-fn convert_samples<S>(input: &AudioBuffer<S>, output: &mut [Vec<f32>])
-where
-    S: Sample + IntoSample<f32>,
-{
-    for (c, dst) in output.iter_mut().enumerate() {
-        let src = input.chan(c);
-        dst.extend(src.iter().map(|&s| s.into_sample()));
-    }
-}
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 797d0d2..189b9e8 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,6 +1,6 @@
 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 9ffd73d..37a3142 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -123,7 +123,7 @@ impl MusicTracker for LastFM {
         };
     }
 
-    async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
+    async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
         todo!();
     }
 }
@@ -319,7 +319,7 @@ impl MusicTracker for DiscordRPC {
         }
     }
 
-    async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
+    async fn track_song(&mut self, _song: Song) -> Result<(), TrackerError> {
         return Ok(());
     }
 
@@ -327,7 +327,7 @@ impl MusicTracker for DiscordRPC {
         return Ok(());
     }
 
-    async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
+    async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
         return Ok(0);
     }
 }
@@ -408,7 +408,7 @@ impl MusicTracker for ListenBrainz {
     async fn test_tracker(&mut self) -> Result<(), TrackerError> {
         todo!()
     }
-    async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
+    async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
         todo!()
     }
 }

From 048254150c8488b1cb5d627e42f65af6a34446ad Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 24 Nov 2023 14:25:31 -0600
Subject: [PATCH 027/136] Ran `cargo clippy`

---
 src/music_controller/config.rs           |  8 ++---
 src/music_controller/music_controller.rs |  4 +--
 src/music_storage/music_db.rs            | 38 +++++++++-----------
 src/music_storage/utils.rs               |  6 ++--
 src/music_tracker/music_tracker.rs       | 44 ++++++++++++------------
 5 files changed, 48 insertions(+), 52 deletions(-)

diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index 82cbd22..f450c9d 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -18,7 +18,7 @@ impl Default for Config {
     fn default() -> Self {
         let path = PathBuf::from("./music_database");
 
-        return Config {
+        Config {
             db_path: Box::new(path),
 
             lastfm: None,
@@ -34,7 +34,7 @@ impl Default for Config {
                 api_url: String::from("https://api.listenbrainz.org"),
                 auth_token: String::from(""),
             }),
-        };
+        }
     }
 }
 
@@ -49,10 +49,10 @@ impl Config {
 
     /// Loads config from given file path
     pub fn from(config_file: &PathBuf) -> std::result::Result<Config, toml::de::Error> {
-        return toml::from_str(
+        toml::from_str(
             &read_to_string(config_file)
                 .expect("Failed to initalize music config: File not found!"),
-        );
+        )
     }
 
     /// Saves config to given path
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs
index 8a86205..6f4881a 100644
--- a/src/music_controller/music_controller.rs
+++ b/src/music_controller/music_controller.rs
@@ -23,7 +23,7 @@ impl MusicController {
             library,
         };
 
-        return Ok(controller);
+        Ok(controller)
     }
 
     /// Creates new music controller from a config at given path
@@ -39,7 +39,7 @@ impl MusicController {
             library,
         };
 
-        return Ok(controller);
+        Ok(controller)
     }
 
     /// Queries the [MusicLibrary], returning a `Vec<Song>`
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 093a7c1..f9ef854 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -115,10 +115,7 @@ impl Song {
             "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,
-                },
+                Some(format) => format.short_name().map(|short| short.to_string()),
                 None => None,
             },
             _ => todo!(), // Other field types are not yet supported
@@ -321,10 +318,9 @@ 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| {
+        self.library.par_iter().for_each(|track| {
             if path == track.location.path() {
-                result.clone().lock().unwrap().push(&track);
-                return;
+                result.clone().lock().unwrap().push(track);
             }
         });
         if result.lock().unwrap().len() > 0 {
@@ -368,7 +364,7 @@ impl MusicLibrary {
             }
             */
 
-            let format = FileFormat::from_file(&path)?;
+            let format = FileFormat::from_file(path)?;
             let extension = match path.extension() {
                 Some(ext) => ext.to_string_lossy().to_ascii_lowercase(),
                 None => String::new(),
@@ -379,7 +375,7 @@ impl MusicLibrary {
             if (format.kind() == Kind::Audio || format.kind() == Kind::Video)
                 && !BLOCKED_EXTENSIONS.contains(&extension.as_str())
             {
-                match self.add_file(&target_file.path()) {
+                match self.add_file(target_file.path()) {
                     Ok(_) => total += 1,
                     Err(_error) => {
                         errors += 1;
@@ -399,7 +395,7 @@ impl MusicLibrary {
         }
 
         // Save the database after scanning finishes
-        self.save(&config).unwrap();
+        self.save(config).unwrap();
 
         println!("ERRORS: {}", errors);
 
@@ -455,7 +451,7 @@ impl MusicLibrary {
         // 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::Embedded(i as usize);
+            let new_art = AlbumArt::Embedded(i);
 
             album_art.push(new_art)
         }
@@ -547,10 +543,10 @@ impl MusicLibrary {
                         None => Duration::from_secs(0)
                     }
                     None => {
-                        match lofty::read_from_path(&audio_location) {
+                        match lofty::read_from_path(audio_location) {
                             Ok(tagged_file) => tagged_file.properties().duration() - start,
 
-                            Err(_) => match Probe::open(&audio_location)?.read() {
+                            Err(_) => match Probe::open(audio_location)?.read() {
                                 Ok(tagged_file) => tagged_file.properties().duration() - start,
 
                                 Err(_) => Duration::from_secs(0),
@@ -561,7 +557,7 @@ impl MusicLibrary {
                 let end = start + duration + postgap;
 
                 // Get the format as a string
-                let format: Option<FileFormat> = match FileFormat::from_file(&audio_location) {
+                let format: Option<FileFormat> = match FileFormat::from_file(audio_location) {
                     Ok(fmt) => Some(fmt),
                     Err(_) => None,
                 };
@@ -637,7 +633,7 @@ impl MusicLibrary {
             None => (),
         }
         match new_song.location {
-            URI::Local(_) if self.query_path(&new_song.location.path()).is_some() => {
+            URI::Local(_) if self.query_path(new_song.location.path()).is_some() => {
                 return Err(format!("Location exists for {:?}", new_song.location).into())
             }
             _ => (),
@@ -664,7 +660,7 @@ impl MusicLibrary {
     pub fn update_uri(&mut self, target_uri: &URI, new_tags: Vec<Tag>) -> Result<(), Box<dyn std::error::Error>> {
         match self.query_uri(target_uri) {
             Some(_) => (),
-            None => return Err(format!("URI not in database!").into()),
+            None => return Err("URI not in database!".to_string().into()),
         }
 
         for tag in new_tags {
@@ -709,11 +705,11 @@ impl MusicLibrary {
         self.library.par_iter().for_each(|track| {
             for tag in target_tags {
                 let track_result = match tag {
-                    Tag::Field(target) => match track.get_field(&target) {
+                    Tag::Field(target) => match track.get_field(target) {
                         Some(value) => value,
                         None => continue,
                     },
-                    _ => match track.get_tag(&tag) {
+                    _ => match track.get_tag(tag) {
                         Some(value) => value.clone(),
                         None => continue,
                     },
@@ -749,7 +745,7 @@ impl MusicLibrary {
                         Some(field_value) => field_value,
                         None => continue,
                     },
-                    _ => match a.get_tag(&sort_option) {
+                    _ => match a.get_tag(sort_option) {
                         Some(tag_value) => tag_value.to_owned(),
                         None => continue,
                     },
@@ -760,7 +756,7 @@ impl MusicLibrary {
                         Some(field_value) => field_value,
                         None => continue,
                     },
-                    _ => match b.get_tag(&sort_option) {
+                    _ => match b.get_tag(sort_option) {
                         Some(tag_value) => tag_value.to_owned(),
                         None => continue,
                     },
@@ -782,7 +778,7 @@ impl MusicLibrary {
             path_a.file_name().cmp(&path_b.file_name())
         });
 
-        if new_songs.len() > 0 {
+        if !new_songs.is_empty() {
             Some(new_songs)
         } else {
             None
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 0847ae0..f4a7947 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -45,12 +45,12 @@ pub(super) fn write_library(
     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 writer = BufWriter::new(fs::File::create(&writer_name)?);
     let mut e = snap::write::FrameEncoder::new(writer);
 
     // Write out the data using bincode
     bincode::serde::encode_into_std_write(
-        &library,
+        library,
         &mut e,
         bincode::config::standard()
             .with_little_endian()
@@ -83,7 +83,7 @@ pub fn find_images(song_path: &PathBuf) -> Result<Vec<AlbumArt>, Box<dyn Error>>
             continue;
         }
 
-        let format = FileFormat::from_file(&path)?.kind();
+        let format = FileFormat::from_file(path)?.kind();
         if format != Kind::Image {
             break
         }
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs
index 37a3142..f5bee70 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker/music_tracker.rs
@@ -41,7 +41,7 @@ pub enum TrackerError {
 
 impl TrackerError {
     pub fn from_surf_error(error: surf::Error) -> TrackerError {
-        return match error.status() {
+        match error.status() {
             StatusCode::Forbidden => TrackerError::InvalidAuth,
             StatusCode::Unauthorized => TrackerError::InvalidAuth,
             StatusCode::NetworkAuthenticationRequired => TrackerError::InvalidAuth,
@@ -50,7 +50,7 @@ impl TrackerError {
             StatusCode::ServiceUnavailable => TrackerError::ServiceUnavailable,
             StatusCode::NotFound => TrackerError::ServiceUnavailable,
             _ => TrackerError::Unknown,
-        };
+        }
     }
 }
 
@@ -85,8 +85,8 @@ impl MusicTracker for LastFM {
         };
 
         params.insert("method", "track.scrobble");
-        params.insert("artist", &artist);
-        params.insert("track", &track);
+        params.insert("artist", artist);
+        params.insert("track", track);
         params.insert("timestamp", &string_timestamp);
 
         return match self.api_request(params).await {
@@ -104,8 +104,8 @@ impl MusicTracker for LastFM {
         };
 
         params.insert("method", "track.updateNowPlaying");
-        params.insert("artist", &artist);
-        params.insert("track", &track);
+        params.insert("artist", artist);
+        params.insert("track", track);
 
         return match self.api_request(params).await {
             Ok(_) => Ok(()),
@@ -160,7 +160,7 @@ impl LastFM {
             auth_token.token
         );
 
-        return Ok(auth_url);
+        Ok(auth_url)
     }
 
     /// Returns a LastFM session key
@@ -186,15 +186,15 @@ impl LastFM {
 
         // Sets session key from received response
         let session_response: Session = serde_json::from_str(&response)?;
-        return Ok(session_response.session.key);
+        Ok(session_response.session.key)
     }
 
     /// Creates a new LastFM struct with a given config
     pub fn new(config: &LastFMConfig) -> LastFM {
-        let last_fm = LastFM {
+        
+        LastFM {
             config: config.clone(),
-        };
-        return last_fm;
+        }
     }
 
     // Creates an api request with the given parameters
@@ -221,9 +221,9 @@ impl LastFM {
 
         let url = "http://ws.audioscrobbler.com/2.0/";
 
-        let response = surf::post(url).body_string(string_params).await;
+        
 
-        return response;
+        surf::post(url).body_string(string_params).await
     }
 
     // Returns an api signature as defined in the last.fm api documentation
@@ -242,7 +242,7 @@ impl LastFM {
         let hash_result = md5_hasher.finalize();
         let hashed_sig = format!("{:#02x}", hash_result);
 
-        return hashed_sig;
+        hashed_sig
     }
 
     // Removes last.fm account from dango-music-player
@@ -265,11 +265,11 @@ pub struct DiscordRPC {
 
 impl DiscordRPC {
     pub fn new(config: &DiscordRPCConfig) -> Self {
-        let rpc = DiscordRPC {
+        
+        DiscordRPC {
             client: discord_presence::client::Client::new(config.dango_client_id),
             config: config.clone(),
-        };
-        return rpc;
+        }
     }
 }
 
@@ -307,8 +307,8 @@ impl MusicTracker for DiscordRPC {
         // Sets discord account activity to current playing song
         let send_activity = self.client.set_activity(|activity| {
             activity
-                .state(format!("{}", album))
-                .details(format!("{}", song_name))
+                .state(album.to_string())
+                .details(song_name.to_string())
                 .assets(|assets| assets.large_image(&self.config.dango_icon))
                 .timestamps(|time| time.start(start_time))
         });
@@ -425,10 +425,10 @@ impl ListenBrainz {
         request: &String,
         endpoint: &String,
     ) -> Result<surf::Response, surf::Error> {
-        let reponse = surf::post(format!("{}{}", &self.config.api_url, endpoint))
+        
+        surf::post(format!("{}{}", &self.config.api_url, endpoint))
             .body_string(request.clone())
             .header("Authorization", format!("Token {}", self.config.auth_token))
-            .await;
-        return reponse;
+            .await
     }
 }

From 6b58ac02cf81d6051d68f5fbd14678c188f33054 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 24 Nov 2023 14:48:15 -0600
Subject: [PATCH 028/136] Fixed `cargo clippy` suggestions

---
 src/lib.rs                                    |  6 ++---
 src/music_controller/config.rs                |  7 +++---
 .../{music_controller.rs => controller.rs}    |  0
 src/music_storage/music_db.rs                 | 24 ++++++++-----------
 src/music_storage/playlist.rs                 |  2 +-
 src/music_storage/utils.rs                    |  7 +++---
 src/{music_tracker => }/music_tracker.rs      |  6 ++---
 7 files changed, 23 insertions(+), 29 deletions(-)
 rename src/music_controller/{music_controller.rs => controller.rs} (100%)
 rename src/{music_tracker => }/music_tracker.rs (99%)

diff --git a/src/lib.rs b/src/lib.rs
index a40622d..b676c9f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,4 @@
-pub mod music_tracker {
-    pub mod music_tracker;
-}
+pub mod music_tracker;
 
 pub mod music_storage {
     pub mod music_db;
@@ -10,5 +8,5 @@ pub mod music_storage {
 
 pub mod music_controller {
     pub mod config;
-    pub mod music_controller;
+    pub mod controller;
 }
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index f450c9d..9b934d4 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -4,7 +4,7 @@ use std::path::PathBuf;
 
 use serde::{Deserialize, Serialize};
 
-use crate::music_tracker::music_tracker::{DiscordRPCConfig, LastFMConfig, ListenBrainzConfig};
+use crate::music_tracker::{DiscordRPCConfig, LastFMConfig, ListenBrainzConfig};
 
 #[derive(Serialize, Deserialize, PartialEq, Eq)]
 pub struct Config {
@@ -66,9 +66,8 @@ impl Config {
         fs::write(&temp_file, toml)?;
 
         // If configuration file already exists, delete it
-        match fs::metadata(config_file) {
-            Ok(_) => fs::remove_file(config_file)?,
-            Err(_) => {}
+        if fs::metadata(config_file).is_ok() {
+            fs::remove_file(config_file)?
         }
 
         fs::rename(temp_file, config_file)?;
diff --git a/src/music_controller/music_controller.rs b/src/music_controller/controller.rs
similarity index 100%
rename from src/music_controller/music_controller.rs
rename to src/music_controller/controller.rs
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index f9ef854..2bc3bd9 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -199,6 +199,7 @@ pub struct Album<'a> {
     discs: BTreeMap<usize, Vec<&'a Song>>,
 }
 
+#[allow(clippy::len_without_is_empty)]
 impl Album<'_> {
     /// Returns the album title
     pub fn title(&self) -> &String {
@@ -383,7 +384,7 @@ impl MusicLibrary {
                     } // TODO: Handle more of these errors
                 };
             } else if extension == "cue" {
-                total += match self.add_cuesheet(&target_file.path().to_path_buf()) {
+                total += match self.add_cuesheet(target_file.path()) {
                     Ok(added) => added,
                     Err(error) => {
                         errors += 1;
@@ -457,7 +458,7 @@ impl MusicLibrary {
         }
 
         // Find images around the music file that can be used
-        let mut found_images = find_images(&target_file.to_path_buf()).unwrap();
+        let mut found_images = find_images(target_file).unwrap();
         album_art.append(&mut found_images);
 
         // Get the format as a string
@@ -497,10 +498,10 @@ impl MusicLibrary {
         Ok(())
     }
 
-    pub fn add_cuesheet(&mut self, cuesheet: &PathBuf) -> Result<usize, Box<dyn Error>> {
+    pub fn add_cuesheet(&mut self, cuesheet: &Path) -> Result<usize, Box<dyn Error>> {
         let mut tracks_added = 0;
 
-        let cue_data = parse_from_file(&cuesheet.as_path().to_string_lossy(), false).unwrap();
+        let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap();
 
         // Get album level information
         let album_title = &cue_data.title;
@@ -515,10 +516,7 @@ impl MusicLibrary {
             }
 
             // Try to remove the original audio file from the db if it exists
-            match self.remove_uri(&URI::Local(audio_location.clone())) {
-                Ok(_) => tracks_added -= 1,
-                Err(_) => ()
-            };
+            if self.remove_uri(&URI::Local(audio_location.clone())).is_ok() { tracks_added -= 1 }
 
             let next_track = file.tracks.clone();
             let mut next_track = next_track.iter().skip(1);
@@ -626,12 +624,10 @@ impl MusicLibrary {
     }
 
     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 => (),
+        if self.query_uri(&new_song.location).is_some() {
+            return Err(format!("URI already in database: {:?}", new_song.location).into())
         }
+
         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())
@@ -854,7 +850,7 @@ impl MusicLibrary {
     /// Queries a list of albums by title
     pub fn query_albums(
         &self,
-        query_string: &String, // The query itself
+        query_string: &str, // The query itself
     ) -> Result<Vec<Album>, Box<dyn Error>> {
         let all_albums = self.albums();
 
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 189b9e8..4f8892d 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,6 +1,6 @@
 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: &[&Path]) {
     unimplemented!()
 }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index f4a7947..0687db4 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,5 +1,6 @@
 use std::io::{BufReader, BufWriter};
-use std::{error::Error, fs, path::PathBuf};
+use std::path::{Path, PathBuf};
+use std::{error::Error, fs};
 use walkdir::WalkDir;
 use file_format::{FileFormat, Kind};
 
@@ -8,7 +9,7 @@ use snap;
 use super::music_db::{Song, AlbumArt, URI};
 use unidecode::unidecode;
 
-pub(super) fn normalize(input_string: &String) -> String {
+pub(super) fn normalize(input_string: &str) -> String {
     let mut normalized = unidecode(input_string);
 
     // Remove non alphanumeric characters
@@ -65,7 +66,7 @@ pub(super) fn write_library(
     Ok(())
 }
 
-pub fn find_images(song_path: &PathBuf) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
+pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
     let mut images: Vec<AlbumArt> = Vec::new();
 
     let song_dir = song_path.parent().ok_or("")?;
diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker.rs
similarity index 99%
rename from src/music_tracker/music_tracker.rs
rename to src/music_tracker.rs
index f5bee70..3899f44 100644
--- a/src/music_tracker/music_tracker.rs
+++ b/src/music_tracker.rs
@@ -422,12 +422,12 @@ impl ListenBrainz {
     // Makes an api request to configured url with given json
     pub async fn api_request(
         &self,
-        request: &String,
+        request: &str,
         endpoint: &String,
     ) -> Result<surf::Response, surf::Error> {
-        
+
         surf::post(format!("{}{}", &self.config.api_url, endpoint))
-            .body_string(request.clone())
+            .body_string(request.to_owned())
             .header("Authorization", format!("Token {}", self.config.auth_token))
             .await
     }

From e82476f2a6f646cbb6ebbc72548bca19ad20397e Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 24 Nov 2023 19:25:35 -0600
Subject: [PATCH 029/136] Removed unused dependencies

---
 Cargo.toml | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index cfde222..9860318 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -18,25 +18,17 @@ serde = { version = "1.0.191", features = ["derive"] }
 time = "0.3.22"
 toml = "0.7.5"
 walkdir = "2.4.0"
-cpal = "0.15.2"
-heapless = "0.7.16"
-rb = "0.4.1"
-symphonia = { version = "0.5.3", features = ["all-codecs"] }
 serde_json = "1.0.104"
 async-std = "1.12.0"
 async-trait = "0.1.73"
 md-5 = "0.10.5"
 surf = "2.3.2"
-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 = { version = "2.0.0-rc.3", features = ["serde"] }
 unidecode = "0.3.0"
 rayon = "1.8.0"
 log = "0.4"
-pretty_env_logger = "0.4"
 base64 = "0.21.5"
 snap = "1.1.0"
 rcue = "0.1.3"

From 64a3b67241a334cdc20ddb63fede8f75a11ae603 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 25 Nov 2023 17:43:28 -0600
Subject: [PATCH 030/136] Removed `music_tracker.rs`, removed unused deps

---
 Cargo.toml                     |   7 -
 src/lib.rs                     |   2 -
 src/music_controller/config.rs |  19 --
 src/music_tracker.rs           | 434 ---------------------------------
 4 files changed, 462 deletions(-)
 delete mode 100644 src/music_tracker.rs

diff --git a/Cargo.toml b/Cargo.toml
index 9860318..90ec0d7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,15 +15,8 @@ categories = ["multimedia::audio"]
 file-format = { version = "0.22.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
 lofty = "0.17.0"
 serde = { version = "1.0.191", features = ["derive"] }
-time = "0.3.22"
 toml = "0.7.5"
 walkdir = "2.4.0"
-serde_json = "1.0.104"
-async-std = "1.12.0"
-async-trait = "0.1.73"
-md-5 = "0.10.5"
-surf = "2.3.2"
-discord-presence = "0.5.18"
 chrono = { version = "0.4.31", features = ["serde"] }
 bincode = { version = "2.0.0-rc.3", features = ["serde"] }
 unidecode = "0.3.0"
diff --git a/src/lib.rs b/src/lib.rs
index b676c9f..d6c8bb0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,3 @@
-pub mod music_tracker;
-
 pub mod music_storage {
     pub mod music_db;
     pub mod playlist;
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index 9b934d4..2014de7 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -4,14 +4,9 @@ use std::path::PathBuf;
 
 use serde::{Deserialize, Serialize};
 
-use crate::music_tracker::{DiscordRPCConfig, LastFMConfig, ListenBrainzConfig};
-
 #[derive(Serialize, Deserialize, PartialEq, Eq)]
 pub struct Config {
     pub db_path: Box<PathBuf>,
-    pub lastfm: Option<LastFMConfig>,
-    pub discord: Option<DiscordRPCConfig>,
-    pub listenbrainz: Option<ListenBrainzConfig>,
 }
 
 impl Default for Config {
@@ -20,20 +15,6 @@ impl Default for Config {
 
         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(""),
-            }),
         }
     }
 }
diff --git a/src/music_tracker.rs b/src/music_tracker.rs
deleted file mode 100644
index 3899f44..0000000
--- a/src/music_tracker.rs
+++ /dev/null
@@ -1,434 +0,0 @@
-use serde_json::json;
-use std::collections::BTreeMap;
-use std::time::{SystemTime, UNIX_EPOCH};
-
-use async_trait::async_trait;
-use discord_presence::Event;
-use md5::{Digest, Md5};
-use serde::{Deserialize, Serialize};
-use surf::StatusCode;
-
-use crate::music_storage::music_db::{Song, Tag};
-
-#[async_trait]
-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>;
-}
-
-#[derive(Debug)]
-pub enum TrackerError {
-    /// Tracker does not accept the song's format/content
-    InvalidSong,
-    /// Tracker requires authentication
-    InvalidAuth,
-    /// Tracker request was malformed
-    InvalidRequest,
-    /// Tracker is unavailable
-    ServiceUnavailable,
-    /// Unknown tracker error
-    Unknown,
-}
-
-impl TrackerError {
-    pub fn from_surf_error(error: surf::Error) -> TrackerError {
-        match error.status() {
-            StatusCode::Forbidden => TrackerError::InvalidAuth,
-            StatusCode::Unauthorized => TrackerError::InvalidAuth,
-            StatusCode::NetworkAuthenticationRequired => TrackerError::InvalidAuth,
-            StatusCode::BadRequest => TrackerError::InvalidRequest,
-            StatusCode::BadGateway => TrackerError::ServiceUnavailable,
-            StatusCode::ServiceUnavailable => TrackerError::ServiceUnavailable,
-            StatusCode::NotFound => TrackerError::ServiceUnavailable,
-            _ => TrackerError::Unknown,
-        }
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
-pub struct LastFMConfig {
-    pub enabled: bool,
-    pub dango_api_key: String,
-    pub shared_secret: String,
-    pub session_key: String,
-}
-
-pub struct LastFM {
-    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 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),
-        };
-
-        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),
-        };
-
-        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!();
-    }
-}
-
-#[derive(Deserialize, Serialize)]
-struct AuthToken {
-    token: String,
-}
-
-#[derive(Deserialize, Serialize, Debug)]
-struct SessionResponse {
-    name: String,
-    key: String,
-    subscriber: i32,
-}
-
-#[derive(Deserialize, Serialize, Debug)]
-struct Session {
-    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 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
-        );
-
-        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> {
-        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}");
-
-        // 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)?;
-        Ok(session_response.session.key)
-    }
-
-    /// Creates a new LastFM struct with a given config
-    pub fn new(config: &LastFMConfig) -> LastFM {
-        
-        LastFM {
-            config: config.clone(),
-        }
-    }
-
-    // Creates an api request with the given parameters
-    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/";
-
-        
-
-        surf::post(url).body_string(string_params).await
-    }
-
-    // Returns an api signature as defined in the last.fm api documentation
-    fn request_sig(params: &BTreeMap<&str, &str>, shared_secret: &str) -> String {
-        let mut sig_string = String::new();
-        // Appends keys and values of parameters to the unhashed sig
-        for key in params.keys() {
-            let param_value = params.get(*key);
-            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);
-
-        hashed_sig
-    }
-
-    // Removes last.fm account from dango-music-player
-    pub fn reset_account() {
-        todo!();
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
-pub struct DiscordRPCConfig {
-    pub enabled: bool,
-    pub dango_client_id: u64,
-    pub dango_icon: String,
-}
-
-pub struct DiscordRPC {
-    config: DiscordRPCConfig,
-    pub client: discord_presence::client::Client,
-}
-
-impl DiscordRPC {
-    pub fn new(config: &DiscordRPCConfig) -> Self {
-        
-        DiscordRPC {
-            client: discord_presence::client::Client::new(config.dango_client_id),
-            config: config.clone(),
-        }
-    }
-}
-
-#[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(&Tag::Title) {
-            song_name
-        } else {
-            &unknown
-        };
-
-        // Sets album
-        let album = if let Some(album) = song.get_tag(&Tag::Album) {
-            album
-        } else {
-            &unknown
-        };
-
-        let _client_thread = self.client.start();
-
-        // Blocks thread execution until it has connected to local discord client
-        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;
-        // Sets discord account activity to current playing song
-        let send_activity = self.client.set_activity(|activity| {
-            activity
-                .state(album.to_string())
-                .details(song_name.to_string())
-                .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(());
-    }
-
-    async fn test_tracker(&mut self) -> Result<(), TrackerError> {
-        return Ok(());
-    }
-
-    async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
-        return Ok(0);
-    }
-}
-
-#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
-pub struct ListenBrainzConfig {
-    pub enabled: bool,
-    pub api_url: String,
-    pub auth_token: String,
-}
-
-pub struct ListenBrainz {
-    config: ListenBrainzConfig,
-}
-
-#[async_trait]
-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),
-        };
-        // Creates a json to submit a single song as defined in the listenbrainz documentation
-        let json_req = json!({
-            "listen_type": "playing_now",
-            "payload": [
-                {
-                    "track_metadata": {
-                        "artist_name": artist,
-                        "track_name": track,
-                    }
-                }
-            ]
-        });
-
-        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)),
-        };
-    }
-
-    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(&Tag::Artist), song.get_tag(&Tag::Title)) {
-            (Some(artist), Some(track)) => (artist, track),
-            _ => return Err(TrackerError::InvalidSong),
-        };
-
-        let json_req = json!({
-            "listen_type": "single",
-            "payload": [
-                {
-                    "listened_at": timestamp,
-                    "track_metadata": {
-                        "artist_name": artist,
-                        "track_name": track,
-                    }
-                }
-            ]
-        });
-
-        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)),
-        };
-    }
-    async fn test_tracker(&mut self) -> Result<(), TrackerError> {
-        todo!()
-    }
-    async fn get_times_tracked(&mut self, _song: &Song) -> Result<u32, TrackerError> {
-        todo!()
-    }
-}
-
-impl ListenBrainz {
-    pub fn new(config: &ListenBrainzConfig) -> Self {
-        ListenBrainz {
-            config: config.clone(),
-        }
-    }
-    // Makes an api request to configured url with given json
-    pub async fn api_request(
-        &self,
-        request: &str,
-        endpoint: &String,
-    ) -> Result<surf::Response, surf::Error> {
-
-        surf::post(format!("{}{}", &self.config.api_url, endpoint))
-            .body_string(request.to_owned())
-            .header("Authorization", format!("Token {}", self.config.auth_token))
-            .await
-    }
-}

From 6845d34ce7f6611647228c649445f2b871da3523 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Thu, 30 Nov 2023 02:32:06 -0600
Subject: [PATCH 031/136] Added enum for fields on `Song` structs, `Field`

---
 Cargo.toml                    |   4 +-
 src/music_storage/music_db.rs | 197 +++++++++++++++++++++++-----------
 src/music_storage/utils.rs    |   1 +
 3 files changed, 137 insertions(+), 65 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 90ec0d7..192d833 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,13 +7,13 @@ description = "A music backend that manages storage, querying, and playback of r
 homepage = "https://dangoware.com/dango-music-player"
 documentation = "https://docs.rs/dango-core"
 readme = "README.md"
-repository = "https://github.com/DangoWare/dango-music-player"
+repository = "https://github.com/Dangoware/dango-music-player"
 keywords = ["audio", "music"]
 categories = ["multimedia::audio"]
 
 [dependencies]
 file-format = { version = "0.22.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
-lofty = "0.17.0"
+lofty = "0.17.1"
 serde = { version = "1.0.191", features = ["derive"] }
 toml = "0.7.5"
 walkdir = "2.4.0"
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 2bc3bd9..17275ae 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -1,5 +1,5 @@
 // Crate things
-use super::utils::{normalize, read_library, write_library, find_images};
+use super::utils::{find_images, normalize, read_library, write_library};
 use crate::music_controller::config::Config;
 
 // Various std things
@@ -8,15 +8,15 @@ use std::error::Error;
 use std::ops::ControlFlow::{Break, Continue};
 
 // Files
-use rcue::parser::parse_from_file;
 use file_format::{FileFormat, Kind};
-use walkdir::WalkDir;
-use lofty::{AudioFile, ItemKey, ItemValue, Probe, TagType, TaggedFileExt, ParseOptions};
+use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
+use rcue::parser::parse_from_file;
 use std::fs;
 use std::path::{Path, PathBuf};
+use walkdir::WalkDir;
 
 // Time
-use chrono::{serde::ts_seconds_option, DateTime, Utc};
+use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
 use std::time::Duration;
 
 // Serialization/Compression
@@ -42,6 +42,8 @@ impl AlbumArt {
     }
 }
 
+/// A tag for a song
+#[non_exhaustive]
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
 pub enum Tag {
     Title,
@@ -73,6 +75,42 @@ impl ToString for Tag {
     }
 }
 
+/// A field within a Song struct
+pub enum Field {
+    Location(URI),
+    Plays(i32),
+    Skips(i32),
+    Favorited(bool),
+    Rating(u8),
+    Format(FileFormat),
+    Duration(Duration),
+    PlayTime(Duration),
+    LastPlayed(DateTime<Utc>),
+    DateAdded(DateTime<Utc>),
+    DateModified(DateTime<Utc>),
+}
+
+impl ToString for Field {
+    fn to_string(&self) -> String {
+        match self {
+            Self::Location(location) => location.to_string(),
+            Self::Plays(plays) => plays.to_string(),
+            Self::Skips(skips) => skips.to_string(),
+            Self::Favorited(fav) => fav.to_string(),
+            Self::Rating(rating) => rating.to_string(),
+            Self::Format(format) => match format.short_name() {
+                Some(name) => name.to_string(),
+                None => format.to_string()
+            },
+            Self::Duration(duration) => duration.as_millis().to_string(),
+            Self::PlayTime(time) => time.as_millis().to_string(),
+            Self::LastPlayed(last) => last.to_rfc2822(),
+            Self::DateAdded(added) => added.to_rfc2822(),
+            Self::DateModified(modified) => modified.to_rfc2822(),
+        }
+    }
+}
+
 /// Stores information about a single song
 #[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct Song {
@@ -84,43 +122,53 @@ pub struct Song {
     pub format: Option<FileFormat>,
     pub duration: Duration,
     pub play_time: Duration,
-    #[serde(with = "ts_seconds_option")]
+    #[serde(with = "ts_milliseconds_option")]
     pub last_played: Option<DateTime<Utc>>,
-    #[serde(with = "ts_seconds_option")]
+    #[serde(with = "ts_milliseconds_option")]
     pub date_added: Option<DateTime<Utc>>,
-    #[serde(with = "ts_seconds_option")]
+    #[serde(with = "ts_milliseconds_option")]
     pub date_modified: Option<DateTime<Utc>>,
     pub album_art: Vec<AlbumArt>,
     pub tags: BTreeMap<Tag, String>,
 }
 
 impl Song {
-    /**
-     * Get a tag's value
-     *
-     * ```
-     * // Assuming an already created song:
-     *
-     * let tag = this_song.get_tag(Tag::Title);
-     *
-     * assert_eq!(tag, "Some Song Title");
-     * ```
-     **/
+    /// Get a tag's value
+    ///
+    /// ```
+    /// use dango_core::music_storage::music_db::Tag;
+    /// // Assuming an already created song:
+    ///
+    /// let tag = this_song.get_tag(Tag::Title);
+    ///
+    /// assert_eq!(tag, "Some Song Title");
+    /// ```
     pub fn get_tag(&self, target_key: &Tag) -> Option<&String> {
         self.tags.get(target_key)
     }
 
-    pub fn get_field(&self, target_field: &str) -> Option<String> {
-        match target_field {
-            "location" => Some(self.location.clone().path_string()),
-            "plays" => Some(self.plays.clone().to_string()),
-            "format" => match self.format {
-                Some(format) => format.short_name().map(|short| short.to_string()),
-                None => None,
-            },
+    pub fn get_field(&self, target_field: &str) -> Option<Field> {
+        let lower_target = target_field.to_lowercase();
+        match lower_target.as_str() {
+            "location"  => Some(Field::Location(self.location.clone())),
+            "plays"     => Some(Field::Plays(self.plays)),
+            "skips"     => Some(Field::Skips(self.skips)),
+            "favorited" => Some(Field::Favorited(self.favorited)),
+            "rating"    => self.rating.map(Field::Rating),
+            "duration"  => Some(Field::Duration(self.duration)),
+            "play_time" => Some(Field::PlayTime(self.play_time)),
+            "format"    => self.format.map(Field::Format),
             _ => todo!(), // Other field types are not yet supported
         }
     }
+
+    pub fn set_tag(&mut self, target_key: Tag, new_value: String) {
+        self.tags.insert(target_key, new_value);
+    }
+
+    pub fn remove_tag(&mut self, target_key: &Tag) {
+        self.tags.remove(target_key);
+    }
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -172,8 +220,10 @@ impl URI {
             URI::Remote(_, location) => location,
         }
     }
+}
 
-    pub fn path_string(&self) -> String {
+impl ToString for URI {
+    fn to_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(),
@@ -302,12 +352,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 = self.library.par_iter().enumerate().try_for_each(|(i, track)| {
-            if path == &track.location {
-                return std::ops::ControlFlow::Break((track, i));
-            }
-            Continue(())
-        });
+        let result = self
+            .library
+            .par_iter()
+            .enumerate()
+            .try_for_each(|(i, track)| {
+                if path == &track.location {
+                    return std::ops::ControlFlow::Break((track, i));
+                }
+                Continue(())
+            });
 
         match result {
             Break(song) => Some(song),
@@ -435,7 +489,11 @@ impl MusicLibrary {
                 ItemKey::Comment => Tag::Comment,
                 ItemKey::AlbumTitle => Tag::Album,
                 ItemKey::DiscNumber => Tag::Disk,
-                ItemKey::Unknown(unknown) if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" => continue,
+                ItemKey::Unknown(unknown)
+                    if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" =>
+                {
+                    continue
+                }
                 ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()),
                 custom => Tag::Key(format!("{:?}", custom)),
             };
@@ -492,7 +550,7 @@ impl MusicLibrary {
             Ok(_) => (),
             Err(_) => {
                 //return Err(error)
-            },
+            }
         };
 
         Ok(())
@@ -516,7 +574,9 @@ impl MusicLibrary {
             }
 
             // Try to remove the original audio file from the db if it exists
-            if self.remove_uri(&URI::Local(audio_location.clone())).is_ok() { tracks_added -= 1 }
+            if self.remove_uri(&URI::Local(audio_location.clone())).is_ok() {
+                tracks_added -= 1
+            }
 
             let next_track = file.tracks.clone();
             let mut next_track = next_track.iter().skip(1);
@@ -538,19 +598,17 @@ impl MusicLibrary {
                 let duration = match next_track.next() {
                     Some(future) => match future.indices.get(0) {
                         Some(val) => val.1 - start,
-                        None => Duration::from_secs(0)
-                    }
-                    None => {
-                        match lofty::read_from_path(audio_location) {
+                        None => Duration::from_secs(0),
+                    },
+                    None => match lofty::read_from_path(audio_location) {
+                        Ok(tagged_file) => tagged_file.properties().duration() - start,
+
+                        Err(_) => match Probe::open(audio_location)?.read() {
                             Ok(tagged_file) => tagged_file.properties().duration() - start,
 
-                            Err(_) => match Probe::open(audio_location)?.read() {
-                                Ok(tagged_file) => tagged_file.properties().duration() - start,
-
-                                Err(_) => Duration::from_secs(0),
-                            },
-                        }
-                    }
+                            Err(_) => Duration::from_secs(0),
+                        },
+                    },
                 };
                 let end = start + duration + postgap;
 
@@ -563,11 +621,15 @@ impl MusicLibrary {
                 // Get some useful tags
                 let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
                 match album_title {
-                    Some(title) => {tags.insert(Tag::Album, title.clone());},
+                    Some(title) => {
+                        tags.insert(Tag::Album, title.clone());
+                    }
                     None => (),
                 }
                 match album_artist {
-                    Some(artist) => {tags.insert(Tag::Album, artist.clone());},
+                    Some(artist) => {
+                        tags.insert(Tag::Album, artist.clone());
+                    }
                     None => (),
                 }
                 tags.insert(Tag::Track, track.no.parse().unwrap_or((i + 1).to_string()));
@@ -625,7 +687,7 @@ impl MusicLibrary {
 
     pub fn add_song(&mut self, new_song: Song) -> Result<(), Box<dyn Error>> {
         if self.query_uri(&new_song.location).is_some() {
-            return Err(format!("URI already in database: {:?}", new_song.location).into())
+            return Err(format!("URI already in database: {:?}", new_song.location).into());
         }
 
         match new_song.location {
@@ -653,11 +715,17 @@ impl MusicLibrary {
     }
 
     /// Scan the song by a location and update its tags
-    pub fn update_uri(&mut self, target_uri: &URI, new_tags: Vec<Tag>) -> Result<(), Box<dyn std::error::Error>> {
-        match self.query_uri(target_uri) {
-            Some(_) => (),
+    pub fn update_uri(
+        &mut self,
+        target_uri: &URI,
+        new_tags: Vec<Tag>,
+    ) -> Result<(), Box<dyn std::error::Error>> {
+        let target_song = match self.query_uri(target_uri) {
+            Some(song) => song,
             None => return Err("URI not in database!".to_string().into()),
-        }
+        };
+
+        println!("{:?}", target_song.0.location);
 
         for tag in new_tags {
             println!("{:?}", tag);
@@ -673,6 +741,7 @@ impl MusicLibrary {
     ///
     /// Example:
     /// ```
+    /// use dango_core::music_storage::music_db::Tag;
     /// query_tracks(
     ///     &String::from("query"),
     ///     &vec![
@@ -702,7 +771,7 @@ impl MusicLibrary {
             for tag in target_tags {
                 let track_result = match tag {
                     Tag::Field(target) => match track.get_field(target) {
-                        Some(value) => value,
+                        Some(value) => value.to_string(),
                         None => continue,
                     },
                     _ => match track.get_tag(tag) {
@@ -723,7 +792,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;
                 }
@@ -738,7 +809,7 @@ impl MusicLibrary {
             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,
+                        Some(field_value) => field_value.to_string(),
                         None => continue,
                     },
                     _ => match a.get_tag(sort_option) {
@@ -749,7 +820,7 @@ impl MusicLibrary {
 
                 let tag_b = match sort_option {
                     Tag::Field(field_selection) => match b.get_field(field_selection) {
-                        Some(field_value) => field_value,
+                        Some(field_value) => field_value.to_string(),
                         None => continue,
                     },
                     _ => match b.get_tag(sort_option) {
@@ -768,8 +839,8 @@ impl MusicLibrary {
             }
 
             // If all tags are equal, sort by Track number
-            let path_a = PathBuf::from(a.get_field("location").unwrap());
-            let path_b = PathBuf::from(b.get_field("location").unwrap());
+            let path_a = PathBuf::from(a.get_field("location").unwrap().to_string());
+            let path_b = PathBuf::from(b.get_field("location").unwrap().to_string());
 
             path_a.file_name().cmp(&path_b.file_name())
         });
@@ -834,8 +905,8 @@ impl MusicLibrary {
                         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());
+                        let path_a = PathBuf::from(a.get_field("location").unwrap().to_string());
+                        let path_b = PathBuf::from(b.get_field("location").unwrap().to_string());
 
                         path_a.file_name().cmp(&path_b.file_name())
                     }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 0687db4..5c4865a 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -14,6 +14,7 @@ pub(super) fn normalize(input_string: &str) -> String {
 
     // Remove non alphanumeric characters
     normalized.retain(|c| c.is_alphanumeric());
+    normalized = normalized.to_ascii_lowercase();
 
     normalized
 }

From abfad7587d4e2360602fad186fc4344b4d4df956 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Thu, 30 Nov 2023 14:35:40 -0600
Subject: [PATCH 032/136] Updated tag parse error handling, fixes #19

---
 src/music_storage/music_db.rs | 33 ++++++++++++++++++---------------
 1 file changed, 18 insertions(+), 15 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 17275ae..47b1a9a 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -460,22 +460,27 @@ impl MusicLibrary {
     pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> {
         let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
 
-        // TODO: Fix error handling here
-        let tagged_file = match Probe::open(target_file)?.options(normal_options).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,
+        let tagged_file: lofty::TaggedFile;
+        let mut duration = Duration::from_secs(0);
+        let tag = match Probe::open(target_file)?.options(normal_options).read() {
+            Ok(file) => {
+                tagged_file = file;
 
-            None => match tagged_file.first_tag() {
-                Some(first_tag) => first_tag,
-                None => blank_tag,
+                duration = tagged_file.properties().duration();
+
+                // Ensure the tags exist, if not, insert blank data
+                match tagged_file.primary_tag() {
+                    Some(primary_tag) => primary_tag,
+
+                    None => match tagged_file.first_tag() {
+                        Some(first_tag) => first_tag,
+                        None => blank_tag,
+                    },
+                }
             },
+
+            Err(_) => blank_tag,
         };
 
         let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
@@ -525,8 +530,6 @@ impl MusicLibrary {
             Err(_) => None,
         };
 
-        let duration = tagged_file.properties().duration();
-
         // TODO: Fix error handling
         let binding = fs::canonicalize(target_file).unwrap();
 

From f9ca472db4e5beb8bda66d2bb52fd8163343121e Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Thu, 30 Nov 2023 14:38:54 -0600
Subject: [PATCH 033/136] Removed `.mid` from blocked extensions

---
 src/music_storage/music_db.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 47b1a9a..912070e 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -294,7 +294,7 @@ impl Album<'_> {
     }
 }
 
-const BLOCKED_EXTENSIONS: [&str; 5] = ["vob", "log", "txt", "sf2", "mid"];
+const BLOCKED_EXTENSIONS: [&str; 5] = ["vob", "log", "txt", "sf2"];
 
 #[derive(Debug)]
 pub struct MusicLibrary {

From c0d7f01b38ebf887f5431eb5c45fcd499649a5b2 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Thu, 30 Nov 2023 14:41:33 -0600
Subject: [PATCH 034/136] Fixed array length for blocked extensions

---
 src/music_storage/music_db.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 912070e..44939df 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -294,7 +294,7 @@ impl Album<'_> {
     }
 }
 
-const BLOCKED_EXTENSIONS: [&str; 5] = ["vob", "log", "txt", "sf2"];
+const BLOCKED_EXTENSIONS: [&str; 4] = ["vob", "log", "txt", "sf2"];
 
 #[derive(Debug)]
 pub struct MusicLibrary {

From 8071a90dfebf3069e4a622452222c9380e49b435 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Thu, 30 Nov 2023 23:34:09 -0600
Subject: [PATCH 035/136] Implemented GStreamer playback

---
 Cargo.toml                    |   2 +
 src/lib.rs                    |   2 +
 src/music_player.rs           | 177 ++++++++++++++++++++++++++++++++++
 src/music_storage/music_db.rs |  23 +++--
 4 files changed, 197 insertions(+), 7 deletions(-)
 create mode 100644 src/music_player.rs

diff --git a/Cargo.toml b/Cargo.toml
index 192d833..ba7c5fd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -25,3 +25,5 @@ log = "0.4"
 base64 = "0.21.5"
 snap = "1.1.0"
 rcue = "0.1.3"
+gstreamer = "0.21.2"
+glib = "0.18.3"
diff --git a/src/lib.rs b/src/lib.rs
index d6c8bb0..d98209c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,3 +8,5 @@ pub mod music_controller {
     pub mod config;
     pub mod controller;
 }
+
+pub mod music_player;
diff --git a/src/music_player.rs b/src/music_player.rs
new file mode 100644
index 0000000..6ec9670
--- /dev/null
+++ b/src/music_player.rs
@@ -0,0 +1,177 @@
+// Crate things
+use crate::music_controller::config::Config;
+use crate::music_storage::music_db::URI;
+use std::error::Error;
+use std::sync::mpsc::{Sender, self, Receiver};
+
+// GStreamer things
+use gst::{ClockTime, Element};
+use gstreamer as gst;
+use gstreamer::prelude::*;
+use glib::FlagsClass;
+
+// Time things
+use chrono::Duration;
+
+enum PlayerCmd {
+    Play,
+}
+
+/// An instance of a music player with a GStreamer backend
+pub struct Player {
+    source: Option<URI>,
+    events: Sender<PlayerCmd>,
+    playbin: Element,
+    position: Duration,
+    duration: Duration,
+    paused: bool,
+    volume: f64,
+    gapless: bool,
+}
+
+impl Default for Player {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+
+impl Player {
+    pub fn new() -> Self {
+        gst::init().unwrap();
+
+        let playbin = gst::ElementFactory::make("playbin")
+            .build()
+            .unwrap();
+
+        let flags = playbin.property_value("flags");
+        let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
+
+        let flags = flags_class
+            .builder_with_value(flags)
+            .unwrap()
+            .set_by_nick("audio")
+            .set_by_nick("download")
+            .unset_by_nick("video")
+            .unset_by_nick("text")
+            .build()
+            .unwrap();
+        playbin.set_property_from_value("flags", &flags);
+
+        playbin
+            .bus()
+            .expect("Failed to get GStreamer message bus");
+
+        let source = None;
+        let (tx, _): (Sender<PlayerCmd>, Receiver<PlayerCmd>) = mpsc::channel();
+        Self {
+            source,
+            events: tx,
+            playbin,
+            paused: false,
+            volume: 0.5,
+            gapless: false,
+            position: Duration::seconds(0),
+            duration: Duration::seconds(0),
+        }
+    }
+
+    pub fn enqueue_next(&mut self, next_track: URI) {
+        self.set_state(gst::State::Ready);
+
+        self.playbin.set_property("uri", next_track.as_uri());
+
+        self.play();
+    }
+
+
+    /// Set the playback volume, accepts a float from 0 to 1
+    pub fn set_volume(&mut self, volume: f64) {
+        self.volume = volume.clamp(0.0, 1.0);
+        self.set_gstreamer_volume(self.volume);
+    }
+
+    /// Set volume of the internal playbin player, can be
+    /// used to bypass the main volume control for seeking
+    fn set_gstreamer_volume(&mut self, volume: f64) {
+        self.playbin.set_property("volume", volume)
+    }
+
+    /// Returns the current volume level, a float from 0 to 1
+    pub fn volume(&mut self) -> f64 {
+        self.volume
+    }
+
+    fn set_state(&mut self, state: gst::State) {
+        self.playbin
+            .set_state(state)
+            .expect("Unable to set the pipeline state");
+    }
+
+    /// If the player is paused or stopped, starts playback
+    pub fn play(&mut self) {
+        self.set_state(gst::State::Playing);
+    }
+
+    /// Pause, if playing
+    pub fn pause(&mut self) {
+        self.paused = true;
+        self.set_state(gst::State::Paused);
+    }
+
+    /// Resume from being paused
+    pub fn resume(&mut self) {
+        self.paused = false;
+        self.set_state(gst::State::Playing);
+    }
+
+    /// Check if playback is paused
+    pub fn is_paused(&mut self) -> bool {
+        self.playbin.current_state() == gst::State::Paused
+    }
+
+    /// Set the playback URI
+    pub fn set_source(&mut self, source: URI) {
+        self.source = Some(source.clone());
+        self.playbin.set_property("uri", source.as_uri())
+    }
+
+    /// Get the current playback position of the player
+    pub fn position(&mut self) -> Option<Duration> {
+        self.playbin.query_position::<ClockTime>().map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
+    }
+
+    /// Get the duration of the currently playing track
+    pub fn duration(&mut self) -> Option<Duration> {
+        self.playbin.query_duration::<ClockTime>().map(|pos| Duration::milliseconds(pos.mseconds() as i64))
+    }
+
+    /// Seek relative to the current position
+    pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box<dyn Error>> {
+        let time_pos = match self.position() {
+            Some(pos) => pos,
+            None => return Err("No position".into())
+        };
+        let seek_pos = time_pos + seek_amount;
+
+        self.seek_to(seek_pos)?;
+        Ok(())
+    }
+
+    /// Seek absolutely
+    pub fn seek_to(&mut self, last_pos: Duration) -> Result<(), Box<dyn Error>> {
+        let duration = match self.duration() {
+            Some(dur) => dur,
+            None => return Err("No duration".into())
+        };
+        let seek_pos = last_pos.clamp(Duration::seconds(0), duration);
+
+        let seek_pos_clock = ClockTime::from_mseconds(seek_pos.num_milliseconds() as u64);
+        self.set_gstreamer_volume(0.0);
+        self
+            .playbin
+            .seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?;
+        self.set_gstreamer_volume(self.volume);
+        Ok(())
+    }
+}
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 44939df..21df4f5 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -180,7 +180,7 @@ pub enum URI {
         start: Duration,
         end: Duration,
     },
-    Remote(Service, PathBuf),
+    Remote(Service, String),
 }
 
 impl URI {
@@ -213,13 +213,22 @@ impl URI {
     }
 
     /// Returns the location as a PathBuf
-    pub fn path(&self) -> &PathBuf {
+    pub fn path(&self) -> PathBuf {
         match self {
-            URI::Local(location) => location,
-            URI::Cue { location, .. } => location,
-            URI::Remote(_, location) => location,
+            URI::Local(location) => location.clone(),
+            URI::Cue { location, .. } => location.clone(),
+            URI::Remote(_, location) => PathBuf::from(location),
         }
     }
+
+    pub fn as_uri(&self) -> String {
+        let path_str = match self {
+            URI::Local(location) => format!("file://{}", location.as_path().to_string_lossy()),
+            URI::Cue { location, .. } => format!("file://{}", location.as_path().to_string_lossy()),
+            URI::Remote(_, location) => location.clone(),
+        };
+        path_str.to_string()
+    }
 }
 
 impl ToString for URI {
@@ -227,7 +236,7 @@ impl ToString for URI {
         let path_str = match self {
             URI::Local(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(),
+            URI::Remote(_, location) => location.into(),
         };
         path_str.to_string()
     }
@@ -371,7 +380,7 @@ impl MusicLibrary {
 
     /// Queries for a [Song] by its [PathBuf], returning a `Vec<Song>`
     /// with matching `PathBuf`s
-    fn query_path(&self, path: &PathBuf) -> Option<Vec<&Song>> {
+    fn query_path(&self, path: PathBuf) -> Option<Vec<&Song>> {
         let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
         self.library.par_iter().for_each(|track| {
             if path == track.location.path() {

From 7dbed859d4a4d36ee09fd15f92af48ea838df6ab Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 2 Dec 2023 02:20:58 -0600
Subject: [PATCH 036/136] Gapless playback now possible, can play CUE tracks

---
 src/music_player.rs | 119 ++++++++++++++++++++++++++++----------------
 1 file changed, 75 insertions(+), 44 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index 6ec9670..5fbe4d3 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -1,52 +1,52 @@
 // Crate things
-use crate::music_controller::config::Config;
+//use crate::music_controller::config::Config;
 use crate::music_storage::music_db::URI;
 use std::error::Error;
-use std::sync::mpsc::{Sender, self, Receiver};
+use std::sync::{Arc, Mutex};
+use std::sync::mpsc::{self, Receiver, Sender};
 
 // GStreamer things
+use glib::{FlagsClass, MainContext};
 use gst::{ClockTime, Element};
 use gstreamer as gst;
 use gstreamer::prelude::*;
-use glib::FlagsClass;
 
 // Time things
 use chrono::Duration;
 
-enum PlayerCmd {
+#[derive(Debug)]
+pub enum PlayerCmd {
     Play,
+    Pause,
+    Eos,
+    AboutToFinish,
 }
 
 /// An instance of a music player with a GStreamer backend
 pub struct Player {
     source: Option<URI>,
-    events: Sender<PlayerCmd>,
+    //pub message_tx: Sender<PlayerCmd>,
+    pub message_rx: Receiver<PlayerCmd>,
     playbin: Element,
-    position: Duration,
-    duration: Duration,
     paused: bool,
     volume: f64,
+    start: Option<Duration>,
+    end: Option<Duration>,
+    position: Option<Duration>,
     gapless: bool,
 }
 
-impl Default for Player {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-
 impl Player {
     pub fn new() -> Self {
+        // Initialize GStreamer
         gst::init().unwrap();
 
-        let playbin = gst::ElementFactory::make("playbin")
-            .build()
-            .unwrap();
+        let playbin = gst::ElementFactory::make("playbin").build().unwrap();
 
         let flags = playbin.property_value("flags");
         let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
 
+        // Set up the Playbin flags to only play audio
         let flags = flags_class
             .builder_with_value(flags)
             .unwrap()
@@ -58,32 +58,55 @@ impl Player {
             .unwrap();
         playbin.set_property_from_value("flags", &flags);
 
-        playbin
-            .bus()
-            .expect("Failed to get GStreamer message bus");
+
+        let (message_tx, message_rx) = std::sync::mpsc::channel();
+        playbin.connect("about-to-finish", false, move |_| {
+                println!("test");
+                message_tx.send(PlayerCmd::AboutToFinish).unwrap();
+                None
+            });
 
         let source = None;
-        let (tx, _): (Sender<PlayerCmd>, Receiver<PlayerCmd>) = mpsc::channel();
         Self {
             source,
-            events: tx,
             playbin,
+            message_rx,
             paused: false,
             volume: 0.5,
             gapless: false,
-            position: Duration::seconds(0),
-            duration: Duration::seconds(0),
+            start: None,
+            end: None,
+            position: None,
         }
     }
 
+    pub fn source(&self) -> &Option<URI> {
+        &self.source
+    }
+
     pub fn enqueue_next(&mut self, next_track: URI) {
         self.set_state(gst::State::Ready);
 
-        self.playbin.set_property("uri", next_track.as_uri());
+        self.set_source(next_track);
 
         self.play();
     }
 
+    /// Set the playback URI
+    pub fn set_source(&mut self, source: URI) {
+        self.source = Some(source.clone());
+        match source {
+            URI::Cue {start, ..} => {
+                self.playbin.set_property("uri", source.as_uri());
+                self.play();
+                while self.state() != gst::State::Playing {
+                    std::thread::sleep(std::time::Duration::from_millis(10));
+                };
+                self.seek_to(Duration::from_std(start).unwrap()).unwrap();
+            }
+            _ => self.playbin.set_property("uri", source.as_uri()),
+        }
+    }
 
     /// Set the playback volume, accepts a float from 0 to 1
     pub fn set_volume(&mut self, volume: f64) {
@@ -130,27 +153,29 @@ impl Player {
         self.playbin.current_state() == gst::State::Paused
     }
 
-    /// Set the playback URI
-    pub fn set_source(&mut self, source: URI) {
-        self.source = Some(source.clone());
-        self.playbin.set_property("uri", source.as_uri())
-    }
-
     /// Get the current playback position of the player
     pub fn position(&mut self) -> Option<Duration> {
-        self.playbin.query_position::<ClockTime>().map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
+        self.position = self
+            .playbin
+            .query_position::<ClockTime>()
+            .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
+        self.position
     }
 
     /// Get the duration of the currently playing track
     pub fn duration(&mut self) -> Option<Duration> {
-        self.playbin.query_duration::<ClockTime>().map(|pos| Duration::milliseconds(pos.mseconds() as i64))
+        if self.end.is_some() && self.start.is_some() {
+            Some(self.end.unwrap() - self.start.unwrap())
+        } else {
+            None
+        }
     }
 
     /// Seek relative to the current position
     pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box<dyn Error>> {
         let time_pos = match self.position() {
             Some(pos) => pos,
-            None => return Err("No position".into())
+            None => return Err("No position".into()),
         };
         let seek_pos = time_pos + seek_amount;
 
@@ -159,19 +184,25 @@ impl Player {
     }
 
     /// Seek absolutely
-    pub fn seek_to(&mut self, last_pos: Duration) -> Result<(), Box<dyn Error>> {
-        let duration = match self.duration() {
-            Some(dur) => dur,
-            None => return Err("No duration".into())
-        };
-        let seek_pos = last_pos.clamp(Duration::seconds(0), duration);
-
-        let seek_pos_clock = ClockTime::from_mseconds(seek_pos.num_milliseconds() as u64);
+    pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
+        let seek_pos_clock = ClockTime::from_useconds(target_pos.num_microseconds().unwrap() as u64);
         self.set_gstreamer_volume(0.0);
-        self
-            .playbin
+        self.playbin
             .seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?;
         self.set_gstreamer_volume(self.volume);
         Ok(())
     }
+
+    pub fn state(&mut self) -> gst::State {
+        self.playbin.current_state()
+    }
+}
+
+impl Drop for Player {
+    /// Cleans up `GStreamer` pipeline when `Backend` is dropped.
+    fn drop(&mut self) {
+        self.playbin
+            .set_state(gst::State::Null)
+            .expect("Unable to set the pipeline to the `Null` state");
+    }
 }

From d12d65c518b985efedc9d8985791a07d01e632a2 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 4 Dec 2023 21:09:10 -0600
Subject: [PATCH 037/136] Fixed "about to finish" message sending improperly

---
 Cargo.toml                    |   2 +
 src/music_player.rs           | 174 +++++++++++++++++++++++++---------
 src/music_storage/music_db.rs |  13 ++-
 3 files changed, 142 insertions(+), 47 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index ba7c5fd..a7eb36f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,3 +27,5 @@ snap = "1.1.0"
 rcue = "0.1.3"
 gstreamer = "0.21.2"
 glib = "0.18.3"
+crossbeam-channel = "0.5.8"
+crossbeam = "0.8.2"
diff --git a/src/music_player.rs b/src/music_player.rs
index 5fbe4d3..2ffbdd7 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -2,8 +2,9 @@
 //use crate::music_controller::config::Config;
 use crate::music_storage::music_db::URI;
 use std::error::Error;
-use std::sync::{Arc, Mutex};
+use std::sync::{Arc, Mutex, RwLock};
 use std::sync::mpsc::{self, Receiver, Sender};
+use crossbeam_channel::bounded;
 
 // GStreamer things
 use glib::{FlagsClass, MainContext};
@@ -26,24 +27,32 @@ pub enum PlayerCmd {
 pub struct Player {
     source: Option<URI>,
     //pub message_tx: Sender<PlayerCmd>,
-    pub message_rx: Receiver<PlayerCmd>,
-    playbin: Element,
+    pub message_rx: crossbeam::channel::Receiver<PlayerCmd>,
+    playbin: Arc<RwLock<Element>>,
     paused: bool,
     volume: f64,
-    start: Option<Duration>,
-    end: Option<Duration>,
-    position: Option<Duration>,
+    start: Arc<RwLock<Option<Duration>>>,
+    end: Arc<RwLock<Option<Duration>>>,
+    pub position: Arc<RwLock<Option<Duration>>>,
     gapless: bool,
 }
 
+impl Default for Player {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
 impl Player {
     pub fn new() -> Self {
         // Initialize GStreamer
         gst::init().unwrap();
 
-        let playbin = gst::ElementFactory::make("playbin").build().unwrap();
+        let playbin_arc = Arc::new(RwLock::new(gst::ElementFactory::make("playbin3").build().unwrap()));
 
-        let flags = playbin.property_value("flags");
+        let playbin = playbin_arc.clone();
+
+        let flags = playbin.read().unwrap().property_value("flags");
         let flags_class = FlagsClass::with_type(flags.type_()).unwrap();
 
         // Set up the Playbin flags to only play audio
@@ -56,15 +65,61 @@ impl Player {
             .unset_by_nick("text")
             .build()
             .unwrap();
-        playbin.set_property_from_value("flags", &flags);
 
+        playbin.write().unwrap().set_property_from_value("flags", &flags);
 
-        let (message_tx, message_rx) = std::sync::mpsc::channel();
-        playbin.connect("about-to-finish", false, move |_| {
-                println!("test");
-                message_tx.send(PlayerCmd::AboutToFinish).unwrap();
-                None
-            });
+        let position = Arc::new(RwLock::new(None));
+        let start = Arc::new(RwLock::new(None));
+        let end: Arc<RwLock<Option<Duration>>> = Arc::new(RwLock::new(None));
+
+        let position_update = position.clone();
+        let start_update = start.clone();
+        let end_update = end.clone();
+        let (message_tx, message_rx) = bounded(1);
+        std::thread::spawn(move || {
+            loop {
+                let mut pos_temp = playbin_arc
+                    .read()
+                    .unwrap()
+                    .query_position::<ClockTime>()
+                    .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
+
+                if pos_temp.is_some()
+                    && start_update.read().unwrap().is_some()
+                    && end_update.read().unwrap().is_some()
+                {
+                    if let Some(time) = *start_update.read().unwrap() { pos_temp = Some(pos_temp.unwrap() - time) }
+
+                    let atf = end_update.read().unwrap().unwrap() - Duration::milliseconds(100);
+                    if pos_temp.unwrap() >= end_update.read().unwrap().unwrap() {
+                        message_tx.try_send(PlayerCmd::Eos).unwrap();
+                        playbin_arc
+                            .write()
+                            .unwrap()
+                            .set_state(gst::State::Ready)
+                            .expect("Unable to set the pipeline state");
+                        *start_update.write().unwrap() = None;
+                        *end_update.write().unwrap() = None;
+                    } else if pos_temp.unwrap() >= atf {
+                        match message_tx.try_send(PlayerCmd::AboutToFinish) {
+                            Ok(_) => println!("Sent ATF"),
+                            Err(err) => println!("{}", err),
+                        }
+                    }
+                }
+
+                *position_update.write().unwrap() = pos_temp;
+
+                std::thread::sleep(std::time::Duration::from_millis(50));
+            }
+        });
+
+        /*
+        playbin.read().unwrap().connect("about-to-finish", false, move |_| {
+            //message_tx.send(PlayerCmd::AboutToFinish).unwrap();
+            None
+        });
+        */
 
         let source = None;
         Self {
@@ -72,11 +127,11 @@ impl Player {
             playbin,
             message_rx,
             paused: false,
-            volume: 0.5,
+            volume: 1.0,
             gapless: false,
-            start: None,
-            end: None,
-            position: None,
+            start,
+            end,
+            position,
         }
     }
 
@@ -84,27 +139,48 @@ impl Player {
         &self.source
     }
 
-    pub fn enqueue_next(&mut self, next_track: URI) {
-        self.set_state(gst::State::Ready);
-
+    pub fn enqueue_next(&mut self, next_track: &URI) {
+        self.ready();
         self.set_source(next_track);
-
         self.play();
     }
 
     /// Set the playback URI
-    pub fn set_source(&mut self, source: URI) {
+    fn set_source(&mut self, source: &URI) {
         self.source = Some(source.clone());
         match source {
-            URI::Cue {start, ..} => {
-                self.playbin.set_property("uri", source.as_uri());
-                self.play();
-                while self.state() != gst::State::Playing {
-                    std::thread::sleep(std::time::Duration::from_millis(10));
+            URI::Cue {start, end, ..} => {
+                self.playbin.write().unwrap().set_property("uri", source.as_uri());
+
+                // Set the start and end positions of the CUE file
+                *self.start.write().unwrap() = Some(Duration::from_std(*start).unwrap());
+                *self.end.write().unwrap() = Some(Duration::from_std(*end).unwrap());
+
+                self.pause();
+
+                // Wait for it to be ready, and then move to the proper position
+                while self.playbin.read().unwrap().query_duration::<ClockTime>().is_none() {
+                    std::thread::sleep(std::time::Duration::from_millis(1));
                 };
-                self.seek_to(Duration::from_std(start).unwrap()).unwrap();
-            }
-            _ => self.playbin.set_property("uri", source.as_uri()),
+
+                self.seek_to(Duration::from_std(*start).unwrap()).unwrap();
+            },
+            _ => {
+                self.playbin.write().unwrap().set_property("uri", source.as_uri());
+
+                self.pause();
+
+                while self.playbin.read().unwrap().query_duration::<ClockTime>().is_none() {
+                    std::thread::sleep(std::time::Duration::from_millis(1));
+                };
+
+                *self.start.write().unwrap() = Some(Duration::seconds(0));
+                *self.end.write().unwrap() = self.playbin
+                    .read()
+                    .unwrap()
+                    .query_duration::<ClockTime>()
+                    .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
+            },
         }
     }
 
@@ -117,7 +193,7 @@ impl Player {
     /// Set volume of the internal playbin player, can be
     /// used to bypass the main volume control for seeking
     fn set_gstreamer_volume(&mut self, volume: f64) {
-        self.playbin.set_property("volume", volume)
+        self.playbin.write().unwrap().set_property("volume", volume)
     }
 
     /// Returns the current volume level, a float from 0 to 1
@@ -127,10 +203,16 @@ impl Player {
 
     fn set_state(&mut self, state: gst::State) {
         self.playbin
+            .write()
+            .unwrap()
             .set_state(state)
             .expect("Unable to set the pipeline state");
     }
 
+    pub fn ready(&mut self) {
+        self.set_state(gst::State::Ready)
+    }
+
     /// If the player is paused or stopped, starts playback
     pub fn play(&mut self) {
         self.set_state(gst::State::Playing);
@@ -150,30 +232,30 @@ impl Player {
 
     /// Check if playback is paused
     pub fn is_paused(&mut self) -> bool {
-        self.playbin.current_state() == gst::State::Paused
+        self.playbin.read().unwrap().current_state() == gst::State::Paused
     }
 
     /// Get the current playback position of the player
     pub fn position(&mut self) -> Option<Duration> {
-        self.position = self
-            .playbin
-            .query_position::<ClockTime>()
-            .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
-        self.position
+        *self.position.read().unwrap()
     }
 
     /// Get the duration of the currently playing track
     pub fn duration(&mut self) -> Option<Duration> {
-        if self.end.is_some() && self.start.is_some() {
-            Some(self.end.unwrap() - self.start.unwrap())
+        if self.end.read().unwrap().is_some() && self.start.read().unwrap().is_some() {
+            Some(self.end.read().unwrap().unwrap() - self.start.read().unwrap().unwrap())
         } else {
-            None
+            self.playbin
+                .read()
+                .unwrap()
+                .query_duration::<ClockTime>()
+                .map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
         }
     }
 
     /// Seek relative to the current position
     pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box<dyn Error>> {
-        let time_pos = match self.position() {
+        let time_pos = match *self.position.read().unwrap() {
             Some(pos) => pos,
             None => return Err("No position".into()),
         };
@@ -188,13 +270,15 @@ impl Player {
         let seek_pos_clock = ClockTime::from_useconds(target_pos.num_microseconds().unwrap() as u64);
         self.set_gstreamer_volume(0.0);
         self.playbin
+            .write()
+            .unwrap()
             .seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?;
         self.set_gstreamer_volume(self.volume);
         Ok(())
     }
 
     pub fn state(&mut self) -> gst::State {
-        self.playbin.current_state()
+        self.playbin.read().unwrap().current_state()
     }
 }
 
@@ -202,6 +286,8 @@ impl Drop for Player {
     /// Cleans up `GStreamer` pipeline when `Backend` is dropped.
     fn drop(&mut self) {
         self.playbin
+            .write()
+            .unwrap()
             .set_state(gst::State::Null)
             .expect("Unable to set the pipeline to the `Null` state");
     }
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 21df4f5..3faf165 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -76,6 +76,7 @@ impl ToString for Tag {
 }
 
 /// A field within a Song struct
+#[derive(Debug)]
 pub enum Field {
     Location(URI),
     Plays(i32),
@@ -399,7 +400,7 @@ impl MusicLibrary {
         &mut self,
         target_path: &str,
         config: &Config,
-    ) -> Result<usize, Box<dyn std::error::Error>> {
+    ) -> Result<i32, Box<dyn std::error::Error>> {
         let mut total = 0;
         let mut errors = 0;
         for target_file in WalkDir::new(target_path)
@@ -568,7 +569,7 @@ impl MusicLibrary {
         Ok(())
     }
 
-    pub fn add_cuesheet(&mut self, cuesheet: &Path) -> Result<usize, Box<dyn Error>> {
+    pub fn add_cuesheet(&mut self, cuesheet: &Path) -> Result<i32, Box<dyn Error>> {
         let mut tracks_added = 0;
 
         let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap();
@@ -602,7 +603,13 @@ impl MusicLibrary {
                     Some(postgap) => postgap,
                     None => Duration::from_secs(0),
                 };
-                let mut start = track.indices[0].1;
+
+                let mut start;
+                if track.indices.len() > 1 {
+                    start = track.indices[1].1;
+                } else {
+                    start = track.indices[0].1;
+                }
                 if !start.is_zero() {
                     start -= pregap;
                 }

From 38d4fe9bc8284a2e982025f1d29c8c25e4271bae Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 4 Dec 2023 22:09:51 -0600
Subject: [PATCH 038/136] Added more error handling

---
 src/music_player.rs | 29 +++++++++++++++--------------
 1 file changed, 15 insertions(+), 14 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index 2ffbdd7..af42728 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -140,9 +140,9 @@ impl Player {
     }
 
     pub fn enqueue_next(&mut self, next_track: &URI) {
-        self.ready();
+        self.ready().unwrap();
         self.set_source(next_track);
-        self.play();
+        self.play().unwrap();
     }
 
     /// Set the playback URI
@@ -156,7 +156,7 @@ impl Player {
                 *self.start.write().unwrap() = Some(Duration::from_std(*start).unwrap());
                 *self.end.write().unwrap() = Some(Duration::from_std(*end).unwrap());
 
-                self.pause();
+                self.pause().unwrap();
 
                 // Wait for it to be ready, and then move to the proper position
                 while self.playbin.read().unwrap().query_duration::<ClockTime>().is_none() {
@@ -168,7 +168,7 @@ impl Player {
             _ => {
                 self.playbin.write().unwrap().set_property("uri", source.as_uri());
 
-                self.pause();
+                self.pause().unwrap();
 
                 while self.playbin.read().unwrap().query_duration::<ClockTime>().is_none() {
                     std::thread::sleep(std::time::Duration::from_millis(1));
@@ -201,33 +201,34 @@ impl Player {
         self.volume
     }
 
-    fn set_state(&mut self, state: gst::State) {
+    fn set_state(&mut self, state: gst::State) -> Result<(), gst::StateChangeError> {
         self.playbin
             .write()
             .unwrap()
-            .set_state(state)
-            .expect("Unable to set the pipeline state");
+            .set_state(state)?;
+
+        Ok(())
     }
 
-    pub fn ready(&mut self) {
+    pub fn ready(&mut self) -> Result<(), gst::StateChangeError> {
         self.set_state(gst::State::Ready)
     }
 
     /// If the player is paused or stopped, starts playback
-    pub fn play(&mut self) {
-        self.set_state(gst::State::Playing);
+    pub fn play(&mut self) -> Result<(), gst::StateChangeError> {
+        self.set_state(gst::State::Playing)
     }
 
     /// Pause, if playing
-    pub fn pause(&mut self) {
+    pub fn pause(&mut self) -> Result<(), gst::StateChangeError> {
         self.paused = true;
-        self.set_state(gst::State::Paused);
+        self.set_state(gst::State::Paused)
     }
 
     /// Resume from being paused
-    pub fn resume(&mut self) {
+    pub fn resume(&mut self) -> Result<(), gst::StateChangeError> {
         self.paused = false;
-        self.set_state(gst::State::Playing);
+        self.set_state(gst::State::Playing)
     }
 
     /// Check if playback is paused

From 38b27c66c2e34730c20ccf1a50be35570be87032 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 4 Dec 2023 22:33:52 -0600
Subject: [PATCH 039/136] Fixed GStreamer URI generation

---
 src/music_storage/music_db.rs | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index 3faf165..f66c8df 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -14,6 +14,7 @@ use rcue::parser::parse_from_file;
 use std::fs;
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
+use glib::filename_to_uri;
 
 // Time
 use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
@@ -224,8 +225,8 @@ impl URI {
 
     pub fn as_uri(&self) -> String {
         let path_str = match self {
-            URI::Local(location) => format!("file://{}", location.as_path().to_string_lossy()),
-            URI::Cue { location, .. } => format!("file://{}", location.as_path().to_string_lossy()),
+            URI::Local(location) => filename_to_uri(location, None).expect("couldn't convert path to URI").to_string(),
+            URI::Cue { location, .. } => filename_to_uri(location, None).expect("couldn't convert path to URI").to_string(),
             URI::Remote(_, location) => location.clone(),
         };
         path_str.to_string()

From d87927a7db5fc6dc2cf153acb24222c2de520448 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 5 Dec 2023 08:19:10 -0600
Subject: [PATCH 040/136] Fixed an issue with CUE playback, fixed an issue with
 CUE file reading

The CUE artist was being inserted as the Album title
---
 src/music_player.rs           | 27 +++++++++++++++++----------
 src/music_storage/music_db.rs |  5 ++++-
 2 files changed, 21 insertions(+), 11 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index af42728..4f30286 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -73,8 +73,8 @@ impl Player {
         let end: Arc<RwLock<Option<Duration>>> = Arc::new(RwLock::new(None));
 
         let position_update = position.clone();
-        let start_update = start.clone();
-        let end_update = end.clone();
+        let start_update = Arc::clone(&start);
+        let end_update = Arc::clone(&end);
         let (message_tx, message_rx) = bounded(1);
         std::thread::spawn(move || {
             loop {
@@ -88,8 +88,6 @@ impl Player {
                     && start_update.read().unwrap().is_some()
                     && end_update.read().unwrap().is_some()
                 {
-                    if let Some(time) = *start_update.read().unwrap() { pos_temp = Some(pos_temp.unwrap() - time) }
-
                     let atf = end_update.read().unwrap().unwrap() - Duration::milliseconds(100);
                     if pos_temp.unwrap() >= end_update.read().unwrap().unwrap() {
                         message_tx.try_send(PlayerCmd::Eos).unwrap();
@@ -102,10 +100,16 @@ impl Player {
                         *end_update.write().unwrap() = None;
                     } else if pos_temp.unwrap() >= atf {
                         match message_tx.try_send(PlayerCmd::AboutToFinish) {
-                            Ok(_) => println!("Sent ATF"),
-                            Err(err) => println!("{}", err),
+                            Ok(_) => (),
+                            Err(_) => (),
                         }
                     }
+
+                    // This has to be done AFTER the current time in the file
+                    // is calculated, or everything else is wrong
+                    if let Some(time) = *start_update.read().unwrap() {
+                        pos_temp = Some(pos_temp.unwrap() - time)
+                    }
                 }
 
                 *position_update.write().unwrap() = pos_temp;
@@ -159,11 +163,14 @@ impl Player {
                 self.pause().unwrap();
 
                 // Wait for it to be ready, and then move to the proper position
-                while self.playbin.read().unwrap().query_duration::<ClockTime>().is_none() {
+                let now = std::time::Instant::now();
+                while now.elapsed() < std::time::Duration::from_millis(20) {
+                    if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
+                        return;
+                    }
                     std::thread::sleep(std::time::Duration::from_millis(1));
-                };
-
-                self.seek_to(Duration::from_std(*start).unwrap()).unwrap();
+                }
+                panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
             },
             _ => {
                 self.playbin.write().unwrap().set_property("uri", source.as_uri());
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index f66c8df..ec56547 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -149,6 +149,7 @@ impl Song {
         self.tags.get(target_key)
     }
 
+    /// Gets an internal field from a song
     pub fn get_field(&self, target_field: &str) -> Option<Field> {
         let lower_target = target_field.to_lowercase();
         match lower_target.as_str() {
@@ -164,10 +165,12 @@ impl Song {
         }
     }
 
+    /// Sets the value of a tag in the song
     pub fn set_tag(&mut self, target_key: Tag, new_value: String) {
         self.tags.insert(target_key, new_value);
     }
 
+    /// Deletes a tag from the song
     pub fn remove_tag(&mut self, target_key: &Tag) {
         self.tags.remove(target_key);
     }
@@ -648,7 +651,7 @@ impl MusicLibrary {
                 }
                 match album_artist {
                     Some(artist) => {
-                        tags.insert(Tag::Album, artist.clone());
+                        tags.insert(Tag::Artist, artist.clone());
                     }
                     None => (),
                 }

From f392e6a0af7afa63c94e509718496bf0d125db42 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 5 Dec 2023 12:59:52 -0600
Subject: [PATCH 041/136] Gapless playback works

---
 src/music_player.rs | 98 ++++++++++++++++++++++++---------------------
 1 file changed, 53 insertions(+), 45 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index 4f30286..929332f 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -1,13 +1,12 @@
 // Crate things
 //use crate::music_controller::config::Config;
 use crate::music_storage::music_db::URI;
-use std::error::Error;
-use std::sync::{Arc, Mutex, RwLock};
-use std::sync::mpsc::{self, Receiver, Sender};
 use crossbeam_channel::bounded;
+use std::error::Error;
+use std::sync::{Arc, RwLock};
 
 // GStreamer things
-use glib::{FlagsClass, MainContext};
+use glib::FlagsClass;
 use gst::{ClockTime, Element};
 use gstreamer as gst;
 use gstreamer::prelude::*;
@@ -34,7 +33,6 @@ pub struct Player {
     start: Arc<RwLock<Option<Duration>>>,
     end: Arc<RwLock<Option<Duration>>>,
     pub position: Arc<RwLock<Option<Duration>>>,
-    gapless: bool,
 }
 
 impl Default for Player {
@@ -48,7 +46,9 @@ impl Player {
         // Initialize GStreamer
         gst::init().unwrap();
 
-        let playbin_arc = Arc::new(RwLock::new(gst::ElementFactory::make("playbin3").build().unwrap()));
+        let playbin_arc = Arc::new(RwLock::new(
+            gst::ElementFactory::make("playbin3").build().unwrap(),
+        ));
 
         let playbin = playbin_arc.clone();
 
@@ -66,7 +66,12 @@ impl Player {
             .build()
             .unwrap();
 
-        playbin.write().unwrap().set_property_from_value("flags", &flags);
+        playbin
+            .write()
+            .unwrap()
+            .set_property_from_value("flags", &flags);
+
+        playbin.write().unwrap().set_property("instant-uri", true);
 
         let position = Arc::new(RwLock::new(None));
         let start = Arc::new(RwLock::new(None));
@@ -88,7 +93,7 @@ impl Player {
                     && start_update.read().unwrap().is_some()
                     && end_update.read().unwrap().is_some()
                 {
-                    let atf = end_update.read().unwrap().unwrap() - Duration::milliseconds(100);
+                    let atf = end_update.read().unwrap().unwrap() - Duration::milliseconds(250);
                     if pos_temp.unwrap() >= end_update.read().unwrap().unwrap() {
                         message_tx.try_send(PlayerCmd::Eos).unwrap();
                         playbin_arc
@@ -99,10 +104,7 @@ impl Player {
                         *start_update.write().unwrap() = None;
                         *end_update.write().unwrap() = None;
                     } else if pos_temp.unwrap() >= atf {
-                        match message_tx.try_send(PlayerCmd::AboutToFinish) {
-                            Ok(_) => (),
-                            Err(_) => (),
-                        }
+                        let _ = message_tx.try_send(PlayerCmd::AboutToFinish);
                     }
 
                     // This has to be done AFTER the current time in the file
@@ -118,13 +120,6 @@ impl Player {
             }
         });
 
-        /*
-        playbin.read().unwrap().connect("about-to-finish", false, move |_| {
-            //message_tx.send(PlayerCmd::AboutToFinish).unwrap();
-            None
-        });
-        */
-
         let source = None;
         Self {
             source,
@@ -132,7 +127,6 @@ impl Player {
             message_rx,
             paused: false,
             volume: 1.0,
-            gapless: false,
             start,
             end,
             position,
@@ -144,23 +138,22 @@ impl Player {
     }
 
     pub fn enqueue_next(&mut self, next_track: &URI) {
-        self.ready().unwrap();
         self.set_source(next_track);
-        self.play().unwrap();
     }
 
     /// Set the playback URI
     fn set_source(&mut self, source: &URI) {
+        let uri = self.playbin.read().unwrap().property_value("current-uri");
         self.source = Some(source.clone());
         match source {
-            URI::Cue {start, end, ..} => {
+            URI::Cue { start, end, .. } => {
                 self.playbin.write().unwrap().set_property("uri", source.as_uri());
 
                 // Set the start and end positions of the CUE file
                 *self.start.write().unwrap() = Some(Duration::from_std(*start).unwrap());
                 *self.end.write().unwrap() = Some(Duration::from_std(*end).unwrap());
 
-                self.pause().unwrap();
+                self.play().unwrap();
 
                 // Wait for it to be ready, and then move to the proper position
                 let now = std::time::Instant::now();
@@ -171,23 +164,21 @@ impl Player {
                     std::thread::sleep(std::time::Duration::from_millis(1));
                 }
                 panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
-            },
+            }
             _ => {
                 self.playbin.write().unwrap().set_property("uri", source.as_uri());
 
-                self.pause().unwrap();
+                self.play().unwrap();
 
-                while self.playbin.read().unwrap().query_duration::<ClockTime>().is_none() {
-                    std::thread::sleep(std::time::Duration::from_millis(1));
-                };
+                while uri.get::<&str>().unwrap_or("") == self.property("current-uri").get::<&str>().unwrap_or("")
+                    || self.raw_duration().is_none()
+                {
+                    std::thread::sleep(std::time::Duration::from_millis(10));
+                }
 
                 *self.start.write().unwrap() = Some(Duration::seconds(0));
-                *self.end.write().unwrap() = self.playbin
-                    .read()
-                    .unwrap()
-                    .query_duration::<ClockTime>()
-                    .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
-            },
+                *self.end.write().unwrap() = self.raw_duration();
+            }
         }
     }
 
@@ -209,10 +200,7 @@ impl Player {
     }
 
     fn set_state(&mut self, state: gst::State) -> Result<(), gst::StateChangeError> {
-        self.playbin
-            .write()
-            .unwrap()
-            .set_state(state)?;
+        self.playbin.write().unwrap().set_state(state)?;
 
         Ok(())
     }
@@ -253,14 +241,18 @@ impl Player {
         if self.end.read().unwrap().is_some() && self.start.read().unwrap().is_some() {
             Some(self.end.read().unwrap().unwrap() - self.start.read().unwrap().unwrap())
         } else {
-            self.playbin
-                .read()
-                .unwrap()
-                .query_duration::<ClockTime>()
-                .map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
+            self.raw_duration()
         }
     }
 
+    pub fn raw_duration(&self) -> Option<Duration> {
+        self.playbin
+            .read()
+            .unwrap()
+            .query_duration::<ClockTime>()
+            .map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
+    }
+
     /// Seek relative to the current position
     pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box<dyn Error>> {
         let time_pos = match *self.position.read().unwrap() {
@@ -275,7 +267,19 @@ impl Player {
 
     /// Seek absolutely
     pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
-        let seek_pos_clock = ClockTime::from_useconds(target_pos.num_microseconds().unwrap() as u64);
+        if self.start.read().unwrap().is_none() {
+            return Err("Failed to seek: No START time".into());
+        }
+
+        if self.end.read().unwrap().is_none() {
+            return Err("Failed to seek: No END time".into());
+        }
+
+        let clamped_target = target_pos.clamp(self.start.read().unwrap().unwrap(), self.end.read().unwrap().unwrap());
+
+        let seek_pos_clock =
+            ClockTime::from_useconds(clamped_target.num_microseconds().unwrap() as u64);
+
         self.set_gstreamer_volume(0.0);
         self.playbin
             .write()
@@ -288,6 +292,10 @@ impl Player {
     pub fn state(&mut self) -> gst::State {
         self.playbin.read().unwrap().current_state()
     }
+
+    pub fn property(&self, property: &str) -> glib::Value  {
+        self.playbin.read().unwrap().property_value(property)
+    }
 }
 
 impl Drop for Player {

From 5c2b718d79dd706a9f2fe92085cc60206ac587ed Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 5 Dec 2023 13:26:47 -0600
Subject: [PATCH 042/136] Indefinite network streams are now properly queued

---
 src/music_player.rs | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index 929332f..0bae684 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -171,7 +171,7 @@ impl Player {
                 self.play().unwrap();
 
                 while uri.get::<&str>().unwrap_or("") == self.property("current-uri").get::<&str>().unwrap_or("")
-                    || self.raw_duration().is_none()
+                    || self.position().is_none()
                 {
                     std::thread::sleep(std::time::Duration::from_millis(10));
                 }
@@ -267,15 +267,22 @@ impl Player {
 
     /// Seek absolutely
     pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
+        let start;
         if self.start.read().unwrap().is_none() {
             return Err("Failed to seek: No START time".into());
+        } else {
+            start = self.start.read().unwrap().unwrap();
         }
 
+        let end;
         if self.end.read().unwrap().is_none() {
             return Err("Failed to seek: No END time".into());
+        } else {
+            end = self.end.read().unwrap().unwrap();
         }
 
-        let clamped_target = target_pos.clamp(self.start.read().unwrap().unwrap(), self.end.read().unwrap().unwrap());
+        let adjusted_target = target_pos + start;
+        let clamped_target = adjusted_target.clamp(start, end);
 
         let seek_pos_clock =
             ClockTime::from_useconds(clamped_target.num_microseconds().unwrap() as u64);

From 17c2745cdd366cb17657fe257344fb14c56029ba Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Thu, 7 Dec 2023 15:14:03 -0600
Subject: [PATCH 043/136] Added buffering and bus message watching

---
 src/music_player.rs | 132 ++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 121 insertions(+), 11 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index 0bae684..647e470 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -1,6 +1,6 @@
 // Crate things
 //use crate::music_controller::config::Config;
-use crate::music_storage::music_db::URI;
+use crate::music_storage::music_db::{URI, Tag};
 use crossbeam_channel::bounded;
 use std::error::Error;
 use std::sync::{Arc, RwLock};
@@ -22,17 +22,55 @@ pub enum PlayerCmd {
     AboutToFinish,
 }
 
+#[derive(Debug)]
+pub enum PlayerState {
+    Playing,
+    Paused,
+    Ready,
+    Buffering(u8),
+    Null,
+    VoidPending,
+}
+
+impl From<gst::State> for PlayerState {
+    fn from(value: gst::State) -> Self {
+        match value {
+            gst::State::VoidPending => Self::VoidPending,
+            gst::State::Playing => Self::Playing,
+            gst::State::Paused => Self::Paused,
+            gst::State::Ready => Self::Ready,
+            gst::State::Null => Self::Null,
+        }
+    }
+}
+
+impl TryInto<gst::State> for PlayerState {
+    fn try_into(self) -> Result<gst::State, Box<dyn Error>> {
+        match self {
+            Self::VoidPending => Ok(gst::State::VoidPending),
+            Self::Playing =>  Ok(gst::State::Playing),
+            Self::Paused => Ok(gst::State::Paused),
+            Self::Ready => Ok(gst::State::Ready),
+            Self::Null => Ok(gst::State::Null),
+            state => Err(format!("Invalid gst::State: {:?}", state).into())
+        }
+    }
+
+    type Error = Box<dyn Error>;
+}
+
 /// An instance of a music player with a GStreamer backend
 pub struct Player {
     source: Option<URI>,
     //pub message_tx: Sender<PlayerCmd>,
     pub message_rx: crossbeam::channel::Receiver<PlayerCmd>,
     playbin: Arc<RwLock<Element>>,
-    paused: bool,
     volume: f64,
     start: Arc<RwLock<Option<Duration>>>,
     end: Arc<RwLock<Option<Duration>>>,
-    pub position: Arc<RwLock<Option<Duration>>>,
+    position: Arc<RwLock<Option<Duration>>>,
+    buffer: Arc<RwLock<Option<u8>>>,
+    paused: Arc<RwLock<bool>>,
 }
 
 impl Default for Player {
@@ -45,6 +83,9 @@ impl Player {
     pub fn new() -> Self {
         // Initialize GStreamer
         gst::init().unwrap();
+        let ctx = glib::MainContext::default();
+        let _guard = ctx.acquire();
+        let mainloop = glib::MainLoop::new(Some(&ctx), false);
 
         let playbin_arc = Arc::new(RwLock::new(
             gst::ElementFactory::make("playbin3").build().unwrap(),
@@ -76,11 +117,14 @@ impl Player {
         let position = Arc::new(RwLock::new(None));
         let start = Arc::new(RwLock::new(None));
         let end: Arc<RwLock<Option<Duration>>> = Arc::new(RwLock::new(None));
+        let buffer = Arc::new(RwLock::new(None));
+        let paused = Arc::new(RwLock::new(false));
 
+        // Set up the thread to monitor the position
         let position_update = position.clone();
         let start_update = Arc::clone(&start);
         let end_update = Arc::clone(&end);
-        let (message_tx, message_rx) = bounded(1);
+        let (message_tx, message_rx) = bounded(1); //TODO: Maybe figure out a better method than making this bounded
         std::thread::spawn(move || {
             loop {
                 let mut pos_temp = playbin_arc
@@ -95,7 +139,7 @@ impl Player {
                 {
                     let atf = end_update.read().unwrap().unwrap() - Duration::milliseconds(250);
                     if pos_temp.unwrap() >= end_update.read().unwrap().unwrap() {
-                        message_tx.try_send(PlayerCmd::Eos).unwrap();
+                        let _ = message_tx.try_send(PlayerCmd::Eos);
                         playbin_arc
                             .write()
                             .unwrap()
@@ -114,22 +158,70 @@ impl Player {
                     }
                 }
 
+                //println!("{:?}", pos_temp);
+
                 *position_update.write().unwrap() = pos_temp;
 
-                std::thread::sleep(std::time::Duration::from_millis(50));
+                std::thread::sleep(std::time::Duration::from_millis(100));
             }
         });
 
+        // Set up the thread to monitor bus messages
+        let playbin_bus_ctrl = Arc::clone(&playbin);
+        let buffer_bus_ctrl = Arc::clone(&buffer);
+        let paused_bus_ctrl = Arc::clone(&paused);
+        let bus_watch = playbin
+            .read()
+            .unwrap()
+            .bus()
+            .expect("Failed to get GStreamer message bus")
+            .add_watch(move |_bus, msg| {
+                match msg.view() {
+                    gst::MessageView::Eos(_) => {},
+                    gst::MessageView::StreamStart(_) => {},
+                    gst::MessageView::Error(e) =>
+                        println!("song {}", e.error()),
+                    gst::MessageView::Tag(tag) => {
+                        if let Some(title) = tag.tags().get::<gst::tags::Title>() {
+                            println!("  Title: {}", title.get());
+                        }
+                        if let Some(album) = tag.tags().get::<gst::tags::Album>() {
+                            println!("  Album: {}", album.get());
+                        }
+                    }
+                    gst::MessageView::Buffering(buffering) => {
+                        let percent = buffering.percent();
+                        if percent < 100 {
+                            *buffer_bus_ctrl.write().unwrap() = Some(percent as u8);
+                            playbin_bus_ctrl.write().unwrap().set_state(gst::State::Paused).unwrap();
+                        } else if *paused_bus_ctrl.read().unwrap() == false {
+                            *buffer_bus_ctrl.write().unwrap() = None;
+                            playbin_bus_ctrl.write().unwrap().set_state(gst::State::Playing).unwrap();
+                        }
+                    }
+                    _ => (),
+                }
+                glib::ControlFlow::Continue
+            })
+            .expect("Failed to connect to GStreamer message bus");
+
+        // Set up a thread to watch the messages
+        std::thread::spawn(move || {
+            let _watch = bus_watch;
+            mainloop.run()
+        });
+
         let source = None;
         Self {
             source,
             playbin,
             message_rx,
-            paused: false,
             volume: 1.0,
             start,
             end,
+            paused,
             position,
+            buffer,
         }
     }
 
@@ -216,13 +308,13 @@ impl Player {
 
     /// Pause, if playing
     pub fn pause(&mut self) -> Result<(), gst::StateChangeError> {
-        self.paused = true;
+        *self.paused.write().unwrap() = true;
         self.set_state(gst::State::Paused)
     }
 
     /// Resume from being paused
     pub fn resume(&mut self) -> Result<(), gst::StateChangeError> {
-        self.paused = false;
+        *self.paused.write().unwrap() = false;
         self.set_state(gst::State::Playing)
     }
 
@@ -296,13 +388,31 @@ impl Player {
         Ok(())
     }
 
-    pub fn state(&mut self) -> gst::State {
-        self.playbin.read().unwrap().current_state()
+    /// Get the current state of the playback
+    pub fn state(&mut self) -> PlayerState {
+        match *self.buffer.read().unwrap() {
+            None => self.playbin.read().unwrap().current_state().into(),
+            Some(value) => {
+                PlayerState::Buffering(value)
+            }
+        }
     }
 
     pub fn property(&self, property: &str) -> glib::Value  {
         self.playbin.read().unwrap().property_value(property)
     }
+
+    /// Stop the playback entirely
+    pub fn stop(&mut self) -> Result<(), gst::StateChangeError> {
+        self.pause()?;
+        self.ready()?;
+
+        // Set all positions to none
+        *self.position.write().unwrap() = None;
+        *self.start.write().unwrap() = None;
+        *self.end.write().unwrap() = None;
+        Ok(())
+    }
 }
 
 impl Drop for Player {

From b2e7367795e1df6f3e91d944656d95b5040a57df Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 8 Dec 2023 00:37:19 -0600
Subject: [PATCH 044/136] Ran `cargo fmt`

---
 src/music_controller/controller.rs | 10 ++----
 src/music_player.rs                | 57 +++++++++++++++++++++---------
 src/music_storage/music_db.rs      | 26 ++++++++------
 src/music_storage/utils.rs         | 11 +++---
 4 files changed, 64 insertions(+), 40 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 6f4881a..333db0b 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -18,10 +18,7 @@ impl MusicController {
             Err(error) => return Err(error),
         };
 
-        let controller = MusicController {
-            config,
-            library,
-        };
+        let controller = MusicController { config, library };
 
         Ok(controller)
     }
@@ -34,10 +31,7 @@ impl MusicController {
             Err(error) => return Err(error),
         };
 
-        let controller = MusicController {
-            config,
-            library,
-        };
+        let controller = MusicController { config, library };
 
         Ok(controller)
     }
diff --git a/src/music_player.rs b/src/music_player.rs
index 647e470..0c9b2d6 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -1,6 +1,6 @@
 // Crate things
 //use crate::music_controller::config::Config;
-use crate::music_storage::music_db::{URI, Tag};
+use crate::music_storage::music_db::{Tag, URI};
 use crossbeam_channel::bounded;
 use std::error::Error;
 use std::sync::{Arc, RwLock};
@@ -48,11 +48,11 @@ impl TryInto<gst::State> for PlayerState {
     fn try_into(self) -> Result<gst::State, Box<dyn Error>> {
         match self {
             Self::VoidPending => Ok(gst::State::VoidPending),
-            Self::Playing =>  Ok(gst::State::Playing),
+            Self::Playing => Ok(gst::State::Playing),
             Self::Paused => Ok(gst::State::Paused),
             Self::Ready => Ok(gst::State::Ready),
             Self::Null => Ok(gst::State::Null),
-            state => Err(format!("Invalid gst::State: {:?}", state).into())
+            state => Err(format!("Invalid gst::State: {:?}", state).into()),
         }
     }
 
@@ -177,10 +177,22 @@ impl Player {
             .expect("Failed to get GStreamer message bus")
             .add_watch(move |_bus, msg| {
                 match msg.view() {
-                    gst::MessageView::Eos(_) => {},
-                    gst::MessageView::StreamStart(_) => {},
-                    gst::MessageView::Error(e) =>
-                        println!("song {}", e.error()),
+                    gst::MessageView::Eos(_) => {}
+                    gst::MessageView::StreamStart(_) => println!("Stream start"),
+                    gst::MessageView::Error(e) => {
+                        println!("ERROR: {}", e.error());
+                        playbin_bus_ctrl
+                            .write()
+                            .unwrap()
+                            .set_state(gst::State::Ready)
+                            .unwrap();
+
+                        playbin_bus_ctrl
+                            .write()
+                            .unwrap()
+                            .set_state(gst::State::Playing)
+                            .unwrap();
+                    },
                     gst::MessageView::Tag(tag) => {
                         if let Some(title) = tag.tags().get::<gst::tags::Title>() {
                             println!("  Title: {}", title.get());
@@ -193,10 +205,18 @@ impl Player {
                         let percent = buffering.percent();
                         if percent < 100 {
                             *buffer_bus_ctrl.write().unwrap() = Some(percent as u8);
-                            playbin_bus_ctrl.write().unwrap().set_state(gst::State::Paused).unwrap();
+                            playbin_bus_ctrl
+                                .write()
+                                .unwrap()
+                                .set_state(gst::State::Paused)
+                                .unwrap();
                         } else if *paused_bus_ctrl.read().unwrap() == false {
                             *buffer_bus_ctrl.write().unwrap() = None;
-                            playbin_bus_ctrl.write().unwrap().set_state(gst::State::Playing).unwrap();
+                            playbin_bus_ctrl
+                                .write()
+                                .unwrap()
+                                .set_state(gst::State::Playing)
+                                .unwrap();
                         }
                     }
                     _ => (),
@@ -239,7 +259,10 @@ impl Player {
         self.source = Some(source.clone());
         match source {
             URI::Cue { start, end, .. } => {
-                self.playbin.write().unwrap().set_property("uri", source.as_uri());
+                self.playbin
+                    .write()
+                    .unwrap()
+                    .set_property("uri", source.as_uri());
 
                 // Set the start and end positions of the CUE file
                 *self.start.write().unwrap() = Some(Duration::from_std(*start).unwrap());
@@ -258,11 +281,15 @@ impl Player {
                 panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
             }
             _ => {
-                self.playbin.write().unwrap().set_property("uri", source.as_uri());
+                self.playbin
+                    .write()
+                    .unwrap()
+                    .set_property("uri", source.as_uri());
 
                 self.play().unwrap();
 
-                while uri.get::<&str>().unwrap_or("") == self.property("current-uri").get::<&str>().unwrap_or("")
+                while uri.get::<&str>().unwrap_or("")
+                    == self.property("current-uri").get::<&str>().unwrap_or("")
                     || self.position().is_none()
                 {
                     std::thread::sleep(std::time::Duration::from_millis(10));
@@ -392,13 +419,11 @@ impl Player {
     pub fn state(&mut self) -> PlayerState {
         match *self.buffer.read().unwrap() {
             None => self.playbin.read().unwrap().current_state().into(),
-            Some(value) => {
-                PlayerState::Buffering(value)
-            }
+            Some(value) => PlayerState::Buffering(value),
         }
     }
 
-    pub fn property(&self, property: &str) -> glib::Value  {
+    pub fn property(&self, property: &str) -> glib::Value {
         self.playbin.read().unwrap().property_value(property)
     }
 
diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs
index ec56547..41d2e22 100644
--- a/src/music_storage/music_db.rs
+++ b/src/music_storage/music_db.rs
@@ -9,12 +9,12 @@ use std::ops::ControlFlow::{Break, Continue};
 
 // Files
 use file_format::{FileFormat, Kind};
+use glib::filename_to_uri;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
 use std::fs;
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
-use glib::filename_to_uri;
 
 // Time
 use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
@@ -102,7 +102,7 @@ impl ToString for Field {
             Self::Rating(rating) => rating.to_string(),
             Self::Format(format) => match format.short_name() {
                 Some(name) => name.to_string(),
-                None => format.to_string()
+                None => format.to_string(),
             },
             Self::Duration(duration) => duration.as_millis().to_string(),
             Self::PlayTime(time) => time.as_millis().to_string(),
@@ -153,14 +153,14 @@ impl Song {
     pub fn get_field(&self, target_field: &str) -> Option<Field> {
         let lower_target = target_field.to_lowercase();
         match lower_target.as_str() {
-            "location"  => Some(Field::Location(self.location.clone())),
-            "plays"     => Some(Field::Plays(self.plays)),
-            "skips"     => Some(Field::Skips(self.skips)),
+            "location" => Some(Field::Location(self.location.clone())),
+            "plays" => Some(Field::Plays(self.plays)),
+            "skips" => Some(Field::Skips(self.skips)),
             "favorited" => Some(Field::Favorited(self.favorited)),
-            "rating"    => self.rating.map(Field::Rating),
-            "duration"  => Some(Field::Duration(self.duration)),
+            "rating" => self.rating.map(Field::Rating),
+            "duration" => Some(Field::Duration(self.duration)),
             "play_time" => Some(Field::PlayTime(self.play_time)),
-            "format"    => self.format.map(Field::Format),
+            "format" => self.format.map(Field::Format),
             _ => todo!(), // Other field types are not yet supported
         }
     }
@@ -228,8 +228,12 @@ impl URI {
 
     pub fn as_uri(&self) -> String {
         let path_str = match self {
-            URI::Local(location) => filename_to_uri(location, None).expect("couldn't convert path to URI").to_string(),
-            URI::Cue { location, .. } => filename_to_uri(location, None).expect("couldn't convert path to URI").to_string(),
+            URI::Local(location) => filename_to_uri(location, None)
+                .expect("couldn't convert path to URI")
+                .to_string(),
+            URI::Cue { location, .. } => filename_to_uri(location, None)
+                .expect("couldn't convert path to URI")
+                .to_string(),
             URI::Remote(_, location) => location.clone(),
         };
         path_str.to_string()
@@ -492,7 +496,7 @@ impl MusicLibrary {
                         None => blank_tag,
                     },
                 }
-            },
+            }
 
             Err(_) => blank_tag,
         };
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 5c4865a..03e9740 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,12 +1,12 @@
+use file_format::{FileFormat, Kind};
 use std::io::{BufReader, BufWriter};
 use std::path::{Path, PathBuf};
 use std::{error::Error, fs};
 use walkdir::WalkDir;
-use file_format::{FileFormat, Kind};
 
 use snap;
 
-use super::music_db::{Song, AlbumArt, URI};
+use super::music_db::{AlbumArt, Song, URI};
 use unidecode::unidecode;
 
 pub(super) fn normalize(input_string: &str) -> String {
@@ -76,8 +76,9 @@ pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
         .into_iter()
         .filter_map(|e| e.ok())
     {
-        if target_file.depth() >= 3 { // Don't recurse very deep
-            break
+        if target_file.depth() >= 3 {
+            // Don't recurse very deep
+            break;
         }
 
         let path = target_file.path();
@@ -87,7 +88,7 @@ pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
 
         let format = FileFormat::from_file(path)?.kind();
         if format != Kind::Image {
-            break
+            break;
         }
 
         let image_uri = URI::Local(path.to_path_buf().canonicalize().unwrap());

From 7a93974f5b77afc09724758cc9efcf50b6d37e5d Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sat, 9 Dec 2023 01:52:57 -0500
Subject: [PATCH 045/136] Added Playlists, Implemented MusicCollection for
 Album and Playlist

---
 src/music_storage/{music_db.rs => library.rs} | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename src/music_storage/{music_db.rs => library.rs} (100%)

diff --git a/src/music_storage/music_db.rs b/src/music_storage/library.rs
similarity index 100%
rename from src/music_storage/music_db.rs
rename to src/music_storage/library.rs

From 15b49840543a21781dc4010bff93e11c63b973f6 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sat, 9 Dec 2023 01:57:46 -0500
Subject: [PATCH 046/136] changed music_db.rs to library.rs

---
 src/lib.rs                            |  3 ++-
 src/music_controller/controller.rs    |  2 +-
 src/music_player.rs                   | 11 +-------
 src/music_storage/library.rs          | 37 ++++++++++++++-------------
 src/music_storage/music_collection.rs |  8 ++++++
 src/music_storage/playlist.rs         | 29 ++++++++++++++++++---
 src/music_storage/utils.rs            |  2 +-
 7 files changed, 57 insertions(+), 35 deletions(-)
 create mode 100644 src/music_storage/music_collection.rs

diff --git a/src/lib.rs b/src/lib.rs
index d98209c..31538fa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,8 @@
 pub mod music_storage {
-    pub mod music_db;
+    pub mod library;
     pub mod playlist;
     mod utils;
+    pub mod  music_collection;
 }
 
 pub mod music_controller {
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 333db0b..6d0acb4 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -2,7 +2,7 @@ use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 
 use crate::music_controller::config::Config;
-use crate::music_storage::music_db::{MusicLibrary, Song, Tag};
+use crate::music_storage::library::{MusicLibrary, Song, Tag};
 
 pub struct MusicController {
     pub config: Arc<RwLock<Config>>,
diff --git a/src/music_player.rs b/src/music_player.rs
index 0c9b2d6..ecafff6 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -1,6 +1,6 @@
 // Crate things
 //use crate::music_controller::config::Config;
-use crate::music_storage::music_db::{Tag, URI};
+use crate::music_storage::library::{Tag, URI};
 use crossbeam_channel::bounded;
 use std::error::Error;
 use std::sync::{Arc, RwLock};
@@ -180,7 +180,6 @@ impl Player {
                     gst::MessageView::Eos(_) => {}
                     gst::MessageView::StreamStart(_) => println!("Stream start"),
                     gst::MessageView::Error(e) => {
-                        println!("ERROR: {}", e.error());
                         playbin_bus_ctrl
                             .write()
                             .unwrap()
@@ -193,14 +192,6 @@ impl Player {
                             .set_state(gst::State::Playing)
                             .unwrap();
                     },
-                    gst::MessageView::Tag(tag) => {
-                        if let Some(title) = tag.tags().get::<gst::tags::Title>() {
-                            println!("  Title: {}", title.get());
-                        }
-                        if let Some(album) = tag.tags().get::<gst::tags::Album>() {
-                            println!("  Album: {}", album.get());
-                        }
-                    }
                     gst::MessageView::Buffering(buffering) => {
                         let percent = buffering.percent();
                         if percent < 100 {
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 41d2e22..4fdcabd 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -1,3 +1,4 @@
+use super::music_collection::MusicCollection;
 // Crate things
 use super::utils::{find_images, normalize, read_library, write_library};
 use crate::music_controller::config::Config;
@@ -269,33 +270,16 @@ pub struct Album<'a> {
 
 #[allow(clippy::len_without_is_empty)]
 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> {
@@ -311,6 +295,23 @@ impl Album<'_> {
         total
     }
 }
+impl MusicCollection for Album<'_> {
+    //returns the Album title
+    fn title(&self) -> &String {
+        self.title
+    }
+    /// Returns the album cover as an AlbumArt struct, if it exists
+    fn cover(&self) -> Option<&AlbumArt> {
+        self.cover
+    }
+    fn tracks(&self) -> Vec<&Song> {
+        let mut songs = Vec::new();
+        for disc in &self.discs {
+            songs.append(&mut disc.1.clone())
+        }
+        songs
+    }
+}
 
 const BLOCKED_EXTENSIONS: [&str; 4] = ["vob", "log", "txt", "sf2"];
 
diff --git a/src/music_storage/music_collection.rs b/src/music_storage/music_collection.rs
new file mode 100644
index 0000000..8abcd71
--- /dev/null
+++ b/src/music_storage/music_collection.rs
@@ -0,0 +1,8 @@
+use crate::music_storage::library::{ AlbumArt, Song };
+
+pub trait MusicCollection {
+    fn title(&self) -> &String;
+    fn cover(&self) -> Option<&AlbumArt>;
+    fn tracks(&self) -> Vec<&Song>;
+}
+
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 4f8892d..2ad4742 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,6 +1,27 @@
-use crate::music_controller::config::Config;
-use std::path::Path;
+use walkdir::Error;
 
-pub fn playlist_add(_config: &Config, _playlist_name: &str, _song_paths: &[&Path]) {
-    unimplemented!()
+use crate::music_controller::config::Config;
+use std::{path::Path, default, thread::AccessError};
+
+use super::{library::{AlbumArt, Song}, music_collection::MusicCollection};
+
+#[derive(Debug, Default)]
+pub struct Playlist<'a> {
+    title: String,
+    cover: Option<&'a AlbumArt>,
+    tracks: Vec<&'a Song>,
+}
+impl MusicCollection for Playlist<'_> {
+    fn title(&self) -> &String {
+        &self.title
+    }
+    fn cover(&self) -> Option<&AlbumArt> {
+        match self.cover {
+            Some(e) => Some(e),
+            None => None,
+        }
+    }
+    fn tracks(&self) -> Vec<&Song> {
+            self.tracks.clone()
+    }
 }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 03e9740..a25313a 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -6,7 +6,7 @@ use walkdir::WalkDir;
 
 use snap;
 
-use super::music_db::{AlbumArt, Song, URI};
+use super::library::{AlbumArt, Song, URI};
 use unidecode::unidecode;
 
 pub(super) fn normalize(input_string: &str) -> String {

From 594e426a3f1c3c28c3fa291a053a2d6c8800cf8d Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sat, 9 Dec 2023 23:37:22 -0500
Subject: [PATCH 047/136] Added db_reader, implemented ExternalLibrary, updated
 Playlist

---
 Cargo.toml                                    |   2 +
 src/lib.rs                                    |  14 ++
 src/music_storage/db_reader/common.rs         |  51 ++++
 src/music_storage/db_reader/extern_library.rs |   9 +
 src/music_storage/db_reader/foobar/reader.rs  | 176 ++++++++++++++
 .../db_reader/musicbee/reader.rs              | 220 +++++++++++++++++
 src/music_storage/db_reader/musicbee/utils.rs |  29 +++
 src/music_storage/db_reader/xml/reader.rs     | 222 ++++++++++++++++++
 src/music_storage/library.rs                  |   5 +-
 src/music_storage/playlist.rs                 |  68 +++++-
 10 files changed, 791 insertions(+), 5 deletions(-)
 create mode 100644 src/music_storage/db_reader/common.rs
 create mode 100644 src/music_storage/db_reader/extern_library.rs
 create mode 100644 src/music_storage/db_reader/foobar/reader.rs
 create mode 100644 src/music_storage/db_reader/musicbee/reader.rs
 create mode 100644 src/music_storage/db_reader/musicbee/utils.rs
 create mode 100644 src/music_storage/db_reader/xml/reader.rs

diff --git a/Cargo.toml b/Cargo.toml
index a7eb36f..76b0ba9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,3 +29,5 @@ gstreamer = "0.21.2"
 glib = "0.18.3"
 crossbeam-channel = "0.5.8"
 crossbeam = "0.8.2"
+quick-xml = "0.31.0"
+leb128 = "0.2.5"
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 31538fa..32a8453 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,6 +3,20 @@ pub mod music_storage {
     pub mod playlist;
     mod utils;
     pub mod  music_collection;
+    pub mod db_reader {
+        pub mod foobar {
+            pub mod reader;
+        }
+        pub mod musicbee {
+            pub mod utils;
+            pub mod reader;
+        }
+        pub mod xml {
+            pub mod reader;
+        }
+        pub mod common;
+        pub mod extern_library;
+    }
 }
 
 pub mod music_controller {
diff --git a/src/music_storage/db_reader/common.rs b/src/music_storage/db_reader/common.rs
new file mode 100644
index 0000000..80e0910
--- /dev/null
+++ b/src/music_storage/db_reader/common.rs
@@ -0,0 +1,51 @@
+use std::path::PathBuf;
+
+use chrono::{DateTime, TimeZone, Utc};
+
+pub fn get_bytes<const S: usize>(iterator: &mut std::vec::IntoIter<u8>) -> [u8; S] {
+    let mut bytes = [0; S];
+
+    for i in 0..S {
+        bytes[i] = iterator.next().unwrap();
+    }
+
+    return bytes;
+}
+
+pub fn get_bytes_vec(iterator: &mut std::vec::IntoIter<u8>, number: usize) -> Vec<u8> {
+    let mut bytes = Vec::new();
+
+    for _ in 0..number {
+        bytes.push(iterator.next().unwrap());
+    }
+
+    return bytes;
+}
+
+/// Converts the windows DateTime into Chrono DateTime
+pub fn get_datetime(iterator: &mut std::vec::IntoIter<u8>, topbyte: bool) -> DateTime<Utc> {
+    let mut datetime_i64 = i64::from_le_bytes(get_bytes(iterator));
+
+    if topbyte {
+        // Zero the topmost byte
+        datetime_i64 = datetime_i64 & 0x00FFFFFFFFFFFFFFF;
+    }
+
+    if datetime_i64 <= 0 {
+        return Utc.timestamp_opt(0, 0).unwrap();
+    }
+
+    let unix_time_ticks = datetime_i64 - 621355968000000000;
+
+    let unix_time_seconds = unix_time_ticks / 10000000;
+
+    let unix_time_nanos = match (unix_time_ticks as f64 / 10000000.0) - unix_time_seconds as f64
+        > 0.0
+    {
+        true => ((unix_time_ticks as f64 / 10000000.0) - unix_time_seconds as f64) * 1000000000.0,
+        false => 0.0,
+    };
+
+    Utc.timestamp_opt(unix_time_seconds, unix_time_nanos as u32)
+        .unwrap()
+}
\ No newline at end of file
diff --git a/src/music_storage/db_reader/extern_library.rs b/src/music_storage/db_reader/extern_library.rs
new file mode 100644
index 0000000..ffe0711
--- /dev/null
+++ b/src/music_storage/db_reader/extern_library.rs
@@ -0,0 +1,9 @@
+use std::path::PathBuf;
+
+
+pub trait ExternalLibrary {
+    fn from_file(&mut self, file: &PathBuf) -> Self;
+    fn write(&self) {
+        unimplemented!();
+    }
+}
\ No newline at end of file
diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
new file mode 100644
index 0000000..017d7fc
--- /dev/null
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -0,0 +1,176 @@
+use chrono::{DateTime, Utc};
+use std::{fs::File, io::Read, path::PathBuf, time::Duration};
+
+use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec, get_datetime};
+
+const MAGIC: [u8; 16] = [
+    0xE1, 0xA0, 0x9C, 0x91, 0xF8, 0x3C, 0x77, 0x42, 0x85, 0x2C, 0x3B, 0xCC, 0x14, 0x01, 0xD3, 0xF2,
+];
+
+#[derive(Debug)]
+pub struct FoobarPlaylist {
+    path: PathBuf,
+    metadata: Vec<u8>,
+}
+
+#[derive(Debug, Default)]
+pub struct FoobarPlaylistTrack {
+    flags: i32,
+    file_name: String,
+    subsong_index: i32,
+    file_size: i64,
+    file_time: DateTime<Utc>,
+    duration: Duration,
+    rpg_album: u32,
+    rpg_track: u32,
+    rpk_album: u32,
+    rpk_track: u32,
+    entries: Vec<(String, String)>,
+}
+
+impl FoobarPlaylist {
+    pub fn new(path: &String) -> Self {
+        FoobarPlaylist {
+            path: PathBuf::from(path),
+            metadata: Vec::new(),
+        }
+    }
+
+    fn get_meta_offset(&self, offset: usize) -> String {
+        let mut result_vec = Vec::new();
+
+        let mut i = offset;
+        loop {
+            if self.metadata[i] == 0x00 {
+                break;
+            }
+
+            result_vec.push(self.metadata[i]);
+            i += 1;
+        }
+
+        String::from_utf8_lossy(&result_vec).into()
+    }
+
+    /// Reads the entire MusicBee library and returns relevant values
+    /// as a `Vec` of `Song`s
+    pub fn read(&mut self) -> Result<Vec<FoobarPlaylistTrack>, Box<dyn std::error::Error>> {
+        let mut f = File::open(&self.path).unwrap();
+        let mut buffer = Vec::new();
+        let mut retrieved_songs: Vec<FoobarPlaylistTrack> = Vec::new();
+
+        // Read the whole file
+        f.read_to_end(&mut buffer)?;
+
+        let mut buf_iter = buffer.into_iter();
+
+        // Parse the header
+        let magic = get_bytes::<16>(&mut buf_iter);
+        if magic != MAGIC {
+            return Err("Magic bytes mismatch!".into());
+        }
+
+        let meta_size = i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize;
+        self.metadata = get_bytes_vec(&mut buf_iter, meta_size);
+        let track_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+        // Read all the track fields
+        for _ in 0..track_count {
+            let flags = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            let has_metadata = (0x01 & flags) != 0;
+            let has_padding = (0x04 & flags) != 0;
+
+            let file_name_offset = i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize;
+            let file_name = self.get_meta_offset(file_name_offset);
+
+            let subsong_index = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            if !has_metadata {
+                let track = FoobarPlaylistTrack {
+                    file_name,
+                    subsong_index,
+                    ..Default::default()
+                };
+                retrieved_songs.push(track);
+                continue;
+            }
+
+            let file_size = i64::from_le_bytes(get_bytes(&mut buf_iter));
+
+            let file_time = get_datetime(&mut buf_iter, false);
+
+            let duration = Duration::from_nanos(u64::from_le_bytes(get_bytes(&mut buf_iter)) / 100);
+
+            let rpg_album = u32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            let rpg_track = u32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            let rpk_album = u32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            let rpk_track = u32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            get_bytes::<4>(&mut buf_iter);
+
+            let mut entries = Vec::new();
+            let primary_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
+            let secondary_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
+            let _secondary_offset = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get primary keys
+            for _ in 0..primary_count {
+                println!("{}", i32::from_le_bytes(get_bytes(&mut buf_iter)));
+
+                let key =
+                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+
+                entries.push((key, String::new()));
+            }
+
+            // Consume unknown 32 bit value
+            println!("unk");
+            get_bytes::<4>(&mut buf_iter);
+
+            // Get primary values
+            for i in 0..primary_count {
+                println!("primkey {i}");
+
+                let value =
+                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+
+                entries[i as usize].1 = value;
+            }
+
+            // Get secondary Keys
+            for _ in 0..secondary_count {
+                let key =
+                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let value =
+                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                entries.push((key, value));
+            }
+
+            if has_padding {
+                get_bytes::<64>(&mut buf_iter);
+            }
+
+            let track = FoobarPlaylistTrack {
+                flags,
+                file_name,
+                subsong_index,
+                file_size,
+                file_time,
+                duration,
+                rpg_album,
+                rpg_track,
+                rpk_album,
+                rpk_track,
+                entries,
+            };
+
+            retrieved_songs.push(track);
+        }
+
+        Ok(retrieved_songs)
+    }
+}
diff --git a/src/music_storage/db_reader/musicbee/reader.rs b/src/music_storage/db_reader/musicbee/reader.rs
new file mode 100644
index 0000000..0a5cac6
--- /dev/null
+++ b/src/music_storage/db_reader/musicbee/reader.rs
@@ -0,0 +1,220 @@
+use super::utils::get_string;
+use crate::music_storage::db_reader::common::{get_bytes, get_datetime};
+use chrono::{DateTime, Utc};
+use std::fs::File;
+use std::io::prelude::*;
+use std::time::Duration;
+
+pub struct MusicBeeDatabase {
+    path: String,
+}
+
+impl MusicBeeDatabase {
+    pub fn new(path: String) -> MusicBeeDatabase {
+        MusicBeeDatabase { path }
+    }
+
+    /// Reads the entire MusicBee library and returns relevant values
+    /// as a `Vec` of `Song`s
+    pub fn read(&self) -> Result<Vec<MusicBeeSong>, Box<dyn std::error::Error>> {
+        let mut f = File::open(&self.path).unwrap();
+        let mut buffer = Vec::new();
+        let mut retrieved_songs: Vec<MusicBeeSong> = Vec::new();
+
+        // Read the whole file
+        f.read_to_end(&mut buffer)?;
+
+        let mut buf_iter = buffer.into_iter();
+
+        // Get the song count from the first 4 bytes
+        // and then right shift it by 8 for some reason
+        let mut database_song_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
+        database_song_count = database_song_count >> 8;
+
+        let mut song_count = 0;
+        loop {
+            // If the file designation is 1, then the end of the database
+            // has been reached
+            let file_designation = match buf_iter.next() {
+                Some(1) => break,
+                Some(value) => value,
+                None => break,
+            };
+
+            song_count += 1;
+
+            // Get the file status. Unknown what this means
+            let status = buf_iter.next().unwrap();
+
+            buf_iter.next(); // Read in a byte to throw it away
+
+            // Get the play count
+            let play_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the time the song was last played, stored as a signed 64 bit number of microseconds
+            let last_played = get_datetime(buf_iter.by_ref(), true);
+
+            // Get the number of times the song was skipped
+            let skip_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the path to the song
+            let path = get_string(buf_iter.by_ref());
+
+            // Get the file size
+            let file_size = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the sample rate
+            let sample_rate = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the channel count
+            let channel_count = buf_iter.next().unwrap();
+
+            // Get the bitrate type (CBR, VBR, etc.)
+            let bitrate_type = buf_iter.next().unwrap();
+
+            // Get the actual bitrate
+            let bitrate = i16::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the track length in milliseconds
+            let track_length =
+                Duration::from_millis(i32::from_le_bytes(get_bytes(&mut buf_iter)) as u64);
+
+            // Get the date added and modified in the same format
+            let date_added = get_datetime(buf_iter.by_ref(), true);
+            let date_modified = get_datetime(buf_iter.by_ref(), true);
+
+            // Gets artwork information
+            //
+            // Artworks are stored as chunks describing the type
+            // (embedded, file), and some other information.
+            let mut artwork: Vec<MusicBeeAlbumArt> = vec![];
+            loop {
+                let artwork_type = buf_iter.next().unwrap();
+                if artwork_type > 253 {
+                    break;
+                }
+
+                let unknown_string = get_string(buf_iter.by_ref());
+                let storage_mode = buf_iter.next().unwrap();
+                let storage_path = get_string(buf_iter.by_ref());
+
+                artwork.push(MusicBeeAlbumArt {
+                    artwork_type,
+                    unknown_string,
+                    storage_mode,
+                    storage_path,
+                });
+            }
+
+            buf_iter.next(); // Read in a byte to throw it away
+
+            // Gets all the tags on the song in the database
+            let mut tags: Vec<MusicBeeTag> = vec![];
+            loop {
+                // If the tag code is 0, the end of the block has been reached, so break.
+                //
+                // If the tag code is 255, it pertains to some CUE file values that are not known
+                // throw away these values
+                let tag_code = match buf_iter.next() {
+                    Some(0) => break,
+                    Some(255) => {
+                        let repeats = u16::from_le_bytes(get_bytes(&mut buf_iter));
+                        for _ in 0..(repeats * 13) - 2 {
+                            buf_iter.next().unwrap();
+                        }
+
+                        255
+                    }
+                    Some(value) => value,
+                    None => panic!(),
+                };
+
+                // Get the string value of the tag
+                let tag_value = get_string(buf_iter.by_ref());
+                tags.push(MusicBeeTag {
+                    tag_code,
+                    tag_value,
+                });
+            }
+
+            // Construct the finished song and add it to the vec
+            let constructed_song = MusicBeeSong {
+                file_designation,
+                status,
+                play_count,
+                last_played,
+                skip_count,
+                path,
+                file_size,
+                sample_rate,
+                channel_count,
+                bitrate_type,
+                bitrate,
+                track_length,
+                date_added,
+                date_modified,
+                artwork,
+                tags,
+            };
+
+            retrieved_songs.push(constructed_song);
+        }
+
+        println!("The database claims you have: {database_song_count} songs\nThe retrieved number is: {song_count} songs");
+
+        match database_song_count == song_count {
+            true => Ok(retrieved_songs),
+            false => Err("Song counts do not match!".into()),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct MusicBeeTag {
+    tag_code: u8,
+    tag_value: String,
+}
+
+#[derive(Debug)]
+pub struct MusicBeeAlbumArt {
+    artwork_type: u8,
+    unknown_string: String,
+    storage_mode: u8,
+    storage_path: String,
+}
+
+#[derive(Debug)]
+pub struct MusicBeeSong {
+    file_designation: u8,
+    status: u8,
+    play_count: u16,
+    pub last_played: DateTime<Utc>,
+    skip_count: u16,
+    path: String,
+    file_size: i32,
+    sample_rate: i32,
+    channel_count: u8,
+    bitrate_type: u8,
+    bitrate: i16,
+    track_length: Duration,
+    date_added: DateTime<Utc>,
+    date_modified: DateTime<Utc>,
+
+    /* Album art stuff */
+    artwork: Vec<MusicBeeAlbumArt>,
+
+    /* All tags */
+    tags: Vec<MusicBeeTag>,
+}
+
+impl MusicBeeSong {
+    pub fn get_tag_code(self, code: u8) -> Option<String> {
+        for tag in &self.tags {
+            if tag.tag_code == code {
+                return Some(tag.tag_value.clone());
+            }
+        }
+
+        None
+    }
+}
diff --git a/src/music_storage/db_reader/musicbee/utils.rs b/src/music_storage/db_reader/musicbee/utils.rs
new file mode 100644
index 0000000..65d6333
--- /dev/null
+++ b/src/music_storage/db_reader/musicbee/utils.rs
@@ -0,0 +1,29 @@
+use leb128;
+
+/// Gets a string from the MusicBee database format
+///
+/// The length of the string is defined by an LEB128 encoded value at the beginning, followed by the string of that length
+pub fn get_string(iterator: &mut std::vec::IntoIter<u8>) -> String {
+    let mut string_length = iterator.next().unwrap() as usize;
+    if string_length == 0 {
+        return String::new();
+    }
+
+    // Decode the LEB128 value
+    let mut leb_bytes: Vec<u8> = vec![];
+    loop {
+        leb_bytes.push(string_length as u8);
+
+        if string_length >> 7 != 1 {
+            break;
+        }
+        string_length = iterator.next().unwrap() as usize;
+    }
+    string_length = leb128::read::unsigned(&mut leb_bytes.as_slice()).unwrap() as usize;
+
+    let mut string_bytes = vec![];
+    for _ in 0..string_length {
+        string_bytes.push(iterator.next().unwrap());
+    }
+    String::from_utf8(string_bytes).unwrap()
+}
diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/xml/reader.rs
new file mode 100644
index 0000000..de2a057
--- /dev/null
+++ b/src/music_storage/db_reader/xml/reader.rs
@@ -0,0 +1,222 @@
+use quick_xml::events::Event;
+use quick_xml::reader::Reader;
+
+use std::collections::{BTreeMap, HashMap};
+use std::io::Error;
+use std::path::PathBuf;
+use std::str::FromStr;
+use std::vec::Vec;
+
+use chrono::prelude::*;
+
+use crate::music_storage::db_reader::extern_library::ExternalLibrary;
+
+#[derive(Debug, Default, Clone)]
+pub struct XmlLibrary {
+    tracks: Vec<XMLSong>
+}
+impl XmlLibrary {
+    fn new() -> Self {
+        Default::default()
+    }
+}
+impl ExternalLibrary for XmlLibrary {
+    fn from_file(&mut self, file: &PathBuf) -> Self {
+        let mut reader = Reader::from_file(file).unwrap();
+        reader.trim_text(true);
+        //count every event, for fun ig?
+        let mut count = 0;
+        //count for skipping useless beginning key
+        let mut count2 = 0;
+        //number of grabbed songs
+        let mut count3 = 0;
+        //number of IDs skipped
+        let mut count4 = 0;
+
+        let mut buf = Vec::new();
+        let mut skip = false;
+
+        let mut converted_songs: Vec<XMLSong> = Vec::new();
+
+
+        let mut song_tags: HashMap<String, String> = HashMap::new();
+        let mut key: String = String::new();
+        let mut tagvalue: String = String::new();
+        let mut key_selected = false;
+
+        use std::time::Instant;
+        let now = Instant::now();
+
+        loop {
+            //push tag to song_tags map
+            if !key.is_empty() && !tagvalue.is_empty() {
+                song_tags.insert(key.clone(), tagvalue.clone());
+                key.clear();
+                tagvalue.clear();
+                key_selected = false;
+
+                //end the song to start a new one, and turn turn current song map into XMLSong
+                if song_tags.contains_key(&"Location".to_string()) {
+                    count3 += 1;
+                    //check for skipped IDs
+                    if &count3.to_string()
+                        != song_tags.get_key_value(&"Track ID".to_string()).unwrap().1
+                    {
+                        count3 += 1;
+                        count4 += 1;
+                    }
+                    converted_songs.push(XMLSong::from_hashmap(&mut song_tags).unwrap());
+                    song_tags.clear();
+                    skip = true;
+                }
+            }
+            match reader.read_event_into(&mut buf) {
+                Ok(Event::Start(_)) => {
+                    count += 1;
+                    count2 += 1;
+                }
+                Ok(Event::Text(e)) => {
+                    if count < 17 && count != 10 {
+                        continue;
+                    }else if skip {
+                        skip = false;
+                        continue;
+                    }
+
+                    let text = e.unescape().unwrap().to_string();
+
+                    if text == count2.to_string() && !key_selected {
+                        continue;
+                    }
+
+                    //Add the key/value depenidng on if the key is selected or not ⛩️sorry buzz
+
+                    match key_selected {
+                        true => tagvalue.push_str(&text),
+                        false => {
+                            key.push_str(&text);
+                            if !key.is_empty() {
+                                key_selected = true
+                            } else {
+                                panic!("Key not selected?!")
+                            }
+                        }
+                        _ => panic!("WHAT DID YOU JUST DO?!🐰🐰🐰🐰"),
+                    }
+                }
+                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
+                Ok(Event::Eof) => break,
+                _ => (),
+            }
+            buf.clear();
+        }
+        let elasped = now.elapsed();
+        println!("\n\nXMLReader\n=========================================\n\nDone!\n{} songs grabbed in {:#?}\nIDs Skipped: {}", count3, elasped, count4);
+        // dbg!(folder);
+        self.tracks.append(converted_songs.as_mut());
+        self.clone()
+    }
+}
+#[derive(Debug, Clone, Default)]
+pub struct XMLSong {
+    pub id: i32,
+    pub plays: i32,
+    pub favorited: bool,
+    pub banned: bool,
+    pub rating: Option<u8>,
+    pub format: Option<String>,
+    pub song_type: Option<String>,
+    pub last_played: Option<DateTime<Utc>>,
+    pub date_added: Option<DateTime<Utc>>,
+    pub date_modified: Option<DateTime<Utc>>,
+    pub tags: BTreeMap<String, String>,
+    pub location: String,
+}
+
+impl XMLSong {
+    pub fn new() -> XMLSong {
+        Default::default()
+    }
+
+
+    fn from_hashmap(map: &mut HashMap<String, String>) -> Result<XMLSong, Error> {
+        let mut song = XMLSong::new();
+        //get the path with the first bit chopped off
+        let path_: String = map.get_key_value("Location").unwrap().1.clone();
+        let track_type: String = map.get_key_value("Track Type").unwrap().1.clone();
+        let path: String = match track_type.as_str() {
+            "File" => {
+                if path_.contains("file://localhost/") {
+                    path_.strip_prefix("file://localhost/").unwrap();
+                }
+                path_
+            }
+            "URL" => path_,
+            _ => path_,
+        };
+
+        for (key, value) in map {
+            match key.as_str() {
+                "Track ID" => song.id = value.parse().unwrap(),
+                "Location" => song.location = path.to_string(),
+                "Play Count" => song.plays = value.parse().unwrap(),
+                "Love" => {
+                    //check if the track is (L)Loved or (B)Banned
+                    match value.as_str() {
+                        "L" => song.favorited = true,
+                        "B" => song.banned = false,
+                        _ => continue,
+                    }
+                }
+                "Rating" => song.rating = Some(value.parse().unwrap()),
+                "Kind" => song.format = Some(value.to_string()),
+                "Play Date UTC" => {
+                    song.last_played = Some(DateTime::<Utc>::from_str(value).unwrap())
+                }
+                "Date Added" => song.date_added = Some(DateTime::<Utc>::from_str(value).unwrap()),
+                "Date Modified" => {
+                    song.date_modified = Some(DateTime::<Utc>::from_str(value).unwrap())
+                }
+                "Track Type" => song.song_type = Some(value.to_string()),
+                _ => {
+                    song.tags.insert(key.to_string(), value.to_string());
+                }
+            }
+        }
+        // println!("{:.2?}", song);
+        Ok(song)
+    }
+}
+
+
+pub fn get_folder(file: &PathBuf) -> String {
+    let mut reader = Reader::from_file(file).unwrap();
+    reader.trim_text(true);
+    //count every event, for fun ig?
+    let mut count = 0;
+    let mut buf = Vec::new();
+    let mut folder = String::new();
+    loop {
+        match reader.read_event_into(&mut buf) {
+            Ok(Event::Start(_)) => {
+                count += 1;
+            }
+            Ok(Event::Text(e)) => {
+                if count == 10 {
+                    folder = String::from(
+                        e.unescape()
+                            .unwrap()
+                            .to_string()
+                            .strip_prefix("file://localhost/")
+                            .unwrap(),
+                    );
+                    return folder;
+                }
+            }
+            Err(_e) => {
+                panic!("oh no! something happened in the public function `get_reader_from_xml()!`")
+            }
+            _ => (),
+        }
+    }
+}
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 4fdcabd..d264bdf 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize};
 use rayon::prelude::*;
 use std::sync::{Arc, Mutex, RwLock};
 
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
 pub enum AlbumArt {
     Embedded(usize),
     External(URI),
@@ -115,7 +115,7 @@ impl ToString for Field {
 }
 
 /// Stores information about a single song
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
 pub struct Song {
     pub location: URI,
     pub plays: i32,
@@ -275,7 +275,6 @@ impl Album<'_> {
         self.artist
     }
 
-    
 
     pub fn discs(&self) -> &BTreeMap<usize, Vec<&Song>> {
         &self.discs
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 2ad4742..710e6fd 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,15 +1,68 @@
+use chrono::Duration;
 use walkdir::Error;
 
 use crate::music_controller::config::Config;
 use std::{path::Path, default, thread::AccessError};
 
-use super::{library::{AlbumArt, Song}, music_collection::MusicCollection};
+use super::{library::{AlbumArt, Song, self, Tag}, music_collection::MusicCollection};
 
-#[derive(Debug, Default)]
+#[derive(Debug, Clone)]
 pub struct Playlist<'a> {
     title: String,
     cover: Option<&'a AlbumArt>,
     tracks: Vec<&'a Song>,
+    play_count: i32,
+    play_time: Duration,
+}
+impl<'a> Playlist<'a> {
+    pub fn new() -> Self {
+        Default::default()
+    }
+    pub fn play_count(&self) -> i32 {
+        self.play_count
+    }
+    pub fn play_time(&self) -> chrono::Duration {
+        self.play_time
+    }
+    pub fn set_tracks(&mut self, songs: Vec<&'a Song>) -> Result<(), Error> {
+        self.tracks = songs;
+        Ok(())
+    }
+    pub fn add_track(&mut self, song: &'a Song) -> Result<(), Error> {
+        self.tracks.push(song);
+        Ok(())
+    }
+    pub fn remove_track(&mut self, index: i32) -> Result<(), Error> {
+        let bun: usize = index as usize;
+        let mut name = String::new();
+        if self.tracks.len() >= bun {
+            name = String::from(self.tracks[bun].tags.get_key_value(&Tag::Title).unwrap().1);
+            self.tracks.remove(bun);
+        }
+        dbg!(name);
+        Ok(())
+    }
+    pub fn get_index(&self, song_name: &str) -> Option<usize> {
+        let mut index = 0;
+        if self.contains(&Tag::Title, song_name) {
+            for track in &self.tracks {
+                index += 1;
+                if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 {
+                    dbg!("Index gotted! ",index);
+                    return Some(index);
+                }
+            }
+        }
+        None
+    }
+    pub fn contains(&self, tag: &Tag, title: &str) -> bool {
+        for track in &self.tracks {
+            if title == track.tags.get_key_value(tag).unwrap().1 {
+                return true;
+            }
+        }
+        false
+    }
 }
 impl MusicCollection for Playlist<'_> {
     fn title(&self) -> &String {
@@ -25,3 +78,14 @@ impl MusicCollection for Playlist<'_> {
             self.tracks.clone()
     }
 }
+impl Default for Playlist<'_> {
+    fn default() -> Self {
+        Playlist { 
+            title: String::default(), 
+            cover: None, 
+            tracks: Vec::default(), 
+            play_count: -1, 
+            play_time: Duration::zero(), 
+        }
+    }
+}
\ No newline at end of file

From 886fc1a3c8038b69fdb4767842179fbad574839d Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 9 Dec 2023 23:39:15 -0600
Subject: [PATCH 048/136] Updated `from_file()` in ExternalLibrary trait

---
 src/music_storage/db_reader/extern_library.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/music_storage/db_reader/extern_library.rs b/src/music_storage/db_reader/extern_library.rs
index ffe0711..047fcc6 100644
--- a/src/music_storage/db_reader/extern_library.rs
+++ b/src/music_storage/db_reader/extern_library.rs
@@ -2,8 +2,8 @@ use std::path::PathBuf;
 
 
 pub trait ExternalLibrary {
-    fn from_file(&mut self, file: &PathBuf) -> Self;
+    fn from_file(file: &PathBuf) -> Self;
     fn write(&self) {
         unimplemented!();
     }
-}
\ No newline at end of file
+}

From 0072963a6092cbbe3c5441547b22526fe156e3b6 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sun, 10 Dec 2023 00:43:39 -0500
Subject: [PATCH 049/136] added ``to_songs()`` in ExternalLibrary

---
 src/music_storage/db_reader/extern_library.rs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/music_storage/db_reader/extern_library.rs b/src/music_storage/db_reader/extern_library.rs
index ffe0711..fa5cd4e 100644
--- a/src/music_storage/db_reader/extern_library.rs
+++ b/src/music_storage/db_reader/extern_library.rs
@@ -1,9 +1,12 @@
 use std::path::PathBuf;
 
+use crate::music_storage::library::Song;
+
 
 pub trait ExternalLibrary {
-    fn from_file(&mut self, file: &PathBuf) -> Self;
+    fn from_file(file: &PathBuf) -> Self;
     fn write(&self) {
         unimplemented!();
     }
+    fn to_songs(&self) -> Vec<Song>;
 }
\ No newline at end of file

From 3c007ed886fa67dd6d426db70ba1928717297570 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sun, 10 Dec 2023 00:29:54 -0600
Subject: [PATCH 050/136] Removed MusicBee library reader

---
 .../db_reader/musicbee/reader.rs              | 220 ------------------
 src/music_storage/db_reader/musicbee/utils.rs |  29 ---
 2 files changed, 249 deletions(-)
 delete mode 100644 src/music_storage/db_reader/musicbee/reader.rs
 delete mode 100644 src/music_storage/db_reader/musicbee/utils.rs

diff --git a/src/music_storage/db_reader/musicbee/reader.rs b/src/music_storage/db_reader/musicbee/reader.rs
deleted file mode 100644
index 0a5cac6..0000000
--- a/src/music_storage/db_reader/musicbee/reader.rs
+++ /dev/null
@@ -1,220 +0,0 @@
-use super::utils::get_string;
-use crate::music_storage::db_reader::common::{get_bytes, get_datetime};
-use chrono::{DateTime, Utc};
-use std::fs::File;
-use std::io::prelude::*;
-use std::time::Duration;
-
-pub struct MusicBeeDatabase {
-    path: String,
-}
-
-impl MusicBeeDatabase {
-    pub fn new(path: String) -> MusicBeeDatabase {
-        MusicBeeDatabase { path }
-    }
-
-    /// Reads the entire MusicBee library and returns relevant values
-    /// as a `Vec` of `Song`s
-    pub fn read(&self) -> Result<Vec<MusicBeeSong>, Box<dyn std::error::Error>> {
-        let mut f = File::open(&self.path).unwrap();
-        let mut buffer = Vec::new();
-        let mut retrieved_songs: Vec<MusicBeeSong> = Vec::new();
-
-        // Read the whole file
-        f.read_to_end(&mut buffer)?;
-
-        let mut buf_iter = buffer.into_iter();
-
-        // Get the song count from the first 4 bytes
-        // and then right shift it by 8 for some reason
-        let mut database_song_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
-        database_song_count = database_song_count >> 8;
-
-        let mut song_count = 0;
-        loop {
-            // If the file designation is 1, then the end of the database
-            // has been reached
-            let file_designation = match buf_iter.next() {
-                Some(1) => break,
-                Some(value) => value,
-                None => break,
-            };
-
-            song_count += 1;
-
-            // Get the file status. Unknown what this means
-            let status = buf_iter.next().unwrap();
-
-            buf_iter.next(); // Read in a byte to throw it away
-
-            // Get the play count
-            let play_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
-
-            // Get the time the song was last played, stored as a signed 64 bit number of microseconds
-            let last_played = get_datetime(buf_iter.by_ref(), true);
-
-            // Get the number of times the song was skipped
-            let skip_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
-
-            // Get the path to the song
-            let path = get_string(buf_iter.by_ref());
-
-            // Get the file size
-            let file_size = i32::from_le_bytes(get_bytes(&mut buf_iter));
-
-            // Get the sample rate
-            let sample_rate = i32::from_le_bytes(get_bytes(&mut buf_iter));
-
-            // Get the channel count
-            let channel_count = buf_iter.next().unwrap();
-
-            // Get the bitrate type (CBR, VBR, etc.)
-            let bitrate_type = buf_iter.next().unwrap();
-
-            // Get the actual bitrate
-            let bitrate = i16::from_le_bytes(get_bytes(&mut buf_iter));
-
-            // Get the track length in milliseconds
-            let track_length =
-                Duration::from_millis(i32::from_le_bytes(get_bytes(&mut buf_iter)) as u64);
-
-            // Get the date added and modified in the same format
-            let date_added = get_datetime(buf_iter.by_ref(), true);
-            let date_modified = get_datetime(buf_iter.by_ref(), true);
-
-            // Gets artwork information
-            //
-            // Artworks are stored as chunks describing the type
-            // (embedded, file), and some other information.
-            let mut artwork: Vec<MusicBeeAlbumArt> = vec![];
-            loop {
-                let artwork_type = buf_iter.next().unwrap();
-                if artwork_type > 253 {
-                    break;
-                }
-
-                let unknown_string = get_string(buf_iter.by_ref());
-                let storage_mode = buf_iter.next().unwrap();
-                let storage_path = get_string(buf_iter.by_ref());
-
-                artwork.push(MusicBeeAlbumArt {
-                    artwork_type,
-                    unknown_string,
-                    storage_mode,
-                    storage_path,
-                });
-            }
-
-            buf_iter.next(); // Read in a byte to throw it away
-
-            // Gets all the tags on the song in the database
-            let mut tags: Vec<MusicBeeTag> = vec![];
-            loop {
-                // If the tag code is 0, the end of the block has been reached, so break.
-                //
-                // If the tag code is 255, it pertains to some CUE file values that are not known
-                // throw away these values
-                let tag_code = match buf_iter.next() {
-                    Some(0) => break,
-                    Some(255) => {
-                        let repeats = u16::from_le_bytes(get_bytes(&mut buf_iter));
-                        for _ in 0..(repeats * 13) - 2 {
-                            buf_iter.next().unwrap();
-                        }
-
-                        255
-                    }
-                    Some(value) => value,
-                    None => panic!(),
-                };
-
-                // Get the string value of the tag
-                let tag_value = get_string(buf_iter.by_ref());
-                tags.push(MusicBeeTag {
-                    tag_code,
-                    tag_value,
-                });
-            }
-
-            // Construct the finished song and add it to the vec
-            let constructed_song = MusicBeeSong {
-                file_designation,
-                status,
-                play_count,
-                last_played,
-                skip_count,
-                path,
-                file_size,
-                sample_rate,
-                channel_count,
-                bitrate_type,
-                bitrate,
-                track_length,
-                date_added,
-                date_modified,
-                artwork,
-                tags,
-            };
-
-            retrieved_songs.push(constructed_song);
-        }
-
-        println!("The database claims you have: {database_song_count} songs\nThe retrieved number is: {song_count} songs");
-
-        match database_song_count == song_count {
-            true => Ok(retrieved_songs),
-            false => Err("Song counts do not match!".into()),
-        }
-    }
-}
-
-#[derive(Debug)]
-pub struct MusicBeeTag {
-    tag_code: u8,
-    tag_value: String,
-}
-
-#[derive(Debug)]
-pub struct MusicBeeAlbumArt {
-    artwork_type: u8,
-    unknown_string: String,
-    storage_mode: u8,
-    storage_path: String,
-}
-
-#[derive(Debug)]
-pub struct MusicBeeSong {
-    file_designation: u8,
-    status: u8,
-    play_count: u16,
-    pub last_played: DateTime<Utc>,
-    skip_count: u16,
-    path: String,
-    file_size: i32,
-    sample_rate: i32,
-    channel_count: u8,
-    bitrate_type: u8,
-    bitrate: i16,
-    track_length: Duration,
-    date_added: DateTime<Utc>,
-    date_modified: DateTime<Utc>,
-
-    /* Album art stuff */
-    artwork: Vec<MusicBeeAlbumArt>,
-
-    /* All tags */
-    tags: Vec<MusicBeeTag>,
-}
-
-impl MusicBeeSong {
-    pub fn get_tag_code(self, code: u8) -> Option<String> {
-        for tag in &self.tags {
-            if tag.tag_code == code {
-                return Some(tag.tag_value.clone());
-            }
-        }
-
-        None
-    }
-}
diff --git a/src/music_storage/db_reader/musicbee/utils.rs b/src/music_storage/db_reader/musicbee/utils.rs
deleted file mode 100644
index 65d6333..0000000
--- a/src/music_storage/db_reader/musicbee/utils.rs
+++ /dev/null
@@ -1,29 +0,0 @@
-use leb128;
-
-/// Gets a string from the MusicBee database format
-///
-/// The length of the string is defined by an LEB128 encoded value at the beginning, followed by the string of that length
-pub fn get_string(iterator: &mut std::vec::IntoIter<u8>) -> String {
-    let mut string_length = iterator.next().unwrap() as usize;
-    if string_length == 0 {
-        return String::new();
-    }
-
-    // Decode the LEB128 value
-    let mut leb_bytes: Vec<u8> = vec![];
-    loop {
-        leb_bytes.push(string_length as u8);
-
-        if string_length >> 7 != 1 {
-            break;
-        }
-        string_length = iterator.next().unwrap() as usize;
-    }
-    string_length = leb128::read::unsigned(&mut leb_bytes.as_slice()).unwrap() as usize;
-
-    let mut string_bytes = vec![];
-    for _ in 0..string_length {
-        string_bytes.push(iterator.next().unwrap());
-    }
-    String::from_utf8(string_bytes).unwrap()
-}

From e5bfde684631782e53a3f44f069413dd3c6c0448 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sun, 10 Dec 2023 00:36:20 -0600
Subject: [PATCH 051/136] Revert "Removed MusicBee library reader"

This reverts commit 942f12f3ebc1c557b8023ad8494375c4115404e1.
---
 .../db_reader/musicbee/reader.rs              | 220 ++++++++++++++++++
 src/music_storage/db_reader/musicbee/utils.rs |  29 +++
 2 files changed, 249 insertions(+)
 create mode 100644 src/music_storage/db_reader/musicbee/reader.rs
 create mode 100644 src/music_storage/db_reader/musicbee/utils.rs

diff --git a/src/music_storage/db_reader/musicbee/reader.rs b/src/music_storage/db_reader/musicbee/reader.rs
new file mode 100644
index 0000000..0a5cac6
--- /dev/null
+++ b/src/music_storage/db_reader/musicbee/reader.rs
@@ -0,0 +1,220 @@
+use super::utils::get_string;
+use crate::music_storage::db_reader::common::{get_bytes, get_datetime};
+use chrono::{DateTime, Utc};
+use std::fs::File;
+use std::io::prelude::*;
+use std::time::Duration;
+
+pub struct MusicBeeDatabase {
+    path: String,
+}
+
+impl MusicBeeDatabase {
+    pub fn new(path: String) -> MusicBeeDatabase {
+        MusicBeeDatabase { path }
+    }
+
+    /// Reads the entire MusicBee library and returns relevant values
+    /// as a `Vec` of `Song`s
+    pub fn read(&self) -> Result<Vec<MusicBeeSong>, Box<dyn std::error::Error>> {
+        let mut f = File::open(&self.path).unwrap();
+        let mut buffer = Vec::new();
+        let mut retrieved_songs: Vec<MusicBeeSong> = Vec::new();
+
+        // Read the whole file
+        f.read_to_end(&mut buffer)?;
+
+        let mut buf_iter = buffer.into_iter();
+
+        // Get the song count from the first 4 bytes
+        // and then right shift it by 8 for some reason
+        let mut database_song_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
+        database_song_count = database_song_count >> 8;
+
+        let mut song_count = 0;
+        loop {
+            // If the file designation is 1, then the end of the database
+            // has been reached
+            let file_designation = match buf_iter.next() {
+                Some(1) => break,
+                Some(value) => value,
+                None => break,
+            };
+
+            song_count += 1;
+
+            // Get the file status. Unknown what this means
+            let status = buf_iter.next().unwrap();
+
+            buf_iter.next(); // Read in a byte to throw it away
+
+            // Get the play count
+            let play_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the time the song was last played, stored as a signed 64 bit number of microseconds
+            let last_played = get_datetime(buf_iter.by_ref(), true);
+
+            // Get the number of times the song was skipped
+            let skip_count = u16::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the path to the song
+            let path = get_string(buf_iter.by_ref());
+
+            // Get the file size
+            let file_size = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the sample rate
+            let sample_rate = i32::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the channel count
+            let channel_count = buf_iter.next().unwrap();
+
+            // Get the bitrate type (CBR, VBR, etc.)
+            let bitrate_type = buf_iter.next().unwrap();
+
+            // Get the actual bitrate
+            let bitrate = i16::from_le_bytes(get_bytes(&mut buf_iter));
+
+            // Get the track length in milliseconds
+            let track_length =
+                Duration::from_millis(i32::from_le_bytes(get_bytes(&mut buf_iter)) as u64);
+
+            // Get the date added and modified in the same format
+            let date_added = get_datetime(buf_iter.by_ref(), true);
+            let date_modified = get_datetime(buf_iter.by_ref(), true);
+
+            // Gets artwork information
+            //
+            // Artworks are stored as chunks describing the type
+            // (embedded, file), and some other information.
+            let mut artwork: Vec<MusicBeeAlbumArt> = vec![];
+            loop {
+                let artwork_type = buf_iter.next().unwrap();
+                if artwork_type > 253 {
+                    break;
+                }
+
+                let unknown_string = get_string(buf_iter.by_ref());
+                let storage_mode = buf_iter.next().unwrap();
+                let storage_path = get_string(buf_iter.by_ref());
+
+                artwork.push(MusicBeeAlbumArt {
+                    artwork_type,
+                    unknown_string,
+                    storage_mode,
+                    storage_path,
+                });
+            }
+
+            buf_iter.next(); // Read in a byte to throw it away
+
+            // Gets all the tags on the song in the database
+            let mut tags: Vec<MusicBeeTag> = vec![];
+            loop {
+                // If the tag code is 0, the end of the block has been reached, so break.
+                //
+                // If the tag code is 255, it pertains to some CUE file values that are not known
+                // throw away these values
+                let tag_code = match buf_iter.next() {
+                    Some(0) => break,
+                    Some(255) => {
+                        let repeats = u16::from_le_bytes(get_bytes(&mut buf_iter));
+                        for _ in 0..(repeats * 13) - 2 {
+                            buf_iter.next().unwrap();
+                        }
+
+                        255
+                    }
+                    Some(value) => value,
+                    None => panic!(),
+                };
+
+                // Get the string value of the tag
+                let tag_value = get_string(buf_iter.by_ref());
+                tags.push(MusicBeeTag {
+                    tag_code,
+                    tag_value,
+                });
+            }
+
+            // Construct the finished song and add it to the vec
+            let constructed_song = MusicBeeSong {
+                file_designation,
+                status,
+                play_count,
+                last_played,
+                skip_count,
+                path,
+                file_size,
+                sample_rate,
+                channel_count,
+                bitrate_type,
+                bitrate,
+                track_length,
+                date_added,
+                date_modified,
+                artwork,
+                tags,
+            };
+
+            retrieved_songs.push(constructed_song);
+        }
+
+        println!("The database claims you have: {database_song_count} songs\nThe retrieved number is: {song_count} songs");
+
+        match database_song_count == song_count {
+            true => Ok(retrieved_songs),
+            false => Err("Song counts do not match!".into()),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct MusicBeeTag {
+    tag_code: u8,
+    tag_value: String,
+}
+
+#[derive(Debug)]
+pub struct MusicBeeAlbumArt {
+    artwork_type: u8,
+    unknown_string: String,
+    storage_mode: u8,
+    storage_path: String,
+}
+
+#[derive(Debug)]
+pub struct MusicBeeSong {
+    file_designation: u8,
+    status: u8,
+    play_count: u16,
+    pub last_played: DateTime<Utc>,
+    skip_count: u16,
+    path: String,
+    file_size: i32,
+    sample_rate: i32,
+    channel_count: u8,
+    bitrate_type: u8,
+    bitrate: i16,
+    track_length: Duration,
+    date_added: DateTime<Utc>,
+    date_modified: DateTime<Utc>,
+
+    /* Album art stuff */
+    artwork: Vec<MusicBeeAlbumArt>,
+
+    /* All tags */
+    tags: Vec<MusicBeeTag>,
+}
+
+impl MusicBeeSong {
+    pub fn get_tag_code(self, code: u8) -> Option<String> {
+        for tag in &self.tags {
+            if tag.tag_code == code {
+                return Some(tag.tag_value.clone());
+            }
+        }
+
+        None
+    }
+}
diff --git a/src/music_storage/db_reader/musicbee/utils.rs b/src/music_storage/db_reader/musicbee/utils.rs
new file mode 100644
index 0000000..65d6333
--- /dev/null
+++ b/src/music_storage/db_reader/musicbee/utils.rs
@@ -0,0 +1,29 @@
+use leb128;
+
+/// Gets a string from the MusicBee database format
+///
+/// The length of the string is defined by an LEB128 encoded value at the beginning, followed by the string of that length
+pub fn get_string(iterator: &mut std::vec::IntoIter<u8>) -> String {
+    let mut string_length = iterator.next().unwrap() as usize;
+    if string_length == 0 {
+        return String::new();
+    }
+
+    // Decode the LEB128 value
+    let mut leb_bytes: Vec<u8> = vec![];
+    loop {
+        leb_bytes.push(string_length as u8);
+
+        if string_length >> 7 != 1 {
+            break;
+        }
+        string_length = iterator.next().unwrap() as usize;
+    }
+    string_length = leb128::read::unsigned(&mut leb_bytes.as_slice()).unwrap() as usize;
+
+    let mut string_bytes = vec![];
+    for _ in 0..string_length {
+        string_bytes.push(iterator.next().unwrap());
+    }
+    String::from_utf8(string_bytes).unwrap()
+}

From 82e429895f20dc93b0e76c9bc7838f761b370bef Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 11 Dec 2023 00:30:26 -0500
Subject: [PATCH 052/136] added to_songs() for XmlLibrary

---
 Cargo.toml                                    |   3 +-
 src/lib.rs                                    |   4 +-
 src/music_player.rs                           |   2 +-
 src/music_storage/db_reader/common.rs         |   2 +-
 src/music_storage/db_reader/extern_library.rs |   1 -
 src/music_storage/db_reader/xml/reader.rs     | 158 ++++++++++++++++--
 src/music_storage/library.rs                  |   1 -
 src/music_storage/music_collection.rs         |   3 +-
 src/music_storage/playlist.rs                 |  25 +--
 9 files changed, 167 insertions(+), 32 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 76b0ba9..1673cfd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,4 +30,5 @@ glib = "0.18.3"
 crossbeam-channel = "0.5.8"
 crossbeam = "0.8.2"
 quick-xml = "0.31.0"
-leb128 = "0.2.5"
\ No newline at end of file
+leb128 = "0.2.5"
+urlencoding = "2.1.3"
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 32a8453..9981527 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,15 +1,15 @@
 pub mod music_storage {
     pub mod library;
+    pub mod music_collection;
     pub mod playlist;
     mod utils;
-    pub mod  music_collection;
     pub mod db_reader {
         pub mod foobar {
             pub mod reader;
         }
         pub mod musicbee {
-            pub mod utils;
             pub mod reader;
+            pub mod utils;
         }
         pub mod xml {
             pub mod reader;
diff --git a/src/music_player.rs b/src/music_player.rs
index ecafff6..4c0ee19 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -191,7 +191,7 @@ impl Player {
                             .unwrap()
                             .set_state(gst::State::Playing)
                             .unwrap();
-                    },
+                    }
                     gst::MessageView::Buffering(buffering) => {
                         let percent = buffering.percent();
                         if percent < 100 {
diff --git a/src/music_storage/db_reader/common.rs b/src/music_storage/db_reader/common.rs
index 80e0910..0849081 100644
--- a/src/music_storage/db_reader/common.rs
+++ b/src/music_storage/db_reader/common.rs
@@ -48,4 +48,4 @@ pub fn get_datetime(iterator: &mut std::vec::IntoIter<u8>, topbyte: bool) -> Dat
 
     Utc.timestamp_opt(unix_time_seconds, unix_time_nanos as u32)
         .unwrap()
-}
\ No newline at end of file
+}
diff --git a/src/music_storage/db_reader/extern_library.rs b/src/music_storage/db_reader/extern_library.rs
index 5a0add5..dc5c306 100644
--- a/src/music_storage/db_reader/extern_library.rs
+++ b/src/music_storage/db_reader/extern_library.rs
@@ -2,7 +2,6 @@ use std::path::PathBuf;
 
 use crate::music_storage::library::Song;
 
-
 pub trait ExternalLibrary {
     fn from_file(file: &PathBuf) -> Self;
     fn write(&self) {
diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/xml/reader.rs
index de2a057..0b1689c 100644
--- a/src/music_storage/db_reader/xml/reader.rs
+++ b/src/music_storage/db_reader/xml/reader.rs
@@ -1,19 +1,26 @@
+use file_format::FileFormat;
+use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt};
 use quick_xml::events::Event;
 use quick_xml::reader::Reader;
-
 use std::collections::{BTreeMap, HashMap};
+use std::fs::File;
 use std::io::Error;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::str::FromStr;
+use std::time::Duration as StdDur;
 use std::vec::Vec;
 
 use chrono::prelude::*;
 
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
+use crate::music_storage::library::{AlbumArt, Service, Song, Tag, URI};
+use crate::music_storage::utils;
+
+use urlencoding::decode;
 
 #[derive(Debug, Default, Clone)]
 pub struct XmlLibrary {
-    tracks: Vec<XMLSong>
+    tracks: Vec<XMLSong>,
 }
 impl XmlLibrary {
     fn new() -> Self {
@@ -21,7 +28,7 @@ impl XmlLibrary {
     }
 }
 impl ExternalLibrary for XmlLibrary {
-    fn from_file(&mut self, file: &PathBuf) -> Self {
+    fn from_file(file: &PathBuf) -> Self {
         let mut reader = Reader::from_file(file).unwrap();
         reader.trim_text(true);
         //count every event, for fun ig?
@@ -38,7 +45,6 @@ impl ExternalLibrary for XmlLibrary {
 
         let mut converted_songs: Vec<XMLSong> = Vec::new();
 
-
         let mut song_tags: HashMap<String, String> = HashMap::new();
         let mut key: String = String::new();
         let mut tagvalue: String = String::new();
@@ -78,7 +84,7 @@ impl ExternalLibrary for XmlLibrary {
                 Ok(Event::Text(e)) => {
                     if count < 17 && count != 10 {
                         continue;
-                    }else if skip {
+                    } else if skip {
                         skip = false;
                         continue;
                     }
@@ -113,10 +119,140 @@ impl ExternalLibrary for XmlLibrary {
         let elasped = now.elapsed();
         println!("\n\nXMLReader\n=========================================\n\nDone!\n{} songs grabbed in {:#?}\nIDs Skipped: {}", count3, elasped, count4);
         // dbg!(folder);
-        self.tracks.append(converted_songs.as_mut());
-        self.clone()
+        let mut lib = XmlLibrary::new();
+        lib.tracks.append(converted_songs.as_mut());
+        lib
+    }
+    fn to_songs(&self) -> Vec<crate::music_storage::library::Song> {
+        let mut count = 0;
+        let mut bun: Vec<Song> = Vec::new();
+        for track in &self.tracks {
+            //grab "other" tags
+            let mut tags_: BTreeMap<Tag, String> = BTreeMap::new();
+            for (key, val) in &track.tags {
+                tags_.insert(to_tag(key.clone()), val.clone());
+            }
+            //make the path readable
+            let loc_ = if track.location.contains("file://localhost/") {
+                decode(track.location.strip_prefix("file://localhost/").unwrap())
+                    .unwrap()
+                    .into_owned()
+            } else {
+                decode(track.location.as_str()).unwrap().into_owned()
+            };
+            let loc = loc_.as_str();
+            if File::open(loc).is_err() && !loc.contains("http") {
+                count += 1;
+                dbg!(loc);
+                continue;
+            }
+
+            let sug: URI = if track.location.contains("file://localhost/") {
+                URI::Local(PathBuf::from(
+                    decode(track.location.strip_prefix("file://localhost/").unwrap())
+                        .unwrap()
+                        .into_owned()
+                        .as_str(),
+                ))
+            } else {
+                URI::Remote(Service::None, decode(&track.location).unwrap().into_owned())
+            };
+            let dur = match get_duration(Path::new(&loc)) {
+                Ok(e) => e,
+                Err(e) => {
+                    dbg!(e);
+                    StdDur::from_secs(0)
+                }
+            };
+            let play_time_ = StdDur::from_secs(track.plays as u64 * dur.as_secs());
+
+            let ny: Song = Song {
+                location: sug,
+                plays: track.plays,
+                skips: 0,
+                favorited: track.favorited,
+                rating: track.rating,
+                format: match FileFormat::from_file(PathBuf::from(&loc)) {
+                    Ok(e) => Some(e),
+                    Err(_) => None,
+                },
+                duration: dur,
+                play_time: play_time_,
+                last_played: track.last_played,
+                date_added: track.date_added,
+                date_modified: track.date_modified,
+                album_art: match get_art(Path::new(&loc)) {
+                    Ok(e) => e,
+                    Err(_) => Vec::new(),
+                },
+                tags: tags_,
+            };
+            // dbg!(&ny.tags);
+            bun.push(ny);
+        }
+        println!("skipped: {}", count);
+        bun
     }
 }
+fn to_tag(string: String) -> Tag {
+    match string.to_lowercase().as_str() {
+        "name" => Tag::Title,
+        "album" => Tag::Album,
+        "artist" => Tag::Artist,
+        "album artist" => Tag::AlbumArtist,
+        "genre" => Tag::Genre,
+        "comment" => Tag::Comment,
+        "track" => Tag::Track,
+        "disc" => Tag::Disk,
+        _ => Tag::Key(string),
+    }
+}
+fn get_duration(file: &Path) -> Result<StdDur, lofty::LoftyError> {
+    let dur = match Probe::open(file)?.read() {
+        Ok(tagged_file) => tagged_file.properties().duration(),
+
+        Err(_) => StdDur::from_secs(0),
+    };
+    Ok(dur)
+}
+fn get_art(file: &Path) -> Result<Vec<AlbumArt>, LoftyError> {
+    let mut album_art: Vec<AlbumArt> = Vec::new();
+
+    let blank_tag = &lofty::Tag::new(TagType::Id3v2);
+    let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
+    let tagged_file: lofty::TaggedFile;
+
+    let tag = match Probe::open(file)?.options(normal_options).read() {
+        Ok(e) => {
+            tagged_file = e;
+            match tagged_file.primary_tag() {
+                Some(primary_tag) => primary_tag,
+
+                None => match tagged_file.first_tag() {
+                    Some(first_tag) => first_tag,
+                    None => blank_tag,
+                },
+            }
+        }
+        Err(_) => blank_tag,
+    };
+    let mut img = match utils::find_images(file) {
+        Ok(e) => e,
+        Err(_) => Vec::new(),
+    };
+    if !img.is_empty() {
+        album_art.append(img.as_mut());
+    }
+
+    for (i, _art) in tag.pictures().iter().enumerate() {
+        let new_art = AlbumArt::Embedded(i);
+
+        album_art.push(new_art)
+    }
+
+    Ok(album_art)
+}
+
 #[derive(Debug, Clone, Default)]
 pub struct XMLSong {
     pub id: i32,
@@ -138,8 +274,7 @@ impl XMLSong {
         Default::default()
     }
 
-
-    fn from_hashmap(map: &mut HashMap<String, String>) -> Result<XMLSong, Error> {
+    fn from_hashmap(map: &mut HashMap<String, String>) -> Result<XMLSong, LoftyError> {
         let mut song = XMLSong::new();
         //get the path with the first bit chopped off
         let path_: String = map.get_key_value("Location").unwrap().1.clone();
@@ -188,8 +323,7 @@ impl XMLSong {
     }
 }
 
-
-pub fn get_folder(file: &PathBuf) -> String {
+fn get_folder(file: &PathBuf) -> String {
     let mut reader = Reader::from_file(file).unwrap();
     reader.trim_text(true);
     //count every event, for fun ig?
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index d264bdf..99b8bb5 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -275,7 +275,6 @@ impl Album<'_> {
         self.artist
     }
 
-
     pub fn discs(&self) -> &BTreeMap<usize, Vec<&Song>> {
         &self.discs
     }
diff --git a/src/music_storage/music_collection.rs b/src/music_storage/music_collection.rs
index 8abcd71..9b1c7ca 100644
--- a/src/music_storage/music_collection.rs
+++ b/src/music_storage/music_collection.rs
@@ -1,8 +1,7 @@
-use crate::music_storage::library::{ AlbumArt, Song };
+use crate::music_storage::library::{AlbumArt, Song};
 
 pub trait MusicCollection {
     fn title(&self) -> &String;
     fn cover(&self) -> Option<&AlbumArt>;
     fn tracks(&self) -> Vec<&Song>;
 }
-
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 710e6fd..ce4366d 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -2,9 +2,12 @@ use chrono::Duration;
 use walkdir::Error;
 
 use crate::music_controller::config::Config;
-use std::{path::Path, default, thread::AccessError};
+use std::{default, path::Path, thread::AccessError};
 
-use super::{library::{AlbumArt, Song, self, Tag}, music_collection::MusicCollection};
+use super::{
+    library::{self, AlbumArt, Song, Tag},
+    music_collection::MusicCollection,
+};
 
 #[derive(Debug, Clone)]
 pub struct Playlist<'a> {
@@ -48,7 +51,7 @@ impl<'a> Playlist<'a> {
             for track in &self.tracks {
                 index += 1;
                 if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 {
-                    dbg!("Index gotted! ",index);
+                    dbg!("Index gotted! ", index);
                     return Some(index);
                 }
             }
@@ -75,17 +78,17 @@ impl MusicCollection for Playlist<'_> {
         }
     }
     fn tracks(&self) -> Vec<&Song> {
-            self.tracks.clone()
+        self.tracks.clone()
     }
 }
 impl Default for Playlist<'_> {
     fn default() -> Self {
-        Playlist { 
-            title: String::default(), 
-            cover: None, 
-            tracks: Vec::default(), 
-            play_count: -1, 
-            play_time: Duration::zero(), 
+        Playlist {
+            title: String::default(),
+            cover: None,
+            tracks: Vec::default(),
+            play_count: -1,
+            play_time: Duration::zero(),
         }
     }
-}
\ No newline at end of file
+}

From 4b55c2eaa8ccc271cd659fc7212482b1e03c9f60 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 11 Dec 2023 00:39:02 -0500
Subject: [PATCH 053/136] fixed tag reading in to_tag()

---
 src/music_storage/db_reader/xml/reader.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/xml/reader.rs
index 0b1689c..bc651c5 100644
--- a/src/music_storage/db_reader/xml/reader.rs
+++ b/src/music_storage/db_reader/xml/reader.rs
@@ -202,8 +202,8 @@ fn to_tag(string: String) -> Tag {
         "album artist" => Tag::AlbumArtist,
         "genre" => Tag::Genre,
         "comment" => Tag::Comment,
-        "track" => Tag::Track,
-        "disc" => Tag::Disk,
+        "track number" => Tag::Track,
+        "disc number" => Tag::Disk,
         _ => Tag::Key(string),
     }
 }

From cab60e1560be223379e3c65e0d8f7d9b41834bc2 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 11 Dec 2023 02:27:33 -0600
Subject: [PATCH 054/136] Made foobar reader compliant with new Trait

---
 src/music_storage/db_reader/foobar/reader.rs | 130 ++++++++++---------
 src/music_storage/db_reader/foobar/utils.rs  |  15 +++
 2 files changed, 83 insertions(+), 62 deletions(-)
 create mode 100644 src/music_storage/db_reader/foobar/utils.rs

diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
index 017d7fc..23872b0 100644
--- a/src/music_storage/db_reader/foobar/reader.rs
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -1,7 +1,10 @@
-use chrono::{DateTime, Utc};
+use std::collections::BTreeMap;
 use std::{fs::File, io::Read, path::PathBuf, time::Duration};
 
-use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec, get_datetime};
+use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec};
+use crate::music_storage::db_reader::extern_library::ExternalLibrary;
+use crate::music_storage::library::{Song, URI};
+use super::utils::meta_offset;
 
 const MAGIC: [u8; 16] = [
     0xE1, 0xA0, 0x9C, 0x91, 0xF8, 0x3C, 0x77, 0x42, 0x85, 0x2C, 0x3B, 0xCC, 0x14, 0x01, 0xD3, 0xF2,
@@ -9,69 +12,31 @@ const MAGIC: [u8; 16] = [
 
 #[derive(Debug)]
 pub struct FoobarPlaylist {
-    path: PathBuf,
     metadata: Vec<u8>,
+    songs: Vec<FoobarPlaylistTrack>,
 }
 
-#[derive(Debug, Default)]
-pub struct FoobarPlaylistTrack {
-    flags: i32,
-    file_name: String,
-    subsong_index: i32,
-    file_size: i64,
-    file_time: DateTime<Utc>,
-    duration: Duration,
-    rpg_album: u32,
-    rpg_track: u32,
-    rpk_album: u32,
-    rpk_track: u32,
-    entries: Vec<(String, String)>,
-}
-
-impl FoobarPlaylist {
-    pub fn new(path: &String) -> Self {
-        FoobarPlaylist {
-            path: PathBuf::from(path),
-            metadata: Vec::new(),
-        }
-    }
-
-    fn get_meta_offset(&self, offset: usize) -> String {
-        let mut result_vec = Vec::new();
-
-        let mut i = offset;
-        loop {
-            if self.metadata[i] == 0x00 {
-                break;
-            }
-
-            result_vec.push(self.metadata[i]);
-            i += 1;
-        }
-
-        String::from_utf8_lossy(&result_vec).into()
-    }
-
+impl ExternalLibrary for FoobarPlaylist {
     /// Reads the entire MusicBee library and returns relevant values
     /// as a `Vec` of `Song`s
-    pub fn read(&mut self) -> Result<Vec<FoobarPlaylistTrack>, Box<dyn std::error::Error>> {
-        let mut f = File::open(&self.path).unwrap();
+    fn from_file(file: &PathBuf) -> Self {
+        let mut f = File::open(file).unwrap();
         let mut buffer = Vec::new();
         let mut retrieved_songs: Vec<FoobarPlaylistTrack> = Vec::new();
 
         // Read the whole file
-        f.read_to_end(&mut buffer)?;
+        f.read_to_end(&mut buffer).unwrap();
 
         let mut buf_iter = buffer.into_iter();
 
         // Parse the header
         let magic = get_bytes::<16>(&mut buf_iter);
         if magic != MAGIC {
-            return Err("Magic bytes mismatch!".into());
+            panic!("Magic bytes mismatch!");
         }
 
         let meta_size = i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize;
-        self.metadata = get_bytes_vec(&mut buf_iter, meta_size);
+        let metadata = get_bytes_vec(&mut buf_iter, meta_size);
         let track_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
 
         // Read all the track fields
@@ -82,7 +47,7 @@ impl FoobarPlaylist {
             let has_padding = (0x04 & flags) != 0;
 
             let file_name_offset = i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize;
-            let file_name = self.get_meta_offset(file_name_offset);
+            let file_name = meta_offset(metadata, file_name_offset);
 
             let subsong_index = i32::from_le_bytes(get_bytes(&mut buf_iter));
 
@@ -98,17 +63,18 @@ impl FoobarPlaylist {
 
             let file_size = i64::from_le_bytes(get_bytes(&mut buf_iter));
 
-            let file_time = get_datetime(&mut buf_iter, false);
+            // TODO: Figure out how to make this work properly
+            let file_time = i64::from_le_bytes(get_bytes(&mut buf_iter));
 
             let duration = Duration::from_nanos(u64::from_le_bytes(get_bytes(&mut buf_iter)) / 100);
 
-            let rpg_album = u32::from_le_bytes(get_bytes(&mut buf_iter));
+            let rpg_album = f32::from_le_bytes(get_bytes(&mut buf_iter));
 
-            let rpg_track = u32::from_le_bytes(get_bytes(&mut buf_iter));
+            let rpg_track = f32::from_le_bytes(get_bytes(&mut buf_iter));
 
-            let rpk_album = u32::from_le_bytes(get_bytes(&mut buf_iter));
+            let rpk_album = f32::from_le_bytes(get_bytes(&mut buf_iter));
 
-            let rpk_track = u32::from_le_bytes(get_bytes(&mut buf_iter));
+            let rpk_track = f32::from_le_bytes(get_bytes(&mut buf_iter));
 
             get_bytes::<4>(&mut buf_iter);
 
@@ -121,8 +87,7 @@ impl FoobarPlaylist {
             for _ in 0..primary_count {
                 println!("{}", i32::from_le_bytes(get_bytes(&mut buf_iter)));
 
-                let key =
-                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let key = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
 
                 entries.push((key, String::new()));
             }
@@ -135,18 +100,15 @@ impl FoobarPlaylist {
             for i in 0..primary_count {
                 println!("primkey {i}");
 
-                let value =
-                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let value = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
 
                 entries[i as usize].1 = value;
             }
 
             // Get secondary Keys
             for _ in 0..secondary_count {
-                let key =
-                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
-                let value =
-                    self.get_meta_offset(i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let key = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let value = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
                 entries.push((key, value));
             }
 
@@ -171,6 +133,50 @@ impl FoobarPlaylist {
             retrieved_songs.push(track);
         }
 
-        Ok(retrieved_songs)
+        Self {
+            songs: retrieved_songs,
+            metadata,
+        }
+    }
+
+    fn to_songs(&self) -> Vec<Song> {
+        self.songs.iter().map(|song| song.find_song()).collect()
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct FoobarPlaylistTrack {
+    flags: i32,
+    file_name: String,
+    subsong_index: i32,
+    file_size: i64,
+    file_time: i64,
+    duration: Duration,
+    rpg_album: f32,
+    rpg_track: f32,
+    rpk_album: f32,
+    rpk_track: f32,
+    entries: Vec<(String, String)>,
+}
+
+impl FoobarPlaylistTrack {
+    fn find_song(&self) -> Song {
+        let location = URI::Local(self.file_name.into());
+
+        Song {
+            location,
+            plays: 0,
+            skips: 0,
+            favorited: false,
+            rating: None,
+            format: None,
+            duration: self.duration,
+            play_time: Duration::from_secs(0),
+            last_played: None,
+            date_added: None,
+            date_modified: None,
+            album_art: Vec::new(),
+            tags: BTreeMap::new(),
+        }
     }
 }
diff --git a/src/music_storage/db_reader/foobar/utils.rs b/src/music_storage/db_reader/foobar/utils.rs
new file mode 100644
index 0000000..62ee270
--- /dev/null
+++ b/src/music_storage/db_reader/foobar/utils.rs
@@ -0,0 +1,15 @@
+pub fn meta_offset(metadata: Vec<u8>, offset: usize) -> String {
+    let mut result_vec = Vec::new();
+
+    let mut i = offset;
+    loop {
+        if metadata[i] == 0x00 {
+            break;
+        }
+
+        result_vec.push(metadata[i]);
+        i += 1;
+    }
+
+    String::from_utf8_lossy(&result_vec).into()
+}

From 5000d24a77b841b764d599e1c5d8fdb1175039f3 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 11 Dec 2023 02:30:03 -0600
Subject: [PATCH 055/136] Fixed errors in foobar reader

---
 src/lib.rs                                   | 1 +
 src/music_storage/db_reader/foobar/reader.rs | 6 +++---
 src/music_storage/db_reader/foobar/utils.rs  | 2 +-
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 9981527..c20df16 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -6,6 +6,7 @@ pub mod music_storage {
     pub mod db_reader {
         pub mod foobar {
             pub mod reader;
+            pub mod utils;
         }
         pub mod musicbee {
             pub mod reader;
diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
index 23872b0..165c08b 100644
--- a/src/music_storage/db_reader/foobar/reader.rs
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -36,7 +36,7 @@ impl ExternalLibrary for FoobarPlaylist {
         }
 
         let meta_size = i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize;
-        let metadata = get_bytes_vec(&mut buf_iter, meta_size);
+        let metadata = &get_bytes_vec(&mut buf_iter, meta_size);
         let track_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
 
         // Read all the track fields
@@ -135,7 +135,7 @@ impl ExternalLibrary for FoobarPlaylist {
 
         Self {
             songs: retrieved_songs,
-            metadata,
+            metadata: metadata.clone(),
         }
     }
 
@@ -161,7 +161,7 @@ pub struct FoobarPlaylistTrack {
 
 impl FoobarPlaylistTrack {
     fn find_song(&self) -> Song {
-        let location = URI::Local(self.file_name.into());
+        let location = URI::Local(self.file_name.clone().into());
 
         Song {
             location,
diff --git a/src/music_storage/db_reader/foobar/utils.rs b/src/music_storage/db_reader/foobar/utils.rs
index 62ee270..4b02f02 100644
--- a/src/music_storage/db_reader/foobar/utils.rs
+++ b/src/music_storage/db_reader/foobar/utils.rs
@@ -1,4 +1,4 @@
-pub fn meta_offset(metadata: Vec<u8>, offset: usize) -> String {
+pub fn meta_offset(metadata: &Vec<u8>, offset: usize) -> String {
     let mut result_vec = Vec::new();
 
     let mut i = offset;

From 045cea90bf83da4bc96896790f5c2fb8aae94cbb Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 11 Dec 2023 02:42:13 -0600
Subject: [PATCH 056/136] Applied clippy suggestions

---
 src/music_player.rs                            | 18 ++++++++----------
 src/music_storage/db_reader/common.rs          | 12 +++++-------
 src/music_storage/db_reader/extern_library.rs  |  4 ++--
 src/music_storage/db_reader/foobar/reader.rs   |  4 ++--
 src/music_storage/db_reader/foobar/utils.rs    |  2 +-
 src/music_storage/db_reader/musicbee/reader.rs |  2 +-
 src/music_storage/db_reader/xml/reader.rs      |  2 +-
 7 files changed, 20 insertions(+), 24 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index 4c0ee19..aed3eaf 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -179,7 +179,7 @@ impl Player {
                 match msg.view() {
                     gst::MessageView::Eos(_) => {}
                     gst::MessageView::StreamStart(_) => println!("Stream start"),
-                    gst::MessageView::Error(e) => {
+                    gst::MessageView::Error(_) => {
                         playbin_bus_ctrl
                             .write()
                             .unwrap()
@@ -201,7 +201,7 @@ impl Player {
                                 .unwrap()
                                 .set_state(gst::State::Paused)
                                 .unwrap();
-                        } else if *paused_bus_ctrl.read().unwrap() == false {
+                        } else if !(*paused_bus_ctrl.read().unwrap()) {
                             *buffer_bus_ctrl.write().unwrap() = None;
                             playbin_bus_ctrl
                                 .write()
@@ -377,19 +377,17 @@ impl Player {
 
     /// Seek absolutely
     pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
-        let start;
-        if self.start.read().unwrap().is_none() {
+        let start = if self.start.read().unwrap().is_none() {
             return Err("Failed to seek: No START time".into());
         } else {
-            start = self.start.read().unwrap().unwrap();
-        }
+            self.start.read().unwrap().unwrap()
+        };
 
-        let end;
-        if self.end.read().unwrap().is_none() {
+        let end = if self.end.read().unwrap().is_none() {
             return Err("Failed to seek: No END time".into());
         } else {
-            end = self.end.read().unwrap().unwrap();
-        }
+            self.end.read().unwrap().unwrap()
+        };
 
         let adjusted_target = target_pos + start;
         let clamped_target = adjusted_target.clamp(start, end);
diff --git a/src/music_storage/db_reader/common.rs b/src/music_storage/db_reader/common.rs
index 0849081..368d86e 100644
--- a/src/music_storage/db_reader/common.rs
+++ b/src/music_storage/db_reader/common.rs
@@ -1,15 +1,13 @@
-use std::path::PathBuf;
-
 use chrono::{DateTime, TimeZone, Utc};
 
 pub fn get_bytes<const S: usize>(iterator: &mut std::vec::IntoIter<u8>) -> [u8; S] {
     let mut bytes = [0; S];
 
-    for i in 0..S {
-        bytes[i] = iterator.next().unwrap();
+    for byte in bytes.iter_mut().take(S) {
+        *byte = iterator.next().unwrap();
     }
 
-    return bytes;
+    bytes
 }
 
 pub fn get_bytes_vec(iterator: &mut std::vec::IntoIter<u8>, number: usize) -> Vec<u8> {
@@ -19,7 +17,7 @@ pub fn get_bytes_vec(iterator: &mut std::vec::IntoIter<u8>, number: usize) -> Ve
         bytes.push(iterator.next().unwrap());
     }
 
-    return bytes;
+    bytes
 }
 
 /// Converts the windows DateTime into Chrono DateTime
@@ -28,7 +26,7 @@ pub fn get_datetime(iterator: &mut std::vec::IntoIter<u8>, topbyte: bool) -> Dat
 
     if topbyte {
         // Zero the topmost byte
-        datetime_i64 = datetime_i64 & 0x00FFFFFFFFFFFFFFF;
+        datetime_i64 &= 0x00FFFFFFFFFFFFFFF;
     }
 
     if datetime_i64 <= 0 {
diff --git a/src/music_storage/db_reader/extern_library.rs b/src/music_storage/db_reader/extern_library.rs
index dc5c306..4312c4a 100644
--- a/src/music_storage/db_reader/extern_library.rs
+++ b/src/music_storage/db_reader/extern_library.rs
@@ -1,9 +1,9 @@
-use std::path::PathBuf;
+use std::path::Path;
 
 use crate::music_storage::library::Song;
 
 pub trait ExternalLibrary {
-    fn from_file(file: &PathBuf) -> Self;
+    fn from_file(file: &Path) -> Self;
     fn write(&self) {
         unimplemented!();
     }
diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
index 165c08b..218d6c0 100644
--- a/src/music_storage/db_reader/foobar/reader.rs
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -1,5 +1,5 @@
 use std::collections::BTreeMap;
-use std::{fs::File, io::Read, path::PathBuf, time::Duration};
+use std::{fs::File, io::Read, path::Path, time::Duration};
 
 use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec};
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
@@ -19,7 +19,7 @@ pub struct FoobarPlaylist {
 impl ExternalLibrary for FoobarPlaylist {
     /// Reads the entire MusicBee library and returns relevant values
     /// as a `Vec` of `Song`s
-    fn from_file(file: &PathBuf) -> Self {
+    fn from_file(file: &Path) -> Self {
         let mut f = File::open(file).unwrap();
         let mut buffer = Vec::new();
         let mut retrieved_songs: Vec<FoobarPlaylistTrack> = Vec::new();
diff --git a/src/music_storage/db_reader/foobar/utils.rs b/src/music_storage/db_reader/foobar/utils.rs
index 4b02f02..278aa1a 100644
--- a/src/music_storage/db_reader/foobar/utils.rs
+++ b/src/music_storage/db_reader/foobar/utils.rs
@@ -1,4 +1,4 @@
-pub fn meta_offset(metadata: &Vec<u8>, offset: usize) -> String {
+pub fn meta_offset(metadata: &[u8], offset: usize) -> String {
     let mut result_vec = Vec::new();
 
     let mut i = offset;
diff --git a/src/music_storage/db_reader/musicbee/reader.rs b/src/music_storage/db_reader/musicbee/reader.rs
index 0a5cac6..9811862 100644
--- a/src/music_storage/db_reader/musicbee/reader.rs
+++ b/src/music_storage/db_reader/musicbee/reader.rs
@@ -29,7 +29,7 @@ impl MusicBeeDatabase {
         // Get the song count from the first 4 bytes
         // and then right shift it by 8 for some reason
         let mut database_song_count = i32::from_le_bytes(get_bytes(&mut buf_iter));
-        database_song_count = database_song_count >> 8;
+        database_song_count >>= 8;
 
         let mut song_count = 0;
         loop {
diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/xml/reader.rs
index bc651c5..fe6bc64 100644
--- a/src/music_storage/db_reader/xml/reader.rs
+++ b/src/music_storage/db_reader/xml/reader.rs
@@ -28,7 +28,7 @@ impl XmlLibrary {
     }
 }
 impl ExternalLibrary for XmlLibrary {
-    fn from_file(file: &PathBuf) -> Self {
+    fn from_file(file: &Path) -> Self {
         let mut reader = Reader::from_file(file).unwrap();
         reader.trim_text(true);
         //count every event, for fun ig?

From 7e041bddd0103e1278ffb603cac70ba9bfd34e25 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 11 Dec 2023 13:16:12 -0500
Subject: [PATCH 057/136] removed some warnings

---
 src/music_storage/db_reader/xml/reader.rs | 64 +++++++++++------------
 1 file changed, 31 insertions(+), 33 deletions(-)

diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/xml/reader.rs
index bc651c5..70e2b77 100644
--- a/src/music_storage/db_reader/xml/reader.rs
+++ b/src/music_storage/db_reader/xml/reader.rs
@@ -4,7 +4,6 @@ use quick_xml::events::Event;
 use quick_xml::reader::Reader;
 use std::collections::{BTreeMap, HashMap};
 use std::fs::File;
-use std::io::Error;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
 use std::time::Duration as StdDur;
@@ -107,7 +106,6 @@ impl ExternalLibrary for XmlLibrary {
                                 panic!("Key not selected?!")
                             }
                         }
-                        _ => panic!("WHAT DID YOU JUST DO?!🐰🐰🐰🐰"),
                     }
                 }
                 Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
@@ -323,34 +321,34 @@ impl XMLSong {
     }
 }
 
-fn get_folder(file: &PathBuf) -> String {
-    let mut reader = Reader::from_file(file).unwrap();
-    reader.trim_text(true);
-    //count every event, for fun ig?
-    let mut count = 0;
-    let mut buf = Vec::new();
-    let mut folder = String::new();
-    loop {
-        match reader.read_event_into(&mut buf) {
-            Ok(Event::Start(_)) => {
-                count += 1;
-            }
-            Ok(Event::Text(e)) => {
-                if count == 10 {
-                    folder = String::from(
-                        e.unescape()
-                            .unwrap()
-                            .to_string()
-                            .strip_prefix("file://localhost/")
-                            .unwrap(),
-                    );
-                    return folder;
-                }
-            }
-            Err(_e) => {
-                panic!("oh no! something happened in the public function `get_reader_from_xml()!`")
-            }
-            _ => (),
-        }
-    }
-}
+// fn get_folder(file: &PathBuf) -> String {
+//     let mut reader = Reader::from_file(file).unwrap();
+//     reader.trim_text(true);
+//     //count every event, for fun ig?
+//     let mut count = 0;
+//     let mut buf = Vec::new();
+//     let mut folder = String::new();
+//     loop {
+//         match reader.read_event_into(&mut buf) {
+//             Ok(Event::Start(_)) => {
+//                 count += 1;
+//             }
+//             Ok(Event::Text(e)) => {
+//                 if count == 10 {
+//                     folder = String::from(
+//                         e.unescape()
+//                             .unwrap()
+//                             .to_string()
+//                             .strip_prefix("file://localhost/")
+//                             .unwrap(),
+//                     );
+//                     return folder;
+//                 }
+//             }
+//             Err(_e) => {
+//                 panic!("oh no! something happened in the public function `get_reader_from_xml()!`")
+//             }
+//             _ => (),
+//         }
+//     }
+// }

From d8be0535c26cf0bb88d1f39875ea4e5f0fc7c793 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Thu, 14 Dec 2023 16:40:04 -0600
Subject: [PATCH 058/136] Changed the method to lock the `playbin` instance

---
 src/music_player.rs | 45 +++++++++++++++++++++++++++++++++------------
 1 file changed, 33 insertions(+), 12 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index aed3eaf..20f9977 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -3,7 +3,7 @@
 use crate::music_storage::library::{Tag, URI};
 use crossbeam_channel::bounded;
 use std::error::Error;
-use std::sync::{Arc, RwLock};
+use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
 
 // GStreamer things
 use glib::FlagsClass;
@@ -292,6 +292,30 @@ impl Player {
         }
     }
 
+    /// Gets a mutable reference to the playbin element
+    fn playbin_mut(
+        &mut self,
+    ) -> Result<RwLockWriteGuard<gst::Element>, std::sync::PoisonError<RwLockWriteGuard<'_, Element>>>
+    {
+        let element = match self.playbin.write() {
+            Ok(element) => element,
+            Err(err) => return Err(err),
+        };
+        Ok(element)
+    }
+
+    /// Gets a read-only reference to the playbin element
+    fn playbin(
+        &self,
+    ) -> Result<RwLockReadGuard<gst::Element>, std::sync::PoisonError<RwLockReadGuard<'_, Element>>>
+    {
+        let element = match self.playbin.read() {
+            Ok(element) => element,
+            Err(err) => return Err(err),
+        };
+        Ok(element)
+    }
+
     /// Set the playback volume, accepts a float from 0 to 1
     pub fn set_volume(&mut self, volume: f64) {
         self.volume = volume.clamp(0.0, 1.0);
@@ -301,7 +325,7 @@ impl Player {
     /// Set volume of the internal playbin player, can be
     /// used to bypass the main volume control for seeking
     fn set_gstreamer_volume(&mut self, volume: f64) {
-        self.playbin.write().unwrap().set_property("volume", volume)
+        self.playbin_mut().unwrap().set_property("volume", volume)
     }
 
     /// Returns the current volume level, a float from 0 to 1
@@ -310,7 +334,7 @@ impl Player {
     }
 
     fn set_state(&mut self, state: gst::State) -> Result<(), gst::StateChangeError> {
-        self.playbin.write().unwrap().set_state(state)?;
+        self.playbin_mut().unwrap().set_state(state)?;
 
         Ok(())
     }
@@ -338,7 +362,7 @@ impl Player {
 
     /// Check if playback is paused
     pub fn is_paused(&mut self) -> bool {
-        self.playbin.read().unwrap().current_state() == gst::State::Paused
+        self.playbin().unwrap().current_state() == gst::State::Paused
     }
 
     /// Get the current playback position of the player
@@ -356,8 +380,7 @@ impl Player {
     }
 
     pub fn raw_duration(&self) -> Option<Duration> {
-        self.playbin
-            .read()
+        self.playbin()
             .unwrap()
             .query_duration::<ClockTime>()
             .map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
@@ -396,8 +419,7 @@ impl Player {
             ClockTime::from_useconds(clamped_target.num_microseconds().unwrap() as u64);
 
         self.set_gstreamer_volume(0.0);
-        self.playbin
-            .write()
+        self.playbin_mut()
             .unwrap()
             .seek_simple(gst::SeekFlags::FLUSH, seek_pos_clock)?;
         self.set_gstreamer_volume(self.volume);
@@ -407,13 +429,13 @@ impl Player {
     /// Get the current state of the playback
     pub fn state(&mut self) -> PlayerState {
         match *self.buffer.read().unwrap() {
-            None => self.playbin.read().unwrap().current_state().into(),
+            None => self.playbin().unwrap().current_state().into(),
             Some(value) => PlayerState::Buffering(value),
         }
     }
 
     pub fn property(&self, property: &str) -> glib::Value {
-        self.playbin.read().unwrap().property_value(property)
+        self.playbin().unwrap().property_value(property)
     }
 
     /// Stop the playback entirely
@@ -432,8 +454,7 @@ impl Player {
 impl Drop for Player {
     /// Cleans up `GStreamer` pipeline when `Backend` is dropped.
     fn drop(&mut self) {
-        self.playbin
-            .write()
+        self.playbin_mut()
             .unwrap()
             .set_state(gst::State::Null)
             .expect("Unable to set the pipeline to the `Null` state");

From eb9b2bf2304b209144185a236ee0ea84df889eb9 Mon Sep 17 00:00:00 2001
From: MrDulfin <74484768+MrDulfin@users.noreply.github.com>
Date: Thu, 14 Dec 2023 18:04:55 -0500
Subject: [PATCH 059/136] Create README.md

---
 README.md | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 README.md

diff --git a/README.md b/README.md
new file mode 100644
index 0000000..751669c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,3 @@
+# dango-core
+
+This is the backend crate for the [Dango Music Player](https://github.com/Dangoware/dango-music-player)

From 42ff88578e9a7d507f0af7faa2b02c0207d016c1 Mon Sep 17 00:00:00 2001
From: MrDulfin <74484768+MrDulfin@users.noreply.github.com>
Date: Thu, 14 Dec 2023 18:17:45 -0500
Subject: [PATCH 060/136] Create LICENSE

---
 LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 661 insertions(+)
 create mode 100644 LICENSE

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0ad25db
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,661 @@
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published
+    by the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<https://www.gnu.org/licenses/>.

From ef78c36982adc6c747a5e08c47183b02eb0d806d Mon Sep 17 00:00:00 2001
From: MrDulfin <74484768+MrDulfin@users.noreply.github.com>
Date: Thu, 14 Dec 2023 18:19:03 -0500
Subject: [PATCH 061/136] added gitignore

---
 .gitignore | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b4ff1a9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+target/
+
+music_database*
+*.db3*
+config.toml
+Cargo.lock
+*.kate-swp*

From 72da619f8e44b07ad1384226e0caafba76ea761a Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 25 Dec 2023 14:22:57 -0500
Subject: [PATCH 062/136] added `to_m3u8()` and placeholders for Playlist

---
 .gitignore                                |  2 +
 Cargo.toml                                |  3 +-
 src/music_storage/db_reader/xml/reader.rs |  3 ++
 src/music_storage/playlist.rs             | 59 ++++++++++++++++++++---
 4 files changed, 60 insertions(+), 7 deletions(-)

diff --git a/.gitignore b/.gitignore
index b4ff1a9..62c5a2f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@ music_database*
 config.toml
 Cargo.lock
 *.kate-swp*
+*.m3u
+*.m3u8
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 1673cfd..18d5519 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,4 +31,5 @@ crossbeam-channel = "0.5.8"
 crossbeam = "0.8.2"
 quick-xml = "0.31.0"
 leb128 = "0.2.5"
-urlencoding = "2.1.3"
\ No newline at end of file
+urlencoding = "2.1.3"
+m3u8-rs = "5.0.5"
diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/xml/reader.rs
index faf5f0c..50beeac 100644
--- a/src/music_storage/db_reader/xml/reader.rs
+++ b/src/music_storage/db_reader/xml/reader.rs
@@ -25,6 +25,9 @@ impl XmlLibrary {
     fn new() -> Self {
         Default::default()
     }
+    pub fn tracks(self) -> Vec<XMLSong> {
+        self.tracks
+    }
 }
 impl ExternalLibrary for XmlLibrary {
     fn from_file(file: &Path) -> Self {
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index ce4366d..1c29bb6 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -2,12 +2,19 @@ use chrono::Duration;
 use walkdir::Error;
 
 use crate::music_controller::config::Config;
-use std::{default, path::Path, thread::AccessError};
-
 use super::{
     library::{self, AlbumArt, Song, Tag},
     music_collection::MusicCollection,
+    db_reader::extern_library::ExternalLibrary
 };
+use crate::music_storage::db_reader::xml::reader::{XmlLibrary};
+
+use std::{default, path::Path, path::PathBuf, thread::AccessError};
+use std::io::Read;
+
+use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
+// use nom::IResult;
+
 
 #[derive(Debug, Clone)]
 pub struct Playlist<'a> {
@@ -47,7 +54,7 @@ impl<'a> Playlist<'a> {
     }
     pub fn get_index(&self, song_name: &str) -> Option<usize> {
         let mut index = 0;
-        if self.contains(&Tag::Title, song_name) {
+        if self.contains_value(&Tag::Title, song_name) {
             for track in &self.tracks {
                 index += 1;
                 if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 {
@@ -58,14 +65,41 @@ impl<'a> Playlist<'a> {
         }
         None
     }
-    pub fn contains(&self, tag: &Tag, title: &str) -> bool {
+    pub fn contains_value(&self, tag: &Tag, value: &str) -> bool {
         for track in &self.tracks {
-            if title == track.tags.get_key_value(tag).unwrap().1 {
+            if value == track.tags.get_key_value(tag).unwrap().1 {
                 return true;
             }
         }
         false
     }
+    pub fn to_m3u8(&mut self) {
+        let seg = &self.tracks.iter().map({
+            |track|
+            MediaSegment {
+                uri: track.location.to_string().into(),
+                duration: track.duration.as_millis() as f32,
+                title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.to_string().into()),
+                ..Default::default()
+            }
+        }).collect::<Vec<MediaSegment>>();
+
+        let m3u8 = MediaPlaylist {
+            version: Some(6),
+            target_duration: 3.0,
+            media_sequence: 338559,
+            discontinuity_sequence: 1234,
+            end_list: true,
+            playlist_type: Some(MediaPlaylistType::Vod),
+            segments: seg.clone(),
+            ..Default::default()
+        };
+        let mut file = std::fs::OpenOptions::new().read(true).create(true).write(true).open("F:\\Dango Music Player\\playlist.m3u8").unwrap();
+        m3u8.write_to(&mut file).unwrap();
+    }
+    pub fn from_file(file: std::fs::File) -> Playlist<'a> {
+        todo!()
+    }
 }
 impl MusicCollection for Playlist<'_> {
     fn title(&self) -> &String {
@@ -87,8 +121,21 @@ impl Default for Playlist<'_> {
             title: String::default(),
             cover: None,
             tracks: Vec::default(),
-            play_count: -1,
+            play_count: 0,
             play_time: Duration::zero(),
         }
     }
 }
+
+#[test]
+fn list_to_m3u8() {
+    let lib = XmlLibrary::from_file(Path::new("F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml"));
+    let mut a = Playlist::new();
+    let c = lib.to_songs();
+    let mut b = c.iter().map({
+        |song|
+        song
+    }).collect::<Vec<&Song>>();
+    a.tracks.append(&mut b);
+    a.to_m3u8()
+}
\ No newline at end of file

From c78f77165e5f4b2f2d3b199c0193b4027a5cfdaa Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Tue, 9 Jan 2024 13:38:54 -0500
Subject: [PATCH 063/136] Cargo formatted and made `query_uri()` public

---
 src/music_storage/db_reader/foobar/reader.rs | 22 ++++++--
 src/music_storage/library.rs                 |  2 +-
 src/music_storage/playlist.rs                | 56 ++++++++++++--------
 3 files changed, 53 insertions(+), 27 deletions(-)

diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
index 218d6c0..3cb417d 100644
--- a/src/music_storage/db_reader/foobar/reader.rs
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -1,10 +1,10 @@
 use std::collections::BTreeMap;
 use std::{fs::File, io::Read, path::Path, time::Duration};
 
+use super::utils::meta_offset;
 use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec};
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
 use crate::music_storage::library::{Song, URI};
-use super::utils::meta_offset;
 
 const MAGIC: [u8; 16] = [
     0xE1, 0xA0, 0x9C, 0x91, 0xF8, 0x3C, 0x77, 0x42, 0x85, 0x2C, 0x3B, 0xCC, 0x14, 0x01, 0xD3, 0xF2,
@@ -87,7 +87,10 @@ impl ExternalLibrary for FoobarPlaylist {
             for _ in 0..primary_count {
                 println!("{}", i32::from_le_bytes(get_bytes(&mut buf_iter)));
 
-                let key = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let key = meta_offset(
+                    metadata,
+                    i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize,
+                );
 
                 entries.push((key, String::new()));
             }
@@ -100,15 +103,24 @@ impl ExternalLibrary for FoobarPlaylist {
             for i in 0..primary_count {
                 println!("primkey {i}");
 
-                let value = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let value = meta_offset(
+                    metadata,
+                    i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize,
+                );
 
                 entries[i as usize].1 = value;
             }
 
             // Get secondary Keys
             for _ in 0..secondary_count {
-                let key = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
-                let value = meta_offset(metadata, i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize);
+                let key = meta_offset(
+                    metadata,
+                    i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize,
+                );
+                let value = meta_offset(
+                    metadata,
+                    i32::from_le_bytes(get_bytes(&mut buf_iter)) as usize,
+                );
                 entries.push((key, value));
             }
 
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 99b8bb5..c3458cf 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -368,7 +368,7 @@ 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)> {
+    pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
         let result = self
             .library
             .par_iter()
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 1c29bb6..6b3dc96 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,21 +1,20 @@
 use chrono::Duration;
 use walkdir::Error;
 
-use crate::music_controller::config::Config;
 use super::{
+    db_reader::extern_library::ExternalLibrary,
     library::{self, AlbumArt, Song, Tag},
     music_collection::MusicCollection,
-    db_reader::extern_library::ExternalLibrary
 };
-use crate::music_storage::db_reader::xml::reader::{XmlLibrary};
+use crate::music_controller::config::Config;
+use crate::music_storage::db_reader::xml::reader::XmlLibrary;
 
-use std::{default, path::Path, path::PathBuf, thread::AccessError};
 use std::io::Read;
+use std::{default, path::Path, path::PathBuf, thread::AccessError};
 
 use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
 // use nom::IResult;
 
-
 #[derive(Debug, Clone)]
 pub struct Playlist<'a> {
     title: String,
@@ -74,15 +73,26 @@ impl<'a> Playlist<'a> {
         false
     }
     pub fn to_m3u8(&mut self) {
-        let seg = &self.tracks.iter().map({
-            |track|
-            MediaSegment {
-                uri: track.location.to_string().into(),
-                duration: track.duration.as_millis() as f32,
-                title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.to_string().into()),
-                ..Default::default()
-            }
-        }).collect::<Vec<MediaSegment>>();
+        let seg = &self
+            .tracks
+            .iter()
+            .map({
+                |track| MediaSegment {
+                    uri: track.location.to_string().into(),
+                    duration: track.duration.as_millis() as f32,
+                    title: Some(
+                        track
+                            .tags
+                            .get_key_value(&Tag::Title)
+                            .unwrap()
+                            .1
+                            .to_string()
+                            .into(),
+                    ),
+                    ..Default::default()
+                }
+            })
+            .collect::<Vec<MediaSegment>>();
 
         let m3u8 = MediaPlaylist {
             version: Some(6),
@@ -94,7 +104,12 @@ impl<'a> Playlist<'a> {
             segments: seg.clone(),
             ..Default::default()
         };
-        let mut file = std::fs::OpenOptions::new().read(true).create(true).write(true).open("F:\\Dango Music Player\\playlist.m3u8").unwrap();
+        let mut file = std::fs::OpenOptions::new()
+            .read(true)
+            .create(true)
+            .write(true)
+            .open("F:\\Dango Music Player\\playlist.m3u8")
+            .unwrap();
         m3u8.write_to(&mut file).unwrap();
     }
     pub fn from_file(file: std::fs::File) -> Playlist<'a> {
@@ -129,13 +144,12 @@ impl Default for Playlist<'_> {
 
 #[test]
 fn list_to_m3u8() {
-    let lib = XmlLibrary::from_file(Path::new("F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml"));
+    let lib = XmlLibrary::from_file(Path::new(
+        "F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml",
+    ));
     let mut a = Playlist::new();
     let c = lib.to_songs();
-    let mut b = c.iter().map({
-        |song|
-        song
-    }).collect::<Vec<&Song>>();
+    let mut b = c.iter().map({ |song| song }).collect::<Vec<&Song>>();
     a.tracks.append(&mut b);
     a.to_m3u8()
-}
\ No newline at end of file
+}

From 4fbda80397c1e01e9b5f7b348b9d0974d1ada78f Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Fri, 12 Jan 2024 23:32:25 -0500
Subject: [PATCH 064/136] test

---
 src/music_storage/library.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index c3458cf..706a05d 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -9,7 +9,7 @@ use std::error::Error;
 use std::ops::ControlFlow::{Break, Continue};
 
 // Files
-use file_format::{FileFormat, Kind};
+use file_format::{ FileFormat, Kind };
 use glib::filename_to_uri;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;

From 278f37dd6a7bd7d680c0abb8a19a7a8145e8b04d Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 12 Jan 2024 22:46:33 -0600
Subject: [PATCH 065/136] Made some changes to error handling in the `Player`
 and other minor changes

---
 Cargo.toml                     |  1 +
 src/music_controller/config.rs |  4 +--
 src/music_player.rs            | 45 +++++++++++++++++++++-------------
 src/music_storage/library.rs   |  4 +--
 4 files changed, 33 insertions(+), 21 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 18d5519..bb0746c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -33,3 +33,4 @@ quick-xml = "0.31.0"
 leb128 = "0.2.5"
 urlencoding = "2.1.3"
 m3u8-rs = "5.0.5"
+thiserror = "1.0.56"
diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
index 2014de7..a9523d3 100644
--- a/src/music_controller/config.rs
+++ b/src/music_controller/config.rs
@@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
 
 #[derive(Serialize, Deserialize, PartialEq, Eq)]
 pub struct Config {
-    pub db_path: Box<PathBuf>,
+    pub db_path: PathBuf,
 }
 
 impl Default for Config {
@@ -14,7 +14,7 @@ impl Default for Config {
         let path = PathBuf::from("./music_database");
 
         Config {
-            db_path: Box::new(path),
+            db_path: path,
         }
     }
 }
diff --git a/src/music_player.rs b/src/music_player.rs
index 20f9977..85563e9 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -1,6 +1,6 @@
 // Crate things
 //use crate::music_controller::config::Config;
-use crate::music_storage::library::{Tag, URI};
+use crate::music_storage::library::URI;
 use crossbeam_channel::bounded;
 use std::error::Error;
 use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
@@ -11,8 +11,9 @@ use gst::{ClockTime, Element};
 use gstreamer as gst;
 use gstreamer::prelude::*;
 
-// Time things
+// Extra things
 use chrono::Duration;
+use thiserror::Error;
 
 #[derive(Debug)]
 pub enum PlayerCmd {
@@ -59,6 +60,22 @@ impl TryInto<gst::State> for PlayerState {
     type Error = Box<dyn Error>;
 }
 
+#[derive(Error, Debug)]
+pub enum PlayerError {
+    #[error("player initialization failed")]
+    Init(#[from] glib::Error),
+    #[error("element factory failed to create playbin3")]
+    Factory(#[from] glib::BoolError),
+    #[error("could not change playback state")]
+    StateChange(#[from] gst::StateChangeError),
+    #[error("failed to build gstreamer item")]
+    Build,
+    #[error("poison error")]
+    Poison,
+    #[error("general player error")]
+    General,
+}
+
 /// An instance of a music player with a GStreamer backend
 pub struct Player {
     source: Option<URI>,
@@ -73,22 +90,16 @@ pub struct Player {
     paused: Arc<RwLock<bool>>,
 }
 
-impl Default for Player {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
 impl Player {
-    pub fn new() -> Self {
-        // Initialize GStreamer
-        gst::init().unwrap();
+    pub fn new() -> Result<Self, PlayerError> {
+        // Initialize GStreamer, maybe figure out how to nicely fail here
+        gst::init()?;
         let ctx = glib::MainContext::default();
         let _guard = ctx.acquire();
         let mainloop = glib::MainLoop::new(Some(&ctx), false);
 
         let playbin_arc = Arc::new(RwLock::new(
-            gst::ElementFactory::make("playbin3").build().unwrap(),
+            gst::ElementFactory::make("playbin3").build()?,
         ));
 
         let playbin = playbin_arc.clone();
@@ -99,13 +110,13 @@ impl Player {
         // Set up the Playbin flags to only play audio
         let flags = flags_class
             .builder_with_value(flags)
-            .unwrap()
+            .ok_or(PlayerError::Build)?
             .set_by_nick("audio")
             .set_by_nick("download")
             .unset_by_nick("video")
             .unset_by_nick("text")
             .build()
-            .unwrap();
+            .ok_or(PlayerError::Build)?;
 
         playbin
             .write()
@@ -125,7 +136,7 @@ impl Player {
         let start_update = Arc::clone(&start);
         let end_update = Arc::clone(&end);
         let (message_tx, message_rx) = bounded(1); //TODO: Maybe figure out a better method than making this bounded
-        std::thread::spawn(move || {
+        std::thread::spawn(move || { //TODO: Figure out how to return errors nicely in threads
             loop {
                 let mut pos_temp = playbin_arc
                     .read()
@@ -223,7 +234,7 @@ impl Player {
         });
 
         let source = None;
-        Self {
+        Ok(Self {
             source,
             playbin,
             message_rx,
@@ -233,7 +244,7 @@ impl Player {
             paused,
             position,
             buffer,
-        }
+        })
     }
 
     pub fn source(&self) -> &Option<URI> {
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index c3458cf..7c5d466 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -332,13 +332,13 @@ impl MusicLibrary {
 
         match global_config.db_path.exists() {
             true => {
-                library = read_library(*global_config.db_path.clone())?;
+                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.exists() {
-                    library = read_library(*backup_path.clone())?;
+                    library = read_library(backup_path.clone())?;
                     write_library(&library, global_config.db_path.to_path_buf(), false)?;
                 } else {
                     write_library(&library, global_config.db_path.to_path_buf(), false)?;

From 463456ad5b43f179503963126f5707a478fcbd3e Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Fri, 12 Jan 2024 23:47:00 -0500
Subject: [PATCH 066/136] test 2

---
 src/music_storage/library.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 706a05d..c3458cf 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -9,7 +9,7 @@ use std::error::Error;
 use std::ops::ControlFlow::{Break, Continue};
 
 // Files
-use file_format::{ FileFormat, Kind };
+use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;

From 9eaccc22fd124c28d56f0a3b4eaf6f7fed9123f1 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 12 Jan 2024 23:01:06 -0600
Subject: [PATCH 067/136] Removed `music_controller` for restructuring

---
 src/music_controller/config.rs     | 57 ------------------------------
 src/music_controller/controller.rs | 50 --------------------------
 2 files changed, 107 deletions(-)
 delete mode 100644 src/music_controller/config.rs
 delete mode 100644 src/music_controller/controller.rs

diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs
deleted file mode 100644
index a9523d3..0000000
--- a/src/music_controller/config.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use std::fs;
-use std::fs::read_to_string;
-use std::path::PathBuf;
-
-use serde::{Deserialize, Serialize};
-
-#[derive(Serialize, Deserialize, PartialEq, Eq)]
-pub struct Config {
-    pub db_path: PathBuf,
-}
-
-impl Default for Config {
-    fn default() -> Self {
-        let path = PathBuf::from("./music_database");
-
-        Config {
-            db_path: path,
-        }
-    }
-}
-
-impl Config {
-    /// Creates and saves a new config with default values
-    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> {
-        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
-        if fs::metadata(config_file).is_ok() {
-            fs::remove_file(config_file)?
-        }
-
-        fs::rename(temp_file, config_file)?;
-        Ok(())
-    }
-}
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
deleted file mode 100644
index 6d0acb4..0000000
--- a/src/music_controller/controller.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-use std::path::PathBuf;
-use std::sync::{Arc, RwLock};
-
-use crate::music_controller::config::Config;
-use crate::music_storage::library::{MusicLibrary, Song, Tag};
-
-pub struct MusicController {
-    pub config: Arc<RwLock<Config>>,
-    pub library: MusicLibrary,
-}
-
-impl MusicController {
-    /// Creates new MusicController with config at given path
-    pub fn new(config_path: &PathBuf) -> Result<MusicController, Box<dyn std::error::Error>> {
-        let config = Arc::new(RwLock::new(Config::new(config_path)?));
-        let library = match MusicLibrary::init(config.clone()) {
-            Ok(library) => library,
-            Err(error) => return Err(error),
-        };
-
-        let controller = MusicController { config, library };
-
-        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 library = match MusicLibrary::init(config.clone()) {
-            Ok(library) => library,
-            Err(error) => return Err(error),
-        };
-
-        let controller = MusicController { config, library };
-
-        Ok(controller)
-    }
-
-    /// 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>,
-    ) -> Option<Vec<&Song>> {
-        self.library
-            .query_tracks(query_string, &target_tags, &sort_by)
-    }
-}

From 3c54745303115ec6aff202621d143693cec27b85 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sat, 13 Jan 2024 00:20:23 -0500
Subject: [PATCH 068/136] updated cargo.toml

---
 Cargo.toml | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index bb0746c..cf407e4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,21 +1,21 @@
 [package]
-name = "dango-core"
-version = "0.1.1"
+name = "dmp-core"
+version = "0.0.0"
 edition = "2021"
 license = "AGPL-3.0-only"
-description = "A music backend that manages storage, querying, and playback of remote and local songs."
-homepage = "https://dangoware.com/dango-music-player"
-documentation = "https://docs.rs/dango-core"
+description = "Backend crate for the Dango Music Player "
+homepage = ""
+documentation = ""
 readme = "README.md"
-repository = "https://github.com/Dangoware/dango-music-player"
-keywords = ["audio", "music"]
-categories = ["multimedia::audio"]
+repository = "https://github.com/Dangoware/dmp-core"
+keywords = []
+categories = []
 
 [dependencies]
-file-format = { version = "0.22.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
-lofty = "0.17.1"
+file-format = { version = "0.23.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
+lofty = "0.18.0"
 serde = { version = "1.0.191", features = ["derive"] }
-toml = "0.7.5"
+toml = "0.8.8"
 walkdir = "2.4.0"
 chrono = { version = "0.4.31", features = ["serde"] }
 bincode = { version = "2.0.0-rc.3", features = ["serde"] }

From 2d0464a9935652aa5621b6bff7f2d5220a490af9 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sat, 13 Jan 2024 00:57:26 -0500
Subject: [PATCH 069/136] removed broken imports

---
 src/lib.rs                    | 5 -----
 src/music_storage/library.rs  | 1 -
 src/music_storage/playlist.rs | 4 +---
 3 files changed, 1 insertion(+), 9 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index c20df16..aca688d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,9 +20,4 @@ pub mod music_storage {
     }
 }
 
-pub mod music_controller {
-    pub mod config;
-    pub mod controller;
-}
-
 pub mod music_player;
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 7c5d466..5394652 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -1,7 +1,6 @@
 use super::music_collection::MusicCollection;
 // Crate things
 use super::utils::{find_images, normalize, read_library, write_library};
-use crate::music_controller::config::Config;
 
 // Various std things
 use std::collections::BTreeMap;
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 6b3dc96..099d015 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -6,7 +6,6 @@ use super::{
     library::{self, AlbumArt, Song, Tag},
     music_collection::MusicCollection,
 };
-use crate::music_controller::config::Config;
 use crate::music_storage::db_reader::xml::reader::XmlLibrary;
 
 use std::io::Read;
@@ -86,7 +85,6 @@ impl<'a> Playlist<'a> {
                             .get_key_value(&Tag::Title)
                             .unwrap()
                             .1
-                            .to_string()
                             .into(),
                     ),
                     ..Default::default()
@@ -149,7 +147,7 @@ fn list_to_m3u8() {
     ));
     let mut a = Playlist::new();
     let c = lib.to_songs();
-    let mut b = c.iter().map({ |song| song }).collect::<Vec<&Song>>();
+    let mut b = c.iter().map( |song| song ).collect::<Vec<&Song>>();
     a.tracks.append(&mut b);
     a.to_m3u8()
 }

From bc75b92237189e9409a50b9aa4270ec662fa4170 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Wed, 17 Jan 2024 05:53:18 -0500
Subject: [PATCH 070/136] messing around with config

---
 Cargo.toml                                |  1 +
 src/config/config.rs                      | 16 +++++
 src/config/other_settings.rs              | 82 +++++++++++++++++++++++
 src/lib.rs                                | 20 ++----
 src/music_storage/db_reader/mod.rs        | 13 ++++
 src/music_storage/db_reader/xml/reader.rs |  4 +-
 src/music_storage/playlist.rs             |  1 +
 7 files changed, 119 insertions(+), 18 deletions(-)
 create mode 100644 src/config/config.rs
 create mode 100644 src/config/other_settings.rs
 create mode 100644 src/music_storage/db_reader/mod.rs

diff --git a/Cargo.toml b/Cargo.toml
index cf407e4..5a2fa1e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -34,3 +34,4 @@ leb128 = "0.2.5"
 urlencoding = "2.1.3"
 m3u8-rs = "5.0.5"
 thiserror = "1.0.56"
+font = "0.27.0"
diff --git a/src/config/config.rs b/src/config/config.rs
new file mode 100644
index 0000000..ee86c15
--- /dev/null
+++ b/src/config/config.rs
@@ -0,0 +1,16 @@
+use std::{path::PathBuf, marker::PhantomData};
+
+#[derive(Debug, Default)]
+pub struct Config {
+    db_path: Option<PathBuf>,
+}
+
+impl Config {
+    pub fn new_main() -> Self {
+        Config::default()
+    }
+    //TODO: Add new function for test tube
+    pub fn load(&self) {
+
+    }
+}
\ No newline at end of file
diff --git a/src/config/other_settings.rs b/src/config/other_settings.rs
new file mode 100644
index 0000000..c1940a4
--- /dev/null
+++ b/src/config/other_settings.rs
@@ -0,0 +1,82 @@
+use std::{marker::PhantomData, fs::File, path::PathBuf};
+
+use font::Font;
+
+pub trait Setting {}
+
+pub struct DropDown {
+    name: String,
+    //value: ???
+}
+impl Setting for DropDown {}
+
+#[derive(Debug, Default)]
+pub struct Slider {
+    name: String,
+    value: i32,
+}
+impl Setting for Slider {}
+
+#[derive(Debug, Default)]
+pub struct CheckBox {
+    name: String,
+    value: bool,
+}
+impl Setting for CheckBox {}
+
+enum TextBoxSize {
+    Small,
+    Large,
+}
+#[derive(Debug, Default)]
+pub struct TextBox<Size = TextBoxSize> {
+    name: String,
+    text: String,
+    size: PhantomData<Size>
+}
+impl Setting for TextBox {}
+
+#[derive(Debug, Default)]
+pub struct SingleSelect {
+    name: String,
+    value: bool,
+}
+impl Setting for SingleSelect {}
+
+#[derive(Debug, Default)]
+pub struct MultiSelect {
+    name: String,
+    value: bool,
+}
+impl Setting for MultiSelect {}
+
+#[derive(Debug, Default)]
+pub struct ConfigCounter {
+    name: String,
+    value: i32,
+}
+impl Setting for ConfigCounter {}
+
+#[derive(Debug, Default)]
+pub struct ConfigFont {
+    name: String,
+    value: Font,
+}
+impl Setting for ConfigFont {}
+
+#[derive(Debug, Default)]
+pub struct ConfigFile {
+    name: String,
+    value: PathBuf,
+}
+impl Setting for ConfigFile {}
+
+#[derive(Debug, Default)]
+pub struct List<T: Setting> {
+    items: Vec<T>
+}
+
+pub struct Form {
+
+}
+
diff --git a/src/lib.rs b/src/lib.rs
index aca688d..6d8be4c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,21 +3,11 @@ pub mod music_storage {
     pub mod music_collection;
     pub mod playlist;
     mod utils;
-    pub mod db_reader {
-        pub mod foobar {
-            pub mod reader;
-            pub mod utils;
-        }
-        pub mod musicbee {
-            pub mod reader;
-            pub mod utils;
-        }
-        pub mod xml {
-            pub mod reader;
-        }
-        pub mod common;
-        pub mod extern_library;
-    }
+    pub mod db_reader;
 }
 
 pub mod music_player;
+pub mod config {
+    pub mod config;
+    pub mod other_settings;
+}
diff --git a/src/music_storage/db_reader/mod.rs b/src/music_storage/db_reader/mod.rs
new file mode 100644
index 0000000..6569a82
--- /dev/null
+++ b/src/music_storage/db_reader/mod.rs
@@ -0,0 +1,13 @@
+pub mod foobar {
+    pub mod reader;
+    pub mod utils;
+}
+pub mod musicbee {
+    pub mod reader;
+    pub mod utils;
+}
+pub mod xml {
+    pub mod reader;
+}
+pub mod common;
+pub mod extern_library;
\ No newline at end of file
diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/xml/reader.rs
index 50beeac..1652551 100644
--- a/src/music_storage/db_reader/xml/reader.rs
+++ b/src/music_storage/db_reader/xml/reader.rs
@@ -92,7 +92,6 @@ impl ExternalLibrary for XmlLibrary {
                     }
 
                     let text = e.unescape().unwrap().to_string();
-
                     if text == count2.to_string() && !key_selected {
                         continue;
                     }
@@ -118,8 +117,7 @@ impl ExternalLibrary for XmlLibrary {
             buf.clear();
         }
         let elasped = now.elapsed();
-        println!("\n\nXMLReader\n=========================================\n\nDone!\n{} songs grabbed in {:#?}\nIDs Skipped: {}", count3, elasped, count4);
-        // dbg!(folder);
+        println!("\n\nXMLReader grabbed {} songs in {:#?} seconds\nIDs Skipped: {}", count3, elasped.as_secs(), count4);
         let mut lib = XmlLibrary::new();
         lib.tracks.append(converted_songs.as_mut());
         lib
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 099d015..4ad49dd 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -102,6 +102,7 @@ impl<'a> Playlist<'a> {
             segments: seg.clone(),
             ..Default::default()
         };
+        //TODO: change this to put in a real file path
         let mut file = std::fs::OpenOptions::new()
             .read(true)
             .create(true)

From 4dfc321d2a9fe07669cfb969dfb735c5f40d3e44 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 17 Jan 2024 04:54:05 -0600
Subject: [PATCH 071/136] re-added the controller

---
 src/lib.rs                         | 3 +--
 src/music_controller/controller.rs | 1 +
 2 files changed, 2 insertions(+), 2 deletions(-)
 create mode 100644 src/music_controller/controller.rs

diff --git a/src/lib.rs b/src/lib.rs
index c20df16..f4a93a8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -20,8 +20,7 @@ pub mod music_storage {
     }
 }
 
-pub mod music_controller {
-    pub mod config;
+pub mod music_controller{
     pub mod controller;
 }
 
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/src/music_controller/controller.rs
@@ -0,0 +1 @@
+ 

From c14ab1f04b124547d0adc6ea4bee594c98061713 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 17 Jan 2024 06:18:09 -0600
Subject: [PATCH 072/136] Minor work starting on `controller.rs`

---
 src/config/config.rs                  |  4 +-
 src/music_controller/controller.rs    | 25 +++++++++++-
 src/music_storage/library.rs          | 56 ++++++++++++++-------------
 src/music_storage/music_collection.rs |  2 +-
 src/music_storage/playlist.rs         | 35 +++++++----------
 5 files changed, 70 insertions(+), 52 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index ee86c15..cac729d 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -2,7 +2,7 @@ use std::{path::PathBuf, marker::PhantomData};
 
 #[derive(Debug, Default)]
 pub struct Config {
-    db_path: Option<PathBuf>,
+    pub db_path: Option<PathBuf>,
 }
 
 impl Config {
@@ -13,4 +13,4 @@ impl Config {
     pub fn load(&self) {
 
     }
-}
\ No newline at end of file
+}
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 8d1c8b6..94509d8 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -1 +1,24 @@
- 
+//! The [Controller] is the input and output for the entire
+//! player. It manages queues, playback, library access, and
+//! other functions
+
+use crate::{
+    music_player::Player,
+    music_storage::library::Song,
+    config::config::Config
+};
+
+struct Queue {
+    player: Player,
+    name: String,
+    songs: Vec<Song>,
+}
+
+pub struct Controller {
+    queues: Vec<Queue>,
+    config: Config,
+}
+
+impl Controller {
+    // more stuff to come
+}
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 5394652..873865f 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -1,6 +1,7 @@
-use super::music_collection::MusicCollection;
 // Crate things
 use super::utils::{find_images, normalize, read_library, write_library};
+use super::music_collection::MusicCollection;
+use crate::config::config::Config;
 
 // Various std things
 use std::collections::BTreeMap;
@@ -269,6 +270,16 @@ pub struct Album<'a> {
 
 #[allow(clippy::len_without_is_empty)]
 impl Album<'_> {
+    //returns the Album title
+    fn title(&self) -> &String {
+        self.title
+    }
+
+    /// Returns the album cover as an AlbumArt struct, if it exists
+    fn cover(&self) -> Option<&AlbumArt> {
+        self.cover
+    }
+
     /// Returns the Album Artist, if they exist
     pub fn artist(&self) -> Option<&String> {
         self.artist
@@ -283,6 +294,14 @@ impl Album<'_> {
         Some(self.discs.get(&disc)?[index])
     }
 
+    fn tracks(&self) -> Vec<&Song> {
+        let mut songs = Vec::new();
+        for disc in &self.discs {
+            songs.append(&mut disc.1.clone())
+        }
+        songs
+    }
+
     /// Returns the number of songs in the album
     pub fn len(&self) -> usize {
         let mut total = 0;
@@ -292,23 +311,6 @@ impl Album<'_> {
         total
     }
 }
-impl MusicCollection for Album<'_> {
-    //returns the Album title
-    fn title(&self) -> &String {
-        self.title
-    }
-    /// Returns the album cover as an AlbumArt struct, if it exists
-    fn cover(&self) -> Option<&AlbumArt> {
-        self.cover
-    }
-    fn tracks(&self) -> Vec<&Song> {
-        let mut songs = Vec::new();
-        for disc in &self.discs {
-            songs.append(&mut disc.1.clone())
-        }
-        songs
-    }
-}
 
 const BLOCKED_EXTENSIONS: [&str; 4] = ["vob", "log", "txt", "sf2"];
 
@@ -327,20 +329,20 @@ 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.unwrap().set_extension("bkp");
 
-        match global_config.db_path.exists() {
+        match global_config.db_path.unwrap().exists() {
             true => {
-                library = read_library(global_config.db_path.clone())?;
+                library = read_library(global_config.db_path.unwrap().clone())?;
             }
             false => {
                 // Create the database if it does not exist
                 // possibly from the backup file
-                if backup_path.exists() {
-                    library = read_library(backup_path.clone())?;
-                    write_library(&library, global_config.db_path.to_path_buf(), false)?;
+                if backup_path.unwrap().exists() {
+                    library = read_library(backup_path.unwrap().clone())?;
+                    write_library(&library, global_config.db_path.unwrap().to_path_buf(), false)?;
                 } else {
-                    write_library(&library, global_config.db_path.to_path_buf(), false)?;
+                    write_library(&library, global_config.db_path.unwrap().to_path_buf(), false)?;
                 }
             }
         };
@@ -350,9 +352,9 @@ impl MusicLibrary {
 
     /// 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() {
+        match config.db_path.unwrap().try_exists() {
             Ok(exists) => {
-                write_library(&self.library, config.db_path.to_path_buf(), exists)?;
+                write_library(&self.library, config.db_path.unwrap().to_path_buf(), exists)?;
             }
             Err(error) => return Err(error.into()),
         }
diff --git a/src/music_storage/music_collection.rs b/src/music_storage/music_collection.rs
index 9b1c7ca..964c79a 100644
--- a/src/music_storage/music_collection.rs
+++ b/src/music_storage/music_collection.rs
@@ -3,5 +3,5 @@ use crate::music_storage::library::{AlbumArt, Song};
 pub trait MusicCollection {
     fn title(&self) -> &String;
     fn cover(&self) -> Option<&AlbumArt>;
-    fn tracks(&self) -> Vec<&Song>;
+    fn tracks(&self) -> Vec<Song>;
 }
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 4ad49dd..62b3aa9 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,15 +1,15 @@
+use std::path::Path;
+
 use chrono::Duration;
 use walkdir::Error;
 
 use super::{
-    db_reader::extern_library::ExternalLibrary,
-    library::{self, AlbumArt, Song, Tag},
-    music_collection::MusicCollection,
+    library::{AlbumArt, Song, Tag},
+    music_collection::MusicCollection, db_reader::{
+        xml::reader::XmlLibrary,
+        extern_library::ExternalLibrary
+    },
 };
-use crate::music_storage::db_reader::xml::reader::XmlLibrary;
-
-use std::io::Read;
-use std::{default, path::Path, path::PathBuf, thread::AccessError};
 
 use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
 // use nom::IResult;
@@ -18,7 +18,7 @@ use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
 pub struct Playlist<'a> {
     title: String,
     cover: Option<&'a AlbumArt>,
-    tracks: Vec<&'a Song>,
+    tracks: Vec<Song>,
     play_count: i32,
     play_time: Duration,
 }
@@ -32,11 +32,11 @@ impl<'a> Playlist<'a> {
     pub fn play_time(&self) -> chrono::Duration {
         self.play_time
     }
-    pub fn set_tracks(&mut self, songs: Vec<&'a Song>) -> Result<(), Error> {
+    pub fn set_tracks(&mut self, songs: Vec<Song>) -> Result<(), Error> {
         self.tracks = songs;
         Ok(())
     }
-    pub fn add_track(&mut self, song: &'a Song) -> Result<(), Error> {
+    pub fn add_track(&mut self, song: Song) -> Result<(), Error> {
         self.tracks.push(song);
         Ok(())
     }
@@ -79,14 +79,7 @@ impl<'a> Playlist<'a> {
                 |track| MediaSegment {
                     uri: track.location.to_string().into(),
                     duration: track.duration.as_millis() as f32,
-                    title: Some(
-                        track
-                            .tags
-                            .get_key_value(&Tag::Title)
-                            .unwrap()
-                            .1
-                            .into(),
-                    ),
+                    title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.into()),
                     ..Default::default()
                 }
             })
@@ -125,8 +118,8 @@ impl MusicCollection for Playlist<'_> {
             None => None,
         }
     }
-    fn tracks(&self) -> Vec<&Song> {
-        self.tracks.clone()
+    fn tracks(&self) -> Vec<Song> {
+        self.tracks
     }
 }
 impl Default for Playlist<'_> {
@@ -148,7 +141,7 @@ fn list_to_m3u8() {
     ));
     let mut a = Playlist::new();
     let c = lib.to_songs();
-    let mut b = c.iter().map( |song| song ).collect::<Vec<&Song>>();
+    let mut b = c.iter().map(|song| song).collect::<Vec<&Song>>();
     a.tracks.append(&mut b);
     a.to_m3u8()
 }

From 4e5a45e544a96a908d701036f6c9289716b0b27a Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Wed, 17 Jan 2024 07:20:51 -0500
Subject: [PATCH 073/136] messing with Config 2: Electric Boogaloo

---
 src/config/config.rs         |  9 +++-
 src/config/other_settings.rs | 84 ++++++------------------------------
 2 files changed, 21 insertions(+), 72 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index ee86c15..7bec4a0 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -1,8 +1,15 @@
 use std::{path::PathBuf, marker::PhantomData};
 
+use crate::music_storage::library::MusicLibrary;
+
+#[derive(Debug, Default)]
+struct ConfigLibrary {
+    name: String,
+    path: PathBuf,
+}
 #[derive(Debug, Default)]
 pub struct Config {
-    db_path: Option<PathBuf>,
+    libraries: Vec<ConfigLibrary>
 }
 
 impl Config {
diff --git a/src/config/other_settings.rs b/src/config/other_settings.rs
index c1940a4..da94670 100644
--- a/src/config/other_settings.rs
+++ b/src/config/other_settings.rs
@@ -2,78 +2,20 @@ use std::{marker::PhantomData, fs::File, path::PathBuf};
 
 use font::Font;
 
-pub trait Setting {}
+pub enum Setting {
+    String {
+        name: String,
+        value: String
+    },
+    Int {
+        name: String,
+        value: i32
+    },
+    Bool {
+        name: String,
+        value: bool
+    },
 
-pub struct DropDown {
-    name: String,
-    //value: ???
-}
-impl Setting for DropDown {}
-
-#[derive(Debug, Default)]
-pub struct Slider {
-    name: String,
-    value: i32,
-}
-impl Setting for Slider {}
-
-#[derive(Debug, Default)]
-pub struct CheckBox {
-    name: String,
-    value: bool,
-}
-impl Setting for CheckBox {}
-
-enum TextBoxSize {
-    Small,
-    Large,
-}
-#[derive(Debug, Default)]
-pub struct TextBox<Size = TextBoxSize> {
-    name: String,
-    text: String,
-    size: PhantomData<Size>
-}
-impl Setting for TextBox {}
-
-#[derive(Debug, Default)]
-pub struct SingleSelect {
-    name: String,
-    value: bool,
-}
-impl Setting for SingleSelect {}
-
-#[derive(Debug, Default)]
-pub struct MultiSelect {
-    name: String,
-    value: bool,
-}
-impl Setting for MultiSelect {}
-
-#[derive(Debug, Default)]
-pub struct ConfigCounter {
-    name: String,
-    value: i32,
-}
-impl Setting for ConfigCounter {}
-
-#[derive(Debug, Default)]
-pub struct ConfigFont {
-    name: String,
-    value: Font,
-}
-impl Setting for ConfigFont {}
-
-#[derive(Debug, Default)]
-pub struct ConfigFile {
-    name: String,
-    value: PathBuf,
-}
-impl Setting for ConfigFile {}
-
-#[derive(Debug, Default)]
-pub struct List<T: Setting> {
-    items: Vec<T>
 }
 
 pub struct Form {

From 4546082e5446031e7f51f8ef20fb175e4f10ac7b Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Wed, 17 Jan 2024 09:07:06 -0500
Subject: [PATCH 074/136] created more functions for the config

---
 Cargo.toml                         |  2 +
 src/config/config.rs               | 72 ++++++++++++++++++++++++++----
 src/music_controller/controller.rs |  4 +-
 src/music_storage/playlist.rs      |  4 +-
 4 files changed, 71 insertions(+), 11 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 5a2fa1e..aaf4409 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,3 +35,5 @@ urlencoding = "2.1.3"
 m3u8-rs = "5.0.5"
 thiserror = "1.0.56"
 font = "0.27.0"
+uuid = { version = "1.6.1", features = ["v4", "serde"]}
+serde_json = "1.0.111"
diff --git a/src/config/config.rs b/src/config/config.rs
index 76ab38a..a54186f 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -1,15 +1,44 @@
-use std::{path::PathBuf, marker::PhantomData};
+use std::{path::{PathBuf, Path}, marker::PhantomData, fs::{File, OpenOptions, self}, io::{Error, Write, Read}, default};
 
-use crate::music_storage::library::MusicLibrary;
+use serde::{Serialize, Deserialize};
+use serde_json::{to_string, to_string_pretty};
+use uuid::Uuid;
 
-#[derive(Debug, Default)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 struct ConfigLibrary {
-    name: String,
-    path: PathBuf,
+    pub name: String,
+    pub path: PathBuf,
+    pub uuid: Uuid
 }
-#[derive(Debug, Default)]
+impl ConfigLibrary {
+    fn new() -> Self {
+        ConfigLibrary {
+            name: String::new(),
+            path: PathBuf::default(),
+            uuid: Uuid::new_v4()
+        }
+    }
+    pub fn open(&self) -> Result<File, Error> {
+        match File::open(self.path.as_path()) {
+            Ok(ok) => Ok(ok),
+            Err(e) => Err(e)
+        }
+    }
+}
+impl Default for ConfigLibrary {
+    fn default() -> Self {
+        ConfigLibrary {
+            name: String::default(),
+            path: PathBuf::default(),
+            uuid: Uuid::new_v4()
+        }
+    }
+}
+#[derive(Debug, Default, Serialize, Deserialize)]
 pub struct Config {
-    libraries: Vec<ConfigLibrary>
+    pub path: PathBuf,
+    default_library: Uuid,
+    pub libraries: Vec<ConfigLibrary>,
 }
 
 impl Config {
@@ -17,7 +46,34 @@ impl Config {
         Config::default()
     }
     //TODO: Add new function for test tube
-    pub fn load(&self) {
+    pub fn set_default_library(&self, uuid: Uuid) {
+        self.default_library = uuid;
+    }
+    //TODO: make this a ConfigError type
+    pub fn default_library(&self) -> Result<&ConfigLibrary, String> {
+        for library in &self.libraries {
+            if library.uuid == self.default_library {
+                return Ok(library)
+            }
+            else {
+                continue;
+            }
+        }
+        Err("No default library!".to_string())
+    }
+    pub fn save(&self) -> Result<(), Error> {
+        let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open("dango_temp_config_save.json")?;
+        let config = to_string_pretty(self)?;
 
+        file.write_all(&config.as_bytes())?;
+        fs::rename("dango_temp_config_save.json", self.path.as_path())?;
+        Ok(())
+    }
+    pub fn load(path: PathBuf) -> Result<Self, Error> {
+        let mut file: File = File::open(path)?;
+        let mut bun: String = String::new();
+        _ = file.read_to_string(&mut bun);
+        let ny: Config = serde_json::from_str::<Config>(&bun)?;
+        Ok(ny)
     }
 }
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 94509d8..7f62871 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -2,6 +2,8 @@
 //! player. It manages queues, playback, library access, and
 //! other functions
 
+use std::sync::{Arc, RwLock};
+
 use crate::{
     music_player::Player,
     music_storage::library::Song,
@@ -16,7 +18,7 @@ struct Queue {
 
 pub struct Controller {
     queues: Vec<Queue>,
-    config: Config,
+    config: Arc<RwLock<Config>>,
 }
 
 impl Controller {
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 62b3aa9..cc8166c 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -119,7 +119,7 @@ impl MusicCollection for Playlist<'_> {
         }
     }
     fn tracks(&self) -> Vec<Song> {
-        self.tracks
+        self.tracks.to_owned()
     }
 }
 impl Default for Playlist<'_> {
@@ -141,7 +141,7 @@ fn list_to_m3u8() {
     ));
     let mut a = Playlist::new();
     let c = lib.to_songs();
-    let mut b = c.iter().map(|song| song).collect::<Vec<&Song>>();
+    let mut b = c.iter().map(|song| song.to_owned()).collect::<Vec<Song>>();
     a.tracks.append(&mut b);
     a.to_m3u8()
 }

From c057352e9b406142806ec3425e02df5f232dd9a7 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Wed, 17 Jan 2024 15:22:28 -0500
Subject: [PATCH 075/136] Changed MusicLibrary and config file handling

---
 src/config/config.rs         | 19 +++++++++----------
 src/music_storage/library.rs | 14 +++++++++++++-
 2 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index a54186f..ec2a521 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -11,12 +11,8 @@ struct ConfigLibrary {
     pub uuid: Uuid
 }
 impl ConfigLibrary {
-    fn new() -> Self {
-        ConfigLibrary {
-            name: String::new(),
-            path: PathBuf::default(),
-            uuid: Uuid::new_v4()
-        }
+    pub fn new() -> Self {
+        ConfigLibrary::default()
     }
     pub fn open(&self) -> Result<File, Error> {
         match File::open(self.path.as_path()) {
@@ -39,6 +35,7 @@ pub struct Config {
     pub path: PathBuf,
     default_library: Uuid,
     pub libraries: Vec<ConfigLibrary>,
+    volume: f32,
 }
 
 impl Config {
@@ -61,15 +58,17 @@ impl Config {
         }
         Err("No default library!".to_string())
     }
-    pub fn save(&self) -> Result<(), Error> {
-        let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open("dango_temp_config_save.json")?;
+    pub fn to_file(&self) -> Result<(), Error> {
+        let mut writer = self.path.clone();
+        writer.set_extension("tmp");
+        let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(writer)?;
         let config = to_string_pretty(self)?;
 
         file.write_all(&config.as_bytes())?;
-        fs::rename("dango_temp_config_save.json", self.path.as_path())?;
+        fs::rename(writer, self.path.as_path())?;
         Ok(())
     }
-    pub fn load(path: PathBuf) -> Result<Self, Error> {
+    pub fn load_file(path: PathBuf) -> Result<Self, Error> {
         let mut file: File = File::open(path)?;
         let mut bun: String = String::new();
         _ = file.read_to_string(&mut bun);
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 873865f..a9ffe7a 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -13,6 +13,7 @@ use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
+use uuid::Uuid;
 use std::fs;
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
@@ -316,16 +317,27 @@ const BLOCKED_EXTENSIONS: [&str; 4] = ["vob", "log", "txt", "sf2"];
 
 #[derive(Debug)]
 pub struct MusicLibrary {
+    pub name: String,
+    pub uuid: Uuid,
     pub library: Vec<Song>,
 }
 
 impl MusicLibrary {
+    pub fn with_uuid(uuid: Uuid, path: PathBuf) -> Result<Self, Box<dyn Error>> {
+        MusicLibrary {
+            name: String::default(),
+            uuid,
+            library: Vec::new(),
+        };
+
+        todo!()
+    }
     /// Initialize the database
     ///
     /// 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 [MusicLibrary] Vec
-    pub fn init(config: Arc<RwLock<Config>>) -> Result<Self, Box<dyn Error>> {
+    pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
         let global_config = &*config.read().unwrap();
         let mut library: Vec<Song> = Vec::new();
         let mut backup_path = global_config.db_path.clone();

From b937ac55f1fec63db176037a637d69e255aaba60 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Fri, 19 Jan 2024 05:33:58 -0500
Subject: [PATCH 076/136] broke code

---
 src/config/config.rs         | 46 +++++++++++++++++++++------
 src/music_storage/library.rs | 61 +++++++++++++++++++++---------------
 src/music_storage/utils.rs   |  7 -----
 3 files changed, 73 insertions(+), 41 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index ec2a521..b3ce860 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -2,10 +2,11 @@ use std::{path::{PathBuf, Path}, marker::PhantomData, fs::{File, OpenOptions, se
 
 use serde::{Serialize, Deserialize};
 use serde_json::{to_string, to_string_pretty};
+use thiserror::Error;
 use uuid::Uuid;
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
-struct ConfigLibrary {
+pub struct ConfigLibrary {
     pub name: String,
     pub path: PathBuf,
     pub uuid: Uuid
@@ -35,33 +36,52 @@ pub struct Config {
     pub path: PathBuf,
     default_library: Uuid,
     pub libraries: Vec<ConfigLibrary>,
+    pub library_folder: PathBuf,
     volume: f32,
 }
 
 impl Config {
+    pub fn new() -> Self {
+        Config {
+            libraries: vec![ConfigLibrary::default()],
+            ..Default::default()
+        }
+    }
     pub fn new_main() -> Self {
         Config::default()
     }
     //TODO: Add new function for test tube
-    pub fn set_default_library(&self, uuid: Uuid) {
-        self.default_library = uuid;
+    pub fn set_default_library(mut self, uuid: &Uuid) {
+        self.default_library = *uuid;
     }
-    //TODO: make this a ConfigError type
-    pub fn default_library(&self) -> Result<&ConfigLibrary, String> {
+    pub fn get_default_library(&self) -> Result<&ConfigLibrary, ConfigError> {
         for library in &self.libraries {
             if library.uuid == self.default_library {
                 return Ok(library)
             }
-            else {
-                continue;
+        }
+        Err(ConfigError::NoDefaultLibrary)
+    }
+    pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
+        for library in &self.libraries {
+            if &library.uuid == uuid {
+                return Ok(library.to_owned())
             }
         }
-        Err("No default library!".to_string())
+        Err(ConfigError::NoConfigLibrary(*uuid))
+    }
+    pub fn library_exists(&self, uuid: &Uuid) -> bool {
+        for library in &self.libraries {
+            if &library.uuid == uuid {
+                return true
+            }
+        }
+        false
     }
     pub fn to_file(&self) -> Result<(), Error> {
         let mut writer = self.path.clone();
         writer.set_extension("tmp");
-        let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(writer)?;
+        let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(&writer)?;
         let config = to_string_pretty(self)?;
 
         file.write_all(&config.as_bytes())?;
@@ -76,3 +96,11 @@ impl Config {
         Ok(ny)
     }
 }
+
+#[derive(Error, Debug)]
+pub enum ConfigError {
+    #[error("No Library Found for {0}!")]
+    NoConfigLibrary(Uuid),
+    #[error("There is no Default Library for this Config")]
+    NoDefaultLibrary
+}
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index a9ffe7a..2bd3145 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -2,6 +2,7 @@
 use super::utils::{find_images, normalize, read_library, write_library};
 use super::music_collection::MusicCollection;
 use crate::config::config::Config;
+use crate::music_storage::library;
 
 // Various std things
 use std::collections::BTreeMap;
@@ -321,52 +322,62 @@ pub struct MusicLibrary {
     pub uuid: Uuid,
     pub library: Vec<Song>,
 }
-
+#[test]
+    fn test_() {
+        let a = MusicLibrary::init(Arc::new(RwLock::from(Config::new())), None).unwrap();
+        dbg!(a);
+    }
 impl MusicLibrary {
-    pub fn with_uuid(uuid: Uuid, path: PathBuf) -> Result<Self, Box<dyn Error>> {
+    pub fn new() -> Self {
         MusicLibrary {
             name: String::default(),
+            uuid: Uuid::default(),
+            library: Vec::new(),
+        }
+    }
+    pub fn with_uuid(uuid: Uuid, path: PathBuf) -> Result<Self, Box<dyn Error>> {
+        MusicLibrary {
+            name: String::new(),
             uuid,
             library: Vec::new(),
         };
 
         todo!()
     }
+
     /// Initialize the database
     ///
     /// 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 [MusicLibrary] Vec
-    pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
+    pub fn init(config: Arc<RwLock<Config>>, uuid: Option<Uuid>) -> Result<Self, Box<dyn Error>> {
         let global_config = &*config.read().unwrap();
-        let mut library: Vec<Song> = Vec::new();
-        let mut backup_path = global_config.db_path.clone();
-        backup_path.unwrap().set_extension("bkp");
 
-        match global_config.db_path.unwrap().exists() {
-            true => {
-                library = read_library(global_config.db_path.unwrap().clone())?;
-            }
-            false => {
-                // Create the database if it does not exist
-                // possibly from the backup file
-                if backup_path.unwrap().exists() {
-                    library = read_library(backup_path.unwrap().clone())?;
-                    write_library(&library, global_config.db_path.unwrap().to_path_buf(), false)?;
-                } else {
-                    write_library(&library, global_config.db_path.unwrap().to_path_buf(), false)?;
+        let mut library = MusicLibrary::new();
+
+        if let Some(uuid) = uuid {
+            match global_config.library_exists(&uuid) {
+                true => {
+                    library.library = read_library(global_config.get_library(&uuid)?.path)?;
+                },
+                false => {
+                    // Create the database if it does not exist
+                    write_library(&library.library, global_config.path.clone())?;
+                    library = MusicLibrary::with_uuid(uuid, global_config.path.parent().unwrap().to_path_buf())?;
                 }
-            }
-        };
-
-        Ok(Self { library })
+            };
+        }else {
+            write_library(&library.library, global_config.path.clone())?;
+        }
+        Ok(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.unwrap().try_exists() {
-            Ok(exists) => {
-                write_library(&self.library, config.db_path.unwrap().to_path_buf(), exists)?;
+        let path = config.get_library(&self.uuid)?.path;
+        match path.try_exists() {
+            Ok(_) => {
+                write_library(&self.library, path)?;
             }
             Err(error) => return Err(error.into()),
         }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index a25313a..c42a6b5 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -38,13 +38,10 @@ pub(super) fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
 pub(super) 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)?);
@@ -58,10 +55,6 @@ pub(super) fn write_library(
             .with_little_endian()
             .with_variable_int_encoding(),
     )?;
-
-    if path.exists() && take_backup {
-        fs::rename(&path, backup_name)?;
-    }
     fs::rename(writer_name, &path)?;
 
     Ok(())

From 0f49c50c42399a8e5bd01d04b0bbc9cb44222fd0 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Fri, 19 Jan 2024 06:59:27 -0500
Subject: [PATCH 077/136] Edited Config and added a test

---
 src/config/config.rs | 40 +++++++++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index b3ce860..8979907 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -32,18 +32,35 @@ impl Default for ConfigLibrary {
     }
 }
 #[derive(Debug, Default, Serialize, Deserialize)]
+pub struct ConfigLibraries {
+    default_library: Uuid,
+    pub library_folder: PathBuf,
+    pub libraries: Vec<ConfigLibrary>,
+}
+#[derive(Debug, Default, Serialize, Deserialize)]
 pub struct Config {
     pub path: PathBuf,
-    default_library: Uuid,
-    pub libraries: Vec<ConfigLibrary>,
-    pub library_folder: PathBuf,
+    pub libraries: ConfigLibraries,
     volume: f32,
 }
-
+#[test]
+fn config_test_() {
+    Config {
+        path: PathBuf::from("F:\\temp\\config.json"),
+        libraries: ConfigLibraries {
+            libraries: vec![ConfigLibrary::default(),ConfigLibrary::default(),ConfigLibrary::default()],
+            ..Default::default()
+        },
+        ..Default::default()
+    }.to_file();
+}
 impl Config {
     pub fn new() -> Self {
         Config {
-            libraries: vec![ConfigLibrary::default()],
+            libraries: ConfigLibraries {
+                libraries: vec![ConfigLibrary::default()],
+                ..Default::default()
+            },
             ..Default::default()
         }
     }
@@ -52,18 +69,18 @@ impl Config {
     }
     //TODO: Add new function for test tube
     pub fn set_default_library(mut self, uuid: &Uuid) {
-        self.default_library = *uuid;
+        self.libraries.default_library = *uuid;
     }
     pub fn get_default_library(&self) -> Result<&ConfigLibrary, ConfigError> {
-        for library in &self.libraries {
-            if library.uuid == self.default_library {
+        for library in &self.libraries.libraries {
+            if library.uuid == self.libraries.default_library {
                 return Ok(library)
             }
         }
         Err(ConfigError::NoDefaultLibrary)
     }
     pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
-        for library in &self.libraries {
+        for library in &self.libraries.libraries {
             if &library.uuid == uuid {
                 return Ok(library.to_owned())
             }
@@ -71,7 +88,7 @@ impl Config {
         Err(ConfigError::NoConfigLibrary(*uuid))
     }
     pub fn library_exists(&self, uuid: &Uuid) -> bool {
-        for library in &self.libraries {
+        for library in &self.libraries.libraries {
             if &library.uuid == uuid {
                 return true
             }
@@ -83,8 +100,9 @@ impl Config {
         writer.set_extension("tmp");
         let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(&writer)?;
         let config = to_string_pretty(self)?;
+        // dbg!(&config);
 
-        file.write_all(&config.as_bytes())?;
+        file.write_all(config.as_bytes())?;
         fs::rename(writer, self.path.as_path())?;
         Ok(())
     }

From 0f5eda5d1d6b239ebc68691f1fb7c46764a1f678 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 19 Jan 2024 21:22:03 -0600
Subject: [PATCH 078/136] Implemented new reading/writing in utils, improved
 library init function

---
 .gitignore                   | 14 ++++--
 src/config/config.rs         | 97 ++++++++++++++++++++++--------------
 src/lib.rs                   |  2 +
 src/music_storage/library.rs | 56 ++++++++++-----------
 src/music_storage/utils.rs   | 17 ++++---
 5 files changed, 108 insertions(+), 78 deletions(-)

diff --git a/.gitignore b/.gitignore
index 62c5a2f..72106f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,15 @@
+# Rust binary output dir
 target/
 
-music_database*
-*.db3*
-config.toml
+# Rust configuration
 Cargo.lock
+
+# Database files
+*.db3*
+music_database*
+
+# Storage formats
 *.kate-swp*
 *.m3u
-*.m3u8
\ No newline at end of file
+*.m3u8
+*.json
diff --git a/src/config/config.rs b/src/config/config.rs
index 8979907..7c3ffa8 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -1,7 +1,11 @@
-use std::{path::{PathBuf, Path}, marker::PhantomData, fs::{File, OpenOptions, self}, io::{Error, Write, Read}, default};
+use std::{
+    path::PathBuf,
+    fs::{File, OpenOptions, self},
+    io::{Error, Write, Read},
+};
 
 use serde::{Serialize, Deserialize};
-use serde_json::{to_string, to_string_pretty};
+use serde_json::to_string_pretty;
 use thiserror::Error;
 use uuid::Uuid;
 
@@ -11,6 +15,7 @@ pub struct ConfigLibrary {
     pub path: PathBuf,
     pub uuid: Uuid
 }
+
 impl ConfigLibrary {
     pub fn new() -> Self {
         ConfigLibrary::default()
@@ -22,6 +27,7 @@ impl ConfigLibrary {
         }
     }
 }
+
 impl Default for ConfigLibrary {
     fn default() -> Self {
         ConfigLibrary {
@@ -31,29 +37,71 @@ impl Default for ConfigLibrary {
         }
     }
 }
+
 #[derive(Debug, Default, Serialize, Deserialize)]
 pub struct ConfigLibraries {
     default_library: Uuid,
     pub library_folder: PathBuf,
     pub libraries: Vec<ConfigLibrary>,
 }
+
+impl ConfigLibraries {
+    //TODO: Add new function for test tube
+    pub fn set_default(mut self, uuid: &Uuid) {
+        self.default_library = *uuid;
+    }
+
+    pub fn get_default(&self) -> Result<&ConfigLibrary, ConfigError> {
+        for library in &self.libraries {
+            if library.uuid == self.default_library {
+                return Ok(library)
+            }
+        }
+        Err(ConfigError::NoDefaultLibrary)
+    }
+
+    pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
+        for library in &self.libraries {
+            if &library.uuid == uuid {
+                return Ok(library.to_owned())
+            }
+        }
+        Err(ConfigError::NoConfigLibrary(*uuid))
+    }
+
+    pub fn uuid_exists(&self, uuid: &Uuid) -> bool {
+        for library in &self.libraries {
+            if &library.uuid == uuid {
+                return true
+            }
+        }
+        false
+    }
+}
+
 #[derive(Debug, Default, Serialize, Deserialize)]
 pub struct Config {
     pub path: PathBuf,
     pub libraries: ConfigLibraries,
     volume: f32,
 }
+
 #[test]
-fn config_test_() {
-    Config {
-        path: PathBuf::from("F:\\temp\\config.json"),
+fn config_test() {
+    let _ = Config {
+        path: PathBuf::from("config_test.json"),
         libraries: ConfigLibraries {
-            libraries: vec![ConfigLibrary::default(),ConfigLibrary::default(),ConfigLibrary::default()],
+            libraries: vec![
+                ConfigLibrary::default(),
+                ConfigLibrary::default(),
+                ConfigLibrary::default()
+            ],
             ..Default::default()
         },
         ..Default::default()
-    }.to_file();
+    }.write_file();
 }
+
 impl Config {
     pub fn new() -> Self {
         Config {
@@ -64,38 +112,12 @@ impl Config {
             ..Default::default()
         }
     }
+
     pub fn new_main() -> Self {
         Config::default()
     }
-    //TODO: Add new function for test tube
-    pub fn set_default_library(mut self, uuid: &Uuid) {
-        self.libraries.default_library = *uuid;
-    }
-    pub fn get_default_library(&self) -> Result<&ConfigLibrary, ConfigError> {
-        for library in &self.libraries.libraries {
-            if library.uuid == self.libraries.default_library {
-                return Ok(library)
-            }
-        }
-        Err(ConfigError::NoDefaultLibrary)
-    }
-    pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
-        for library in &self.libraries.libraries {
-            if &library.uuid == uuid {
-                return Ok(library.to_owned())
-            }
-        }
-        Err(ConfigError::NoConfigLibrary(*uuid))
-    }
-    pub fn library_exists(&self, uuid: &Uuid) -> bool {
-        for library in &self.libraries.libraries {
-            if &library.uuid == uuid {
-                return true
-            }
-        }
-        false
-    }
-    pub fn to_file(&self) -> Result<(), Error> {
+
+    pub fn write_file(&self) -> Result<(), Error> {
         let mut writer = self.path.clone();
         writer.set_extension("tmp");
         let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(&writer)?;
@@ -106,7 +128,8 @@ impl Config {
         fs::rename(writer, self.path.as_path())?;
         Ok(())
     }
-    pub fn load_file(path: PathBuf) -> Result<Self, Error> {
+
+    pub fn read_file(path: PathBuf) -> Result<Self, Error> {
         let mut file: File = File::open(path)?;
         let mut bun: String = String::new();
         _ = file.read_to_string(&mut bun);
diff --git a/src/lib.rs b/src/lib.rs
index 3222def..2c479ae 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -3,6 +3,8 @@ pub mod music_storage {
     pub mod music_collection;
     pub mod playlist;
     mod utils;
+
+    #[allow(dead_code)]
     pub mod db_reader;
 }
 
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 2bd3145..4e4711b 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -1,8 +1,6 @@
 // Crate things
-use super::utils::{find_images, normalize, read_library, write_library};
-use super::music_collection::MusicCollection;
+use super::utils::{find_images, normalize, read_file, write_file};
 use crate::config::config::Config;
-use crate::music_storage::library;
 
 // Various std things
 use std::collections::BTreeMap;
@@ -316,25 +314,30 @@ impl Album<'_> {
 
 const BLOCKED_EXTENSIONS: [&str; 4] = ["vob", "log", "txt", "sf2"];
 
-#[derive(Debug)]
+#[derive(Debug, Serialize, Deserialize)]
 pub struct MusicLibrary {
     pub name: String,
     pub uuid: Uuid,
     pub library: Vec<Song>,
 }
+
 #[test]
-    fn test_() {
-        let a = MusicLibrary::init(Arc::new(RwLock::from(Config::new())), None).unwrap();
-        dbg!(a);
-    }
+fn library_init() {
+    let uuidv4 = Uuid::new_v4();
+    let a = MusicLibrary::init(Arc::new(RwLock::from(Config::new())), uuidv4).unwrap();
+    dbg!(a);
+}
+
 impl MusicLibrary {
-    pub fn new() -> Self {
+    pub fn new(name: String, uuid: Uuid) -> Self {
         MusicLibrary {
-            name: String::default(),
-            uuid: Uuid::default(),
+            name,
+            uuid,
             library: Vec::new(),
         }
     }
+
+    /*
     pub fn with_uuid(uuid: Uuid, path: PathBuf) -> Result<Self, Box<dyn Error>> {
         MusicLibrary {
             name: String::new(),
@@ -344,40 +347,35 @@ impl MusicLibrary {
 
         todo!()
     }
+    */
 
     /// Initialize the database
     ///
     /// 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 [MusicLibrary] Vec
-    pub fn init(config: Arc<RwLock<Config>>, uuid: Option<Uuid>) -> Result<Self, Box<dyn Error>> {
+    pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
         let global_config = &*config.read().unwrap();
 
-        let mut library = MusicLibrary::new();
+        let library: MusicLibrary = match global_config.libraries.uuid_exists(&uuid) {
+            true => read_file(global_config.libraries.get_library(&uuid)?.path)?,
+            false => {
+                // If the library does not exist, re-create it
+                let lib = MusicLibrary::new(String::new(), uuid);
+                write_file(&lib, global_config.libraries.get_library(&uuid)?.path)?;
+                lib
+            }
+        };
 
-        if let Some(uuid) = uuid {
-            match global_config.library_exists(&uuid) {
-                true => {
-                    library.library = read_library(global_config.get_library(&uuid)?.path)?;
-                },
-                false => {
-                    // Create the database if it does not exist
-                    write_library(&library.library, global_config.path.clone())?;
-                    library = MusicLibrary::with_uuid(uuid, global_config.path.parent().unwrap().to_path_buf())?;
-                }
-            };
-        }else {
-            write_library(&library.library, global_config.path.clone())?;
-        }
         Ok(library)
     }
 
     /// Serializes the database out to the file specified in the config
     pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
-        let path = config.get_library(&self.uuid)?.path;
+        let path = config.libraries.get_library(&self.uuid)?.path;
         match path.try_exists() {
             Ok(_) => {
-                write_library(&self.library, path)?;
+                write_file(&self.library, path)?;
             }
             Err(error) => return Err(error.into()),
         }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index c42a6b5..945aa4c 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -6,7 +6,7 @@ use walkdir::WalkDir;
 
 use snap;
 
-use super::library::{AlbumArt, Song, URI};
+use super::library::{AlbumArt, URI};
 use unidecode::unidecode;
 
 pub(super) fn normalize(input_string: &str) -> String {
@@ -19,35 +19,36 @@ pub(super) fn normalize(input_string: &str) -> String {
     normalized
 }
 
-pub(super) fn read_library(path: PathBuf) -> Result<Vec<Song>, Box<dyn Error>> {
+pub(super) fn read_file<T: for<'de> serde::Deserialize<'de>>(path: PathBuf) -> Result<T, 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(
+    let library: T = bincode::serde::decode_from_std_read(
         &mut d,
         bincode::config::standard()
             .with_little_endian()
             .with_variable_int_encoding(),
     )?;
+
     Ok(library)
 }
 
-pub(super) fn write_library(
-    library: &Vec<Song>,
+pub(super) fn write_file<T: serde::Serialize>(
+    library: &T,
     path: PathBuf,
 ) -> Result<(), Box<dyn Error>> {
-    // Create 2 new names for the file, a temporary one for writing out, and a backup
+    // Create a temporary name for writing out
     let mut writer_name = path.clone();
     writer_name.set_extension("tmp");
 
-    // Create a new BufWriter on the file and make a snap frame encoer for it too
+    // Create a new BufWriter on the file and a snap frame encoder
     let writer = BufWriter::new(fs::File::create(&writer_name)?);
     let mut e = snap::write::FrameEncoder::new(writer);
 
-    // Write out the data using bincode
+    // Write out the data
     bincode::serde::encode_into_std_write(
         library,
         &mut e,

From 097cc1147ef3c3d929f2c1bec7a103f402b3aa07 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Fri, 19 Jan 2024 22:35:19 -0500
Subject: [PATCH 079/136] renamed xml to itunes

---
 .../db_reader/{xml => itunes}/reader.rs       | 64 +++++--------------
 src/music_storage/db_reader/mod.rs            |  2 +-
 src/music_storage/playlist.rs                 |  4 +-
 3 files changed, 19 insertions(+), 51 deletions(-)
 rename src/music_storage/db_reader/{xml => itunes}/reader.rs (86%)

diff --git a/src/music_storage/db_reader/xml/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
similarity index 86%
rename from src/music_storage/db_reader/xml/reader.rs
rename to src/music_storage/db_reader/itunes/reader.rs
index 1652551..c2063c0 100644
--- a/src/music_storage/db_reader/xml/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -18,18 +18,18 @@ use crate::music_storage::utils;
 use urlencoding::decode;
 
 #[derive(Debug, Default, Clone)]
-pub struct XmlLibrary {
-    tracks: Vec<XMLSong>,
+pub struct ITunesLibrary {
+    tracks: Vec<ITunesSong>,
 }
-impl XmlLibrary {
+impl ITunesLibrary {
     fn new() -> Self {
         Default::default()
     }
-    pub fn tracks(self) -> Vec<XMLSong> {
+    pub fn tracks(self) -> Vec<ITunesSong> {
         self.tracks
     }
 }
-impl ExternalLibrary for XmlLibrary {
+impl ExternalLibrary for ITunesLibrary {
     fn from_file(file: &Path) -> Self {
         let mut reader = Reader::from_file(file).unwrap();
         reader.trim_text(true);
@@ -45,7 +45,7 @@ impl ExternalLibrary for XmlLibrary {
         let mut buf = Vec::new();
         let mut skip = false;
 
-        let mut converted_songs: Vec<XMLSong> = Vec::new();
+        let mut converted_songs: Vec<ITunesSong> = Vec::new();
 
         let mut song_tags: HashMap<String, String> = HashMap::new();
         let mut key: String = String::new();
@@ -63,7 +63,7 @@ impl ExternalLibrary for XmlLibrary {
                 tagvalue.clear();
                 key_selected = false;
 
-                //end the song to start a new one, and turn turn current song map into XMLSong
+                //end the song to start a new one, and turn turn current song map into iTunesSong
                 if song_tags.contains_key(&"Location".to_string()) {
                     count3 += 1;
                     //check for skipped IDs
@@ -73,7 +73,7 @@ impl ExternalLibrary for XmlLibrary {
                         count3 += 1;
                         count4 += 1;
                     }
-                    converted_songs.push(XMLSong::from_hashmap(&mut song_tags).unwrap());
+                    converted_songs.push(ITunesSong::from_hashmap(&mut song_tags).unwrap());
                     song_tags.clear();
                     skip = true;
                 }
@@ -117,8 +117,8 @@ impl ExternalLibrary for XmlLibrary {
             buf.clear();
         }
         let elasped = now.elapsed();
-        println!("\n\nXMLReader grabbed {} songs in {:#?} seconds\nIDs Skipped: {}", count3, elasped.as_secs(), count4);
-        let mut lib = XmlLibrary::new();
+        println!("\n\niTunesReader grabbed {} songs in {:#?} seconds\nIDs Skipped: {}", count3, elasped.as_secs(), count4);
+        let mut lib = ITunesLibrary::new();
         lib.tracks.append(converted_songs.as_mut());
         lib
     }
@@ -253,7 +253,7 @@ fn get_art(file: &Path) -> Result<Vec<AlbumArt>, LoftyError> {
 }
 
 #[derive(Debug, Clone, Default)]
-pub struct XMLSong {
+pub struct ITunesSong {
     pub id: i32,
     pub plays: i32,
     pub favorited: bool,
@@ -268,13 +268,13 @@ pub struct XMLSong {
     pub location: String,
 }
 
-impl XMLSong {
-    pub fn new() -> XMLSong {
+impl ITunesSong {
+    pub fn new() -> ITunesSong {
         Default::default()
     }
 
-    fn from_hashmap(map: &mut HashMap<String, String>) -> Result<XMLSong, LoftyError> {
-        let mut song = XMLSong::new();
+    fn from_hashmap(map: &mut HashMap<String, String>) -> Result<ITunesSong, LoftyError> {
+        let mut song = ITunesSong::new();
         //get the path with the first bit chopped off
         let path_: String = map.get_key_value("Location").unwrap().1.clone();
         let track_type: String = map.get_key_value("Track Type").unwrap().1.clone();
@@ -320,36 +320,4 @@ impl XMLSong {
         // println!("{:.2?}", song);
         Ok(song)
     }
-}
-
-// fn get_folder(file: &PathBuf) -> String {
-//     let mut reader = Reader::from_file(file).unwrap();
-//     reader.trim_text(true);
-//     //count every event, for fun ig?
-//     let mut count = 0;
-//     let mut buf = Vec::new();
-//     let mut folder = String::new();
-//     loop {
-//         match reader.read_event_into(&mut buf) {
-//             Ok(Event::Start(_)) => {
-//                 count += 1;
-//             }
-//             Ok(Event::Text(e)) => {
-//                 if count == 10 {
-//                     folder = String::from(
-//                         e.unescape()
-//                             .unwrap()
-//                             .to_string()
-//                             .strip_prefix("file://localhost/")
-//                             .unwrap(),
-//                     );
-//                     return folder;
-//                 }
-//             }
-//             Err(_e) => {
-//                 panic!("oh no! something happened in the public function `get_reader_from_xml()!`")
-//             }
-//             _ => (),
-//         }
-//     }
-// }
+}
\ No newline at end of file
diff --git a/src/music_storage/db_reader/mod.rs b/src/music_storage/db_reader/mod.rs
index 6569a82..2a5b979 100644
--- a/src/music_storage/db_reader/mod.rs
+++ b/src/music_storage/db_reader/mod.rs
@@ -6,7 +6,7 @@ pub mod musicbee {
     pub mod reader;
     pub mod utils;
 }
-pub mod xml {
+pub mod itunes {
     pub mod reader;
 }
 pub mod common;
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index cc8166c..a35e9c1 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -6,7 +6,7 @@ use walkdir::Error;
 use super::{
     library::{AlbumArt, Song, Tag},
     music_collection::MusicCollection, db_reader::{
-        xml::reader::XmlLibrary,
+        itunes::reader::ITunesLibrary,
         extern_library::ExternalLibrary
     },
 };
@@ -136,7 +136,7 @@ impl Default for Playlist<'_> {
 
 #[test]
 fn list_to_m3u8() {
-    let lib = XmlLibrary::from_file(Path::new(
+    let lib = ITunesLibrary::from_file(Path::new(
         "F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml",
     ));
     let mut a = Playlist::new();

From e931c2b5286d2ec64844508d2dd18620b24594c6 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 20 Jan 2024 20:38:50 -0600
Subject: [PATCH 080/136] Modified `library.rs` to properly save with bincode,
 added new functions to it as well

---
 Cargo.toml                   |  9 +++---
 src/config/config.rs         | 41 +++++++++++++++------------
 src/music_storage/library.rs | 49 +++++++++++++++-----------------
 src/music_storage/utils.rs   | 55 ++++++++++++++++++++----------------
 4 files changed, 80 insertions(+), 74 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index aaf4409..70c70e1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,8 +14,7 @@ categories = []
 [dependencies]
 file-format = { version = "0.23.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
 lofty = "0.18.0"
-serde = { version = "1.0.191", features = ["derive"] }
-toml = "0.8.8"
+serde = { version = "1.0.195", features = ["derive"] }
 walkdir = "2.4.0"
 chrono = { version = "0.4.31", features = ["serde"] }
 bincode = { version = "2.0.0-rc.3", features = ["serde"] }
@@ -23,10 +22,10 @@ unidecode = "0.3.0"
 rayon = "1.8.0"
 log = "0.4"
 base64 = "0.21.5"
-snap = "1.1.0"
+snap = "1"
 rcue = "0.1.3"
-gstreamer = "0.21.2"
-glib = "0.18.3"
+gstreamer = "0.21.3"
+glib = "0.18.5"
 crossbeam-channel = "0.5.8"
 crossbeam = "0.8.2"
 quick-xml = "0.31.0"
diff --git a/src/config/config.rs b/src/config/config.rs
index 7c3ffa8..206287a 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -16,10 +16,25 @@ pub struct ConfigLibrary {
     pub uuid: Uuid
 }
 
-impl ConfigLibrary {
-    pub fn new() -> Self {
-        ConfigLibrary::default()
+impl Default for ConfigLibrary {
+    fn default() -> Self {
+        ConfigLibrary {
+            name: String::new(),
+            path: PathBuf::from("library"),
+            uuid: Uuid::new_v4(),
+        }
     }
+}
+
+impl ConfigLibrary {
+    pub fn new(path: PathBuf, name: String) -> Self {
+        ConfigLibrary {
+            name,
+            path,
+            uuid: Uuid::new_v4(),
+        }
+    }
+
     pub fn open(&self) -> Result<File, Error> {
         match File::open(self.path.as_path()) {
             Ok(ok) => Ok(ok),
@@ -28,19 +43,9 @@ impl ConfigLibrary {
     }
 }
 
-impl Default for ConfigLibrary {
-    fn default() -> Self {
-        ConfigLibrary {
-            name: String::default(),
-            path: PathBuf::default(),
-            uuid: Uuid::new_v4()
-        }
-    }
-}
-
 #[derive(Debug, Default, Serialize, Deserialize)]
 pub struct ConfigLibraries {
-    default_library: Uuid,
+    pub default_library: Uuid,
     pub library_folder: PathBuf,
     pub libraries: Vec<ConfigLibrary>,
 }
@@ -83,7 +88,7 @@ impl ConfigLibraries {
 pub struct Config {
     pub path: PathBuf,
     pub libraries: ConfigLibraries,
-    volume: f32,
+    pub volume: f32,
 }
 
 #[test]
@@ -92,9 +97,9 @@ fn config_test() {
         path: PathBuf::from("config_test.json"),
         libraries: ConfigLibraries {
             libraries: vec![
-                ConfigLibrary::default(),
-                ConfigLibrary::default(),
-                ConfigLibrary::default()
+                ConfigLibrary::new(PathBuf::from("library1"), String::from("library1")),
+                ConfigLibrary::new(PathBuf::from("library2"), String::from("library2")),
+                ConfigLibrary::new(PathBuf::from("library3"), String::from("library3"))
             ],
             ..Default::default()
         },
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 4e4711b..444532f 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -323,13 +323,15 @@ pub struct MusicLibrary {
 
 #[test]
 fn library_init() {
-    let uuidv4 = Uuid::new_v4();
-    let a = MusicLibrary::init(Arc::new(RwLock::from(Config::new())), uuidv4).unwrap();
+    let config = Config::read_file(PathBuf::from("config_test.json")).unwrap();
+    let target_uuid = config.libraries.libraries[0].uuid;
+    let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap();
     dbg!(a);
 }
 
 impl MusicLibrary {
-    pub fn new(name: String, uuid: Uuid) -> Self {
+    /// Create a new library from a name and [Uuid]
+    fn new(name: String, uuid: Uuid) -> Self {
         MusicLibrary {
             name,
             uuid,
@@ -337,18 +339,6 @@ impl MusicLibrary {
         }
     }
 
-    /*
-    pub fn with_uuid(uuid: Uuid, path: PathBuf) -> Result<Self, Box<dyn Error>> {
-        MusicLibrary {
-            name: String::new(),
-            uuid,
-            library: Vec::new(),
-        };
-
-        todo!()
-    }
-    */
-
     /// Initialize the database
     ///
     /// If the database file already exists, return the [MusicLibrary], otherwise create
@@ -357,7 +347,7 @@ impl MusicLibrary {
     pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
         let global_config = &*config.read().unwrap();
 
-        let library: MusicLibrary = match global_config.libraries.uuid_exists(&uuid) {
+        let library: MusicLibrary = match global_config.libraries.get_library(&uuid)?.path.exists() {
             true => read_file(global_config.libraries.get_library(&uuid)?.path)?,
             false => {
                 // If the library does not exist, re-create it
@@ -370,13 +360,24 @@ impl MusicLibrary {
         Ok(library)
     }
 
+    //#[cfg(debug_assertions)] // We probably wouldn't want to use this for real, but maybe it would have some utility?
+    pub fn from_path(path: PathBuf) -> Result<Self, Box<dyn Error>> {
+        let library: MusicLibrary = match path.exists() {
+            true => read_file(path)?,
+            false => {
+                let lib = MusicLibrary::new(String::new(), Uuid::new_v4());
+                write_file(&lib, path)?;
+                lib
+            }
+        };
+        Ok(library)
+    }
+
     /// Serializes the database out to the file specified in the config
     pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
         let path = config.libraries.get_library(&self.uuid)?.path;
         match path.try_exists() {
-            Ok(_) => {
-                write_file(&self.library, path)?;
-            }
+            Ok(_) => write_file(self, path)?,
             Err(error) => return Err(error.into()),
         }
 
@@ -384,12 +385,12 @@ impl MusicLibrary {
     }
 
     /// Returns the library size in number of tracks
-    pub fn size(&self) -> usize {
+    pub fn len_tracks(&self) -> usize {
         self.library.len()
     }
 
     /// Queries for a [Song] by its [URI], returning a single `Song`
-    /// with the `URI` that matches
+    /// with the `URI` that matches along with its position in the library
     pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
         let result = self
             .library
@@ -408,7 +409,7 @@ impl MusicLibrary {
         }
     }
 
-    /// Queries for a [Song] by its [PathBuf], returning a `Vec<Song>`
+    /// Queries for a [Song] by its [PathBuf], returning a `Vec<&Song>`
     /// with matching `PathBuf`s
     fn query_path(&self, path: PathBuf) -> Option<Vec<&Song>> {
         let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
@@ -428,7 +429,6 @@ impl MusicLibrary {
     pub fn scan_folder(
         &mut self,
         target_path: &str,
-        config: &Config,
     ) -> Result<i32, Box<dyn std::error::Error>> {
         let mut total = 0;
         let mut errors = 0;
@@ -488,9 +488,6 @@ impl MusicLibrary {
             }
         }
 
-        // Save the database after scanning finishes
-        self.save(config).unwrap();
-
         println!("ERRORS: {}", errors);
 
         Ok(total)
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 945aa4c..a14dbe1 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,15 +1,17 @@
-use file_format::{FileFormat, Kind};
+use std::fs::{File, self};
 use std::io::{BufReader, BufWriter};
 use std::path::{Path, PathBuf};
-use std::{error::Error, fs};
+use std::error::Error;
+
 use walkdir::WalkDir;
-
+use file_format::{FileFormat, Kind};
 use snap;
-
-use super::library::{AlbumArt, URI};
 use unidecode::unidecode;
 
+use super::library::{AlbumArt, URI};
+
 pub(super) fn normalize(input_string: &str) -> String {
+    // Normalize the string to latin characters... this needs a lot of work
     let mut normalized = unidecode(input_string);
 
     // Remove non alphanumeric characters
@@ -19,25 +21,10 @@ pub(super) fn normalize(input_string: &str) -> String {
     normalized
 }
 
-pub(super) fn read_file<T: for<'de> serde::Deserialize<'de>>(path: PathBuf) -> Result<T, 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: T = bincode::serde::decode_from_std_read(
-        &mut d,
-        bincode::config::standard()
-            .with_little_endian()
-            .with_variable_int_encoding(),
-    )?;
-
-    Ok(library)
-}
-
+/// Write any data structure which implements [serde::Serialize]
+/// out to a [bincode] encoded file compressed using [snap]
 pub(super) fn write_file<T: serde::Serialize>(
-    library: &T,
+    library: T,
     path: PathBuf,
 ) -> Result<(), Box<dyn Error>> {
     // Create a temporary name for writing out
@@ -45,7 +32,7 @@ pub(super) fn write_file<T: serde::Serialize>(
     writer_name.set_extension("tmp");
 
     // Create a new BufWriter on the file and a snap frame encoder
-    let writer = BufWriter::new(fs::File::create(&writer_name)?);
+    let writer = BufWriter::new(File::create(&writer_name)?);
     let mut e = snap::write::FrameEncoder::new(writer);
 
     // Write out the data
@@ -61,6 +48,24 @@ pub(super) fn write_file<T: serde::Serialize>(
     Ok(())
 }
 
+/// Read a file serialized out with [write_file] and turn it into
+/// the desired structure
+pub(super) fn read_file<T: for<'de> serde::Deserialize<'de>>(path: PathBuf) -> Result<T, Box<dyn Error>> {
+    // Create a new snap reader over the file
+    let file_reader = BufReader::new(File::open(path)?);
+    let mut d = snap::read::FrameDecoder::new(file_reader);
+
+    // Decode the library from the serialized data into the vec
+    let library: T = bincode::serde::decode_from_std_read(
+        &mut d,
+        bincode::config::standard()
+            .with_little_endian()
+            .with_variable_int_encoding(),
+    )?;
+
+    Ok(library)
+}
+
 pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
     let mut images: Vec<AlbumArt> = Vec::new();
 
@@ -85,7 +90,7 @@ pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
             break;
         }
 
-        let image_uri = URI::Local(path.to_path_buf().canonicalize().unwrap());
+        let image_uri = URI::Local(path.to_path_buf().canonicalize()?);
 
         images.push(AlbumArt::External(image_uri));
     }

From 7c5a9c282b156337d7de5ab9d4bacafbafa257f1 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sun, 21 Jan 2024 05:24:10 -0500
Subject: [PATCH 081/136] added more tests

---
 .gitignore           |  2 +-
 src/config/config.rs | 77 ++++++++++++++++++++++++++++++++------------
 2 files changed, 57 insertions(+), 22 deletions(-)

diff --git a/.gitignore b/.gitignore
index 72106f3..a9e4c0b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
 # Rust binary output dir
 target/
-
+test-config/
 # Rust configuration
 Cargo.lock
 
diff --git a/src/config/config.rs b/src/config/config.rs
index 206287a..bc13836 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -1,7 +1,7 @@
 use std::{
     path::PathBuf,
     fs::{File, OpenOptions, self},
-    io::{Error, Write, Read},
+    io::{Error, Write, Read}, sync::{Arc, RwLock},
 };
 
 use serde::{Serialize, Deserialize};
@@ -9,11 +9,14 @@ use serde_json::to_string_pretty;
 use thiserror::Error;
 use uuid::Uuid;
 
+use crate::music_storage::library::{MusicLibrary, self};
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ConfigLibrary {
     pub name: String,
     pub path: PathBuf,
-    pub uuid: Uuid
+    pub uuid: Uuid,
+    pub scan_folders: Option<Vec<PathBuf>>,
 }
 
 impl Default for ConfigLibrary {
@@ -22,16 +25,18 @@ impl Default for ConfigLibrary {
             name: String::new(),
             path: PathBuf::from("library"),
             uuid: Uuid::new_v4(),
+            scan_folders: None,
         }
     }
 }
 
 impl ConfigLibrary {
-    pub fn new(path: PathBuf, name: String) -> Self {
+    pub fn new(path: PathBuf, name: String, scan_folders: Option<Vec<PathBuf>>) -> Self {
         ConfigLibrary {
             name,
             path,
             uuid: Uuid::new_v4(),
+            scan_folders,
         }
     }
 
@@ -43,7 +48,7 @@ impl ConfigLibrary {
     }
 }
 
-#[derive(Debug, Default, Serialize, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize, Clone)]
 pub struct ConfigLibraries {
     pub default_library: Uuid,
     pub library_folder: PathBuf,
@@ -84,29 +89,13 @@ impl ConfigLibraries {
     }
 }
 
-#[derive(Debug, Default, Serialize, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize, Clone)]
 pub struct Config {
     pub path: PathBuf,
     pub libraries: ConfigLibraries,
     pub volume: f32,
 }
 
-#[test]
-fn config_test() {
-    let _ = Config {
-        path: PathBuf::from("config_test.json"),
-        libraries: ConfigLibraries {
-            libraries: vec![
-                ConfigLibrary::new(PathBuf::from("library1"), String::from("library1")),
-                ConfigLibrary::new(PathBuf::from("library2"), String::from("library2")),
-                ConfigLibrary::new(PathBuf::from("library3"), String::from("library3"))
-            ],
-            ..Default::default()
-        },
-        ..Default::default()
-    }.write_file();
-}
-
 impl Config {
     pub fn new() -> Self {
         Config {
@@ -150,3 +139,49 @@ pub enum ConfigError {
     #[error("There is no Default Library for this Config")]
     NoDefaultLibrary
 }
+
+
+#[test]
+fn config_test() {
+    let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None);
+    let lib_b = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
+    let lib_c = ConfigLibrary::new(PathBuf::from("test-config/library3"), String::from("library3"), None);
+    let config = Config {
+        path: PathBuf::from("test-config/config_test.json"),
+        libraries: ConfigLibraries {
+            libraries: vec![
+                lib_a.clone(),
+                lib_b.clone(),
+                lib_c.clone(),
+            ],
+            ..Default::default()
+        },
+        ..Default::default()
+    };
+    config.write_file();
+    let arc = Arc::new(RwLock::from(config));
+    MusicLibrary::init(arc.clone(), lib_a.uuid.clone()).unwrap();
+    MusicLibrary::init(arc.clone(), lib_b.uuid.clone()).unwrap();
+    MusicLibrary::init(arc.clone(), lib_c.uuid.clone()).unwrap();
+
+}
+
+#[test]
+fn test2() {
+    let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
+    let uuid = config.libraries.get_default().unwrap().uuid.clone();
+    let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
+    lib.scan_folder("test-config/music/").unwrap();
+    lib.save(&config).unwrap();
+    dbg!(&lib);
+    dbg!(&config);
+}
+
+#[test]
+fn test3() {
+    let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
+    let uuid = config.libraries.get_default().unwrap().uuid;
+    let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
+
+    dbg!(lib);
+}
\ No newline at end of file

From 03c8d67b5eb3682390792b13801d6453408dee9b Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 22 Jan 2024 14:06:13 -0500
Subject: [PATCH 082/136] s

---
 Cargo.toml                          | 1 +
 src/lib.rs                          | 2 ++
 src/music_controller/connections.rs | 9 +++++++++
 3 files changed, 12 insertions(+)
 create mode 100644 src/music_controller/connections.rs

diff --git a/Cargo.toml b/Cargo.toml
index 70c70e1..7b4d63a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,3 +36,4 @@ thiserror = "1.0.56"
 font = "0.27.0"
 uuid = { version = "1.6.1", features = ["v4", "serde"]}
 serde_json = "1.0.111"
+discord-presence = "0.5.18"
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index 2c479ae..3119019 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -10,9 +10,11 @@ pub mod music_storage {
 
 pub mod music_controller{
     pub mod controller;
+    pub mod connections;
 }
 
 pub mod music_player;
+#[allow(clippy::module_inception)]
 pub mod config {
     pub mod config;
     pub mod other_settings;
diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs
new file mode 100644
index 0000000..e383e1a
--- /dev/null
+++ b/src/music_controller/connections.rs
@@ -0,0 +1,9 @@
+use std::{env, thread, time};
+use discord_presence::{Client, Event};
+
+use super::controller::Controller;
+
+
+impl Controller {
+    //more stuff goes here
+}

From fd82f3072caec5a496b70ece93804975d49337ad Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 22 Jan 2024 14:07:04 -0500
Subject: [PATCH 083/136] removed rpc stuff

---
 Cargo.toml                          | 3 +--
 src/music_controller/connections.rs | 1 -
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 7b4d63a..9c8d51a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -35,5 +35,4 @@ m3u8-rs = "5.0.5"
 thiserror = "1.0.56"
 font = "0.27.0"
 uuid = { version = "1.6.1", features = ["v4", "serde"]}
-serde_json = "1.0.111"
-discord-presence = "0.5.18"
\ No newline at end of file
+serde_json = "1.0.111"
\ No newline at end of file
diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs
index e383e1a..86d8f29 100644
--- a/src/music_controller/connections.rs
+++ b/src/music_controller/connections.rs
@@ -1,5 +1,4 @@
 use std::{env, thread, time};
-use discord_presence::{Client, Event};
 
 use super::controller::Controller;
 

From ccbd11175ba54e646e3be73fd4460bd4bf5c9915 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 23 Jan 2024 22:21:04 -0600
Subject: [PATCH 084/136] Updated `music_player.rs` to more properly handle its
 own thread

---
 src/music_player.rs          | 170 +++++++++++++++++++----------------
 src/music_storage/library.rs |  11 ++-
 2 files changed, 103 insertions(+), 78 deletions(-)

diff --git a/src/music_player.rs b/src/music_player.rs
index 85563e9..dff9748 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -1,7 +1,7 @@
 // Crate things
 //use crate::music_controller::config::Config;
 use crate::music_storage::library::URI;
-use crossbeam_channel::bounded;
+use crossbeam_channel::unbounded;
 use std::error::Error;
 use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
 
@@ -23,7 +23,7 @@ pub enum PlayerCmd {
     AboutToFinish,
 }
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq)]
 pub enum PlayerState {
     Playing,
     Paused,
@@ -76,18 +76,29 @@ pub enum PlayerError {
     General,
 }
 
+#[derive(Debug, PartialEq, Eq)]
+enum PlaybackStats {
+    Idle,
+    Switching,
+    Playing{
+        start: Duration,
+        end:   Duration,
+    }
+}
+
 /// An instance of a music player with a GStreamer backend
 pub struct Player {
-    source: Option<URI>,
+    source:     Option<URI>,
     //pub message_tx: Sender<PlayerCmd>,
     pub message_rx: crossbeam::channel::Receiver<PlayerCmd>,
-    playbin: Arc<RwLock<Element>>,
-    volume: f64,
-    start: Arc<RwLock<Option<Duration>>>,
-    end: Arc<RwLock<Option<Duration>>>,
-    position: Arc<RwLock<Option<Duration>>>,
-    buffer: Arc<RwLock<Option<u8>>>,
-    paused: Arc<RwLock<bool>>,
+
+    playback_tx: crossbeam::channel::Sender<PlaybackStats>,
+    playbin:    Arc<RwLock<Element>>,
+    volume:     f64,
+    start:      Option<Duration>,
+    end:        Option<Duration>,
+    paused:     bool,
+    position:   Arc<RwLock<Option<Duration>>>,
 }
 
 impl Player {
@@ -118,69 +129,58 @@ impl Player {
             .build()
             .ok_or(PlayerError::Build)?;
 
-        playbin
-            .write()
-            .unwrap()
-            .set_property_from_value("flags", &flags);
-
+        playbin.write().unwrap().set_property_from_value("flags", &flags);
         playbin.write().unwrap().set_property("instant-uri", true);
 
         let position = Arc::new(RwLock::new(None));
-        let start = Arc::new(RwLock::new(None));
-        let end: Arc<RwLock<Option<Duration>>> = Arc::new(RwLock::new(None));
-        let buffer = Arc::new(RwLock::new(None));
-        let paused = Arc::new(RwLock::new(false));
 
         // Set up the thread to monitor the position
-        let position_update = position.clone();
-        let start_update = Arc::clone(&start);
-        let end_update = Arc::clone(&end);
-        let (message_tx, message_rx) = bounded(1); //TODO: Maybe figure out a better method than making this bounded
-        std::thread::spawn(move || { //TODO: Figure out how to return errors nicely in threads
+        let (playback_tx, playback_rx) = unbounded();
+        let (stat_tx, stat_rx) = unbounded::<PlaybackStats>();
+        let position_update = Arc::clone(&position);
+        let _playback_monitor = std::thread::spawn(move || { //TODO: Figure out how to return errors nicely in threads
+            let mut stats = PlaybackStats::Idle;
+            let mut pos_temp;
             loop {
-                let mut pos_temp = playbin_arc
+                match stat_rx.recv_timeout(std::time::Duration::from_millis(100)) {
+                    Ok(res) => stats = res,
+                    Err(_) => {}
+                };
+
+                pos_temp = playbin_arc
                     .read()
                     .unwrap()
                     .query_position::<ClockTime>()
                     .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
 
-                if pos_temp.is_some()
-                    && start_update.read().unwrap().is_some()
-                    && end_update.read().unwrap().is_some()
-                {
-                    let atf = end_update.read().unwrap().unwrap() - Duration::milliseconds(250);
-                    if pos_temp.unwrap() >= end_update.read().unwrap().unwrap() {
-                        let _ = message_tx.try_send(PlayerCmd::Eos);
-                        playbin_arc
-                            .write()
-                            .unwrap()
-                            .set_state(gst::State::Ready)
-                            .expect("Unable to set the pipeline state");
-                        *start_update.write().unwrap() = None;
-                        *end_update.write().unwrap() = None;
-                    } else if pos_temp.unwrap() >= atf {
-                        let _ = message_tx.try_send(PlayerCmd::AboutToFinish);
-                    }
+                match stats {
+                    PlaybackStats::Playing{start, end} if pos_temp.is_some() => {
+                        // Check if the current playback position is close to the end
+                        let finish_point = end - Duration::milliseconds(250);
+                        if pos_temp.unwrap() >= end {
+                            let _ = playback_tx.try_send(PlayerCmd::Eos);
+                            playbin_arc
+                                .write()
+                                .unwrap()
+                                .set_state(gst::State::Ready)
+                                .expect("Unable to set the pipeline state");
+                        } else if pos_temp.unwrap() >= finish_point {
+                            let _ = playback_tx.try_send(PlayerCmd::AboutToFinish);
+                        }
 
-                    // This has to be done AFTER the current time in the file
-                    // is calculated, or everything else is wrong
-                    if let Some(time) = *start_update.read().unwrap() {
-                        pos_temp = Some(pos_temp.unwrap() - time)
+                        // This has to be done AFTER the current time in the file
+                        // is calculated, or everything else is wrong
+                        pos_temp = Some(pos_temp.unwrap() - start)
                     }
+                    _ => println!("waiting!")
                 }
 
-                //println!("{:?}", pos_temp);
-
                 *position_update.write().unwrap() = pos_temp;
-
-                std::thread::sleep(std::time::Duration::from_millis(100));
             }
         });
 
         // Set up the thread to monitor bus messages
         let playbin_bus_ctrl = Arc::clone(&playbin);
-        let buffer_bus_ctrl = Arc::clone(&buffer);
-        let paused_bus_ctrl = Arc::clone(&paused);
         let bus_watch = playbin
             .read()
             .unwrap()
@@ -203,17 +203,16 @@ impl Player {
                             .set_state(gst::State::Playing)
                             .unwrap();
                     }
+                    /* TODO: Fix buffering!!
                     gst::MessageView::Buffering(buffering) => {
                         let percent = buffering.percent();
                         if percent < 100 {
-                            *buffer_bus_ctrl.write().unwrap() = Some(percent as u8);
                             playbin_bus_ctrl
                                 .write()
                                 .unwrap()
                                 .set_state(gst::State::Paused)
                                 .unwrap();
-                        } else if !(*paused_bus_ctrl.read().unwrap()) {
-                            *buffer_bus_ctrl.write().unwrap() = None;
+                        } else if !(buffering) {
                             playbin_bus_ctrl
                                 .write()
                                 .unwrap()
@@ -221,6 +220,7 @@ impl Player {
                                 .unwrap();
                         }
                     }
+                    */
                     _ => (),
                 }
                 glib::ControlFlow::Continue
@@ -237,13 +237,13 @@ impl Player {
         Ok(Self {
             source,
             playbin,
-            message_rx,
+            message_rx: playback_rx,
+            playback_tx: stat_tx,
             volume: 1.0,
-            start,
-            end,
-            paused,
+            start: None,
+            end: None,
+            paused: false,
             position,
-            buffer,
         })
     }
 
@@ -257,6 +257,9 @@ impl Player {
 
     /// Set the playback URI
     fn set_source(&mut self, source: &URI) {
+        // Make sure the playback tracker knows the stuff is stopped
+        self.playback_tx.send(PlaybackStats::Switching).unwrap();
+
         let uri = self.playbin.read().unwrap().property_value("current-uri");
         self.source = Some(source.clone());
         match source {
@@ -267,12 +270,17 @@ impl Player {
                     .set_property("uri", source.as_uri());
 
                 // Set the start and end positions of the CUE file
-                *self.start.write().unwrap() = Some(Duration::from_std(*start).unwrap());
-                *self.end.write().unwrap() = Some(Duration::from_std(*end).unwrap());
+                self.start = Some(Duration::from_std(*start).unwrap());
+                self.end = Some(Duration::from_std(*end).unwrap());
 
-                self.play().unwrap();
+                // Send the updated position to the tracker
+                self.playback_tx.send(PlaybackStats::Playing{
+                    start: self.start.unwrap(),
+                    end: self.end.unwrap()
+                }).unwrap();
 
                 // Wait for it to be ready, and then move to the proper position
+                self.play().unwrap();
                 let now = std::time::Instant::now();
                 while now.elapsed() < std::time::Duration::from_millis(20) {
                     if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
@@ -297,8 +305,14 @@ impl Player {
                     std::thread::sleep(std::time::Duration::from_millis(10));
                 }
 
-                *self.start.write().unwrap() = Some(Duration::seconds(0));
-                *self.end.write().unwrap() = self.raw_duration();
+                self.start = Some(Duration::seconds(0));
+                self.end = self.raw_duration();
+
+                // Send the updated position to the tracker
+                self.playback_tx.send(PlaybackStats::Playing{
+                    start: self.start.unwrap(),
+                    end: self.end.unwrap()
+                }).unwrap();
             }
         }
     }
@@ -361,13 +375,13 @@ impl Player {
 
     /// Pause, if playing
     pub fn pause(&mut self) -> Result<(), gst::StateChangeError> {
-        *self.paused.write().unwrap() = true;
+        //*self.paused.write().unwrap() = true;
         self.set_state(gst::State::Paused)
     }
 
     /// Resume from being paused
     pub fn resume(&mut self) -> Result<(), gst::StateChangeError> {
-        *self.paused.write().unwrap() = false;
+        //*self.paused.write().unwrap() = false;
         self.set_state(gst::State::Playing)
     }
 
@@ -383,8 +397,8 @@ impl Player {
 
     /// Get the duration of the currently playing track
     pub fn duration(&mut self) -> Option<Duration> {
-        if self.end.read().unwrap().is_some() && self.start.read().unwrap().is_some() {
-            Some(self.end.read().unwrap().unwrap() - self.start.read().unwrap().unwrap())
+        if self.end.is_some() && self.start.is_some() {
+            Some(self.end.unwrap() - self.start.unwrap())
         } else {
             self.raw_duration()
         }
@@ -411,16 +425,16 @@ impl Player {
 
     /// Seek absolutely
     pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
-        let start = if self.start.read().unwrap().is_none() {
+        let start = if self.start.is_none() {
             return Err("Failed to seek: No START time".into());
         } else {
-            self.start.read().unwrap().unwrap()
+            self.start.unwrap()
         };
 
-        let end = if self.end.read().unwrap().is_none() {
+        let end = if self.end.is_none() {
             return Err("Failed to seek: No END time".into());
         } else {
-            self.end.read().unwrap().unwrap()
+            self.end.unwrap()
         };
 
         let adjusted_target = target_pos + start;
@@ -439,10 +453,13 @@ impl Player {
 
     /// Get the current state of the playback
     pub fn state(&mut self) -> PlayerState {
+        self.playbin().unwrap().current_state().into()
+        /*
         match *self.buffer.read().unwrap() {
             None => self.playbin().unwrap().current_state().into(),
             Some(value) => PlayerState::Buffering(value),
         }
+        */
     }
 
     pub fn property(&self, property: &str) -> glib::Value {
@@ -454,10 +471,13 @@ impl Player {
         self.pause()?;
         self.ready()?;
 
+        // Send the updated position to the tracker
+        self.playback_tx.send(PlaybackStats::Idle).unwrap();
+
         // Set all positions to none
         *self.position.write().unwrap() = None;
-        *self.start.write().unwrap() = None;
-        *self.end.write().unwrap() = None;
+        self.start = None;
+        self.end = None;
         Ok(())
     }
 }
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 444532f..bf7620e 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -374,7 +374,7 @@ impl MusicLibrary {
     }
 
     /// Serializes the database out to the file specified in the config
-    pub fn save(&self, config: &Config) -> Result<(), Box<dyn Error>> {
+    pub fn save(&self, config: Config) -> Result<(), Box<dyn Error>> {
         let path = config.libraries.get_library(&self.uuid)?.path;
         match path.try_exists() {
             Ok(_) => write_file(self, path)?,
@@ -389,6 +389,11 @@ impl MusicLibrary {
         self.library.len()
     }
 
+    /// Returns the library size in number of albums
+    pub fn len_albums(&self) -> usize {
+        self.albums().len()
+    }
+
     /// Queries for a [Song] by its [URI], returning a single `Song`
     /// with the `URI` that matches along with its position in the library
     pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
@@ -641,7 +646,7 @@ impl MusicLibrary {
                 }
 
                 let duration = match next_track.next() {
-                    Some(future) => match future.indices.get(0) {
+                    Some(future) => match future.indices.first() {
                         Some(val) => val.1 - start,
                         None => Duration::from_secs(0),
                     },
@@ -923,7 +928,7 @@ 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 album_art = result.album_art.first();
 
                     let new_album = Album {
                         title,

From 04c7ddb87698052fce8b959a654f48a6613a015a Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 24 Jan 2024 02:47:57 -0600
Subject: [PATCH 085/136] Cleaned up code, updated crates

---
 Cargo.toml                 |  6 +++---
 src/music_player.rs        | 24 ++++++++++++++++--------
 src/music_storage/utils.rs |  4 ++--
 3 files changed, 21 insertions(+), 13 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 9c8d51a..0267611 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,12 +13,11 @@ categories = []
 
 [dependencies]
 file-format = { version = "0.23.0", features = ["reader-asf", "reader-ebml", "reader-mp4", "reader-rm", "reader-txt", "reader-xml", "serde"] }
-lofty = "0.18.0"
+lofty = "0.18.2"
 serde = { version = "1.0.195", features = ["derive"] }
 walkdir = "2.4.0"
 chrono = { version = "0.4.31", features = ["serde"] }
 bincode = { version = "2.0.0-rc.3", features = ["serde"] }
-unidecode = "0.3.0"
 rayon = "1.8.0"
 log = "0.4"
 base64 = "0.21.5"
@@ -35,4 +34,5 @@ m3u8-rs = "5.0.5"
 thiserror = "1.0.56"
 font = "0.27.0"
 uuid = { version = "1.6.1", features = ["v4", "serde"]}
-serde_json = "1.0.111"
\ No newline at end of file
+serde_json = "1.0.111"
+deunicode = "1.4.2"
diff --git a/src/music_player.rs b/src/music_player.rs
index dff9748..4e7de23 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -83,7 +83,8 @@ enum PlaybackStats {
     Playing{
         start: Duration,
         end:   Duration,
-    }
+    },
+    Finished // When this is sent, the thread will die!
 }
 
 /// An instance of a music player with a GStreamer backend
@@ -142,10 +143,10 @@ impl Player {
             let mut stats = PlaybackStats::Idle;
             let mut pos_temp;
             loop {
-                match stat_rx.recv_timeout(std::time::Duration::from_millis(100)) {
-                    Ok(res) => stats = res,
-                    Err(_) => {}
-                };
+                // Check for new messages or updates about how to proceed
+                if let Ok(res) = stat_rx.recv_timeout(std::time::Duration::from_millis(100)) {
+                    stats = res
+                }
 
                 pos_temp = playbin_arc
                     .read()
@@ -171,8 +172,13 @@ impl Player {
                         // This has to be done AFTER the current time in the file
                         // is calculated, or everything else is wrong
                         pos_temp = Some(pos_temp.unwrap() - start)
-                    }
-                    _ => println!("waiting!")
+                    },
+                    PlaybackStats::Finished => {
+                        *position_update.write().unwrap() = None;
+                        break
+                    },
+                    PlaybackStats::Idle | PlaybackStats::Switching => println!("waiting!"),
+                    _ => ()
                 }
 
                 *position_update.write().unwrap() = pos_temp;
@@ -483,11 +489,13 @@ impl Player {
 }
 
 impl Drop for Player {
-    /// Cleans up `GStreamer` pipeline when `Backend` is dropped.
+    /// Cleans up the `GStreamer` pipeline and the monitoring
+    /// thread when [Player] is dropped.
     fn drop(&mut self) {
         self.playbin_mut()
             .unwrap()
             .set_state(gst::State::Null)
             .expect("Unable to set the pipeline to the `Null` state");
+        let _ = self.playback_tx.send(PlaybackStats::Finished);
     }
 }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index a14dbe1..0d6fb47 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -6,13 +6,13 @@ use std::error::Error;
 use walkdir::WalkDir;
 use file_format::{FileFormat, Kind};
 use snap;
-use unidecode::unidecode;
+use deunicode::deunicode_with_tofu;
 
 use super::library::{AlbumArt, URI};
 
 pub(super) fn normalize(input_string: &str) -> String {
     // Normalize the string to latin characters... this needs a lot of work
-    let mut normalized = unidecode(input_string);
+    let mut normalized = deunicode_with_tofu(input_string, " ");
 
     // Remove non alphanumeric characters
     normalized.retain(|c| c.is_alphanumeric());

From 9bb1ed7ab5f2de959b2f186b8b1c9c4a3f2c9bd3 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sun, 4 Feb 2024 03:47:02 -0500
Subject: [PATCH 086/136] made 2 tiny changes

---
 src/config/config.rs          | 2 +-
 src/music_storage/playlist.rs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index bc13836..283d9c6 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -172,7 +172,7 @@ fn test2() {
     let uuid = config.libraries.get_default().unwrap().uuid.clone();
     let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
     lib.scan_folder("test-config/music/").unwrap();
-    lib.save(&config).unwrap();
+    lib.save(config.clone()).unwrap();
     dbg!(&lib);
     dbg!(&config);
 }
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index a35e9c1..730af56 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -104,7 +104,7 @@ impl<'a> Playlist<'a> {
             .unwrap();
         m3u8.write_to(&mut file).unwrap();
     }
-    pub fn from_file(file: std::fs::File) -> Playlist<'a> {
+    pub fn from_m3u8(file: std::fs::File) -> Playlist<'a> {
         todo!()
     }
 }

From 0a68a12546b71618424427694153acbd9af6c8ed Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Tue, 6 Feb 2024 20:36:31 -0500
Subject: [PATCH 087/136] moved Song creation functions to the `Song` struct

---
 src/config/config.rs          |   6 +-
 src/music_storage/library.rs  | 444 ++++++++++++++++++----------------
 src/music_storage/playlist.rs |  83 ++++---
 3 files changed, 285 insertions(+), 248 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index 283d9c6..e3f021f 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -137,7 +137,11 @@ pub enum ConfigError {
     #[error("No Library Found for {0}!")]
     NoConfigLibrary(Uuid),
     #[error("There is no Default Library for this Config")]
-    NoDefaultLibrary
+    NoDefaultLibrary,
+    //TODO: do something about playlists
+    #[error("Please provide a better m3u8 Playlist")]
+    BadPlaylist,
+
 }
 
 
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index bf7620e..6b216e3 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -175,6 +175,225 @@ impl Song {
     pub fn remove_tag(&mut self, target_key: &Tag) {
         self.tags.remove(target_key);
     }
+
+    /// Creates a `Song` from a song file
+    pub fn from_file(target_file: &Path) -> Result<Self, Box<dyn Error>> {
+        let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
+
+        let blank_tag = &lofty::Tag::new(TagType::Id3v2);
+        let tagged_file: lofty::TaggedFile;
+        let mut duration = Duration::from_secs(0);
+        let tag = match Probe::open(target_file)?.options(normal_options).read() {
+            Ok(file) => {
+                tagged_file = file;
+
+                duration = tagged_file.properties().duration();
+
+                // Ensure the tags exist, if not, insert blank data
+                match tagged_file.primary_tag() {
+                    Some(primary_tag) => primary_tag,
+
+                    None => match tagged_file.first_tag() {
+                        Some(first_tag) => first_tag,
+                        None => blank_tag,
+                    },
+                }
+            }
+
+            Err(_) => blank_tag,
+        };
+
+        let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
+        for item in tag.items() {
+            let key = match item.key() {
+                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" || unknown == "Acoustid Fingerprint" =>
+                {
+                    continue
+                }
+                ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()),
+                custom => Tag::Key(format!("{:?}", custom)),
+            };
+
+            let value = match item.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 from the file
+        let mut album_art: Vec<AlbumArt> = Vec::new();
+        for (i, _art) in tag.pictures().iter().enumerate() {
+            let new_art = AlbumArt::Embedded(i);
+
+            album_art.push(new_art)
+        }
+
+        // Find images around the music file that can be used
+        let mut found_images = find_images(target_file).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),
+            Err(_) => None,
+        };
+
+        // TODO: Fix error handling
+        let binding = fs::canonicalize(target_file).unwrap();
+
+        let new_song = Song {
+            location: URI::Local(binding),
+            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,
+        };
+        Ok(new_song)
+    }
+
+    /// creates a `Vec<Song>` from a cue file
+
+    pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, &PathBuf)>), Box<dyn Error>> {
+        let mut tracks = Vec::new();
+
+        let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap();
+
+        // Get album level information
+        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 file in cue_data.files.iter() {
+            let audio_location = &parent_dir.join(file.file.clone());
+
+            if !audio_location.exists() {
+                continue;
+            }
+
+            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;
+                if track.indices.len() > 1 {
+                    start = track.indices[1].1;
+                } else {
+                    start = track.indices[0].1;
+                }
+                if !start.is_zero() {
+                    start -= pregap;
+                }
+
+                let duration = match next_track.next() {
+                    Some(future) => match future.indices.first() {
+                        Some(val) => val.1 - start,
+                        None => Duration::from_secs(0),
+                    },
+                    None => match lofty::read_from_path(audio_location) {
+                        Ok(tagged_file) => tagged_file.properties().duration() - start,
+
+                        Err(_) => match Probe::open(audio_location)?.read() {
+                            Ok(tagged_file) => tagged_file.properties().duration() - start,
+
+                            Err(_) => Duration::from_secs(0),
+                        },
+                    },
+                };
+                let end = start + duration + postgap;
+
+                // 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();
+                match album_title {
+                    Some(title) => {
+                        tags.insert(Tag::Album, title.clone());
+                    }
+                    None => (),
+                }
+                match album_artist {
+                    Some(artist) => {
+                        tags.insert(Tag::Artist, 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,
+                };
+                tracks.push((new_song, audio_location));
+            }
+        }
+     Ok((tracks))
+    }
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -499,97 +718,8 @@ impl MusicLibrary {
     }
 
     pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> {
-        let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
-
-        let blank_tag = &lofty::Tag::new(TagType::Id3v2);
-        let tagged_file: lofty::TaggedFile;
-        let mut duration = Duration::from_secs(0);
-        let tag = match Probe::open(target_file)?.options(normal_options).read() {
-            Ok(file) => {
-                tagged_file = file;
-
-                duration = tagged_file.properties().duration();
-
-                // Ensure the tags exist, if not, insert blank data
-                match tagged_file.primary_tag() {
-                    Some(primary_tag) => primary_tag,
-
-                    None => match tagged_file.first_tag() {
-                        Some(first_tag) => first_tag,
-                        None => blank_tag,
-                    },
-                }
-            }
-
-            Err(_) => blank_tag,
-        };
-
-        let mut tags: BTreeMap<Tag, String> = BTreeMap::new();
-        for item in tag.items() {
-            let key = match item.key() {
-                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" || unknown == "Acoustid Fingerprint" =>
-                {
-                    continue
-                }
-                ItemKey::Unknown(unknown) => Tag::Key(unknown.to_string()),
-                custom => Tag::Key(format!("{:?}", custom)),
-            };
-
-            let value = match item.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 from the file
-        let mut album_art: Vec<AlbumArt> = Vec::new();
-        for (i, _art) in tag.pictures().iter().enumerate() {
-            let new_art = AlbumArt::Embedded(i);
-
-            album_art.push(new_art)
-        }
-
-        // Find images around the music file that can be used
-        let mut found_images = find_images(target_file).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),
-            Err(_) => None,
-        };
-
-        // TODO: Fix error handling
-        let binding = fs::canonicalize(target_file).unwrap();
-
-        let new_song = Song {
-            location: URI::Local(binding),
-            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,
-        };
 
+        let new_song = Song::from_file(target_file)?;
         match self.add_song(new_song) {
             Ok(_) => (),
             Err(_) => {
@@ -601,137 +731,23 @@ impl MusicLibrary {
     }
 
     pub fn add_cuesheet(&mut self, cuesheet: &Path) -> Result<i32, Box<dyn Error>> {
-        let mut tracks_added = 0;
+        let tracks = Song::from_cue(cuesheet)?;
+        let mut tracks_added = tracks.len() as i32;
 
-        let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap();
-
-        // Get album level information
-        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 file in cue_data.files.iter() {
-            let audio_location = &parent_dir.join(file.file.clone());
-
-            if !audio_location.exists() {
-                continue;
-            }
 
+        for (new_song, location) in tracks {
             // Try to remove the original audio file from the db if it exists
-            if self.remove_uri(&URI::Local(audio_location.clone())).is_ok() {
+            if self.remove_uri(&URI::Local(location.clone())).is_ok() {
                 tracks_added -= 1
             }
-
-            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;
-                if track.indices.len() > 1 {
-                    start = track.indices[1].1;
-                } else {
-                    start = track.indices[0].1;
+            match self.add_song(new_song) {
+                Ok(_) => {},
+                Err(_error) => {
+                    //println!("{}", _error);
+                    continue;
                 }
-                if !start.is_zero() {
-                    start -= pregap;
-                }
-
-                let duration = match next_track.next() {
-                    Some(future) => match future.indices.first() {
-                        Some(val) => val.1 - start,
-                        None => Duration::from_secs(0),
-                    },
-                    None => match lofty::read_from_path(audio_location) {
-                        Ok(tagged_file) => tagged_file.properties().duration() - start,
-
-                        Err(_) => match Probe::open(audio_location)?.read() {
-                            Ok(tagged_file) => tagged_file.properties().duration() - start,
-
-                            Err(_) => Duration::from_secs(0),
-                        },
-                    },
-                };
-                let end = start + duration + postgap;
-
-                // 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();
-                match album_title {
-                    Some(title) => {
-                        tags.insert(Tag::Album, title.clone());
-                    }
-                    None => (),
-                }
-                match album_artist {
-                    Some(artist) => {
-                        tags.insert(Tag::Artist, 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)
     }
 
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 730af56..c9ad352 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,7 +1,9 @@
-use std::path::Path;
+use std::{fs::File, path::{Path, PathBuf}, io::{Read, Error}};
 
+use bincode::config;
 use chrono::Duration;
-use walkdir::Error;
+use uuid::Uuid;
+// use walkdir::Error;
 
 use super::{
     library::{AlbumArt, Song, Tag},
@@ -11,14 +13,14 @@ use super::{
     },
 };
 
-use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment};
-// use nom::IResult;
+use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2, MasterPlaylist};
+
 
 #[derive(Debug, Clone)]
 pub struct Playlist<'a> {
     title: String,
     cover: Option<&'a AlbumArt>,
-    tracks: Vec<Song>,
+    tracks: Vec<Uuid>,
     play_count: i32,
     play_time: Duration,
 }
@@ -32,43 +34,37 @@ impl<'a> Playlist<'a> {
     pub fn play_time(&self) -> chrono::Duration {
         self.play_time
     }
-    pub fn set_tracks(&mut self, songs: Vec<Song>) -> Result<(), Error> {
+    pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
         self.tracks = songs;
-        Ok(())
     }
-    pub fn add_track(&mut self, song: Song) -> Result<(), Error> {
+    pub fn add_track(&mut self, track: Uuid) -> Result<(), Error> {
         self.tracks.push(song);
         Ok(())
     }
     pub fn remove_track(&mut self, index: i32) -> Result<(), Error> {
-        let bun: usize = index as usize;
-        let mut name = String::new();
-        if self.tracks.len() >= bun {
-            name = String::from(self.tracks[bun].tags.get_key_value(&Tag::Title).unwrap().1);
-            self.tracks.remove(bun);
+        let index = index as usize;
+        if (self.tracks.len() - 1) >= index {
+            self.tracks.remove(index);
         }
-        dbg!(name);
         Ok(())
     }
-    pub fn get_index(&self, song_name: &str) -> Option<usize> {
-        let mut index = 0;
-        if self.contains_value(&Tag::Title, song_name) {
-            for track in &self.tracks {
-                index += 1;
-                if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 {
-                    dbg!("Index gotted! ", index);
-                    return Some(index);
-                }
-            }
-        }
-        None
-    }
+    // pub fn get_index(&self, song_name: &str) -> Option<usize> {
+    //     let mut index = 0;
+    //     if self.contains_value(&Tag::Title, song_name) {
+    //         for track in &self.tracks {
+    //             index += 1;
+    //             if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 {
+    //                 dbg!("Index gotted! ", index);
+    //                 return Some(index);
+    //             }
+    //         }
+    //     }
+    //     None
+    // }
     pub fn contains_value(&self, tag: &Tag, value: &str) -> bool {
-        for track in &self.tracks {
-            if value == track.tags.get_key_value(tag).unwrap().1 {
-                return true;
-            }
-        }
+        &self.tracks.iter().for_each(|track| {
+
+        });
         false
     }
     pub fn to_m3u8(&mut self) {
@@ -104,7 +100,28 @@ impl<'a> Playlist<'a> {
             .unwrap();
         m3u8.write_to(&mut file).unwrap();
     }
-    pub fn from_m3u8(file: std::fs::File) -> Playlist<'a> {
+    pub fn from_m3u8(path: &str) -> Result<Playlist<'a>, Error> {
+        let mut file = match File::open(path) {
+            Ok(file) => file,
+            Err(e) => return Err(e),
+        };
+        let mut bytes = Vec::new();
+        file.read_to_end(&mut bytes).unwrap();
+
+        let parsed = m3u8_rs::parse_playlist(&bytes);
+
+        let playlist = match parsed {
+            Result::Ok((i, playlist)) => playlist,
+            Result::Err(e) => panic!("Parsing error: \n{}", e),
+        };
+
+        match playlist {
+            List2::MasterPlaylist(_) => panic!(),
+            List2::MediaPlaylist(pl) => {
+                let values = pl.segments.iter().map(|seg| seg.uri.to_owned() ).collect::<Vec<String>>();
+            }
+        }
+
         todo!()
     }
 }

From 4a7a6096d644f66e0673ed48a78ec85928f7e0ab Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Fri, 9 Feb 2024 23:52:24 -0500
Subject: [PATCH 088/136] addded small changes

---
 src/config/config.rs          |  2 +-
 src/music_storage/library.rs  |  6 ++--
 src/music_storage/playlist.rs | 61 +++++++++++++++++++++--------------
 3 files changed, 41 insertions(+), 28 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index e3f021f..9314d65 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -92,6 +92,7 @@ impl ConfigLibraries {
 #[derive(Debug, Default, Serialize, Deserialize, Clone)]
 pub struct Config {
     pub path: PathBuf,
+    pub backup_folder: Option<PathBuf>,
     pub libraries: ConfigLibraries,
     pub volume: f32,
 }
@@ -144,7 +145,6 @@ pub enum ConfigError {
 
 }
 
-
 #[test]
 fn config_test() {
     let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None);
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 6b216e3..69acb27 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -6,6 +6,7 @@ use crate::config::config::Config;
 use std::collections::BTreeMap;
 use std::error::Error;
 use std::ops::ControlFlow::{Break, Continue};
+use std::ops::Deref;
 
 // Files
 use file_format::{FileFormat, Kind};
@@ -273,7 +274,7 @@ impl Song {
 
     /// creates a `Vec<Song>` from a cue file
 
-    pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, &PathBuf)>), Box<dyn Error>> {
+    pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, PathBuf)>), Box<dyn Error>> {
         let mut tracks = Vec::new();
 
         let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap();
@@ -286,6 +287,7 @@ impl Song {
         for file in cue_data.files.iter() {
             let audio_location = &parent_dir.join(file.file.clone());
 
+
             if !audio_location.exists() {
                 continue;
             }
@@ -389,7 +391,7 @@ impl Song {
                     tags,
                     album_art,
                 };
-                tracks.push((new_song, audio_location));
+                tracks.push((new_song, audio_location.clone()));
             }
         }
      Ok((tracks))
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index c9ad352..c2c6b74 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -5,6 +5,8 @@ use chrono::Duration;
 use uuid::Uuid;
 // use walkdir::Error;
 
+use crate::music_controller::controller::Controller;
+
 use super::{
     library::{AlbumArt, Song, Tag},
     music_collection::MusicCollection, db_reader::{
@@ -15,12 +17,17 @@ use super::{
 
 use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2, MasterPlaylist};
 
-
+#[derive(Debug, Clone)]
+pub enum SortOrder {
+    Manual,
+    Tag(Tag)
+}
 #[derive(Debug, Clone)]
 pub struct Playlist<'a> {
     title: String,
     cover: Option<&'a AlbumArt>,
     tracks: Vec<Uuid>,
+    sort_order: SortOrder,
     play_count: i32,
     play_time: Duration,
 }
@@ -35,10 +42,10 @@ impl<'a> Playlist<'a> {
         self.play_time
     }
     pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
-        self.tracks = songs;
+        self.tracks = tracks;
     }
     pub fn add_track(&mut self, track: Uuid) -> Result<(), Error> {
-        self.tracks.push(song);
+        self.tracks.push(track);
         Ok(())
     }
     pub fn remove_track(&mut self, index: i32) -> Result<(), Error> {
@@ -67,16 +74,18 @@ impl<'a> Playlist<'a> {
         });
         false
     }
-    pub fn to_m3u8(&mut self) {
-        let seg = &self
-            .tracks
+    pub fn to_m3u8(&mut self, tracks: Vec<Song>) {
+        let seg = tracks
             .iter()
             .map({
-                |track| MediaSegment {
-                    uri: track.location.to_string().into(),
-                    duration: track.duration.as_millis() as f32,
-                    title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.into()),
-                    ..Default::default()
+                |track| {
+
+                    MediaSegment {
+                        uri: track.location.to_string().into(),
+                        duration: track.duration.as_millis() as f32,
+                        title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.into()),
+                        ..Default::default()
+                    }
                 }
             })
             .collect::<Vec<MediaSegment>>();
@@ -124,8 +133,6 @@ impl<'a> Playlist<'a> {
 
         todo!()
     }
-}
-impl MusicCollection for Playlist<'_> {
     fn title(&self) -> &String {
         &self.title
     }
@@ -135,30 +142,34 @@ impl MusicCollection for Playlist<'_> {
             None => None,
         }
     }
-    fn tracks(&self) -> Vec<Song> {
+    fn tracks(&self) -> Vec<Uuid> {
         self.tracks.to_owned()
     }
 }
+
+
+
 impl Default for Playlist<'_> {
     fn default() -> Self {
         Playlist {
             title: String::default(),
             cover: None,
             tracks: Vec::default(),
+            sort_order: SortOrder::Manual,
             play_count: 0,
             play_time: Duration::zero(),
         }
     }
 }
 
-#[test]
-fn list_to_m3u8() {
-    let lib = ITunesLibrary::from_file(Path::new(
-        "F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml",
-    ));
-    let mut a = Playlist::new();
-    let c = lib.to_songs();
-    let mut b = c.iter().map(|song| song.to_owned()).collect::<Vec<Song>>();
-    a.tracks.append(&mut b);
-    a.to_m3u8()
-}
+// #[test]
+// fn list_to_m3u8() {
+//     let lib = ITunesLibrary::from_file(Path::new(
+//         "F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml",
+//     ));
+//     let mut a = Playlist::new();
+//     let c = lib.to_songs();
+//     let mut b = c.iter().map(|song| song.to_owned()).collect::<Vec<Song>>();
+//     a.tracks.append(&mut b);
+//     a.to_m3u8()
+// }

From 96069fd9bc912d4e74bb073eec145a1d1af68d71 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 9 Feb 2024 23:23:08 -0600
Subject: [PATCH 089/136] Added `.exists()` for `URI`

---
 src/music_storage/library.rs | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 69acb27..502429b 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -460,6 +460,14 @@ impl URI {
         };
         path_str.to_string()
     }
+
+    pub fn exists(&self) -> Result<bool, std::io::Error> {
+        match self {
+            URI::Local(loc) => loc.try_exists(),
+            URI::Cue {location, ..} => location.try_exists(),
+            URI::Remote(_, _loc) => todo!(),
+        }
+    }
 }
 
 impl ToString for URI {
@@ -699,7 +707,7 @@ impl MusicLibrary {
                     Ok(_) => total += 1,
                     Err(_error) => {
                         errors += 1;
-                        println!("{}, {:?}: {}", format, target_file.file_name(), _error)
+                        //println!("{}, {:?}: {}", format, target_file.file_name(), _error)
                     } // TODO: Handle more of these errors
                 };
             } else if extension == "cue" {
@@ -707,15 +715,13 @@ impl MusicLibrary {
                     Ok(added) => added,
                     Err(error) => {
                         errors += 1;
-                        println!("{}", error);
+                        //println!("{}", error);
                         0
                     }
                 }
             }
         }
 
-        println!("ERRORS: {}", errors);
-
         Ok(total)
     }
 

From 3ad8b78e9dc760ca11eb0001a0347272ceb13c12 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Sun, 11 Feb 2024 15:54:11 -0500
Subject: [PATCH 090/136] started work on controller protoype

---
 src/music_controller/controller.rs           | 206 ++++++++++++++++++-
 src/music_storage/db_reader/itunes/reader.rs |  21 +-
 src/music_storage/playlist.rs                |  12 +-
 3 files changed, 226 insertions(+), 13 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 7f62871..98c3ca8 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -2,25 +2,219 @@
 //! player. It manages queues, playback, library access, and
 //! other functions
 
+use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
+use std::time::Duration;
+use crossbeam_channel::{Sender, Receiver};
+// use std::sync::mpsc;
+use crossbeam_channel;
+use gstreamer::format::Default;
+use gstreamer::query::Uri;
+use std::thread::{self, sleep, spawn};
 
+use std::error::Error;
+use crossbeam_channel::unbounded;
+use rayon::iter::Rev;
+use uuid::Uuid;
+
+use crate::config;
+use crate::music_storage::library::Tag;
+use crate::music_storage::playlist::Playlist;
 use crate::{
     music_player::Player,
-    music_storage::library::Song,
-    config::config::Config
+    music_storage::{
+        library::{MusicLibrary, Song}
+    },
+    config::config::Config,
 };
 
 struct Queue {
     player: Player,
     name: String,
-    songs: Vec<Song>,
+    songs: Playlist,
+}
+impl Queue {
+    fn new() -> Result<Self, Box<dyn Error>> {
+        Ok(
+            Queue {
+            player: Player::new()?,
+            name: String::new(),
+            songs: Playlist::new()
+            }
+        )
+    }
 }
 
 pub struct Controller {
-    queues: Vec<Queue>,
+    // queues: Vec<Queue>,
     config: Arc<RwLock<Config>>,
+    // library: MusicLibrary,
+    controller_mail: MailMan<ControllerCommand, ControllerResponse>,
+    db_mail: MailMan<DatabaseCommand, DatabaseResponse>,
+    queue_mail: Vec<MailMan<QueueCommand, QueueResponse>>,
+}
+#[derive(Debug)]
+
+pub enum ControllerCommand {
+    Default,
+    Test
+}
+#[derive(Debug)]
+
+enum ControllerResponse {
+    Empty,
+    QueueMailMan(MailMan<QueueCommand, QueueResponse>),
+
+}
+#[derive(Debug)]
+
+pub enum DatabaseCommand {
+    Default,
+    Test,
+    GetSongs,
+
+}
+#[derive(Debug)]
+
+enum DatabaseResponse {
+    Empty,
+    Songs(Vec<Song>),
+}
+#[derive(Debug)]
+enum QueueCommand {
+    Default,
+    Test,
+    Play,
+    Pause,
+}
+#[derive(Debug)]
+enum QueueResponse {
+    Default,
+    Test,
 }
 
-impl Controller {
-    // more stuff to come
+#[derive(Debug)]
+
+struct MailMan<T, U> {
+    pub tx: Sender<T>,
+    rx: Receiver<U>
 }
+impl<T> MailMan<T, T> {
+    pub fn new() -> Self {
+        let (tx, rx) = unbounded::<T>();
+        MailMan { tx, rx }
+    }
+}
+impl<T, U> MailMan<T, U> {
+    pub fn double() -> (MailMan<T, U>, MailMan<U, T>) {
+        let (tx, rx) = unbounded::<T>();
+        let (tx1, rx1) = unbounded::<U>();
+
+        (
+            MailMan { tx, rx: rx1 },
+            MailMan { tx: tx1, rx }
+        )
+    }
+
+    pub fn send(&self, mail: T) -> Result<(), Box<dyn Error>> {
+        &self.tx.send(mail).unwrap();
+        Ok(())
+    }
+
+    pub fn recv(&self) -> Result<U, Box<dyn Error>> {
+        let u = self.rx.recv().unwrap();
+        Ok(u)
+    }
+}
+
+#[allow(unused_variables)]
+impl Controller {
+    pub fn start(config: PathBuf) -> Result<Self, Box<dyn Error>> {
+        let config = Config::read_file(config)?;
+        let uuid = config.libraries.get_default()?.uuid;
+
+        let config = Arc::new(RwLock::from(config));
+        let lib = MusicLibrary::init(config.clone(), uuid)?;
+
+        let (out_thread_controller, in_thread) = MailMan::double();
+        let monitor_thread = spawn(move || {
+            use ControllerCommand::*;
+            loop {
+                let command = in_thread.recv().unwrap();
+
+                match command {
+                    Default => (),
+                    Test => {
+                        in_thread.send(ControllerResponse::Empty).unwrap();
+                    },
+                }
+            }
+        });
+
+
+        let (out_thread_db, in_thread) = MailMan::double();
+        let db_monitor = spawn(move || {
+            use DatabaseCommand::*;
+            loop {
+                let command = in_thread.recv().unwrap();
+
+                match command {
+                    Default => {},
+                    Test => {
+                        in_thread.send(DatabaseResponse::Empty).unwrap();
+                    },
+                    GetSongs => {
+                        let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect();
+                        in_thread.send(DatabaseResponse::Songs(songs)).unwrap();
+                    },
+
+                }
+            }
+        });
+
+
+
+        Ok(
+            Controller {
+                // queues: Vec::new(),
+                config,
+                controller_mail: out_thread_controller,
+                db_mail: out_thread_db,
+                queue_mail: Vec::new(),
+            }
+        )
+    }
+    fn get_db_songs(&self) -> Vec<Song> {
+        self.db_mail.send(DatabaseCommand::GetSongs);
+        match self.db_mail.recv().unwrap() {
+            DatabaseResponse::Songs(songs) => songs,
+            _ => Vec::new()
+        }
+
+    }
+    pub fn new_queue(&mut self) {
+        let (out_thread_queue, in_thread) = MailMan::<QueueCommand, QueueResponse>::double();
+        let queues_monitor =  spawn(move || {
+            use QueueCommand::*;
+            loop {
+                let command = in_thread.recv().unwrap();
+                match command {
+                    Default => {},
+                    Test => {},
+                    Play => {},
+                    Pause => {},
+                }
+            }
+        });
+        self.queue_mail.push(out_thread_queue);
+    }
+}
+
+#[test]
+fn name() {
+    let a = Controller::start(PathBuf::from("test-config/config_test.json")).unwrap();
+    // sleep(Duration::from_millis(5000));
+    _ = a.controller_mail.send(ControllerCommand::Test);
+    // dbg!(a.get_db_songs());
+    // sleep(Duration::from_secs(6));
+}
\ No newline at end of file
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index c2063c0..3bc73f6 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -2,17 +2,20 @@ use file_format::FileFormat;
 use lofty::{AudioFile, LoftyError, ParseOptions, Probe, TagType, TaggedFileExt};
 use quick_xml::events::Event;
 use quick_xml::reader::Reader;
+use uuid::Uuid;
 use std::collections::{BTreeMap, HashMap};
 use std::fs::File;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
+use std::sync::{Arc, RwLock};
 use std::time::Duration as StdDur;
 use std::vec::Vec;
 
 use chrono::prelude::*;
 
+use crate::config::config::{Config, ConfigLibrary};
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
-use crate::music_storage::library::{AlbumArt, Service, Song, Tag, URI};
+use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI};
 use crate::music_storage::utils;
 
 use urlencoding::decode;
@@ -320,4 +323,20 @@ impl ITunesSong {
         // println!("{:.2?}", song);
         Ok(song)
     }
+}
+
+#[test]
+fn itunes_lib_test() {
+    let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
+    let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
+    config.libraries.libraries.push(config_lib.clone());
+
+    let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs();
+
+    let mut library = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config_lib.uuid).unwrap();
+
+    songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
+
+    config.write_file().unwrap();
+    library.save(config).unwrap();
 }
\ No newline at end of file
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index c2c6b74..69ae390 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -23,15 +23,15 @@ pub enum SortOrder {
     Tag(Tag)
 }
 #[derive(Debug, Clone)]
-pub struct Playlist<'a> {
+pub struct Playlist {
     title: String,
-    cover: Option<&'a AlbumArt>,
+    cover: Option<AlbumArt>,
     tracks: Vec<Uuid>,
     sort_order: SortOrder,
     play_count: i32,
     play_time: Duration,
 }
-impl<'a> Playlist<'a> {
+impl Playlist {
     pub fn new() -> Self {
         Default::default()
     }
@@ -109,7 +109,7 @@ impl<'a> Playlist<'a> {
             .unwrap();
         m3u8.write_to(&mut file).unwrap();
     }
-    pub fn from_m3u8(path: &str) -> Result<Playlist<'a>, Error> {
+    pub fn from_m3u8(path: &str) -> Result<Playlist, Error> {
         let mut file = match File::open(path) {
             Ok(file) => file,
             Err(e) => return Err(e),
@@ -137,7 +137,7 @@ impl<'a> Playlist<'a> {
         &self.title
     }
     fn cover(&self) -> Option<&AlbumArt> {
-        match self.cover {
+        match &self.cover {
             Some(e) => Some(e),
             None => None,
         }
@@ -149,7 +149,7 @@ impl<'a> Playlist<'a> {
 
 
 
-impl Default for Playlist<'_> {
+impl Default for Playlist {
     fn default() -> Self {
         Playlist {
             title: String::default(),

From 599ddc584c6e5041f107c543a06aba82ece6def4 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 12 Feb 2024 13:43:30 -0500
Subject: [PATCH 091/136] Finished Controller Prototype

---
 .gitignore                                   |   2 +
 src/config/config.rs                         |   6 +-
 src/music_controller/controller.rs           | 120 ++++++++++++++++---
 src/music_storage/db_reader/foobar/reader.rs |   3 +
 src/music_storage/db_reader/itunes/reader.rs |   1 +
 src/music_storage/library.rs                 |  32 ++++-
 6 files changed, 139 insertions(+), 25 deletions(-)

diff --git a/.gitignore b/.gitignore
index a9e4c0b..7a0594f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,5 @@ music_database*
 *.m3u
 *.m3u8
 *.json
+*.zip
+*.xml
diff --git a/src/config/config.rs b/src/config/config.rs
index 9314d65..15b7121 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -71,8 +71,10 @@ impl ConfigLibraries {
     }
 
     pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
+        dbg!(&uuid);
         for library in &self.libraries {
             if &library.uuid == uuid {
+                dbg!(&library.uuid);
                 return Ok(library.to_owned())
             }
         }
@@ -128,8 +130,8 @@ impl Config {
         let mut file: File = File::open(path)?;
         let mut bun: String = String::new();
         _ = file.read_to_string(&mut bun);
-        let ny: Config = serde_json::from_str::<Config>(&bun)?;
-        Ok(ny)
+        let config: Config = serde_json::from_str::<Config>(&bun)?;
+        Ok(config)
     }
 }
 
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 98c3ca8..da2ea0f 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -2,7 +2,7 @@
 //! player. It manages queues, playback, library access, and
 //! other functions
 
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 use std::sync::{Arc, RwLock};
 use std::time::Duration;
 use crossbeam_channel::{Sender, Receiver};
@@ -18,7 +18,7 @@ use rayon::iter::Rev;
 use uuid::Uuid;
 
 use crate::config;
-use crate::music_storage::library::Tag;
+use crate::music_storage::library::{Tag, URI};
 use crate::music_storage::playlist::Playlist;
 use crate::{
     music_player::Player,
@@ -31,7 +31,7 @@ use crate::{
 struct Queue {
     player: Player,
     name: String,
-    songs: Playlist,
+    songs: Vec<Song>,
 }
 impl Queue {
     fn new() -> Result<Self, Box<dyn Error>> {
@@ -39,10 +39,15 @@ impl Queue {
             Queue {
             player: Player::new()?,
             name: String::new(),
-            songs: Playlist::new()
+            songs: Vec::new()
             }
         )
     }
+    fn set_tracks(&mut self, tracks: Vec<Song>) {
+        let mut tracks = tracks;
+        self.songs.clear();
+        self.songs.append(&mut tracks);
+    }
 }
 
 pub struct Controller {
@@ -54,39 +59,47 @@ pub struct Controller {
     queue_mail: Vec<MailMan<QueueCommand, QueueResponse>>,
 }
 #[derive(Debug)]
-
 pub enum ControllerCommand {
     Default,
     Test
 }
-#[derive(Debug)]
 
+#[derive(Debug)]
 enum ControllerResponse {
     Empty,
     QueueMailMan(MailMan<QueueCommand, QueueResponse>),
 
 }
-#[derive(Debug)]
 
+#[derive(Debug)]
 pub enum DatabaseCommand {
     Default,
     Test,
     GetSongs,
+    QueryUuid(Uuid),
+    QueryUuids(Vec<Uuid>),
+    ReadFolder(String),
 
 }
-#[derive(Debug)]
 
+#[derive(Debug)]
 enum DatabaseResponse {
     Empty,
+    Song(Song),
     Songs(Vec<Song>),
 }
+
 #[derive(Debug)]
 enum QueueCommand {
     Default,
     Test,
     Play,
     Pause,
+    SetSongs(Vec<Song>),
+    // SetLocation(URI),
+    Enqueue(URI),
 }
+
 #[derive(Debug)]
 enum QueueResponse {
     Default,
@@ -94,11 +107,11 @@ enum QueueResponse {
 }
 
 #[derive(Debug)]
-
 struct MailMan<T, U> {
     pub tx: Sender<T>,
     rx: Receiver<U>
 }
+
 impl<T> MailMan<T, T> {
     pub fn new() -> Self {
         let (tx, rx) = unbounded::<T>();
@@ -129,12 +142,13 @@ impl<T, U> MailMan<T, U> {
 
 #[allow(unused_variables)]
 impl Controller {
-    pub fn start(config: PathBuf) -> Result<Self, Box<dyn Error>> {
-        let config = Config::read_file(config)?;
+    pub fn start(config_path: String) -> Result<Self, Box<dyn Error>> {
+        let config_path = PathBuf::from(config_path);
+        let config = Config::read_file(config_path)?;
         let uuid = config.libraries.get_default()?.uuid;
 
         let config = Arc::new(RwLock::from(config));
-        let lib = MusicLibrary::init(config.clone(), uuid)?;
+        let mut lib = MusicLibrary::init(config.clone(), uuid)?;
 
         let (out_thread_controller, in_thread) = MailMan::double();
         let monitor_thread = spawn(move || {
@@ -167,6 +181,26 @@ impl Controller {
                         let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect();
                         in_thread.send(DatabaseResponse::Songs(songs)).unwrap();
                     },
+                    QueryUuid(uuid) => {
+                        match lib.query_uuid(&uuid) {
+                            Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(),
+                            None => in_thread.send(DatabaseResponse::Empty).unwrap(),
+                        }
+                    },
+                    QueryUuids(uuids) => {
+                        let mut vec = Vec::new();
+                        for uuid in uuids {
+                            match lib.query_uuid(&uuid) {
+                                Some(song) => vec.push(song.0.clone()),
+                                None => unimplemented!()
+                            }
+                        }
+                        in_thread.send(DatabaseResponse::Songs(vec)).unwrap();
+                    },
+                    ReadFolder(folder) => {
+                        lib.scan_folder(&folder).unwrap();
+                        in_thread.send(DatabaseResponse::Empty).unwrap();
+                    }
 
                 }
             }
@@ -184,6 +218,7 @@ impl Controller {
             }
         )
     }
+
     fn get_db_songs(&self) -> Vec<Song> {
         self.db_mail.send(DatabaseCommand::GetSongs);
         match self.db_mail.recv().unwrap() {
@@ -192,29 +227,76 @@ impl Controller {
         }
 
     }
+
     pub fn new_queue(&mut self) {
         let (out_thread_queue, in_thread) = MailMan::<QueueCommand, QueueResponse>::double();
         let queues_monitor =  spawn(move || {
             use QueueCommand::*;
+            let mut queue = Queue::new().unwrap();
             loop {
                 let command = in_thread.recv().unwrap();
                 match command {
                     Default => {},
-                    Test => {},
-                    Play => {},
+                    Test => { in_thread.send(QueueResponse::Test).unwrap() },
+                    Play => {
+                        queue.player.play().unwrap();
+                        in_thread.send(QueueResponse::Default).unwrap();
+                    },
                     Pause => {},
+                    SetSongs(songs) => {
+                        queue.set_tracks(songs);
+                        in_thread.send(QueueResponse::Default).unwrap();
+                    },
+                    Enqueue(uri) => {
+                        queue.player.enqueue_next(&uri);
+                    }
                 }
             }
         });
         self.queue_mail.push(out_thread_queue);
     }
+
+    fn play(&self, index: usize) -> Result<(), Box<dyn Error>> {
+        let mail = &self.queue_mail[index];
+        mail.send(QueueCommand::Play)?;
+        dbg!(mail.recv()?);
+        Ok(())
+    }
+
+    fn set_songs(&self, index: usize, songs: Vec<Song>) -> Result<(), Box<dyn Error>> {
+        let mail = &self.queue_mail[index];
+        mail.send(QueueCommand::SetSongs(songs))?;
+        dbg!(mail.recv()?);
+        Ok(())
+    }
+
+    fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
+        let mail = &self.queue_mail[index];
+        mail.send(QueueCommand::Enqueue(uri))?;
+        dbg!(mail.recv()?);
+        Ok(())
+    }
+    fn scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
+        let mail = &self.db_mail;
+        mail.send(DatabaseCommand::ReadFolder(folder))?;
+        dbg!(mail.recv()?);
+        Ok(())
+    }
+
 }
 
 #[test]
 fn name() {
-    let a = Controller::start(PathBuf::from("test-config/config_test.json")).unwrap();
-    // sleep(Duration::from_millis(5000));
-    _ = a.controller_mail.send(ControllerCommand::Test);
-    // dbg!(a.get_db_songs());
-    // sleep(Duration::from_secs(6));
+    let mut a = match Controller::start("test-config/config_test.json".to_string()) {
+        Ok(c) => c,
+        Err(e) => panic!("{e}")
+    };
+    sleep(Duration::from_millis(500));
+    a.scan_folder("test-config/music/".to_string());
+    a.new_queue();
+    let songs = a.get_db_songs();
+    a.enqueue(0, songs[0].location.clone());
+    a.play(0).unwrap();
+
+    sleep(Duration::from_secs(10));
 }
\ No newline at end of file
diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
index 3cb417d..324b863 100644
--- a/src/music_storage/db_reader/foobar/reader.rs
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -1,6 +1,8 @@
 use std::collections::BTreeMap;
 use std::{fs::File, io::Read, path::Path, time::Duration};
 
+use uuid::Uuid;
+
 use super::utils::meta_offset;
 use crate::music_storage::db_reader::common::{get_bytes, get_bytes_vec};
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
@@ -177,6 +179,7 @@ impl FoobarPlaylistTrack {
 
         Song {
             location,
+            uuid: Uuid::new_v4(),
             plays: 0,
             skips: 0,
             favorited: false,
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index 3bc73f6..15f89a9 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -170,6 +170,7 @@ impl ExternalLibrary for ITunesLibrary {
 
             let ny: Song = Song {
                 location: sug,
+                uuid: Uuid::new_v4(),
                 plays: track.plays,
                 skips: 0,
                 favorited: track.favorited,
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 502429b..e788639 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -119,6 +119,7 @@ impl ToString for Field {
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
 pub struct Song {
     pub location: URI,
+    pub uuid: Uuid,
     pub plays: i32,
     pub skips: i32,
     pub favorited: bool,
@@ -256,6 +257,7 @@ impl Song {
 
         let new_song = Song {
             location: URI::Local(binding),
+            uuid: Uuid::new_v4(),
             plays: 0,
             skips: 0,
             favorited: false,
@@ -378,6 +380,7 @@ impl Song {
                         start,
                         end,
                     },
+                    uuid: Uuid::new_v4(),
                     plays: 0,
                     skips: 0,
                     favorited: false,
@@ -552,7 +555,7 @@ pub struct MusicLibrary {
 
 #[test]
 fn library_init() {
-    let config = Config::read_file(PathBuf::from("config_test.json")).unwrap();
+    let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
     let target_uuid = config.libraries.libraries[0].uuid;
     let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap();
     dbg!(a);
@@ -575,13 +578,14 @@ impl MusicLibrary {
     /// the [MusicLibrary] Vec
     pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
         let global_config = &*config.read().unwrap();
+        let path = global_config.libraries.get_library(&uuid)?.path;
 
-        let library: MusicLibrary = match global_config.libraries.get_library(&uuid)?.path.exists() {
-            true => read_file(global_config.libraries.get_library(&uuid)?.path)?,
+        let library: MusicLibrary = match path.exists() {
+            true => read_file(path)?,
             false => {
                 // If the library does not exist, re-create it
                 let lib = MusicLibrary::new(String::new(), uuid);
-                write_file(&lib, global_config.libraries.get_library(&uuid)?.path)?;
+                write_file(&lib, path)?;
                 lib
             }
         };
@@ -643,6 +647,26 @@ impl MusicLibrary {
         }
     }
 
+    /// Queries for a [Song] by its [Uuid], returning a single `Song`
+    /// with the `Uuid` that matches along with its position in the library
+    pub fn query_uuid(&self, uuid: &Uuid) -> Option<(&Song, usize)> {
+        let result = self
+            .library
+            .par_iter()
+            .enumerate()
+            .try_for_each(|(i, track)| {
+                if uuid == &track.uuid {
+                    return std::ops::ControlFlow::Break((track, i));
+                }
+                Continue(())
+            });
+
+        match result {
+            Break(song) => Some(song),
+            Continue(_) => None,
+        }
+    }
+
     /// Queries for a [Song] by its [PathBuf], returning a `Vec<&Song>`
     /// with matching `PathBuf`s
     fn query_path(&self, path: PathBuf) -> Option<Vec<&Song>> {

From e25a7bfcc8862ca5fc5b643b0738b04ae1c2a193 Mon Sep 17 00:00:00 2001
From: MrDulfin <Dulfinaminator@gmail.com>
Date: Mon, 12 Feb 2024 14:12:51 -0500
Subject: [PATCH 092/136] playing 2 queues at once works

---
 src/music_controller/controller.rs | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index da2ea0f..d887ccd 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -249,6 +249,7 @@ impl Controller {
                     },
                     Enqueue(uri) => {
                         queue.player.enqueue_next(&uri);
+                        // in_thread.send(QueueResponse::Default).unwrap();
                     }
                 }
             }
@@ -273,7 +274,7 @@ impl Controller {
     fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
         mail.send(QueueCommand::Enqueue(uri))?;
-        dbg!(mail.recv()?);
+        // dbg!(mail.recv()?);
         Ok(())
     }
     fn scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
@@ -294,9 +295,12 @@ fn name() {
     sleep(Duration::from_millis(500));
     a.scan_folder("test-config/music/".to_string());
     a.new_queue();
+    a.new_queue();
     let songs = a.get_db_songs();
-    a.enqueue(0, songs[0].location.clone());
+    a.enqueue(0, songs[1].location.clone());
+    a.enqueue(1, songs[2].location.clone());
     a.play(0).unwrap();
+    a.play(1).unwrap();
 
     sleep(Duration::from_secs(10));
 }
\ No newline at end of file

From c842bf0b9cfabadab4e915f625198cd6ea73f3ef Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 16 Feb 2024 00:26:07 -0500
Subject: [PATCH 093/136] edited the Play command and the test function

---
 src/music_controller/controller.rs | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index d887ccd..777b7e2 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -239,8 +239,11 @@ impl Controller {
                     Default => {},
                     Test => { in_thread.send(QueueResponse::Test).unwrap() },
                     Play => {
-                        queue.player.play().unwrap();
-                        in_thread.send(QueueResponse::Default).unwrap();
+                        match queue.player.play() {
+                            Ok(_) => in_thread.send(QueueResponse::Default).unwrap(),
+                            Err(_) => unimplemented!()
+                        };
+
                     },
                     Pause => {},
                     SetSongs(songs) => {
@@ -248,7 +251,10 @@ impl Controller {
                         in_thread.send(QueueResponse::Default).unwrap();
                     },
                     Enqueue(uri) => {
-                        queue.player.enqueue_next(&uri);
+                        if uri.exists().unwrap() {
+                            queue.player.enqueue_next(&uri);
+                        }
+
                         // in_thread.send(QueueResponse::Default).unwrap();
                     }
                 }
@@ -295,12 +301,12 @@ fn name() {
     sleep(Duration::from_millis(500));
     a.scan_folder("test-config/music/".to_string());
     a.new_queue();
-    a.new_queue();
+    // a.new_queue();
     let songs = a.get_db_songs();
-    a.enqueue(0, songs[1].location.clone());
-    a.enqueue(1, songs[2].location.clone());
+    a.enqueue(0, songs[4].location.clone());
+    // a.enqueue(1, songs[2].location.clone());
     a.play(0).unwrap();
-    a.play(1).unwrap();
+    // a.play(1).unwrap();
 
     sleep(Duration::from_secs(10));
-}
\ No newline at end of file
+}

From 3c9311003731292c8f2a7025e9959ba9b1a5fc2c Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 16 Feb 2024 00:56:38 -0500
Subject: [PATCH 094/136] Added #![allow(unused)]

---
 src/lib.rs                         | 2 ++
 src/music_controller/controller.rs | 4 +---
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 3119019..41894fa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,3 +1,5 @@
+#![allow(unused)]
+
 pub mod music_storage {
     pub mod library;
     pub mod music_collection;
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 777b7e2..11a6fd4 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -22,9 +22,7 @@ use crate::music_storage::library::{Tag, URI};
 use crate::music_storage::playlist::Playlist;
 use crate::{
     music_player::Player,
-    music_storage::{
-        library::{MusicLibrary, Song}
-    },
+    music_storage::library::{MusicLibrary, Song},
     config::config::Config,
 };
 

From c6b561c7af509411f01bbf1baa5b701dc215abb3 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 16 Feb 2024 22:24:02 -0500
Subject: [PATCH 095/136] made prepatory changes to the library

---
 src/config/config.rs                         | 31 ++++++++++++++----
 src/music_controller/controller.rs           | 34 ++++++++++----------
 src/music_storage/db_reader/foobar/reader.rs |  1 +
 src/music_storage/db_reader/itunes/reader.rs | 11 +++++--
 src/music_storage/library.rs                 | 25 +++++++++++++-
 5 files changed, 75 insertions(+), 27 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index 15b7121..918af12 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -4,7 +4,7 @@ use std::{
     io::{Error, Write, Read}, sync::{Arc, RwLock},
 };
 
-use serde::{Serialize, Deserialize};
+use serde::{Deserialize, Serialize};
 use serde_json::to_string_pretty;
 use thiserror::Error;
 use uuid::Uuid;
@@ -126,6 +126,23 @@ impl Config {
         Ok(())
     }
 
+    pub fn save_backup(&self) -> Result<(), Box<dyn std::error::Error>> {
+        match &self.backup_folder {
+            Some(path) => {
+                let mut writer = path.clone();
+                writer.set_extension("tmp");
+                let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(&writer)?;
+                let config = to_string_pretty(self)?;
+                // dbg!(&config);
+
+                file.write_all(config.as_bytes())?;
+                fs::rename(writer, self.path.as_path())?;
+                Ok(())
+            },
+            None => Err(ConfigError::NoBackupLibrary.into())
+        }
+    }
+
     pub fn read_file(path: PathBuf) -> Result<Self, Error> {
         let mut file: File = File::open(path)?;
         let mut bun: String = String::new();
@@ -144,6 +161,8 @@ pub enum ConfigError {
     //TODO: do something about playlists
     #[error("Please provide a better m3u8 Playlist")]
     BadPlaylist,
+    #[error("No backup Config folder present")]
+    NoBackupLibrary,
 
 }
 
@@ -166,16 +185,16 @@ fn config_test() {
     };
     config.write_file();
     let arc = Arc::new(RwLock::from(config));
-    MusicLibrary::init(arc.clone(), lib_a.uuid.clone()).unwrap();
-    MusicLibrary::init(arc.clone(), lib_b.uuid.clone()).unwrap();
-    MusicLibrary::init(arc.clone(), lib_c.uuid.clone()).unwrap();
+    MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap();
+    MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap();
+    MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap();
 
 }
 
 #[test]
 fn test2() {
     let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
-    let uuid = config.libraries.get_default().unwrap().uuid.clone();
+    let uuid = config.libraries.get_default().unwrap().uuid;
     let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
     lib.scan_folder("test-config/music/").unwrap();
     lib.save(config.clone()).unwrap();
@@ -187,7 +206,7 @@ fn test2() {
 fn test3() {
     let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
     let uuid = config.libraries.get_default().unwrap().uuid;
-    let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
+    let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
 
     dbg!(lib);
 }
\ No newline at end of file
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 11a6fd4..750ee02 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -52,12 +52,12 @@ pub struct Controller {
     // queues: Vec<Queue>,
     config: Arc<RwLock<Config>>,
     // library: MusicLibrary,
-    controller_mail: MailMan<ControllerCommand, ControllerResponse>,
-    db_mail: MailMan<DatabaseCommand, DatabaseResponse>,
-    queue_mail: Vec<MailMan<QueueCommand, QueueResponse>>,
+    controller_mail: MailMan<ControllerCmd, ControllerResponse>,
+    db_mail: MailMan<DatabaseCmd, DatabaseResponse>,
+    queue_mail: Vec<MailMan<QueueCmd, QueueResponse>>,
 }
 #[derive(Debug)]
-pub enum ControllerCommand {
+pub enum ControllerCmd {
     Default,
     Test
 }
@@ -65,12 +65,12 @@ pub enum ControllerCommand {
 #[derive(Debug)]
 enum ControllerResponse {
     Empty,
-    QueueMailMan(MailMan<QueueCommand, QueueResponse>),
+    QueueMailMan(MailMan<QueueCmd, QueueResponse>),
 
 }
 
 #[derive(Debug)]
-pub enum DatabaseCommand {
+pub enum DatabaseCmd {
     Default,
     Test,
     GetSongs,
@@ -88,7 +88,7 @@ enum DatabaseResponse {
 }
 
 #[derive(Debug)]
-enum QueueCommand {
+enum QueueCmd {
     Default,
     Test,
     Play,
@@ -128,7 +128,7 @@ impl<T, U> MailMan<T, U> {
     }
 
     pub fn send(&self, mail: T) -> Result<(), Box<dyn Error>> {
-        &self.tx.send(mail).unwrap();
+        self.tx.send(mail).unwrap();
         Ok(())
     }
 
@@ -150,7 +150,7 @@ impl Controller {
 
         let (out_thread_controller, in_thread) = MailMan::double();
         let monitor_thread = spawn(move || {
-            use ControllerCommand::*;
+            use ControllerCmd::*;
             loop {
                 let command = in_thread.recv().unwrap();
 
@@ -166,7 +166,7 @@ impl Controller {
 
         let (out_thread_db, in_thread) = MailMan::double();
         let db_monitor = spawn(move || {
-            use DatabaseCommand::*;
+            use DatabaseCmd::*;
             loop {
                 let command = in_thread.recv().unwrap();
 
@@ -218,7 +218,7 @@ impl Controller {
     }
 
     fn get_db_songs(&self) -> Vec<Song> {
-        self.db_mail.send(DatabaseCommand::GetSongs);
+        self.db_mail.send(DatabaseCmd::GetSongs);
         match self.db_mail.recv().unwrap() {
             DatabaseResponse::Songs(songs) => songs,
             _ => Vec::new()
@@ -227,9 +227,9 @@ impl Controller {
     }
 
     pub fn new_queue(&mut self) {
-        let (out_thread_queue, in_thread) = MailMan::<QueueCommand, QueueResponse>::double();
+        let (out_thread_queue, in_thread) = MailMan::<QueueCmd, QueueResponse>::double();
         let queues_monitor =  spawn(move || {
-            use QueueCommand::*;
+            use QueueCmd::*;
             let mut queue = Queue::new().unwrap();
             loop {
                 let command = in_thread.recv().unwrap();
@@ -263,27 +263,27 @@ impl Controller {
 
     fn play(&self, index: usize) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
-        mail.send(QueueCommand::Play)?;
+        mail.send(QueueCmd::Play)?;
         dbg!(mail.recv()?);
         Ok(())
     }
 
     fn set_songs(&self, index: usize, songs: Vec<Song>) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
-        mail.send(QueueCommand::SetSongs(songs))?;
+        mail.send(QueueCmd::SetSongs(songs))?;
         dbg!(mail.recv()?);
         Ok(())
     }
 
     fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
-        mail.send(QueueCommand::Enqueue(uri))?;
+        mail.send(QueueCmd::Enqueue(uri))?;
         // dbg!(mail.recv()?);
         Ok(())
     }
     fn scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
         let mail = &self.db_mail;
-        mail.send(DatabaseCommand::ReadFolder(folder))?;
+        mail.send(DatabaseCmd::ReadFolder(folder))?;
         dbg!(mail.recv()?);
         Ok(())
     }
diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
index 324b863..815f9a7 100644
--- a/src/music_storage/db_reader/foobar/reader.rs
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -183,6 +183,7 @@ impl FoobarPlaylistTrack {
             plays: 0,
             skips: 0,
             favorited: false,
+            // banned: None,
             rating: None,
             format: None,
             duration: self.duration,
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index 15f89a9..decf390 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -15,7 +15,7 @@ use chrono::prelude::*;
 
 use crate::config::config::{Config, ConfigLibrary};
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
-use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI};
+use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI, BannedType};
 use crate::music_storage::utils;
 
 use urlencoding::decode;
@@ -149,7 +149,7 @@ impl ExternalLibrary for ITunesLibrary {
                 continue;
             }
 
-            let sug: URI = if track.location.contains("file://localhost/") {
+            let location: URI = if track.location.contains("file://localhost/") {
                 URI::Local(PathBuf::from(
                     decode(track.location.strip_prefix("file://localhost/").unwrap())
                         .unwrap()
@@ -169,11 +169,16 @@ impl ExternalLibrary for ITunesLibrary {
             let play_time_ = StdDur::from_secs(track.plays as u64 * dur.as_secs());
 
             let ny: Song = Song {
-                location: sug,
+                location,
                 uuid: Uuid::new_v4(),
                 plays: track.plays,
                 skips: 0,
                 favorited: track.favorited,
+                // banned: if track.banned {
+                //     Some(BannedType::All)
+                // }else {
+                //     None
+                // },
                 rating: track.rating,
                 format: match FileFormat::from_file(PathBuf::from(&loc)) {
                     Ok(e) => Some(e),
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index e788639..93b9991 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -115,6 +115,26 @@ impl ToString for Field {
     }
 }
 
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
+pub enum BannedType {
+    Shuffle,
+    All,
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
+pub enum DoNotTrack {
+    // TODO: add services to not track
+}
+
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
+enum SongType {
+    // TODO: add song types
+    Main,
+    Instrumental,
+    Remix,
+    Custom(String)
+}
+
 /// Stores information about a single song
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
 pub struct Song {
@@ -123,6 +143,7 @@ pub struct Song {
     pub plays: i32,
     pub skips: i32,
     pub favorited: bool,
+    // pub banned: Option<BannedType>,
     pub rating: Option<u8>,
     pub format: Option<FileFormat>,
     pub duration: Duration,
@@ -261,6 +282,7 @@ impl Song {
             plays: 0,
             skips: 0,
             favorited: false,
+            // banned: None,
             rating: None,
             format,
             duration,
@@ -384,6 +406,7 @@ impl Song {
                     plays: 0,
                     skips: 0,
                     favorited: false,
+                    // banned: None,
                     rating: None,
                     format,
                     duration,
@@ -397,7 +420,7 @@ impl Song {
                 tracks.push((new_song, audio_location.clone()));
             }
         }
-     Ok((tracks))
+     Ok(tracks)
     }
 }
 

From d2822bbb37a3ffb772e4c3f161a21e4dc2b2ab97 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 17 Feb 2024 21:30:53 -0600
Subject: [PATCH 096/136] Moved location of `uri.exists()` to in `Player`

---
 src/music_controller/controller.rs |  4 +---
 src/music_player.rs                | 17 +++++++++++++----
 2 files changed, 14 insertions(+), 7 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 750ee02..d6d1398 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -249,9 +249,7 @@ impl Controller {
                         in_thread.send(QueueResponse::Default).unwrap();
                     },
                     Enqueue(uri) => {
-                        if uri.exists().unwrap() {
-                            queue.player.enqueue_next(&uri);
-                        }
+                        queue.player.enqueue_next(&uri).unwrap();
 
                         // in_thread.send(QueueResponse::Default).unwrap();
                     }
diff --git a/src/music_player.rs b/src/music_player.rs
index 4e7de23..12af300 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -68,6 +68,8 @@ pub enum PlayerError {
     Factory(#[from] glib::BoolError),
     #[error("could not change playback state")]
     StateChange(#[from] gst::StateChangeError),
+    #[error("the file or source is not found")]
+    NotFound,
     #[error("failed to build gstreamer item")]
     Build,
     #[error("poison error")]
@@ -257,12 +259,17 @@ impl Player {
         &self.source
     }
 
-    pub fn enqueue_next(&mut self, next_track: &URI) {
-        self.set_source(next_track);
+    pub fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
+        self.set_source(next_track)
     }
 
     /// Set the playback URI
-    fn set_source(&mut self, source: &URI) {
+    fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> {
+        if !source.exists().is_ok_and(|x| x) {
+            // If the source doesn't exist, gstreamer will crash!
+            return Err(PlayerError::NotFound)
+        }
+
         // Make sure the playback tracker knows the stuff is stopped
         self.playback_tx.send(PlaybackStats::Switching).unwrap();
 
@@ -290,7 +297,7 @@ impl Player {
                 let now = std::time::Instant::now();
                 while now.elapsed() < std::time::Duration::from_millis(20) {
                     if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
-                        return;
+                        return Ok(());
                     }
                     std::thread::sleep(std::time::Duration::from_millis(1));
                 }
@@ -321,6 +328,8 @@ impl Player {
                 }).unwrap();
             }
         }
+
+        Ok(())
     }
 
     /// Gets a mutable reference to the playbin element

From 74e2933de19609ee90f88f3c16852421d7c4723c Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 23 Feb 2024 20:51:59 -0500
Subject: [PATCH 097/136] Added more functions for testing

---
 src/music_controller/controller.rs | 101 ++++++++++++++++++++++-------
 1 file changed, 77 insertions(+), 24 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index d6d1398..2d6a383 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -41,6 +41,7 @@ impl Queue {
             }
         )
     }
+
     fn set_tracks(&mut self, tracks: Vec<Song>) {
         let mut tracks = tracks;
         self.songs.clear();
@@ -73,6 +74,7 @@ enum ControllerResponse {
 pub enum DatabaseCmd {
     Default,
     Test,
+    SaveLibrary,
     GetSongs,
     QueryUuid(Uuid),
     QueryUuids(Vec<Uuid>),
@@ -85,6 +87,7 @@ enum DatabaseResponse {
     Empty,
     Song(Song),
     Songs(Vec<Song>),
+    Library(MusicLibrary),
 }
 
 #[derive(Debug)]
@@ -96,12 +99,14 @@ enum QueueCmd {
     SetSongs(Vec<Song>),
     // SetLocation(URI),
     Enqueue(URI),
+    SetVolume(f64),
 }
 
 #[derive(Debug)]
 enum QueueResponse {
     Default,
     Test,
+    Index(i32),
 }
 
 #[derive(Debug)]
@@ -145,9 +150,10 @@ impl Controller {
         let config = Config::read_file(config_path)?;
         let uuid = config.libraries.get_default()?.uuid;
 
-        let config = Arc::new(RwLock::from(config));
-        let mut lib = MusicLibrary::init(config.clone(), uuid)?;
+        let config_ = Arc::new(RwLock::from(config));
+        let mut lib = MusicLibrary::init(config_.clone(), uuid)?;
 
+        let config = config_.clone();
         let (out_thread_controller, in_thread) = MailMan::double();
         let monitor_thread = spawn(move || {
             use ControllerCmd::*;
@@ -163,7 +169,7 @@ impl Controller {
             }
         });
 
-
+        let config = config_.clone();
         let (out_thread_db, in_thread) = MailMan::double();
         let db_monitor = spawn(move || {
             use DatabaseCmd::*;
@@ -179,6 +185,10 @@ impl Controller {
                         let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect();
                         in_thread.send(DatabaseResponse::Songs(songs)).unwrap();
                     },
+                    SaveLibrary => {
+                        //TODO: make this send lib ref to the function to save instead
+                        lib.save(config.read().unwrap().to_owned()).unwrap();
+                    },
                     QueryUuid(uuid) => {
                         match lib.query_uuid(&uuid) {
                             Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(),
@@ -209,7 +219,7 @@ impl Controller {
         Ok(
             Controller {
                 // queues: Vec::new(),
-                config,
+                config: config_.clone(),
                 controller_mail: out_thread_controller,
                 db_mail: out_thread_db,
                 queue_mail: Vec::new(),
@@ -217,16 +227,27 @@ impl Controller {
         )
     }
 
-    fn get_db_songs(&self) -> Vec<Song> {
+    fn lib_get_songs(&self) -> Vec<Song> {
         self.db_mail.send(DatabaseCmd::GetSongs);
         match self.db_mail.recv().unwrap() {
             DatabaseResponse::Songs(songs) => songs,
             _ => Vec::new()
         }
-
     }
 
-    pub fn new_queue(&mut self) {
+    fn lib_scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
+        let mail = &self.db_mail;
+        mail.send(DatabaseCmd::ReadFolder(folder))?;
+        dbg!(mail.recv()?);
+        Ok(())
+    }
+
+    pub fn lib_save(&self) -> Result<(), Box<dyn Error>> {
+        self.db_mail.send(DatabaseCmd::SaveLibrary);
+        Ok(())
+    }
+
+    pub fn q_new(&mut self) -> Result<usize, Box<dyn Error>> {
         let (out_thread_queue, in_thread) = MailMan::<QueueCmd, QueueResponse>::double();
         let queues_monitor =  spawn(move || {
             use QueueCmd::*;
@@ -239,11 +260,16 @@ impl Controller {
                     Play => {
                         match queue.player.play() {
                             Ok(_) => in_thread.send(QueueResponse::Default).unwrap(),
-                            Err(_) => unimplemented!()
+                            Err(_) => todo!()
                         };
 
                     },
-                    Pause => {},
+                    Pause => {
+                        match queue.player.pause() {
+                            Ok(_) => in_thread.send(QueueResponse::Default).unwrap(),
+                            Err(_) => todo!()
+                        }
+                    },
                     SetSongs(songs) => {
                         queue.set_tracks(songs);
                         in_thread.send(QueueResponse::Default).unwrap();
@@ -252,57 +278,84 @@ impl Controller {
                         queue.player.enqueue_next(&uri).unwrap();
 
                         // in_thread.send(QueueResponse::Default).unwrap();
+                    },
+                    SetVolume(vol) => {
+                        queue.player.set_volume(vol);
                     }
                 }
             }
         });
         self.queue_mail.push(out_thread_queue);
+        Ok((self.queue_mail.len() - 1))
     }
 
-    fn play(&self, index: usize) -> Result<(), Box<dyn Error>> {
+    fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
         mail.send(QueueCmd::Play)?;
         dbg!(mail.recv()?);
         Ok(())
     }
 
-    fn set_songs(&self, index: usize, songs: Vec<Song>) -> Result<(), Box<dyn Error>> {
+    fn q_pause(&self, index: usize) -> Result<(), Box<dyn Error>> {
+        let mail = &self.queue_mail[index];
+        mail.send(QueueCmd::Pause)?;
+        dbg!(mail.recv()?);
+        Ok(())
+    }
+
+    pub fn q_set_volume(&self, index: usize, volume: f64) -> Result<(), Box<dyn Error>> {
+        let mail = &self.queue_mail[index];
+        mail.send(QueueCmd::SetVolume(volume))?;
+        Ok(())
+    }
+
+    fn q_set_songs(&self, index: usize, songs: Vec<Song>) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
         mail.send(QueueCmd::SetSongs(songs))?;
         dbg!(mail.recv()?);
         Ok(())
     }
 
-    fn enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
+    fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
         mail.send(QueueCmd::Enqueue(uri))?;
         // dbg!(mail.recv()?);
         Ok(())
     }
-    fn scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
-        let mail = &self.db_mail;
-        mail.send(DatabaseCmd::ReadFolder(folder))?;
-        dbg!(mail.recv()?);
-        Ok(())
-    }
+
 
 }
 
 #[test]
-fn name() {
+fn play_test() {
     let mut a = match Controller::start("test-config/config_test.json".to_string()) {
         Ok(c) => c,
         Err(e) => panic!("{e}")
     };
     sleep(Duration::from_millis(500));
-    a.scan_folder("test-config/music/".to_string());
-    a.new_queue();
+
+    let i = a.q_new().unwrap();
+    a.q_set_volume(i, 0.04);
     // a.new_queue();
-    let songs = a.get_db_songs();
-    a.enqueue(0, songs[4].location.clone());
+    let songs = a.lib_get_songs();
+    a.q_enqueue(i, songs[2].location.clone());
     // a.enqueue(1, songs[2].location.clone());
-    a.play(0).unwrap();
+    a.q_play(i).unwrap();
     // a.play(1).unwrap();
 
     sleep(Duration::from_secs(10));
+    a.q_pause(i);
+    sleep(Duration::from_secs(10));
+    a.q_play(i);
+    sleep(Duration::from_secs(1000));
+}
+
+#[test]
+fn test_() {
+    let a = match Controller::start("test-config/config_test.json".to_string()) {
+        Ok(c) => c,
+        Err(e) => panic!("{e}")
+    };
+    a.lib_scan_folder("F:/Music/Mp3".to_string());
+    a.lib_save();
 }

From 0e6a676e09352f73c916da93dcc8ffc23048e38f Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sat, 24 Feb 2024 20:25:56 -0500
Subject: [PATCH 098/136] moved queue to its own file

---
 src/lib.rs                         |  1 +
 src/music_controller/controller.rs | 24 +-----------------------
 src/music_controller/queue.rs      | 25 +++++++++++++++++++++++++
 3 files changed, 27 insertions(+), 23 deletions(-)
 create mode 100644 src/music_controller/queue.rs

diff --git a/src/lib.rs b/src/lib.rs
index 41894fa..a9d0db0 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,6 +13,7 @@ pub mod music_storage {
 pub mod music_controller{
     pub mod controller;
     pub mod connections;
+    pub mod queue;
 }
 
 pub mod music_player;
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 2d6a383..42f8f0e 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -24,31 +24,9 @@ use crate::{
     music_player::Player,
     music_storage::library::{MusicLibrary, Song},
     config::config::Config,
+    music_controller::queue::Queue,
 };
 
-struct Queue {
-    player: Player,
-    name: String,
-    songs: Vec<Song>,
-}
-impl Queue {
-    fn new() -> Result<Self, Box<dyn Error>> {
-        Ok(
-            Queue {
-            player: Player::new()?,
-            name: String::new(),
-            songs: Vec::new()
-            }
-        )
-    }
-
-    fn set_tracks(&mut self, tracks: Vec<Song>) {
-        let mut tracks = tracks;
-        self.songs.clear();
-        self.songs.append(&mut tracks);
-    }
-}
-
 pub struct Controller {
     // queues: Vec<Queue>,
     config: Arc<RwLock<Config>>,
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
new file mode 100644
index 0000000..7eaace7
--- /dev/null
+++ b/src/music_controller/queue.rs
@@ -0,0 +1,25 @@
+use crate::{music_player::Player, music_storage::library::Song};
+use std::error::Error;
+
+pub struct Queue {
+    pub player: Player,
+    pub name: String,
+    pub songs: Vec<Song>,
+}
+impl Queue {
+    pub fn new() -> Result<Self, Box<dyn Error>> {
+        Ok(
+            Queue {
+            player: Player::new()?,
+            name: String::new(),
+            songs: Vec::new()
+            }
+        )
+    }
+
+    pub fn set_tracks(&mut self, tracks: Vec<Song>) {
+        let mut tracks = tracks;
+        self.songs.clear();
+        self.songs.append(&mut tracks);
+    }
+}

From 79ec5aa1ef3e2ba9d3c0e810a06fc8e9186805ff Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Mon, 26 Feb 2024 20:17:37 -0500
Subject: [PATCH 099/136] Added function to add items to the queue. currently
 does not work with songs that have the `Played` State

---
 src/music_controller/controller.rs | 24 ++++----
 src/music_controller/queue.rs      | 97 +++++++++++++++++++++++++++---
 src/music_player.rs                |  1 +
 3 files changed, 102 insertions(+), 20 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 42f8f0e..8d7f08e 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -27,6 +27,8 @@ use crate::{
     music_controller::queue::Queue,
 };
 
+use super::queue::{QueueItem, QueueState};
+
 pub struct Controller {
     // queues: Vec<Queue>,
     config: Arc<RwLock<Config>>,
@@ -74,7 +76,7 @@ enum QueueCmd {
     Test,
     Play,
     Pause,
-    SetSongs(Vec<Song>),
+    // SetSongs(Vec<QueueItem<QueueState>>),
     // SetLocation(URI),
     Enqueue(URI),
     SetVolume(f64),
@@ -248,10 +250,10 @@ impl Controller {
                             Err(_) => todo!()
                         }
                     },
-                    SetSongs(songs) => {
-                        queue.set_tracks(songs);
-                        in_thread.send(QueueResponse::Default).unwrap();
-                    },
+                    // SetSongs(songs) => {
+                    //     queue.set_tracks(songs);
+                    //     in_thread.send(QueueResponse::Default).unwrap();
+                    // },
                     Enqueue(uri) => {
                         queue.player.enqueue_next(&uri).unwrap();
 
@@ -287,12 +289,12 @@ impl Controller {
         Ok(())
     }
 
-    fn q_set_songs(&self, index: usize, songs: Vec<Song>) -> Result<(), Box<dyn Error>> {
-        let mail = &self.queue_mail[index];
-        mail.send(QueueCmd::SetSongs(songs))?;
-        dbg!(mail.recv()?);
-        Ok(())
-    }
+    // fn q_set_songs(&self, index: usize, songs: Vec<QueueItem<QueueState>>) -> Result<(), Box<dyn Error>> {
+    //     let mail = &self.queue_mail[index];
+    //     mail.send(QueueCmd::SetSongs(songs))?;
+    //     dbg!(mail.recv()?);
+    //     Ok(())
+    // }
 
     fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 7eaace7..7785d4d 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,25 +1,104 @@
-use crate::{music_player::Player, music_storage::library::Song};
-use std::error::Error;
+use uuid::Uuid;
 
-pub struct Queue {
+use crate::{music_player::Player, music_storage::library::{Album, Song}};
+use std::{error::Error, path::Path};
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum QueueState {
+    Played,
+    Current,
+    AddHere,
+    None,
+}
+#[derive(Debug, Clone)]
+pub enum QueueItemType<'a> {
+    Song(Uuid),
+    Album(Album<'a>)
+}
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct QueueItem<'a> {
+    item: QueueItemType<'a>,
+    state: QueueState
+}
+
+
+#[derive(Debug)]
+pub struct Queue<'a> {
     pub player: Player,
     pub name: String,
-    pub songs: Vec<Song>,
+    pub items: Vec<QueueItem<'a>>,
 }
-impl Queue {
+
+impl<'a> Queue<'a> {
     pub fn new() -> Result<Self, Box<dyn Error>> {
         Ok(
             Queue {
             player: Player::new()?,
             name: String::new(),
-            songs: Vec::new()
+            items: Vec::new()
             }
         )
     }
 
-    pub fn set_tracks(&mut self, tracks: Vec<Song>) {
+    pub fn set_items(&mut self, tracks: Vec<QueueItem<'a>>) {
         let mut tracks = tracks;
-        self.songs.clear();
-        self.songs.append(&mut tracks);
+        self.items.clear();
+        self.items.append(&mut tracks);
+    }
+
+    pub fn current_index(&self) -> i16 {
+        let e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
+        dbg!(&e);
+                e as i16 - 1
+    }
+
+    pub fn add_item(&mut self, item: QueueItemType<'a>) -> Result<(), Box<dyn Error>> {
+        use QueueState::*;
+        let ind = self.current_index();
+        let mut i: i16 = 1;
+        self.items = self.items.iter().enumerate().map(|(j, item_)| {
+            let mut item_ = item_.to_owned();
+            if item_.state == AddHere {
+                i = j as i16 + 2;
+                item_.state = None;
+            }
+            if item_.state == Current {
+                i = j as i16 + 2;
+            }
+            item_
+        }).collect::<Vec<QueueItem>>();
+        let pos = (ind + i) as usize;
+        dbg!(&pos);
+        self.items.insert(
+            pos,
+            QueueItem {
+                item: item.clone(),
+                state: if pos == self.items.len() && i == 1 {
+                    Current
+                }else {
+                    AddHere
+                }
+            }
+        );
+        Ok(())
     }
 }
+
+
+#[test]
+fn itemaddtest() {
+    let mut q = Queue::new().unwrap();
+    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
+    dbg!(&q.items);
+    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
+    dbg!(&q.items);
+    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
+    dbg!(&q.items);
+    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
+    dbg!(&q.items);
+    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
+    dbg!(&q.items);
+    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
+    dbg!(&q.items);
+}
\ No newline at end of file
diff --git a/src/music_player.rs b/src/music_player.rs
index 12af300..9893088 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -90,6 +90,7 @@ enum PlaybackStats {
 }
 
 /// An instance of a music player with a GStreamer backend
+#[derive(Debug)]
 pub struct Player {
     source:     Option<URI>,
     //pub message_tx: Sender<PlayerCmd>,

From e175fe733778d67d0aa01c1d6bafef0c7e24ee8a Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Tue, 27 Feb 2024 01:23:09 -0500
Subject: [PATCH 100/136] added `remove_item()` function to Queue

---
 src/music_controller/queue.rs | 96 +++++++++++++++++++++++++----------
 src/music_storage/playlist.rs |  2 +
 2 files changed, 71 insertions(+), 27 deletions(-)

diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 7785d4d..05bb35e 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,7 +1,7 @@
 use uuid::Uuid;
 
-use crate::{music_player::Player, music_storage::library::{Album, Song}};
-use std::{error::Error, path::Path};
+use crate::{music_player::Player, music_storage::library::{Album, Song, URI}};
+use std::{error::Error, ops::Add, path::Path};
 
 #[derive(Debug, PartialEq, Clone, Copy)]
 pub enum QueueState {
@@ -11,15 +11,31 @@ pub enum QueueState {
     None,
 }
 #[derive(Debug, Clone)]
+#[non_exhaustive]
 pub enum QueueItemType<'a> {
     Song(Uuid),
-    Album(Album<'a>)
+    ExternalSong(URI),
+    Album{
+        album: Album<'a>,
+        shuffled: bool,
+    }
+}
+
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub enum QueueSource {
+    Library,
+    Playlist(Uuid),
+    Search,
+    Queue,
+    File,
 }
 #[derive(Debug, Clone)]
 #[non_exhaustive]
 pub struct QueueItem<'a> {
     item: QueueItemType<'a>,
-    state: QueueState
+    state: QueueState,
+    source: QueueSource
 }
 
 
@@ -47,29 +63,34 @@ impl<'a> Queue<'a> {
         self.items.append(&mut tracks);
     }
 
-    pub fn current_index(&self) -> i16 {
-        let e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
-        dbg!(&e);
-                e as i16 - 1
+    pub fn current_index(&mut self) -> i16 {
+        let mut i = 1;
+        let mut e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
+        while e >= 51 {
+            self.items.remove(0);
+            e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
+            i+=1;
+        }
+        e as i16 - 1
     }
 
-    pub fn add_item(&mut self, item: QueueItemType<'a>) -> Result<(), Box<dyn Error>> {
+    pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource) -> Result<(), Box<dyn Error>> {
         use QueueState::*;
         let ind = self.current_index();
         let mut i: i16 = 1;
-        self.items = self.items.iter().enumerate().map(|(j, item_)| {
+             self.items = self.items.iter().enumerate().map(|(j, item_)| {
             let mut item_ = item_.to_owned();
             if item_.state == AddHere {
-                i = j as i16 + 2;
+                i = j as i16 + 1 - ind;
                 item_.state = None;
             }
             if item_.state == Current {
-                i = j as i16 + 2;
+                i = j as i16 + 1 - ind;
             }
             item_
         }).collect::<Vec<QueueItem>>();
         let pos = (ind + i) as usize;
-        dbg!(&pos);
+        // dbg!(&pos, &i, &ind);
         self.items.insert(
             pos,
             QueueItem {
@@ -78,27 +99,48 @@ impl<'a> Queue<'a> {
                     Current
                 }else {
                     AddHere
-                }
+                },
+                source
             }
         );
         Ok(())
     }
+
+    pub fn remove_item(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
+        use QueueState::*;
+        let ind = (self.current_index() + index as i16 + 1) as usize;
+
+        if ind < self.items.len() {
+            // update the state of the next item to replace the item being removed
+            if self.items.get(ind + 1).is_some() {
+                self.items[ind + 1].state = self.items[ind].state;
+            }
+            self.items[ind].state = None;
+            self.items.remove(ind);
+
+            Ok(())
+        }else {
+            Err("No Songs to remove!".into())
+        }
+    }
 }
 
 
 #[test]
-fn itemaddtest() {
+fn item_add_test() {
     let mut q = Queue::new().unwrap();
-    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
-    dbg!(&q.items);
-    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
-    dbg!(&q.items);
-    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
-    dbg!(&q.items);
-    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
-    dbg!(&q.items);
-    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
-    dbg!(&q.items);
-    q.add_item(QueueItemType::Song(Uuid::new_v4())).unwrap();
-    dbg!(&q.items);
+    for _ in 0..5 {
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue });
+    }
+    for _ in 0..3 {
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library).unwrap();
+    }
+    dbg!(&q.items, &q.items.len());
+
+    for _ in 0..1 {
+    q.remove_item(0).inspect_err(|e| println!("{e:?}"));
+    dbg!(&q.items.len());
+    }
+
+    dbg!(&q.items, &q.items.len());
 }
\ No newline at end of file
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 69ae390..ca1832b 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -24,6 +24,7 @@ pub enum SortOrder {
 }
 #[derive(Debug, Clone)]
 pub struct Playlist {
+    uuid: Uuid,
     title: String,
     cover: Option<AlbumArt>,
     tracks: Vec<Uuid>,
@@ -152,6 +153,7 @@ impl Playlist {
 impl Default for Playlist {
     fn default() -> Self {
         Playlist {
+            uuid: Uuid::new_v4(),
             title: String::default(),
             cover: None,
             tracks: Vec::default(),

From 1c14bc5ebbfa93dc448f63b57ca9e8558cdaaa52 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Tue, 27 Feb 2024 23:28:52 -0500
Subject: [PATCH 101/136] added queue clearing functions

---
 src/music_controller/queue.rs | 86 ++++++++++++++++++++++++++++-------
 src/music_storage/library.rs  |  2 +-
 2 files changed, 70 insertions(+), 18 deletions(-)

diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 05bb35e..8764d8f 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,7 +1,8 @@
+use font::opentype::tables::font_variations::InstanceFlags;
 use uuid::Uuid;
 
 use crate::{music_player::Player, music_storage::library::{Album, Song, URI}};
-use std::{error::Error, ops::Add, path::Path};
+use std::{error::Error, path::Path};
 
 #[derive(Debug, PartialEq, Clone, Copy)]
 pub enum QueueState {
@@ -10,7 +11,7 @@ pub enum QueueState {
     AddHere,
     None,
 }
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub enum QueueItemType<'a> {
     Song(Uuid),
@@ -18,10 +19,11 @@ pub enum QueueItemType<'a> {
     Album{
         album: Album<'a>,
         shuffled: bool,
-    }
+    },
+    None
 }
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub enum QueueSource {
     Library,
@@ -30,12 +32,23 @@ pub enum QueueSource {
     Queue,
     File,
 }
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub struct QueueItem<'a> {
     item: QueueItemType<'a>,
     state: QueueState,
-    source: QueueSource
+    source: QueueSource,
+    by_human: bool
+}
+impl QueueItem<'_> {
+    fn new() -> Self {
+        QueueItem {
+            item: QueueItemType::None,
+            state: QueueState::None,
+            source: QueueSource::Library,
+            by_human: false
+        }
+    }
 }
 
 
@@ -66,7 +79,8 @@ impl<'a> Queue<'a> {
     pub fn current_index(&mut self) -> i16 {
         let mut i = 1;
         let mut e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
-        while e >= 51 {
+        // TODO: make the max number of past songs modular
+        while e > 50 {
             self.items.remove(0);
             e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
             i+=1;
@@ -74,7 +88,7 @@ impl<'a> Queue<'a> {
         e as i16 - 1
     }
 
-    pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource) -> Result<(), Box<dyn Error>> {
+    pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box<dyn Error>> {
         use QueueState::*;
         let ind = self.current_index();
         let mut i: i16 = 1;
@@ -100,7 +114,8 @@ impl<'a> Queue<'a> {
                 }else {
                     AddHere
                 },
-                source
+                source,
+                by_human
             }
         );
         Ok(())
@@ -114,15 +129,52 @@ impl<'a> Queue<'a> {
             // update the state of the next item to replace the item being removed
             if self.items.get(ind + 1).is_some() {
                 self.items[ind + 1].state = self.items[ind].state;
+            }else if self.items[ind].state != Current {
+                self.items[ind - 1].state = self.items[ind].state;
             }
             self.items[ind].state = None;
             self.items.remove(ind);
-
             Ok(())
         }else {
             Err("No Songs to remove!".into())
         }
     }
+
+    pub fn clear(&mut self) {
+        self.items.retain(|item| item.state == QueueState::Played );
+    }
+
+    pub fn clear_except(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
+        let mut index = index;
+        let ind = self.current_index();
+
+        if ind != -1 {
+            index += ind as usize;
+        }else {
+            index -=1
+        }
+
+        if !self.is_empty() && index < self.items.len() {
+            let i = self.items[index].clone();
+            self.items.retain(|item| item.state == QueueState::Played || *item == i );
+            self.items[(ind+1) as usize].state = QueueState::Current
+        }else {
+            return Err("index out of bounds!".into());
+        }
+        Ok(())
+    }
+
+    pub fn clear_played(&mut self) {
+        self.items.retain(|item| item.state != QueueState::Played );
+    }
+
+    pub fn clear_all(&mut self) {
+        self.items.clear()
+    }
+
+    fn is_empty(&self) -> bool {
+        self.items.iter().filter(|item| item.state != QueueState::Played).collect::<Vec<_>>().len() == 0
+    }
 }
 
 
@@ -130,17 +182,17 @@ impl<'a> Queue<'a> {
 fn item_add_test() {
     let mut q = Queue::new().unwrap();
     for _ in 0..5 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue });
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false });
     }
+    q.clear();
+    for _ in 0..5 {
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+    }
+    // q.clear_played();
     for _ in 0..3 {
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library).unwrap();
-    }
-    dbg!(&q.items, &q.items.len());
-
-    for _ in 0..1 {
     q.remove_item(0).inspect_err(|e| println!("{e:?}"));
-    dbg!(&q.items.len());
     }
+    q.clear_except(4).inspect_err(|e| println!("{e:?}"));
 
     dbg!(&q.items, &q.items.len());
 }
\ No newline at end of file
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 93b9991..9915682 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -515,7 +515,7 @@ pub enum Service {
     None,
 }
 
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
 pub struct Album<'a> {
     title: &'a String,
     artist: Option<&'a String>,

From 588a9cbd94ef06e1f5a42d8881bcebb34f00ce8b Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 10 Mar 2024 19:22:31 -0400
Subject: [PATCH 102/136] Added more queue functions

---
 src/music_controller/queue.rs | 192 ++++++++++++++++++++++++++++++----
 src/music_player.rs           |   2 +-
 2 files changed, 175 insertions(+), 19 deletions(-)

diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 8764d8f..f727fea 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,8 +1,8 @@
 use font::opentype::tables::font_variations::InstanceFlags;
 use uuid::Uuid;
 
-use crate::{music_player::Player, music_storage::library::{Album, Song, URI}};
-use std::{error::Error, path::Path};
+use crate::{music_player::Player, music_storage::library::{Album, MusicLibrary, Song, URI}};
+use std::{error::Error, ops::Add, path::Path, sync::{Arc, RwLock}, thread::sleep, time::Duration};
 
 #[derive(Debug, PartialEq, Clone, Copy)]
 pub enum QueueState {
@@ -19,8 +19,36 @@ pub enum QueueItemType<'a> {
     Album{
         album: Album<'a>,
         shuffled: bool,
+        // disc #, track #
+        current: (i32, i32)
     },
-    None
+    None,
+    Test
+}
+impl QueueItemType<'_> {
+    fn get_uri(&self, lib: Arc<RwLock<MusicLibrary>>) -> Option<URI> {
+        use QueueItemType::*;
+
+        let lib = lib.read().unwrap();
+        match self {
+            Song(uuid) => {
+                if let Some((song, _))  = lib.query_uuid(uuid) {
+                    Some(song.location.clone())
+                }else {
+                    Option::None
+                }
+            },
+            Album{album, shuffled, current: (disc, index)} => {
+                if !shuffled {
+                    Some(album.track(*disc as usize, *index as usize).unwrap().location.clone())
+                }else {
+                    todo!()
+                }
+            },
+            ExternalSong(uri) => { Some(uri.clone()) },
+            _ => { Option::None }
+        }
+    }
 }
 
 #[derive(Debug, Clone, PartialEq)]
@@ -70,12 +98,6 @@ impl<'a> Queue<'a> {
         )
     }
 
-    pub fn set_items(&mut self, tracks: Vec<QueueItem<'a>>) {
-        let mut tracks = tracks;
-        self.items.clear();
-        self.items.append(&mut tracks);
-    }
-
     pub fn current_index(&mut self) -> i16 {
         let mut i = 1;
         let mut e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
@@ -88,12 +110,27 @@ impl<'a> Queue<'a> {
         e as i16 - 1
     }
 
+    fn contains_state(&self, state: QueueState) -> bool {
+        !self.items.iter().filter(|item| item.state == state ).collect::<Vec<_>>().is_empty()
+    }
+
+    fn is_empty(&self) -> bool {
+        self.items.iter().filter(|item| item.state != QueueState::Played).collect::<Vec<_>>().is_empty()
+    }
+
+    pub fn set_items(&mut self, tracks: Vec<QueueItem<'a>>) {
+        let mut tracks = tracks;
+        self.items.clear();
+        self.items.append(&mut tracks);
+    }
+
     pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box<dyn Error>> {
         use QueueState::*;
         let ind = self.current_index();
         let mut i: i16 = 1;
-             self.items = self.items.iter().enumerate().map(|(j, item_)| {
+        self.items = self.items.iter().enumerate().map(|(j, item_)| {
             let mut item_ = item_.to_owned();
+            // get the index of the current AddHere item and give it to i
             if item_.state == AddHere {
                 i = j as i16 + 1 - ind;
                 item_.state = None;
@@ -121,6 +158,33 @@ impl<'a> Queue<'a> {
         Ok(())
     }
 
+    pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) {
+        use QueueState::*;
+        let ind = self.current_index();
+        let empty = self.is_empty();
+
+        self.items.insert(
+            // index would go out of bounds if empty ( current index = -1 )
+            if empty {
+                (ind + 1) as usize
+            }else {
+                (ind + 2) as usize
+            },
+            QueueItem {
+                item,
+                state: if empty {
+                    Current
+                }else if self.items.get((ind + 1) as usize).is_none() || (!self.contains_state(AddHere) && self.items.get((ind + 1) as usize).is_some()) {
+                    AddHere
+                }else {
+                    None
+                },
+                source,
+                by_human: true
+            }
+        )
+    }
+
     pub fn remove_item(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
         use QueueState::*;
         let ind = (self.current_index() + index as i16 + 1) as usize;
@@ -147,14 +211,15 @@ impl<'a> Queue<'a> {
     pub fn clear_except(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
         let mut index = index;
         let ind = self.current_index();
+        let empty = self.is_empty();
 
-        if ind != -1 {
+        if !empty {
             index += ind as usize;
         }else {
             index -=1
         }
 
-        if !self.is_empty() && index < self.items.len() {
+        if !empty && index < self.items.len() {
             let i = self.items[index].clone();
             self.items.retain(|item| item.state == QueueState::Played || *item == i );
             self.items[(ind+1) as usize].state = QueueState::Current
@@ -172,8 +237,65 @@ impl<'a> Queue<'a> {
         self.items.clear()
     }
 
-    fn is_empty(&self) -> bool {
-        self.items.iter().filter(|item| item.state != QueueState::Played).collect::<Vec<_>>().len() == 0
+    fn move_to(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
+        let mut index = index;
+        let empty = self.is_empty();
+        let ind = self.current_index();
+
+        if !empty {
+            index += ind as usize;
+        }else {
+            return Err("Nothing in the queue to move to!".into());
+        }
+
+        dbg!(1);
+        if !empty && index < self.items.len() -1 {
+            // TODO: make this check for player position
+            let pos = self.player.position();
+            if pos.is_some_and(|dur| !dur.is_zero() ) {
+                self.items[ind as usize].state = QueueState::Played
+            }
+            dbg!(2);
+
+            let to_item = self.items[index].clone();
+            let new_ind = self.current_index() as usize;
+            dbg!(3);
+
+            // dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len());
+            loop {
+            dbg!(4);
+
+                if self.items[new_ind + 1].item != to_item.item {
+                    self.remove_item(0);
+                dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len());
+                sleep(Duration::from_millis(1000));
+                }else {
+                    break;
+                }
+            }
+        }else {
+            return Err("index out of bounds!".into());
+        }
+        Ok(())
+    }
+
+    pub fn swap(&mut self, index1: usize, index2: usize) {}
+
+    pub fn move_item(&mut self, item: usize, to_index: usize) {}
+
+    pub fn next() {}
+
+    pub fn prev() {}
+
+    pub fn enqueue_item(&mut self, item: QueueItem, lib: Arc<RwLock<MusicLibrary>>) -> Result<(), Box<dyn Error>> {
+        use QueueItemType::*;
+
+        if let Some(uri) = item.item.get_uri(lib) {
+            self.player.enqueue_next(&uri)?;
+        }else {
+            return Err("this item does not exist!".into());
+        }
+        Ok(())
     }
 }
 
@@ -185,14 +307,48 @@ fn item_add_test() {
         q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false });
     }
     q.clear();
-    for _ in 0..5 {
+    for _ in 0..1 {
         q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
     }
     // q.clear_played();
-    for _ in 0..3 {
-    q.remove_item(0).inspect_err(|e| println!("{e:?}"));
+    // for _ in 0..3 {
+    // q.remove_item(0).inspect_err(|e| println!("{e:?}"));
+    // }
+    for _ in 0..2 {
+        q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::None, source: QueueSource::Queue, by_human: false });
     }
-    q.clear_except(4).inspect_err(|e| println!("{e:?}"));
+    q.add_item_next(QueueItemType::Test, QueueSource::File);
 
+    dbg!(&q.items, &q.items.len());
+}
+
+#[test]
+fn test_() {
+    let mut q = Queue::new().unwrap();
+    for _ in 0..100 {
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false });
+    }
+    for _ in 0..2 {
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+    }
+    q.add_item_next(QueueItemType::Test, QueueSource::Queue);
+
+    dbg!(&q.items, &q.items.len());
+
+}
+
+#[test]
+fn move_test() {
+    let mut q = Queue::new().unwrap();
+    // for _ in 0..1 {
+    //     q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false });
+    // }
+    for _ in 0..5 {
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+    }
+    q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap();
+    dbg!(&q.items, &q.items.len());
+
+    q.move_to(3).inspect_err(|e| {dbg!(e);});
     dbg!(&q.items, &q.items.len());
 }
\ No newline at end of file
diff --git a/src/music_player.rs b/src/music_player.rs
index 9893088..e310403 100644
--- a/src/music_player.rs
+++ b/src/music_player.rs
@@ -180,7 +180,7 @@ impl Player {
                         *position_update.write().unwrap() = None;
                         break
                     },
-                    PlaybackStats::Idle | PlaybackStats::Switching => println!("waiting!"),
+                    PlaybackStats::Idle | PlaybackStats::Switching => {},
                     _ => ()
                 }
 

From 96cfbe9a50fb3dcdfaceff8184a1ecf47788438f Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Mon, 11 Mar 2024 20:29:28 -0400
Subject: [PATCH 103/136] test commit

---
 src/music_controller/queue.rs | 164 ++++++++++++++++++----------------
 1 file changed, 86 insertions(+), 78 deletions(-)

diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index f727fea..516d04b 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -9,7 +9,7 @@ pub enum QueueState {
     Played,
     Current,
     AddHere,
-    None,
+    NoState,
 }
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
@@ -56,8 +56,6 @@ impl QueueItemType<'_> {
 pub enum QueueSource {
     Library,
     Playlist(Uuid),
-    Search,
-    Queue,
     File,
 }
 #[derive(Debug, Clone, PartialEq)]
@@ -72,7 +70,7 @@ impl QueueItem<'_> {
     fn new() -> Self {
         QueueItem {
             item: QueueItemType::None,
-            state: QueueState::None,
+            state: QueueState::NoState,
             source: QueueSource::Library,
             by_human: false
         }
@@ -88,6 +86,9 @@ pub struct Queue<'a> {
 }
 
 impl<'a> Queue<'a> {
+    fn dbg_items(&self) {
+        dbg!(self.items.iter().map(|item| item.item.clone() ).collect::<Vec<QueueItemType>>(), self.items.len());
+    }
     pub fn new() -> Result<Self, Box<dyn Error>> {
         Ok(
             Queue {
@@ -98,16 +99,18 @@ impl<'a> Queue<'a> {
         )
     }
 
-    pub fn current_index(&mut self) -> i16 {
-        let mut i = 1;
+    pub fn current_index(&mut self/* , max: usize */) -> Option<usize> {
         let mut e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
         // TODO: make the max number of past songs modular
         while e > 50 {
             self.items.remove(0);
-            e = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
-            i+=1;
+            e -=1;
+        }
+        if e == 0 {
+            None
+        }else {
+            Some(e - 1)
         }
-        e as i16 - 1
     }
 
     fn contains_state(&self, state: QueueState) -> bool {
@@ -126,27 +129,28 @@ impl<'a> Queue<'a> {
 
     pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box<dyn Error>> {
         use QueueState::*;
-        let ind = self.current_index();
-        let mut i: i16 = 1;
+        let mut i: usize = 0;
+        let ind =  self.current_index();
+
         self.items = self.items.iter().enumerate().map(|(j, item_)| {
             let mut item_ = item_.to_owned();
             // get the index of the current AddHere item and give it to i
             if item_.state == AddHere {
-                i = j as i16 + 1 - ind;
-                item_.state = None;
-            }
-            if item_.state == Current {
-                i = j as i16 + 1 - ind;
+                i = j - ind.unwrap_or(0);
+                item_.state = NoState;
+            } else if item_.state == Current {
+                i = j - ind.unwrap_or(0);
             }
             item_
         }).collect::<Vec<QueueItem>>();
-        let pos = (ind + i) as usize;
+
+        let pos = ind.unwrap_or(0) + i  + if !self.is_empty() || (self.is_empty() && ind == None) { 0 } else { 1 };
         // dbg!(&pos, &i, &ind);
         self.items.insert(
             pos,
             QueueItem {
                 item: item.clone(),
-                state: if pos == self.items.len() && i == 1 {
+                state: if pos == self.items.len() && i == 0 {
                     Current
                 }else {
                     AddHere
@@ -160,24 +164,20 @@ impl<'a> Queue<'a> {
 
     pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) {
         use QueueState::*;
-        let ind = self.current_index();
+        let ind_ =  self.current_index();
+        let ind =  ind_.unwrap_or(0);
         let empty = self.is_empty();
 
         self.items.insert(
-            // index would go out of bounds if empty ( current index = -1 )
-            if empty {
-                (ind + 1) as usize
-            }else {
-                (ind + 2) as usize
-            },
+            (ind + if !empty && ind_ == None { 1 } else { 2 }),
             QueueItem {
                 item,
                 state: if empty {
                     Current
-                }else if self.items.get((ind + 1) as usize).is_none() || (!self.contains_state(AddHere) && self.items.get((ind + 1) as usize).is_some()) {
+                }else if self.items.get(ind + 1).is_none() || (!self.contains_state(AddHere) && self.items.get(ind + 1).is_some()) {
                     AddHere
                 }else {
-                    None
+                    NoState
                 },
                 source,
                 by_human: true
@@ -187,17 +187,19 @@ impl<'a> Queue<'a> {
 
     pub fn remove_item(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
         use QueueState::*;
-        let ind = (self.current_index() + index as i16 + 1) as usize;
+        let remove_index: usize = (if let Some(current_index) =  self.current_index() { dbg!(&current_index); current_index } else { 0 } + index );
 
-        if ind < self.items.len() {
+        // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]);
+
+        if remove_index < self.items.len() {
             // update the state of the next item to replace the item being removed
-            if self.items.get(ind + 1).is_some() {
-                self.items[ind + 1].state = self.items[ind].state;
-            }else if self.items[ind].state != Current {
-                self.items[ind - 1].state = self.items[ind].state;
+            if self.items.get(remove_index + 1).is_some() {
+                self.items[remove_index + 1].state = self.items[remove_index].state;
+            }else if self.items[remove_index].state != Current {
+                self.items[remove_index - 1].state = self.items[remove_index].state;
             }
-            self.items[ind].state = None;
-            self.items.remove(ind);
+            self.items[remove_index].state = NoState;
+            self.items.remove(remove_index);
             Ok(())
         }else {
             Err("No Songs to remove!".into())
@@ -210,11 +212,14 @@ impl<'a> Queue<'a> {
 
     pub fn clear_except(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
         let mut index = index;
-        let ind = self.current_index();
+        let ind = match self.current_index() {
+            Some(e) => e,
+            None =>  return Err("nothing to clear!".into())
+        };
         let empty = self.is_empty();
 
         if !empty {
-            index += ind as usize;
+            index += ind;
         }else {
             index -=1
         }
@@ -222,7 +227,7 @@ impl<'a> Queue<'a> {
         if !empty && index < self.items.len() {
             let i = self.items[index].clone();
             self.items.retain(|item| item.state == QueueState::Played || *item == i );
-            self.items[(ind+1) as usize].state = QueueState::Current
+            self.items[ind+1].state = QueueState::Current
         }else {
             return Err("index out of bounds!".into());
         }
@@ -238,37 +243,26 @@ impl<'a> Queue<'a> {
     }
 
     fn move_to(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
-        let mut index = index;
         let empty = self.is_empty();
-        let ind = self.current_index();
+        let nothing_error = Err("Nothing in the queue to move to!".into());
+        let ind = self.current_index().unwrap_or(0);
+        let index = if !empty { index + ind } else { return nothing_error; };
 
-        if !empty {
-            index += ind as usize;
-        }else {
-            return Err("Nothing in the queue to move to!".into());
-        }
-
-        dbg!(1);
         if !empty && index < self.items.len() -1 {
-            // TODO: make this check for player position
-            let pos = self.player.position();
-            if pos.is_some_and(|dur| !dur.is_zero() ) {
-                self.items[ind as usize].state = QueueState::Played
+            let position = self.player.position();
+            if position.is_some_and(|dur| !dur.is_zero() ) {
+                self.items[ind].state = QueueState::Played;
             }
-            dbg!(2);
 
             let to_item = self.items[index].clone();
-            let new_ind = self.current_index() as usize;
-            dbg!(3);
+            let ind = self.current_index().unwrap_or(0);
 
-            // dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len());
             loop {
-            dbg!(4);
-
-                if self.items[new_ind + 1].item != to_item.item {
-                    self.remove_item(0);
-                dbg!(&self.items, &new_ind, &to_item.item, &self.items[new_ind + 1].item, &self.items.len());
-                sleep(Duration::from_millis(1000));
+                if self.items[ind].item != to_item.item {
+                    if let Err(e) = self.remove_item(0) {
+                        dbg!(&e); self.dbg_items(); return Err(e);
+                    }
+                // dbg!(&to_item.item, &self.items[ind].item);
                 }else {
                     break;
                 }
@@ -303,21 +297,34 @@ impl<'a> Queue<'a> {
 #[test]
 fn item_add_test() {
     let mut q = Queue::new().unwrap();
-    for _ in 0..5 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false });
-    }
-    q.clear();
+    dbg!(1);
     for _ in 0..1 {
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
+    }
+    dbg!(2);
+
+    // q.clear();
+    dbg!(3);
+
+    for _ in 0..5 {
+        // dbg!("tick!");
         q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+        // dbg!(&q.items, &q.items.len());
     }
+    dbg!(4);
+    dbg!(&q.items, &q.items.len());
+
     // q.clear_played();
-    // for _ in 0..3 {
-    // q.remove_item(0).inspect_err(|e| println!("{e:?}"));
-    // }
-    for _ in 0..2 {
-        q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::None, source: QueueSource::Queue, by_human: false });
+    for _ in 0..1 {
+    q.remove_item(0).inspect_err(|e| println!("{e:?}"));
     }
-    q.add_item_next(QueueItemType::Test, QueueSource::File);
+    // for _ in 0..2 {
+    //     q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: QueueSource::Library, by_human: false });
+    // }
+    // dbg!(5);
+
+    // q.add_item_next(QueueItemType::Test, QueueSource::File);
+    // dbg!(6);
 
     dbg!(&q.items, &q.items.len());
 }
@@ -325,13 +332,13 @@ fn item_add_test() {
 #[test]
 fn test_() {
     let mut q = Queue::new().unwrap();
-    for _ in 0..100 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false });
+    for _ in 0..1 {
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
     }
     for _ in 0..2 {
         q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
     }
-    q.add_item_next(QueueItemType::Test, QueueSource::Queue);
+    q.add_item_next(QueueItemType::Test, QueueSource::File);
 
     dbg!(&q.items, &q.items.len());
 
@@ -340,15 +347,16 @@ fn test_() {
 #[test]
 fn move_test() {
     let mut q = Queue::new().unwrap();
-    // for _ in 0..1 {
-    //     q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::Queue, by_human: false });
-    // }
+    for _ in 0..1 {
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
+    }
     for _ in 0..5 {
         q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
     }
-    q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap();
+    // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap();
     dbg!(&q.items, &q.items.len());
 
     q.move_to(3).inspect_err(|e| {dbg!(e);});
     dbg!(&q.items, &q.items.len());
+    // q.dbg_items();
 }
\ No newline at end of file

From 7dcca941749c739cd9674cd6b2bfe2a0c54da7e5 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 15 Mar 2024 21:23:23 -0400
Subject: [PATCH 104/136] committing possibly broken code as a backup

---
 src/music_controller/queue.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 516d04b..fec5209 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -332,10 +332,10 @@ fn item_add_test() {
 #[test]
 fn test_() {
     let mut q = Queue::new().unwrap();
-    for _ in 0..1 {
+    for _ in 0..400 {
         q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
     }
-    for _ in 0..2 {
+    for _ in 0..50000 {
         q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
     }
     q.add_item_next(QueueItemType::Test, QueueSource::File);

From f7960518ca186eb96901213a5fb5ac0104bdbda8 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 23 Mar 2024 16:07:35 -0500
Subject: [PATCH 105/136] Fixes album art discovery

---
 src/music_storage/library.rs | 2 +-
 src/music_storage/utils.rs   | 9 +++------
 2 files changed, 4 insertions(+), 7 deletions(-)

diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 502429b..84fc2e0 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -274,7 +274,7 @@ impl Song {
 
     /// creates a `Vec<Song>` from a cue file
 
-    pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, PathBuf)>), Box<dyn Error>> {
+    pub fn from_cue(cuesheet: &Path) -> Result<Vec<(Self, PathBuf)>, Box<dyn Error>> {
         let mut tracks = Vec::new();
 
         let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap();
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 0d6fb47..f4665a3 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -74,12 +74,9 @@ pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
         .follow_links(true)
         .into_iter()
         .filter_map(|e| e.ok())
+        .filter(|e| e.depth() < 3) // Don't recurse very deep
     {
-        if target_file.depth() >= 3 {
-            // Don't recurse very deep
-            break;
-        }
-
+        println!("{:?}", target_file);
         let path = target_file.path();
         if !path.is_file() {
             continue;
@@ -87,7 +84,7 @@ pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
 
         let format = FileFormat::from_file(path)?.kind();
         if format != Kind::Image {
-            break;
+            continue;
         }
 
         let image_uri = URI::Local(path.to_path_buf().canonicalize()?);

From c4c842e637e03da7d8bb2a3827433bd7cc3cca3d Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sat, 23 Mar 2024 23:40:10 -0400
Subject: [PATCH 106/136] added function to show album art anf other small
 changes

---
 Cargo.toml                   |  3 ++
 src/music_storage/library.rs | 85 ++++++++++++++++++++++++++++++++++--
 src/music_storage/utils.rs   | 18 +++++---
 3 files changed, 95 insertions(+), 11 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 0267611..a3c737b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,3 +36,6 @@ font = "0.27.0"
 uuid = { version = "1.6.1", features = ["v4", "serde"]}
 serde_json = "1.0.111"
 deunicode = "1.4.2"
+opener = { version = "0.7.0", features = ["reveal"]}
+image = "0.25.0"
+tempfile = "3.10.1"
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 9915682..f91b590 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -5,16 +5,19 @@ use crate::config::config::Config;
 // Various std things
 use std::collections::BTreeMap;
 use std::error::Error;
+use std::io::BufWriter;
 use std::ops::ControlFlow::{Break, Continue};
 use std::ops::Deref;
 
 // Files
 use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
+use image::guess_format;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
+use tempfile::tempfile;
 use uuid::Uuid;
-use std::fs;
+use std::fs::{self, OpenOptions};
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
 
@@ -158,6 +161,15 @@ pub struct Song {
     pub tags: BTreeMap<Tag, String>,
 }
 
+#[test]
+fn get_art_test() {
+    use urlencoding::decode;
+
+    let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap();
+    s.open_album_art(0).inspect_err(|e| println!("{e:?}"));
+
+}
+
 impl Song {
     /// Get a tag's value
     ///
@@ -298,7 +310,7 @@ impl Song {
 
     /// creates a `Vec<Song>` from a cue file
 
-    pub fn from_cue(cuesheet: &Path) -> Result<(Vec<(Self, PathBuf)>), Box<dyn Error>> {
+    pub fn from_cue(cuesheet: &Path) -> Result<Vec<(Self, PathBuf)>, Box<dyn Error>> {
         let mut tracks = Vec::new();
 
         let cue_data = parse_from_file(&cuesheet.to_string_lossy(), false).unwrap();
@@ -422,6 +434,71 @@ impl Song {
         }
      Ok(tracks)
     }
+
+    pub fn open_album_art(&self, index: usize) -> Result<(), Box<dyn Error>> {
+        use opener::open;
+        use urlencoding::decode;
+
+        if index >= self.album_art.len() {
+            return Err("index out of bounds?".into());
+        }
+
+        let uri: String = match &self.album_art[index] {
+            AlbumArt::External(uri) => {
+                decode(match uri.as_uri().strip_prefix("file:///") { Some(e) => e, None => return Err("Invalid path?".into()) })?.into_owned()
+            },
+            AlbumArt::Embedded(_) => {
+
+                let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
+                let blank_tag = &lofty::Tag::new(TagType::Id3v2);
+                let tagged_file: lofty::TaggedFile;
+
+                let uri = dbg!(urlencoding::decode(self.location.as_uri().strip_prefix("file:///").unwrap())?.into_owned());
+
+                let tag = match Probe::open(uri)?.options(normal_options).read() {
+                    Ok(file) => {
+                        tagged_file = file;
+
+                        match tagged_file.primary_tag() {
+                            Some(primary_tag) => primary_tag,
+
+                            None => match tagged_file.first_tag() {
+                                Some(first_tag) => first_tag,
+                                None => blank_tag,
+                            },
+                        }
+                    }
+
+                    Err(_) => blank_tag,
+                };
+
+                let data = tag.pictures()[index].data();
+                let format = dbg!(guess_format(data)?);
+                let img = image::load_from_memory(data)?;
+
+                let mut location = String::new();
+                let i: u32 = 0;
+                loop {
+                    use image::ImageFormat::*;
+                    //TODO: create a place for temporary images
+                    let fmt = match format {
+                        Jpeg => "jpeg",
+                        Png => "png",
+                        _ => todo!(),
+                    };
+
+                    location = format!("./test-config/images/tempcover{i}.{fmt}.tmp");
+                    break;
+                };
+                img.save_with_format(&location, format)?;
+
+                location.to_string()
+            },
+        };
+        open(uri)?;
+
+        Ok(())
+    }
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -526,7 +603,7 @@ pub struct Album<'a> {
 #[allow(clippy::len_without_is_empty)]
 impl Album<'_> {
     //returns the Album title
-    fn title(&self) -> &String {
+    pub fn title(&self) -> &String {
         self.title
     }
 
@@ -1060,4 +1137,4 @@ impl MusicLibrary {
 
         Ok(albums)
     }
-}
+}
\ No newline at end of file
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 0d6fb47..9d832e1 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,5 +1,7 @@
+use std::any::Any;
 use std::fs::{File, self};
 use std::io::{BufReader, BufWriter};
+use std::os::windows::fs::MetadataExt;
 use std::path::{Path, PathBuf};
 use std::error::Error;
 
@@ -74,20 +76,22 @@ pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
         .follow_links(true)
         .into_iter()
         .filter_map(|e| e.ok())
+        .filter(|e| e.depth() < 3) // Don't recurse very deep
     {
-        if target_file.depth() >= 3 {
-            // Don't recurse very deep
-            break;
-        }
-
+        // println!("{:?}", target_file);
         let path = target_file.path();
-        if !path.is_file() {
+        if !path.is_file() || !path.exists() {
             continue;
         }
 
         let format = FileFormat::from_file(path)?.kind();
         if format != Kind::Image {
-            break;
+            continue;
+        }
+
+        #[cfg(target_family = "windows")]
+        if (4 & path.metadata().unwrap().file_attributes()) == 4  {
+            continue;
         }
 
         let image_uri = URI::Local(path.to_path_buf().canonicalize()?);

From 9da9b00befa600d6694e5b7ec7989580848f9008 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Sat, 23 Mar 2024 23:34:47 -0500
Subject: [PATCH 107/136] Removed unused imports, moved some tests to `mod
 tests`

---
 src/config/config.rs                         | 93 +++++++++++---------
 src/config/other_settings.rs                 |  4 -
 src/lib.rs                                   |  2 +-
 src/music_controller/connections.rs          |  2 -
 src/music_controller/controller.rs           | 83 +++++++++--------
 src/music_controller/queue.rs                | 18 ++--
 src/music_storage/db_reader/itunes/reader.rs | 35 +++++---
 src/music_storage/library.rs                 | 56 ++++++------
 src/music_storage/playlist.rs                | 17 +---
 src/music_storage/utils.rs                   |  6 +-
 10 files changed, 152 insertions(+), 164 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index 918af12..76c59cc 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -1,7 +1,7 @@
 use std::{
     path::PathBuf,
     fs::{File, OpenOptions, self},
-    io::{Error, Write, Read}, sync::{Arc, RwLock},
+    io::{Error, Write, Read},
 };
 
 use serde::{Deserialize, Serialize};
@@ -9,8 +9,6 @@ use serde_json::to_string_pretty;
 use thiserror::Error;
 use uuid::Uuid;
 
-use crate::music_storage::library::{MusicLibrary, self};
-
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct ConfigLibrary {
     pub name: String,
@@ -166,47 +164,54 @@ pub enum ConfigError {
 
 }
 
-#[test]
-fn config_test() {
-    let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None);
-    let lib_b = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
-    let lib_c = ConfigLibrary::new(PathBuf::from("test-config/library3"), String::from("library3"), None);
-    let config = Config {
-        path: PathBuf::from("test-config/config_test.json"),
-        libraries: ConfigLibraries {
-            libraries: vec![
-                lib_a.clone(),
-                lib_b.clone(),
-                lib_c.clone(),
-            ],
+#[cfg(test)]
+mod tests {
+    use std::{path::PathBuf, sync::{Arc, RwLock}};
+    use crate::music_storage::library::MusicLibrary;
+    use super::{Config, ConfigLibraries, ConfigLibrary};
+
+    #[test]
+    fn config_test() {
+        let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None);
+        let lib_b = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
+        let lib_c = ConfigLibrary::new(PathBuf::from("test-config/library3"), String::from("library3"), None);
+        let config = Config {
+            path: PathBuf::from("test-config/config_test.json"),
+            libraries: ConfigLibraries {
+                libraries: vec![
+                    lib_a.clone(),
+                    lib_b.clone(),
+                    lib_c.clone(),
+                ],
+                ..Default::default()
+            },
             ..Default::default()
-        },
-        ..Default::default()
-    };
-    config.write_file();
-    let arc = Arc::new(RwLock::from(config));
-    MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap();
-    MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap();
-    MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap();
+        };
+        config.write_file();
+        let arc = Arc::new(RwLock::from(config));
+        MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap();
+        MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap();
+        MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap();
 
+    }
+
+    #[test]
+    fn test2() {
+        let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
+        let uuid = config.libraries.get_default().unwrap().uuid;
+        let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
+        lib.scan_folder("test-config/music/").unwrap();
+        lib.save(config.clone()).unwrap();
+        dbg!(&lib);
+        dbg!(&config);
+    }
+
+    #[test]
+    fn test3() {
+        let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
+        let uuid = config.libraries.get_default().unwrap().uuid;
+        let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
+
+        dbg!(lib);
+    }
 }
-
-#[test]
-fn test2() {
-    let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
-    let uuid = config.libraries.get_default().unwrap().uuid;
-    let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
-    lib.scan_folder("test-config/music/").unwrap();
-    lib.save(config.clone()).unwrap();
-    dbg!(&lib);
-    dbg!(&config);
-}
-
-#[test]
-fn test3() {
-    let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
-    let uuid = config.libraries.get_default().unwrap().uuid;
-    let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
-
-    dbg!(lib);
-}
\ No newline at end of file
diff --git a/src/config/other_settings.rs b/src/config/other_settings.rs
index da94670..164221f 100644
--- a/src/config/other_settings.rs
+++ b/src/config/other_settings.rs
@@ -1,7 +1,3 @@
-use std::{marker::PhantomData, fs::File, path::PathBuf};
-
-use font::Font;
-
 pub enum Setting {
     String {
         name: String,
diff --git a/src/lib.rs b/src/lib.rs
index a9d0db0..379a56c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,4 +1,4 @@
-#![allow(unused)]
+#[allow(dead_code)]
 
 pub mod music_storage {
     pub mod library;
diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs
index 86d8f29..ac4abd9 100644
--- a/src/music_controller/connections.rs
+++ b/src/music_controller/connections.rs
@@ -1,5 +1,3 @@
-use std::{env, thread, time};
-
 use super::controller::Controller;
 
 
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 8d7f08e..ebcc27f 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -2,33 +2,23 @@
 //! player. It manages queues, playback, library access, and
 //! other functions
 
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
-use std::time::Duration;
 use crossbeam_channel::{Sender, Receiver};
-// use std::sync::mpsc;
 use crossbeam_channel;
-use gstreamer::format::Default;
-use gstreamer::query::Uri;
-use std::thread::{self, sleep, spawn};
+use std::thread::spawn;
 
 use std::error::Error;
 use crossbeam_channel::unbounded;
-use rayon::iter::Rev;
 use uuid::Uuid;
 
-use crate::config;
 use crate::music_storage::library::{Tag, URI};
-use crate::music_storage::playlist::Playlist;
 use crate::{
-    music_player::Player,
     music_storage::library::{MusicLibrary, Song},
     config::config::Config,
     music_controller::queue::Queue,
 };
 
-use super::queue::{QueueItem, QueueState};
-
 pub struct Controller {
     // queues: Vec<Queue>,
     config: Arc<RwLock<Config>>,
@@ -266,7 +256,7 @@ impl Controller {
             }
         });
         self.queue_mail.push(out_thread_queue);
-        Ok((self.queue_mail.len() - 1))
+        Ok(self.queue_mail.len() - 1)
     }
 
     fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> {
@@ -306,36 +296,43 @@ impl Controller {
 
 }
 
-#[test]
-fn play_test() {
-    let mut a = match Controller::start("test-config/config_test.json".to_string()) {
-        Ok(c) => c,
-        Err(e) => panic!("{e}")
-    };
-    sleep(Duration::from_millis(500));
+#[cfg(test)]
+mod tests {
+    use std::{thread::sleep, time::Duration};
 
-    let i = a.q_new().unwrap();
-    a.q_set_volume(i, 0.04);
-    // a.new_queue();
-    let songs = a.lib_get_songs();
-    a.q_enqueue(i, songs[2].location.clone());
-    // a.enqueue(1, songs[2].location.clone());
-    a.q_play(i).unwrap();
-    // a.play(1).unwrap();
+    use super::Controller;
 
-    sleep(Duration::from_secs(10));
-    a.q_pause(i);
-    sleep(Duration::from_secs(10));
-    a.q_play(i);
-    sleep(Duration::from_secs(1000));
-}
-
-#[test]
-fn test_() {
-    let a = match Controller::start("test-config/config_test.json".to_string()) {
-        Ok(c) => c,
-        Err(e) => panic!("{e}")
-    };
-    a.lib_scan_folder("F:/Music/Mp3".to_string());
-    a.lib_save();
+    #[test]
+    fn play_test() {
+        let mut a = match Controller::start("test-config/config_test.json".to_string()) {
+            Ok(c) => c,
+            Err(e) => panic!("{e}")
+        };
+        sleep(Duration::from_millis(500));
+
+        let i = a.q_new().unwrap();
+        a.q_set_volume(i, 0.04);
+        // a.new_queue();
+        let songs = a.lib_get_songs();
+        a.q_enqueue(i, songs[2].location.clone());
+        // a.enqueue(1, songs[2].location.clone());
+        a.q_play(i).unwrap();
+        // a.play(1).unwrap();
+
+        sleep(Duration::from_secs(10));
+        a.q_pause(i);
+        sleep(Duration::from_secs(10));
+        a.q_play(i);
+        sleep(Duration::from_secs(1000));
+    }
+
+    #[test]
+    fn test_() {
+        let a = match Controller::start("test-config/config_test.json".to_string()) {
+            Ok(c) => c,
+            Err(e) => panic!("{e}")
+        };
+        a.lib_scan_folder("F:/Music/Mp3".to_string());
+        a.lib_save();
+    }
 }
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index fec5209..8dfae31 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,8 +1,12 @@
-use font::opentype::tables::font_variations::InstanceFlags;
 use uuid::Uuid;
-
-use crate::{music_player::Player, music_storage::library::{Album, MusicLibrary, Song, URI}};
-use std::{error::Error, ops::Add, path::Path, sync::{Arc, RwLock}, thread::sleep, time::Duration};
+use crate::{
+    music_player::Player,
+    music_storage::library::{Album, MusicLibrary, URI}
+};
+use std::{
+    error::Error,
+    sync::{Arc, RwLock}
+};
 
 #[derive(Debug, PartialEq, Clone, Copy)]
 pub enum QueueState {
@@ -169,7 +173,7 @@ impl<'a> Queue<'a> {
         let empty = self.is_empty();
 
         self.items.insert(
-            (ind + if !empty && ind_ == None { 1 } else { 2 }),
+            ind + if !empty && ind_ == None { 1 } else { 2 },
             QueueItem {
                 item,
                 state: if empty {
@@ -282,8 +286,6 @@ impl<'a> Queue<'a> {
     pub fn prev() {}
 
     pub fn enqueue_item(&mut self, item: QueueItem, lib: Arc<RwLock<MusicLibrary>>) -> Result<(), Box<dyn Error>> {
-        use QueueItemType::*;
-
         if let Some(uri) = item.item.get_uri(lib) {
             self.player.enqueue_next(&uri)?;
         }else {
@@ -359,4 +361,4 @@ fn move_test() {
     q.move_to(3).inspect_err(|e| {dbg!(e);});
     dbg!(&q.items, &q.items.len());
     // q.dbg_items();
-}
\ No newline at end of file
+}
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index decf390..90a48f2 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -7,15 +7,13 @@ use std::collections::{BTreeMap, HashMap};
 use std::fs::File;
 use std::path::{Path, PathBuf};
 use std::str::FromStr;
-use std::sync::{Arc, RwLock};
 use std::time::Duration as StdDur;
 use std::vec::Vec;
 
 use chrono::prelude::*;
 
-use crate::config::config::{Config, ConfigLibrary};
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
-use crate::music_storage::library::{AlbumArt, MusicLibrary, Service, Song, Tag, URI, BannedType};
+use crate::music_storage::library::{AlbumArt, Service, Song, Tag, URI};
 use crate::music_storage::utils;
 
 use urlencoding::decode;
@@ -331,18 +329,27 @@ impl ITunesSong {
     }
 }
 
-#[test]
-fn itunes_lib_test() {
-    let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
-    let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
-    config.libraries.libraries.push(config_lib.clone());
+#[cfg(test)]
+mod tests {
+    use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}};
 
-    let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs();
+    use crate::{config::config::{Config, ConfigLibrary}, music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary}};
 
-    let mut library = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config_lib.uuid).unwrap();
+    use super::ITunesLibrary;
 
-    songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
+    #[test]
+    fn itunes_lib_test() {
+        let mut config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
+        let config_lib = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
+        config.libraries.libraries.push(config_lib.clone());
 
-    config.write_file().unwrap();
-    library.save(config).unwrap();
-}
\ No newline at end of file
+        let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs();
+
+        let mut library = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config_lib.uuid).unwrap();
+
+        songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
+
+        config.write_file().unwrap();
+        library.save(config).unwrap();
+    }
+}
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index f91b590..8c547c0 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -5,9 +5,7 @@ use crate::config::config::Config;
 // Various std things
 use std::collections::BTreeMap;
 use std::error::Error;
-use std::io::BufWriter;
 use std::ops::ControlFlow::{Break, Continue};
-use std::ops::Deref;
 
 // Files
 use file_format::{FileFormat, Kind};
@@ -15,11 +13,12 @@ use glib::filename_to_uri;
 use image::guess_format;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
-use tempfile::tempfile;
 use uuid::Uuid;
-use std::fs::{self, OpenOptions};
+use std::fs;
+use tempfile::TempDir;
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
+use image::ImageFormat::*;
 
 // Time
 use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
@@ -163,11 +162,8 @@ pub struct Song {
 
 #[test]
 fn get_art_test() {
-    use urlencoding::decode;
-
     let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap();
-    s.open_album_art(0).inspect_err(|e| println!("{e:?}"));
-
+    s.open_album_art(0).inspect_err(|e| println!("{e:?}")).unwrap();
 }
 
 impl Song {
@@ -440,22 +436,24 @@ impl Song {
         use urlencoding::decode;
 
         if index >= self.album_art.len() {
-            return Err("index out of bounds?".into());
+            return Err("Index out of bounds".into());
         }
 
-        let uri: String = match &self.album_art[index] {
+        let uri = match &self.album_art[index] {
             AlbumArt::External(uri) => {
-                decode(match uri.as_uri().strip_prefix("file:///") { Some(e) => e, None => return Err("Invalid path?".into()) })?.into_owned()
+                PathBuf::from(decode(match uri.as_uri().strip_prefix("file:///") {
+                    Some(e) => e,
+                    None => return Err("Invalid path?".into())
+                })?.to_owned().to_string())
             },
             AlbumArt::Embedded(_) => {
-
                 let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
                 let blank_tag = &lofty::Tag::new(TagType::Id3v2);
                 let tagged_file: lofty::TaggedFile;
 
-                let uri = dbg!(urlencoding::decode(self.location.as_uri().strip_prefix("file:///").unwrap())?.into_owned());
+                let uri = urlencoding::decode(self.location.as_uri().strip_prefix("file://").unwrap())?.into_owned();
 
-                let tag = match Probe::open(uri)?.options(normal_options).read() {
+                let tag = match Probe::open(uri).unwrap().options(normal_options).read() {
                     Ok(file) => {
                         tagged_file = file;
 
@@ -476,26 +474,22 @@ impl Song {
                 let format = dbg!(guess_format(data)?);
                 let img = image::load_from_memory(data)?;
 
-                let mut location = String::new();
-                let i: u32 = 0;
-                loop {
-                    use image::ImageFormat::*;
-                    //TODO: create a place for temporary images
-                    let fmt = match format {
-                        Jpeg => "jpeg",
-                        Png => "png",
-                        _ => todo!(),
-                    };
-
-                    location = format!("./test-config/images/tempcover{i}.{fmt}.tmp");
-                    break;
+                let tmp_dir = TempDir::new()?;
+                let fmt = match format {
+                    Jpeg => "jpeg",
+                    Png => "png",
+                    _ => todo!(),
                 };
-                img.save_with_format(&location, format)?;
 
-                location.to_string()
+                let file_path = tmp_dir.path().join(format!("{}.{fmt}", self.uuid));
+
+                open(&file_path).unwrap();
+                img.save_with_format(&file_path, format).unwrap();
+
+                file_path
             },
         };
-        open(uri)?;
+        dbg!(open(uri)?);
 
         Ok(())
     }
@@ -1137,4 +1131,4 @@ impl MusicLibrary {
 
         Ok(albums)
     }
-}
\ No newline at end of file
+}
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index ca1832b..783a293 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,21 +1,10 @@
-use std::{fs::File, path::{Path, PathBuf}, io::{Read, Error}};
+use std::{fs::File, io::{Read, Error}};
 
-use bincode::config;
 use chrono::Duration;
 use uuid::Uuid;
-// use walkdir::Error;
+use super::library::{AlbumArt, Song, Tag};
 
-use crate::music_controller::controller::Controller;
-
-use super::{
-    library::{AlbumArt, Song, Tag},
-    music_collection::MusicCollection, db_reader::{
-        itunes::reader::ITunesLibrary,
-        extern_library::ExternalLibrary
-    },
-};
-
-use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2, MasterPlaylist};
+use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2};
 
 #[derive(Debug, Clone)]
 pub enum SortOrder {
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 9d832e1..1f68a8c 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -1,10 +1,7 @@
-use std::any::Any;
 use std::fs::{File, self};
 use std::io::{BufReader, BufWriter};
-use std::os::windows::fs::MetadataExt;
 use std::path::{Path, PathBuf};
 use std::error::Error;
-
 use walkdir::WalkDir;
 use file_format::{FileFormat, Kind};
 use snap;
@@ -12,6 +9,9 @@ use deunicode::deunicode_with_tofu;
 
 use super::library::{AlbumArt, URI};
 
+#[cfg(target_family = "windows")]
+use std::os::windows::fs::MetadataExt;
+
 pub(super) fn normalize(input_string: &str) -> String {
     // Normalize the string to latin characters... this needs a lot of work
     let mut normalized = deunicode_with_tofu(input_string, " ");

From 162982ef868cda38161c223d9b754fb82033be58 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 24 Mar 2024 21:36:22 -0400
Subject: [PATCH 108/136] Updated album art viewer function to

---
 src/lib.rs                    |   2 -
 src/music_controller/queue.rs | 312 ++++++++++++++++++++--------------
 src/music_storage/library.rs  |  75 +++++---
 3 files changed, 235 insertions(+), 154 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 379a56c..387a488 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,3 @@
-#[allow(dead_code)]
-
 pub mod music_storage {
     pub mod library;
     pub mod music_collection;
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 8dfae31..6180e9d 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,6 +1,6 @@
 use uuid::Uuid;
 use crate::{
-    music_player::Player,
+    music_player::{Player, PlayerError},
     music_storage::library::{Album, MusicLibrary, URI}
 };
 use std::{
@@ -8,13 +8,25 @@ use std::{
     sync::{Arc, RwLock}
 };
 
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum QueueError {
+    #[error("Index out of bounds! Index {0} is over len {1}")]
+    OutOfBounds(usize, usize),
+    #[error("The Queue is empty!")]
+    EmptyQueue
+
+}
+
 #[derive(Debug, PartialEq, Clone, Copy)]
 pub enum QueueState {
     Played,
-    Current,
+    First,
     AddHere,
     NoState,
 }
+
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub enum QueueItemType<'a> {
@@ -23,12 +35,20 @@ pub enum QueueItemType<'a> {
     Album{
         album: Album<'a>,
         shuffled: bool,
+        order: Option<Vec<Uuid>>,
         // disc #, track #
         current: (i32, i32)
     },
+    Playlist {
+        uuid: Uuid,
+        shuffled: bool,
+        order: Option<Vec<Uuid>>,
+        current: Uuid
+    },
     None,
     Test
 }
+
 impl QueueItemType<'_> {
     fn get_uri(&self, lib: Arc<RwLock<MusicLibrary>>) -> Option<URI> {
         use QueueItemType::*;
@@ -42,7 +62,7 @@ impl QueueItemType<'_> {
                     Option::None
                 }
             },
-            Album{album, shuffled, current: (disc, index)} => {
+            Album{album, shuffled, current: (disc, index), ..} => {
                 if !shuffled {
                     Some(album.track(*disc as usize, *index as usize).unwrap().location.clone())
                 }else {
@@ -55,19 +75,23 @@ impl QueueItemType<'_> {
     }
 }
 
+// TODO: move this to a different location to be used elsewhere
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
-pub enum QueueSource {
+pub enum PlayerLocation {
+    Test,
     Library,
     Playlist(Uuid),
     File,
+    Custom
 }
+
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub struct QueueItem<'a> {
     item: QueueItemType<'a>,
     state: QueueState,
-    source: QueueSource,
+    source: PlayerLocation,
     by_human: bool
 }
 impl QueueItem<'_> {
@@ -75,7 +99,7 @@ impl QueueItem<'_> {
         QueueItem {
             item: QueueItemType::None,
             state: QueueState::NoState,
-            source: QueueSource::Library,
+            source: PlayerLocation::Library,
             by_human: false
         }
     }
@@ -87,111 +111,83 @@ pub struct Queue<'a> {
     pub player: Player,
     pub name: String,
     pub items: Vec<QueueItem<'a>>,
+    pub played: Vec<QueueItem<'a>>,
+    pub loop_: bool
 }
 
 impl<'a> Queue<'a> {
+    fn has_addhere(&self) -> bool {
+        for item in &self.items {
+            if item.state == QueueState::AddHere {
+                return true
+            }
+        }
+        false
+    }
+
     fn dbg_items(&self) {
         dbg!(self.items.iter().map(|item| item.item.clone() ).collect::<Vec<QueueItemType>>(), self.items.len());
     }
-    pub fn new() -> Result<Self, Box<dyn Error>> {
+
+    pub fn new() -> Result<Self, PlayerError> {
         Ok(
             Queue {
             player: Player::new()?,
             name: String::new(),
-            items: Vec::new()
+            items: Vec::new(),
+            played: Vec::new(),
+            loop_: false,
             }
         )
     }
 
-    pub fn current_index(&mut self/* , max: usize */) -> Option<usize> {
-        let mut e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
-        // TODO: make the max number of past songs modular
-        while e > 50 {
-            self.items.remove(0);
-            e -=1;
-        }
-        if e == 0 {
-            None
-        }else {
-            Some(e - 1)
-        }
-    }
-
-    fn contains_state(&self, state: QueueState) -> bool {
-        !self.items.iter().filter(|item| item.state == state ).collect::<Vec<_>>().is_empty()
-    }
-
-    fn is_empty(&self) -> bool {
-        self.items.iter().filter(|item| item.state != QueueState::Played).collect::<Vec<_>>().is_empty()
-    }
-
     pub fn set_items(&mut self, tracks: Vec<QueueItem<'a>>) {
         let mut tracks = tracks;
         self.items.clear();
         self.items.append(&mut tracks);
     }
 
-    pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box<dyn Error>> {
-        use QueueState::*;
+    pub fn add_item(&mut self, item: QueueItemType<'a>, source: PlayerLocation, by_human: bool) {
         let mut i: usize = 0;
-        let ind =  self.current_index();
 
         self.items = self.items.iter().enumerate().map(|(j, item_)| {
             let mut item_ = item_.to_owned();
             // get the index of the current AddHere item and give it to i
-            if item_.state == AddHere {
-                i = j - ind.unwrap_or(0);
-                item_.state = NoState;
-            } else if item_.state == Current {
-                i = j - ind.unwrap_or(0);
+            if item_.state == QueueState::AddHere {
+                i = j;
+                item_.state = QueueState::NoState;
             }
             item_
         }).collect::<Vec<QueueItem>>();
 
-        let pos = ind.unwrap_or(0) + i  + if !self.is_empty() || (self.is_empty() && ind == None) { 0 } else { 1 };
-        // dbg!(&pos, &i, &ind);
-        self.items.insert(
-            pos,
-            QueueItem {
-                item: item.clone(),
-                state: if pos == self.items.len() && i == 0 {
-                    Current
-                }else {
-                    AddHere
-                },
-                source,
-                by_human
-            }
-        );
-        Ok(())
+        self.items.insert(i + if self.items.is_empty() { 0 } else { 1 }, QueueItem {
+            item,
+            state: QueueState::AddHere,
+            source,
+            by_human
+        });
     }
 
-    pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) {
+    pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: PlayerLocation) {
         use QueueState::*;
-        let ind_ =  self.current_index();
-        let ind =  ind_.unwrap_or(0);
-        let empty = self.is_empty();
+        let empty = self.items.is_empty();
 
         self.items.insert(
-            ind + if !empty && ind_ == None { 1 } else { 2 },
+            (if empty { 0 } else { 1 }),
             QueueItem {
                 item,
-                state: if empty {
-                    Current
-                }else if self.items.get(ind + 1).is_none() || (!self.contains_state(AddHere) && self.items.get(ind + 1).is_some()) {
-                    AddHere
-                }else {
-                    NoState
-                },
+                state: if (self.items.get(1).is_none() || (!self.has_addhere() && self.items.get(1).is_some()) || empty) { AddHere } else { NoState },
                 source,
                 by_human: true
             }
         )
     }
 
-    pub fn remove_item(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
-        use QueueState::*;
-        let remove_index: usize = (if let Some(current_index) =  self.current_index() { dbg!(&current_index); current_index } else { 0 } + index );
+    pub fn add_multi(&mut self, items: Vec<QueueItemType>, source: PlayerLocation, by_human: bool) {
+
+    }
+
+    pub fn remove_item(&mut self, remove_index: usize) -> Result<(), QueueError> {
 
         // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]);
 
@@ -199,39 +195,29 @@ impl<'a> Queue<'a> {
             // update the state of the next item to replace the item being removed
             if self.items.get(remove_index + 1).is_some() {
                 self.items[remove_index + 1].state = self.items[remove_index].state;
-            }else if self.items[remove_index].state != Current {
-                self.items[remove_index - 1].state = self.items[remove_index].state;
             }
-            self.items[remove_index].state = NoState;
+            self.items[remove_index].state = QueueState::NoState;
             self.items.remove(remove_index);
             Ok(())
         }else {
-            Err("No Songs to remove!".into())
+            Err(QueueError::EmptyQueue)
         }
     }
 
     pub fn clear(&mut self) {
-        self.items.retain(|item| item.state == QueueState::Played );
+        self.items.clear();
     }
 
     pub fn clear_except(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
-        let mut index = index;
-        let ind = match self.current_index() {
-            Some(e) => e,
-            None =>  return Err("nothing to clear!".into())
-        };
-        let empty = self.is_empty();
-
-        if !empty {
-            index += ind;
-        }else {
-            index -=1
-        }
+        use QueueState::*;
+        let empty = self.items.is_empty();
 
         if !empty && index < self.items.len() {
             let i = self.items[index].clone();
-            self.items.retain(|item| item.state == QueueState::Played || *item == i );
-            self.items[ind+1].state = QueueState::Current
+            self.items.retain(|item| *item == i );
+            self.items[0].state = AddHere;
+        }else if empty {
+            return Err("Queue is empty!".into());
         }else {
             return Err("index out of bounds!".into());
         }
@@ -239,49 +225,117 @@ impl<'a> Queue<'a> {
     }
 
     pub fn clear_played(&mut self) {
-        self.items.retain(|item| item.state != QueueState::Played );
+        self.played.clear();
     }
 
     pub fn clear_all(&mut self) {
-        self.items.clear()
+        self.items.clear();
+        self.played.clear();
     }
 
-    fn move_to(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
-        let empty = self.is_empty();
-        let nothing_error = Err("Nothing in the queue to move to!".into());
-        let ind = self.current_index().unwrap_or(0);
-        let index = if !empty { index + ind } else { return nothing_error; };
 
-        if !empty && index < self.items.len() -1 {
+    // TODO: uh, fix this?
+    fn move_to(&mut self, index: usize) -> Result<(), QueueError> {
+        use QueueState::*;
+
+        let empty = self.items.is_empty();
+        let nothing_error = Err(QueueError::EmptyQueue);
+        let index = if !empty { index } else { return nothing_error; };
+
+        if !empty && index < self.items.len() {
             let position = self.player.position();
             if position.is_some_and(|dur| !dur.is_zero() ) {
-                self.items[ind].state = QueueState::Played;
+                self.played.push(self.items[0].clone());
             }
 
             let to_item = self.items[index].clone();
-            let ind = self.current_index().unwrap_or(0);
 
             loop {
-                if self.items[ind].item != to_item.item {
+                let empty = !self.items.is_empty();
+                let item = self.items[0].item.to_owned();
+
+                if item != to_item.item && !empty {
+                    if self.items[0].state == AddHere && self.items.get(1).is_some() {
+                        self.items[1].state = AddHere;
+                    }
                     if let Err(e) = self.remove_item(0) {
                         dbg!(&e); self.dbg_items(); return Err(e);
                     }
                 // dbg!(&to_item.item, &self.items[ind].item);
+                }else if empty {
+                    return nothing_error;
                 }else {
                     break;
                 }
             }
         }else {
-            return Err("index out of bounds!".into());
+            return Err(QueueError::EmptyQueue.into());
         }
         Ok(())
     }
 
-    pub fn swap(&mut self, index1: usize, index2: usize) {}
+    pub fn swap(&mut self, a: usize, b: usize) {
+        self.items.swap(a, b)
+    }
 
-    pub fn move_item(&mut self, item: usize, to_index: usize) {}
+    pub fn move_item(&mut self, a: usize, b: usize) {
+        let item = self.items[a].to_owned();
+        if a != b {
+            self.items.remove(a);
+        }
+        self.items.insert(b, item);
+    }
 
-    pub fn next() {}
+    #[allow(clippy::should_implement_trait)]
+    pub fn next(&mut self, lib: Arc<RwLock<MusicLibrary>>) -> Result<OutQueue, Box<dyn Error>> {
+
+
+
+        if self.items.is_empty() {
+            if self.loop_ {
+                return Err(QueueError::EmptyQueue.into()); // TODO: add function to loop the queue
+            }else {
+                return Err(QueueError::EmptyQueue.into());
+            }
+        }
+        // TODO: add an algorithm to detect if the song should be skipped
+        let item = self.items[0].clone();
+        let uri: URI = match &self.items[1].item {
+            QueueItemType::Song(uuid) => {
+                // TODO:  Refactor later for  multiple URIs
+                match &lib.read().unwrap().query_uuid(uuid) {
+                    Some(song) => song.0.location.clone(),
+                    None => return Err("Uuid does not exist!".into()),
+                }
+            },
+            QueueItemType::Album { album, current, ..} => {
+                let (disc, track) = (current.0 as usize, current.1 as usize);
+                match album.track(disc, track) {
+                    Some(track) => track.location.clone(),
+                    None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into())
+                }
+            },
+            QueueItemType::Playlist { current, .. } => {
+                // TODO:  Refactor later for  multiple URIs
+                match &lib.read().unwrap().query_uuid(current) {
+                    Some(song) => song.0.location.clone(),
+                    None => return Err("Uuid does not exist!".into()),
+                }
+            },
+            _ => todo!()
+        };
+        if !self.player.is_paused() {
+            self.player.enqueue_next(&uri)?;
+            self.player.play()?
+        }
+        if self.items[0].state == QueueState::AddHere || !self.has_addhere() {
+            self.items[1].state = QueueState::AddHere;
+        }
+        self.played.push(item);
+        self.items.remove(0);
+
+        Ok(todo!())
+    }
 
     pub fn prev() {}
 
@@ -293,40 +347,42 @@ impl<'a> Queue<'a> {
         }
         Ok(())
     }
+    pub fn check_played(&mut self) {
+        while self.played.len() > 50 {
+            self.played.remove(0);
+        }
+    }
+}
+
+pub struct OutQueue {
+
+}
+
+pub enum OutQueueItem {
+
 }
 
 
 #[test]
 fn item_add_test() {
     let mut q = Queue::new().unwrap();
-    dbg!(1);
-    for _ in 0..1 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
-    }
-    dbg!(2);
-
-    // q.clear();
-    dbg!(3);
 
     for _ in 0..5 {
         // dbg!("tick!");
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
         // dbg!(&q.items, &q.items.len());
     }
-    dbg!(4);
-    dbg!(&q.items, &q.items.len());
 
-    // q.clear_played();
     for _ in 0..1 {
     q.remove_item(0).inspect_err(|e| println!("{e:?}"));
     }
-    // for _ in 0..2 {
-    //     q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: QueueSource::Library, by_human: false });
-    // }
-    // dbg!(5);
+    for _ in 0..2 {
+        q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false });
+    }
+    dbg!(5);
 
-    // q.add_item_next(QueueItemType::Test, QueueSource::File);
-    // dbg!(6);
+    q.add_item_next(QueueItemType::Test, PlayerLocation::Test);
+    dbg!(6);
 
     dbg!(&q.items, &q.items.len());
 }
@@ -335,25 +391,23 @@ fn item_add_test() {
 fn test_() {
     let mut q = Queue::new().unwrap();
     for _ in 0..400 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::NoState, source: PlayerLocation::File, by_human: false });
     }
     for _ in 0..50000 {
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
     }
-    q.add_item_next(QueueItemType::Test, QueueSource::File);
+    // q.add_item_next(QueueItemType::Test, PlayerLocation::File);
 
-    dbg!(&q.items, &q.items.len());
+    // dbg!(&q.items, &q.items.len());
 
 }
 
 #[test]
 fn move_test() {
     let mut q = Queue::new().unwrap();
-    for _ in 0..1 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
-    }
+
     for _ in 0..5 {
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
     }
     // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap();
     dbg!(&q.items, &q.items.len());
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 8c547c0..1a198f8 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -5,7 +5,9 @@ use crate::config::config::Config;
 // Various std things
 use std::collections::BTreeMap;
 use std::error::Error;
+use std::io::Write;
 use std::ops::ControlFlow::{Break, Continue};
+use std::thread::sleep;
 
 // Files
 use file_format::{FileFormat, Kind};
@@ -14,7 +16,7 @@ use image::guess_format;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
 use uuid::Uuid;
-use std::fs;
+use std::fs::{self, File};
 use tempfile::TempDir;
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
@@ -160,11 +162,6 @@ pub struct Song {
     pub tags: BTreeMap<Tag, String>,
 }
 
-#[test]
-fn get_art_test() {
-    let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap();
-    s.open_album_art(0).inspect_err(|e| println!("{e:?}")).unwrap();
-}
 
 impl Song {
     /// Get a tag's value
@@ -431,7 +428,7 @@ impl Song {
      Ok(tracks)
     }
 
-    pub fn open_album_art(&self, index: usize) -> Result<(), Box<dyn Error>> {
+    pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box<dyn Error>> {
         use opener::open;
         use urlencoding::decode;
 
@@ -451,9 +448,21 @@ impl Song {
                 let blank_tag = &lofty::Tag::new(TagType::Id3v2);
                 let tagged_file: lofty::TaggedFile;
 
-                let uri = urlencoding::decode(self.location.as_uri().strip_prefix("file://").unwrap())?.into_owned();
+                #[cfg(windows)]
+                let uri = urlencoding::decode(
+                    match self.location.as_uri().strip_prefix("file:///") {
+                        Some(str) => str,
+                        None => return Err("invalid path.. again?".into())
+                })?.into_owned();
 
-                let tag = match Probe::open(uri).unwrap().options(normal_options).read() {
+                #[cfg(unix)]
+                let uri = urlencoding::decode(
+                    match self.location.as_uri().strip_prefix("file://") {
+                        Some(str) => str,
+                        None => return Err("invalid path.. again?".into())
+                })?.into_owned();
+
+                let tag = match Probe::open(uri)?.options(normal_options).read() {
                     Ok(file) => {
                         tagged_file = file;
 
@@ -471,26 +480,16 @@ impl Song {
                 };
 
                 let data = tag.pictures()[index].data();
-                let format = dbg!(guess_format(data)?);
-                let img = image::load_from_memory(data)?;
 
-                let tmp_dir = TempDir::new()?;
-                let fmt = match format {
-                    Jpeg => "jpeg",
-                    Png => "png",
-                    _ => todo!(),
-                };
+                let fmt = FileFormat::from_bytes(data);
+                let file_path = temp_dir.path().join(format!("{}_{index}.{}", self.uuid, fmt.extension()));
 
-                let file_path = tmp_dir.path().join(format!("{}.{fmt}", self.uuid));
-
-                open(&file_path).unwrap();
-                img.save_with_format(&file_path, format).unwrap();
+                File::create(&file_path)?.write_all(data)?;
 
                 file_path
             },
         };
-        dbg!(open(uri)?);
-
+        dbg!(open(dbg!(uri))?);
         Ok(())
     }
 }
@@ -558,6 +557,14 @@ impl URI {
         path_str.to_string()
     }
 
+    pub fn as_path(&self) -> Result<&PathBuf, Box<dyn Error>> {
+        if let Self::Local(path) = self {
+            Ok(path)
+        }else {
+            Err("This URI is not local!".into())
+        }
+    }
+
     pub fn exists(&self) -> Result<bool, std::io::Error> {
         match self {
             URI::Local(loc) => loc.try_exists(),
@@ -1132,3 +1139,25 @@ impl MusicLibrary {
         Ok(albums)
     }
 }
+
+#[cfg(test)]
+mod test {
+    use std::{path::Path, thread::sleep, time::{Duration, Instant}};
+
+    use tempfile::TempDir;
+
+    use super::Song;
+
+
+    #[test]
+    fn get_art_test() {
+        let s = Song::from_file(Path::new(".\\test-config\\music\\Snail_s House - Hot Milk.mp3")).unwrap();
+        let dir = &TempDir::new().unwrap();
+
+        let now = Instant::now();
+        _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}"));
+        println!("{}ms", now.elapsed().as_millis() );
+
+        sleep(Duration::from_secs(1));
+    }
+}
\ No newline at end of file

From 259dbec3a0f0ad0720ae40f3557c0d6d786e721a Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 24 Mar 2024 21:36:22 -0400
Subject: [PATCH 109/136] Updated album art viewer function

---
 src/lib.rs                    |   2 -
 src/music_controller/queue.rs | 312 ++++++++++++++++++++--------------
 src/music_storage/library.rs  |  75 +++++---
 3 files changed, 235 insertions(+), 154 deletions(-)

diff --git a/src/lib.rs b/src/lib.rs
index 379a56c..387a488 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,3 @@
-#[allow(dead_code)]
-
 pub mod music_storage {
     pub mod library;
     pub mod music_collection;
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 8dfae31..6180e9d 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,6 +1,6 @@
 use uuid::Uuid;
 use crate::{
-    music_player::Player,
+    music_player::{Player, PlayerError},
     music_storage::library::{Album, MusicLibrary, URI}
 };
 use std::{
@@ -8,13 +8,25 @@ use std::{
     sync::{Arc, RwLock}
 };
 
+use thiserror::Error;
+
+#[derive(Error, Debug)]
+pub enum QueueError {
+    #[error("Index out of bounds! Index {0} is over len {1}")]
+    OutOfBounds(usize, usize),
+    #[error("The Queue is empty!")]
+    EmptyQueue
+
+}
+
 #[derive(Debug, PartialEq, Clone, Copy)]
 pub enum QueueState {
     Played,
-    Current,
+    First,
     AddHere,
     NoState,
 }
+
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub enum QueueItemType<'a> {
@@ -23,12 +35,20 @@ pub enum QueueItemType<'a> {
     Album{
         album: Album<'a>,
         shuffled: bool,
+        order: Option<Vec<Uuid>>,
         // disc #, track #
         current: (i32, i32)
     },
+    Playlist {
+        uuid: Uuid,
+        shuffled: bool,
+        order: Option<Vec<Uuid>>,
+        current: Uuid
+    },
     None,
     Test
 }
+
 impl QueueItemType<'_> {
     fn get_uri(&self, lib: Arc<RwLock<MusicLibrary>>) -> Option<URI> {
         use QueueItemType::*;
@@ -42,7 +62,7 @@ impl QueueItemType<'_> {
                     Option::None
                 }
             },
-            Album{album, shuffled, current: (disc, index)} => {
+            Album{album, shuffled, current: (disc, index), ..} => {
                 if !shuffled {
                     Some(album.track(*disc as usize, *index as usize).unwrap().location.clone())
                 }else {
@@ -55,19 +75,23 @@ impl QueueItemType<'_> {
     }
 }
 
+// TODO: move this to a different location to be used elsewhere
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
-pub enum QueueSource {
+pub enum PlayerLocation {
+    Test,
     Library,
     Playlist(Uuid),
     File,
+    Custom
 }
+
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub struct QueueItem<'a> {
     item: QueueItemType<'a>,
     state: QueueState,
-    source: QueueSource,
+    source: PlayerLocation,
     by_human: bool
 }
 impl QueueItem<'_> {
@@ -75,7 +99,7 @@ impl QueueItem<'_> {
         QueueItem {
             item: QueueItemType::None,
             state: QueueState::NoState,
-            source: QueueSource::Library,
+            source: PlayerLocation::Library,
             by_human: false
         }
     }
@@ -87,111 +111,83 @@ pub struct Queue<'a> {
     pub player: Player,
     pub name: String,
     pub items: Vec<QueueItem<'a>>,
+    pub played: Vec<QueueItem<'a>>,
+    pub loop_: bool
 }
 
 impl<'a> Queue<'a> {
+    fn has_addhere(&self) -> bool {
+        for item in &self.items {
+            if item.state == QueueState::AddHere {
+                return true
+            }
+        }
+        false
+    }
+
     fn dbg_items(&self) {
         dbg!(self.items.iter().map(|item| item.item.clone() ).collect::<Vec<QueueItemType>>(), self.items.len());
     }
-    pub fn new() -> Result<Self, Box<dyn Error>> {
+
+    pub fn new() -> Result<Self, PlayerError> {
         Ok(
             Queue {
             player: Player::new()?,
             name: String::new(),
-            items: Vec::new()
+            items: Vec::new(),
+            played: Vec::new(),
+            loop_: false,
             }
         )
     }
 
-    pub fn current_index(&mut self/* , max: usize */) -> Option<usize> {
-        let mut e  = self.items.iter().filter(|song| song.state == QueueState::Played ).collect::<Vec<&QueueItem>>().len();
-        // TODO: make the max number of past songs modular
-        while e > 50 {
-            self.items.remove(0);
-            e -=1;
-        }
-        if e == 0 {
-            None
-        }else {
-            Some(e - 1)
-        }
-    }
-
-    fn contains_state(&self, state: QueueState) -> bool {
-        !self.items.iter().filter(|item| item.state == state ).collect::<Vec<_>>().is_empty()
-    }
-
-    fn is_empty(&self) -> bool {
-        self.items.iter().filter(|item| item.state != QueueState::Played).collect::<Vec<_>>().is_empty()
-    }
-
     pub fn set_items(&mut self, tracks: Vec<QueueItem<'a>>) {
         let mut tracks = tracks;
         self.items.clear();
         self.items.append(&mut tracks);
     }
 
-    pub fn add_item(&mut self, item: QueueItemType<'a>, source: QueueSource, by_human: bool) -> Result<(), Box<dyn Error>> {
-        use QueueState::*;
+    pub fn add_item(&mut self, item: QueueItemType<'a>, source: PlayerLocation, by_human: bool) {
         let mut i: usize = 0;
-        let ind =  self.current_index();
 
         self.items = self.items.iter().enumerate().map(|(j, item_)| {
             let mut item_ = item_.to_owned();
             // get the index of the current AddHere item and give it to i
-            if item_.state == AddHere {
-                i = j - ind.unwrap_or(0);
-                item_.state = NoState;
-            } else if item_.state == Current {
-                i = j - ind.unwrap_or(0);
+            if item_.state == QueueState::AddHere {
+                i = j;
+                item_.state = QueueState::NoState;
             }
             item_
         }).collect::<Vec<QueueItem>>();
 
-        let pos = ind.unwrap_or(0) + i  + if !self.is_empty() || (self.is_empty() && ind == None) { 0 } else { 1 };
-        // dbg!(&pos, &i, &ind);
-        self.items.insert(
-            pos,
-            QueueItem {
-                item: item.clone(),
-                state: if pos == self.items.len() && i == 0 {
-                    Current
-                }else {
-                    AddHere
-                },
-                source,
-                by_human
-            }
-        );
-        Ok(())
+        self.items.insert(i + if self.items.is_empty() { 0 } else { 1 }, QueueItem {
+            item,
+            state: QueueState::AddHere,
+            source,
+            by_human
+        });
     }
 
-    pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: QueueSource) {
+    pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: PlayerLocation) {
         use QueueState::*;
-        let ind_ =  self.current_index();
-        let ind =  ind_.unwrap_or(0);
-        let empty = self.is_empty();
+        let empty = self.items.is_empty();
 
         self.items.insert(
-            ind + if !empty && ind_ == None { 1 } else { 2 },
+            (if empty { 0 } else { 1 }),
             QueueItem {
                 item,
-                state: if empty {
-                    Current
-                }else if self.items.get(ind + 1).is_none() || (!self.contains_state(AddHere) && self.items.get(ind + 1).is_some()) {
-                    AddHere
-                }else {
-                    NoState
-                },
+                state: if (self.items.get(1).is_none() || (!self.has_addhere() && self.items.get(1).is_some()) || empty) { AddHere } else { NoState },
                 source,
                 by_human: true
             }
         )
     }
 
-    pub fn remove_item(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
-        use QueueState::*;
-        let remove_index: usize = (if let Some(current_index) =  self.current_index() { dbg!(&current_index); current_index } else { 0 } + index );
+    pub fn add_multi(&mut self, items: Vec<QueueItemType>, source: PlayerLocation, by_human: bool) {
+
+    }
+
+    pub fn remove_item(&mut self, remove_index: usize) -> Result<(), QueueError> {
 
         // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]);
 
@@ -199,39 +195,29 @@ impl<'a> Queue<'a> {
             // update the state of the next item to replace the item being removed
             if self.items.get(remove_index + 1).is_some() {
                 self.items[remove_index + 1].state = self.items[remove_index].state;
-            }else if self.items[remove_index].state != Current {
-                self.items[remove_index - 1].state = self.items[remove_index].state;
             }
-            self.items[remove_index].state = NoState;
+            self.items[remove_index].state = QueueState::NoState;
             self.items.remove(remove_index);
             Ok(())
         }else {
-            Err("No Songs to remove!".into())
+            Err(QueueError::EmptyQueue)
         }
     }
 
     pub fn clear(&mut self) {
-        self.items.retain(|item| item.state == QueueState::Played );
+        self.items.clear();
     }
 
     pub fn clear_except(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
-        let mut index = index;
-        let ind = match self.current_index() {
-            Some(e) => e,
-            None =>  return Err("nothing to clear!".into())
-        };
-        let empty = self.is_empty();
-
-        if !empty {
-            index += ind;
-        }else {
-            index -=1
-        }
+        use QueueState::*;
+        let empty = self.items.is_empty();
 
         if !empty && index < self.items.len() {
             let i = self.items[index].clone();
-            self.items.retain(|item| item.state == QueueState::Played || *item == i );
-            self.items[ind+1].state = QueueState::Current
+            self.items.retain(|item| *item == i );
+            self.items[0].state = AddHere;
+        }else if empty {
+            return Err("Queue is empty!".into());
         }else {
             return Err("index out of bounds!".into());
         }
@@ -239,49 +225,117 @@ impl<'a> Queue<'a> {
     }
 
     pub fn clear_played(&mut self) {
-        self.items.retain(|item| item.state != QueueState::Played );
+        self.played.clear();
     }
 
     pub fn clear_all(&mut self) {
-        self.items.clear()
+        self.items.clear();
+        self.played.clear();
     }
 
-    fn move_to(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
-        let empty = self.is_empty();
-        let nothing_error = Err("Nothing in the queue to move to!".into());
-        let ind = self.current_index().unwrap_or(0);
-        let index = if !empty { index + ind } else { return nothing_error; };
 
-        if !empty && index < self.items.len() -1 {
+    // TODO: uh, fix this?
+    fn move_to(&mut self, index: usize) -> Result<(), QueueError> {
+        use QueueState::*;
+
+        let empty = self.items.is_empty();
+        let nothing_error = Err(QueueError::EmptyQueue);
+        let index = if !empty { index } else { return nothing_error; };
+
+        if !empty && index < self.items.len() {
             let position = self.player.position();
             if position.is_some_and(|dur| !dur.is_zero() ) {
-                self.items[ind].state = QueueState::Played;
+                self.played.push(self.items[0].clone());
             }
 
             let to_item = self.items[index].clone();
-            let ind = self.current_index().unwrap_or(0);
 
             loop {
-                if self.items[ind].item != to_item.item {
+                let empty = !self.items.is_empty();
+                let item = self.items[0].item.to_owned();
+
+                if item != to_item.item && !empty {
+                    if self.items[0].state == AddHere && self.items.get(1).is_some() {
+                        self.items[1].state = AddHere;
+                    }
                     if let Err(e) = self.remove_item(0) {
                         dbg!(&e); self.dbg_items(); return Err(e);
                     }
                 // dbg!(&to_item.item, &self.items[ind].item);
+                }else if empty {
+                    return nothing_error;
                 }else {
                     break;
                 }
             }
         }else {
-            return Err("index out of bounds!".into());
+            return Err(QueueError::EmptyQueue.into());
         }
         Ok(())
     }
 
-    pub fn swap(&mut self, index1: usize, index2: usize) {}
+    pub fn swap(&mut self, a: usize, b: usize) {
+        self.items.swap(a, b)
+    }
 
-    pub fn move_item(&mut self, item: usize, to_index: usize) {}
+    pub fn move_item(&mut self, a: usize, b: usize) {
+        let item = self.items[a].to_owned();
+        if a != b {
+            self.items.remove(a);
+        }
+        self.items.insert(b, item);
+    }
 
-    pub fn next() {}
+    #[allow(clippy::should_implement_trait)]
+    pub fn next(&mut self, lib: Arc<RwLock<MusicLibrary>>) -> Result<OutQueue, Box<dyn Error>> {
+
+
+
+        if self.items.is_empty() {
+            if self.loop_ {
+                return Err(QueueError::EmptyQueue.into()); // TODO: add function to loop the queue
+            }else {
+                return Err(QueueError::EmptyQueue.into());
+            }
+        }
+        // TODO: add an algorithm to detect if the song should be skipped
+        let item = self.items[0].clone();
+        let uri: URI = match &self.items[1].item {
+            QueueItemType::Song(uuid) => {
+                // TODO:  Refactor later for  multiple URIs
+                match &lib.read().unwrap().query_uuid(uuid) {
+                    Some(song) => song.0.location.clone(),
+                    None => return Err("Uuid does not exist!".into()),
+                }
+            },
+            QueueItemType::Album { album, current, ..} => {
+                let (disc, track) = (current.0 as usize, current.1 as usize);
+                match album.track(disc, track) {
+                    Some(track) => track.location.clone(),
+                    None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into())
+                }
+            },
+            QueueItemType::Playlist { current, .. } => {
+                // TODO:  Refactor later for  multiple URIs
+                match &lib.read().unwrap().query_uuid(current) {
+                    Some(song) => song.0.location.clone(),
+                    None => return Err("Uuid does not exist!".into()),
+                }
+            },
+            _ => todo!()
+        };
+        if !self.player.is_paused() {
+            self.player.enqueue_next(&uri)?;
+            self.player.play()?
+        }
+        if self.items[0].state == QueueState::AddHere || !self.has_addhere() {
+            self.items[1].state = QueueState::AddHere;
+        }
+        self.played.push(item);
+        self.items.remove(0);
+
+        Ok(todo!())
+    }
 
     pub fn prev() {}
 
@@ -293,40 +347,42 @@ impl<'a> Queue<'a> {
         }
         Ok(())
     }
+    pub fn check_played(&mut self) {
+        while self.played.len() > 50 {
+            self.played.remove(0);
+        }
+    }
+}
+
+pub struct OutQueue {
+
+}
+
+pub enum OutQueueItem {
+
 }
 
 
 #[test]
 fn item_add_test() {
     let mut q = Queue::new().unwrap();
-    dbg!(1);
-    for _ in 0..1 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
-    }
-    dbg!(2);
-
-    // q.clear();
-    dbg!(3);
 
     for _ in 0..5 {
         // dbg!("tick!");
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
         // dbg!(&q.items, &q.items.len());
     }
-    dbg!(4);
-    dbg!(&q.items, &q.items.len());
 
-    // q.clear_played();
     for _ in 0..1 {
     q.remove_item(0).inspect_err(|e| println!("{e:?}"));
     }
-    // for _ in 0..2 {
-    //     q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: QueueSource::Library, by_human: false });
-    // }
-    // dbg!(5);
+    for _ in 0..2 {
+        q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false });
+    }
+    dbg!(5);
 
-    // q.add_item_next(QueueItemType::Test, QueueSource::File);
-    // dbg!(6);
+    q.add_item_next(QueueItemType::Test, PlayerLocation::Test);
+    dbg!(6);
 
     dbg!(&q.items, &q.items.len());
 }
@@ -335,25 +391,23 @@ fn item_add_test() {
 fn test_() {
     let mut q = Queue::new().unwrap();
     for _ in 0..400 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
+        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::NoState, source: PlayerLocation::File, by_human: false });
     }
     for _ in 0..50000 {
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
     }
-    q.add_item_next(QueueItemType::Test, QueueSource::File);
+    // q.add_item_next(QueueItemType::Test, PlayerLocation::File);
 
-    dbg!(&q.items, &q.items.len());
+    // dbg!(&q.items, &q.items.len());
 
 }
 
 #[test]
 fn move_test() {
     let mut q = Queue::new().unwrap();
-    for _ in 0..1 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::Played, source: QueueSource::File, by_human: false });
-    }
+
     for _ in 0..5 {
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), QueueSource::Library, true).unwrap();
+        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
     }
     // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap();
     dbg!(&q.items, &q.items.len());
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 8c547c0..1a198f8 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -5,7 +5,9 @@ use crate::config::config::Config;
 // Various std things
 use std::collections::BTreeMap;
 use std::error::Error;
+use std::io::Write;
 use std::ops::ControlFlow::{Break, Continue};
+use std::thread::sleep;
 
 // Files
 use file_format::{FileFormat, Kind};
@@ -14,7 +16,7 @@ use image::guess_format;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
 use uuid::Uuid;
-use std::fs;
+use std::fs::{self, File};
 use tempfile::TempDir;
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
@@ -160,11 +162,6 @@ pub struct Song {
     pub tags: BTreeMap<Tag, String>,
 }
 
-#[test]
-fn get_art_test() {
-    let s = Song::from_file(Path::new("F:\\Music\\Mp3\\ななひら\\Colory Starry\\05 - Fly Away!.mp3")).unwrap();
-    s.open_album_art(0).inspect_err(|e| println!("{e:?}")).unwrap();
-}
 
 impl Song {
     /// Get a tag's value
@@ -431,7 +428,7 @@ impl Song {
      Ok(tracks)
     }
 
-    pub fn open_album_art(&self, index: usize) -> Result<(), Box<dyn Error>> {
+    pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box<dyn Error>> {
         use opener::open;
         use urlencoding::decode;
 
@@ -451,9 +448,21 @@ impl Song {
                 let blank_tag = &lofty::Tag::new(TagType::Id3v2);
                 let tagged_file: lofty::TaggedFile;
 
-                let uri = urlencoding::decode(self.location.as_uri().strip_prefix("file://").unwrap())?.into_owned();
+                #[cfg(windows)]
+                let uri = urlencoding::decode(
+                    match self.location.as_uri().strip_prefix("file:///") {
+                        Some(str) => str,
+                        None => return Err("invalid path.. again?".into())
+                })?.into_owned();
 
-                let tag = match Probe::open(uri).unwrap().options(normal_options).read() {
+                #[cfg(unix)]
+                let uri = urlencoding::decode(
+                    match self.location.as_uri().strip_prefix("file://") {
+                        Some(str) => str,
+                        None => return Err("invalid path.. again?".into())
+                })?.into_owned();
+
+                let tag = match Probe::open(uri)?.options(normal_options).read() {
                     Ok(file) => {
                         tagged_file = file;
 
@@ -471,26 +480,16 @@ impl Song {
                 };
 
                 let data = tag.pictures()[index].data();
-                let format = dbg!(guess_format(data)?);
-                let img = image::load_from_memory(data)?;
 
-                let tmp_dir = TempDir::new()?;
-                let fmt = match format {
-                    Jpeg => "jpeg",
-                    Png => "png",
-                    _ => todo!(),
-                };
+                let fmt = FileFormat::from_bytes(data);
+                let file_path = temp_dir.path().join(format!("{}_{index}.{}", self.uuid, fmt.extension()));
 
-                let file_path = tmp_dir.path().join(format!("{}.{fmt}", self.uuid));
-
-                open(&file_path).unwrap();
-                img.save_with_format(&file_path, format).unwrap();
+                File::create(&file_path)?.write_all(data)?;
 
                 file_path
             },
         };
-        dbg!(open(uri)?);
-
+        dbg!(open(dbg!(uri))?);
         Ok(())
     }
 }
@@ -558,6 +557,14 @@ impl URI {
         path_str.to_string()
     }
 
+    pub fn as_path(&self) -> Result<&PathBuf, Box<dyn Error>> {
+        if let Self::Local(path) = self {
+            Ok(path)
+        }else {
+            Err("This URI is not local!".into())
+        }
+    }
+
     pub fn exists(&self) -> Result<bool, std::io::Error> {
         match self {
             URI::Local(loc) => loc.try_exists(),
@@ -1132,3 +1139,25 @@ impl MusicLibrary {
         Ok(albums)
     }
 }
+
+#[cfg(test)]
+mod test {
+    use std::{path::Path, thread::sleep, time::{Duration, Instant}};
+
+    use tempfile::TempDir;
+
+    use super::Song;
+
+
+    #[test]
+    fn get_art_test() {
+        let s = Song::from_file(Path::new(".\\test-config\\music\\Snail_s House - Hot Milk.mp3")).unwrap();
+        let dir = &TempDir::new().unwrap();
+
+        let now = Instant::now();
+        _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}"));
+        println!("{}ms", now.elapsed().as_millis() );
+
+        sleep(Duration::from_secs(1));
+    }
+}
\ No newline at end of file

From 1734a39db566cb349a9127dcfd38c02adbd775ea Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 24 Mar 2024 21:40:19 -0400
Subject: [PATCH 110/136] removed `image` crate

---
 Cargo.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml
index a3c737b..266e022 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,5 +37,4 @@ uuid = { version = "1.6.1", features = ["v4", "serde"]}
 serde_json = "1.0.111"
 deunicode = "1.4.2"
 opener = { version = "0.7.0", features = ["reveal"]}
-image = "0.25.0"
 tempfile = "3.10.1"

From 50f42e3a265c1e698537861d4ef1422bcd9bec10 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 24 Mar 2024 21:41:43 -0400
Subject: [PATCH 111/136] removed `image` crate

---
 Cargo.toml                   | 1 -
 src/music_storage/library.rs | 2 --
 2 files changed, 3 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index a3c737b..266e022 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -37,5 +37,4 @@ uuid = { version = "1.6.1", features = ["v4", "serde"]}
 serde_json = "1.0.111"
 deunicode = "1.4.2"
 opener = { version = "0.7.0", features = ["reveal"]}
-image = "0.25.0"
 tempfile = "3.10.1"
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 1a198f8..2095821 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -12,7 +12,6 @@ use std::thread::sleep;
 // Files
 use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
-use image::guess_format;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
 use uuid::Uuid;
@@ -20,7 +19,6 @@ use std::fs::{self, File};
 use tempfile::TempDir;
 use std::path::{Path, PathBuf};
 use walkdir::WalkDir;
-use image::ImageFormat::*;
 
 // Time
 use chrono::{serde::ts_milliseconds_option, DateTime, Utc};

From 7da0b1a1dbff0af4e5ae1c6d02c1b1a82cc0ae8f Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sat, 30 Mar 2024 00:55:56 -0400
Subject: [PATCH 112/136] updated dependencies and made small changes to the
 library

---
 Cargo.toml                   |  7 +++----
 src/music_storage/library.rs | 13 ++++++++-----
 2 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 266e022..2d106a4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -32,9 +32,8 @@ leb128 = "0.2.5"
 urlencoding = "2.1.3"
 m3u8-rs = "5.0.5"
 thiserror = "1.0.56"
-font = "0.27.0"
-uuid = { version = "1.6.1", features = ["v4", "serde"]}
+uuid = { version = "1.6.1", features = ["v4", "serde"] }
 serde_json = "1.0.111"
 deunicode = "1.4.2"
-opener = { version = "0.7.0", features = ["reveal"]}
-tempfile = "3.10.1"
+opener = { version = "0.7.0", features = ["reveal"] }
+tempfile = "3.10.1"
\ No newline at end of file
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 2095821..1afc991 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -7,7 +7,7 @@ use std::collections::BTreeMap;
 use std::error::Error;
 use std::io::Write;
 use std::ops::ControlFlow::{Break, Continue};
-use std::thread::sleep;
+
 
 // Files
 use file_format::{FileFormat, Kind};
@@ -426,6 +426,8 @@ impl Song {
      Ok(tracks)
     }
 
+    /// Takes the AlbumArt[index] and opens it in the native file viewer
+
     pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box<dyn Error>> {
         use opener::open;
         use urlencoding::decode;
@@ -446,14 +448,14 @@ impl Song {
                 let blank_tag = &lofty::Tag::new(TagType::Id3v2);
                 let tagged_file: lofty::TaggedFile;
 
-                #[cfg(windows)]
+                #[cfg(target_family = "windows")]
                 let uri = urlencoding::decode(
                     match self.location.as_uri().strip_prefix("file:///") {
                         Some(str) => str,
                         None => return Err("invalid path.. again?".into())
                 })?.into_owned();
 
-                #[cfg(unix)]
+                #[cfg(target_family = "unix")]
                 let uri = urlencoding::decode(
                     match self.location.as_uri().strip_prefix("file://") {
                         Some(str) => str,
@@ -1149,13 +1151,14 @@ mod test {
 
     #[test]
     fn get_art_test() {
-        let s = Song::from_file(Path::new(".\\test-config\\music\\Snail_s House - Hot Milk.mp3")).unwrap();
+        let s = Song::from_file(Path::new("")).unwrap();
         let dir = &TempDir::new().unwrap();
 
         let now = Instant::now();
         _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}"));
+        _ = s.open_album_art(1, dir).inspect_err(|e| println!("{e:?}"));
         println!("{}ms", now.elapsed().as_millis() );
 
-        sleep(Duration::from_secs(1));
+        sleep(Duration::from_secs(20));
     }
 }
\ No newline at end of file

From f02f5bca41c75dd022e25c11b3d60c477ad66c3f Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Wed, 3 Apr 2024 01:00:52 -0400
Subject: [PATCH 113/136] first draft of library Rework

---
 Cargo.toml                                   |   3 +-
 src/music_controller/controller.rs           |   2 +-
 src/music_controller/queue.rs                |  11 +-
 src/music_storage/db_reader/foobar/reader.rs |   6 +-
 src/music_storage/db_reader/itunes/reader.rs |  17 ++-
 src/music_storage/library.rs                 | 127 ++++++++++++++-----
 src/music_storage/playlist.rs                |  40 +++++-
 7 files changed, 149 insertions(+), 57 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 2d106a4..3c7d698 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,4 +36,5 @@ uuid = { version = "1.6.1", features = ["v4", "serde"] }
 serde_json = "1.0.111"
 deunicode = "1.4.2"
 opener = { version = "0.7.0", features = ["reveal"] }
-tempfile = "3.10.1"
\ No newline at end of file
+tempfile = "3.10.1"
+nestify = "0.3.3"
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index ebcc27f..ca14d12 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -314,7 +314,7 @@ mod tests {
         a.q_set_volume(i, 0.04);
         // a.new_queue();
         let songs = a.lib_get_songs();
-        a.q_enqueue(i, songs[2].location.clone());
+        a.q_enqueue(i, songs[2].primary_uri().unwrap().0.clone());
         // a.enqueue(1, songs[2].location.clone());
         a.q_play(i).unwrap();
         // a.play(1).unwrap();
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 6180e9d..78bbe7c 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -57,14 +57,14 @@ impl QueueItemType<'_> {
         match self {
             Song(uuid) => {
                 if let Some((song, _))  = lib.query_uuid(uuid) {
-                    Some(song.location.clone())
+                    Some(song.primary_uri().unwrap().0.clone()) // TODO: error handle these better
                 }else {
                     Option::None
                 }
             },
             Album{album, shuffled, current: (disc, index), ..} => {
                 if !shuffled {
-                    Some(album.track(*disc as usize, *index as usize).unwrap().location.clone())
+                    Some(album.track(*disc as usize, *index as usize).unwrap().primary_uri().unwrap().0.clone())
                 }else {
                     todo!()
                 }
@@ -302,23 +302,22 @@ impl<'a> Queue<'a> {
         let item = self.items[0].clone();
         let uri: URI = match &self.items[1].item {
             QueueItemType::Song(uuid) => {
-                // TODO:  Refactor later for  multiple URIs
                 match &lib.read().unwrap().query_uuid(uuid) {
-                    Some(song) => song.0.location.clone(),
+                    Some(song) => song.0.primary_uri()?.0.clone(),
                     None => return Err("Uuid does not exist!".into()),
                 }
             },
             QueueItemType::Album { album, current, ..} => {
                 let (disc, track) = (current.0 as usize, current.1 as usize);
                 match album.track(disc, track) {
-                    Some(track) => track.location.clone(),
+                    Some(track) => track.primary_uri()?.0.clone(),
                     None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into())
                 }
             },
             QueueItemType::Playlist { current, .. } => {
                 // TODO:  Refactor later for  multiple URIs
                 match &lib.read().unwrap().query_uuid(current) {
-                    Some(song) => song.0.location.clone(),
+                    Some(song) => song.0.primary_uri()?.0.clone(),
                     None => return Err("Uuid does not exist!".into()),
                 }
             },
diff --git a/src/music_storage/db_reader/foobar/reader.rs b/src/music_storage/db_reader/foobar/reader.rs
index 815f9a7..4d07e71 100644
--- a/src/music_storage/db_reader/foobar/reader.rs
+++ b/src/music_storage/db_reader/foobar/reader.rs
@@ -176,14 +176,15 @@ pub struct FoobarPlaylistTrack {
 impl FoobarPlaylistTrack {
     fn find_song(&self) -> Song {
         let location = URI::Local(self.file_name.clone().into());
+        let internal_tags = Vec::new();
 
         Song {
-            location,
+            location: vec![location],
             uuid: Uuid::new_v4(),
             plays: 0,
             skips: 0,
             favorited: false,
-            // banned: None,
+            banned: None,
             rating: None,
             format: None,
             duration: self.duration,
@@ -193,6 +194,7 @@ impl FoobarPlaylistTrack {
             date_modified: None,
             album_art: Vec::new(),
             tags: BTreeMap::new(),
+            internal_tags,
         }
     }
 }
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index 90a48f2..f6032db 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -13,7 +13,7 @@ use std::vec::Vec;
 use chrono::prelude::*;
 
 use crate::music_storage::db_reader::extern_library::ExternalLibrary;
-use crate::music_storage::library::{AlbumArt, Service, Song, Tag, URI};
+use crate::music_storage::library::{AlbumArt, BannedType, Service, Song, Tag, URI};
 use crate::music_storage::utils;
 
 use urlencoding::decode;
@@ -166,17 +166,19 @@ impl ExternalLibrary for ITunesLibrary {
             };
             let play_time_ = StdDur::from_secs(track.plays as u64 * dur.as_secs());
 
+            let internal_tags = Vec::new(); // TODO: handle internal tags generation
+
             let ny: Song = Song {
-                location,
+                location: vec![location],
                 uuid: Uuid::new_v4(),
                 plays: track.plays,
                 skips: 0,
                 favorited: track.favorited,
-                // banned: if track.banned {
-                //     Some(BannedType::All)
-                // }else {
-                //     None
-                // },
+                banned: if track.banned {
+                        Some(BannedType::All)
+                    }else {
+                        None
+                    },
                 rating: track.rating,
                 format: match FileFormat::from_file(PathBuf::from(&loc)) {
                     Ok(e) => Some(e),
@@ -192,6 +194,7 @@ impl ExternalLibrary for ITunesLibrary {
                     Err(_) => Vec::new(),
                 },
                 tags: tags_,
+                internal_tags,
             };
             // dbg!(&ny.tags);
             bun.push(ny);
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 1afc991..ab2c231 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -1,3 +1,4 @@
+use super::playlist::PlaylistFolder;
 // Crate things
 use super::utils::{find_images, normalize, read_file, write_file};
 use crate::config::config::Config;
@@ -117,35 +118,58 @@ impl ToString for Field {
     }
 }
 
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+#[non_exhaustive]
+pub enum InternalTag {
+    DoNotTrack(DoNotTrack),
+    SongType(SongType),
+    SongLink(Uuid, SongType),
+    // Volume Adjustment from -100% to 100%
+    VolumeAdjustment(i8),
+}
+
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
+#[non_exhaustive]
 pub enum BannedType {
     Shuffle,
     All,
 }
 
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+#[non_exhaustive]
 pub enum DoNotTrack {
     // TODO: add services to not track
+    LastFM,
+    LibreFM,
+    MusicBrainz,
+    Discord,
 }
 
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
-enum SongType {
-    // TODO: add song types
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+#[non_exhaustive]
+pub enum SongType {
+    // TODO: add MORE?! song types
     Main,
     Instrumental,
     Remix,
     Custom(String)
 }
 
+impl Default for SongType {
+    fn default() -> Self {
+        SongType::Main
+    }
+}
+
 /// Stores information about a single song
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
 pub struct Song {
-    pub location: URI,
+    pub location: Vec<URI>,
     pub uuid: Uuid,
     pub plays: i32,
     pub skips: i32,
     pub favorited: bool,
-    // pub banned: Option<BannedType>,
+    pub banned: Option<BannedType>,
     pub rating: Option<u8>,
     pub format: Option<FileFormat>,
     pub duration: Duration,
@@ -158,6 +182,7 @@ pub struct Song {
     pub date_modified: Option<DateTime<Utc>>,
     pub album_art: Vec<AlbumArt>,
     pub tags: BTreeMap<Tag, String>,
+    pub internal_tags: Vec<InternalTag>
 }
 
 
@@ -180,7 +205,7 @@ impl Song {
     pub fn get_field(&self, target_field: &str) -> Option<Field> {
         let lower_target = target_field.to_lowercase();
         match lower_target.as_str() {
-            "location" => Some(Field::Location(self.location.clone())),
+            "location" => Some(Field::Location(self.primary_uri().unwrap().0.clone())), //TODO: make this not unwrap()
             "plays" => Some(Field::Plays(self.plays)),
             "skips" => Some(Field::Skips(self.skips)),
             "favorited" => Some(Field::Favorited(self.favorited)),
@@ -279,13 +304,15 @@ impl Song {
         // TODO: Fix error handling
         let binding = fs::canonicalize(target_file).unwrap();
 
+        // TODO: Handle creation of internal tag: Song Type and Song Links
+        let internal_tags = { Vec::new() };
         let new_song = Song {
-            location: URI::Local(binding),
+            location: vec![URI::Local(binding)],
             uuid: Uuid::new_v4(),
             plays: 0,
             skips: 0,
             favorited: false,
-            // banned: None,
+            banned: None,
             rating: None,
             format,
             duration,
@@ -295,6 +322,7 @@ impl Song {
             date_modified: Some(chrono::offset::Utc::now()),
             tags,
             album_art,
+            internal_tags,
         };
         Ok(new_song)
     }
@@ -399,17 +427,17 @@ impl Song {
                 let album_art = find_images(&audio_location.to_path_buf()).unwrap();
 
                 let new_song = Song {
-                    location: URI::Cue {
+                    location: vec![URI::Cue {
                         location: audio_location.clone(),
                         index: i,
                         start,
                         end,
-                    },
+                    }],
                     uuid: Uuid::new_v4(),
                     plays: 0,
                     skips: 0,
                     favorited: false,
-                    // banned: None,
+                    banned: None,
                     rating: None,
                     format,
                     duration,
@@ -419,6 +447,7 @@ impl Song {
                     date_modified: Some(chrono::offset::Utc::now()),
                     tags,
                     album_art,
+                    internal_tags: Vec::new()
                 };
                 tracks.push((new_song, audio_location.clone()));
             }
@@ -448,16 +477,17 @@ impl Song {
                 let blank_tag = &lofty::Tag::new(TagType::Id3v2);
                 let tagged_file: lofty::TaggedFile;
 
+                // TODO: add support for other URI types... or don't
                 #[cfg(target_family = "windows")]
                 let uri = urlencoding::decode(
-                    match self.location.as_uri().strip_prefix("file:///") {
+                    match self.primary_uri()?.0.as_uri().strip_prefix("file:///") {
                         Some(str) => str,
                         None => return Err("invalid path.. again?".into())
                 })?.into_owned();
 
                 #[cfg(target_family = "unix")]
                 let uri = urlencoding::decode(
-                    match self.location.as_uri().strip_prefix("file://") {
+                    match self.primary_uri()?.as_uri().strip_prefix("file://") {
                         Some(str) => str,
                         None => return Err("invalid path.. again?".into())
                 })?.into_owned();
@@ -492,6 +522,26 @@ impl Song {
         dbg!(open(dbg!(uri))?);
         Ok(())
     }
+
+    /// Returns a reference to the first valid URI in the song, and any invalid URIs that come before it, or errors if there are no valid URIs
+    #[allow(clippy::type_complexity)]
+    pub fn primary_uri(&self) -> Result<(&URI, Option<Vec<&URI>>), Box<dyn Error>> {
+        let mut invalid_uris = Vec::new();
+        let mut valid_uri = None;
+
+        for uri in &self.location {
+            if uri.exists()? {
+                valid_uri = Some(uri);
+                break;
+            }else {
+                invalid_uris.push(uri);
+            }
+        }
+        match valid_uri {
+            Some(uri) => Ok((uri, if !invalid_uris.is_empty() { Some(invalid_uris) } else { None } )),
+            None => Err("No valid URIs for this song".into())
+        }
+    }
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
@@ -652,14 +702,8 @@ pub struct MusicLibrary {
     pub name: String,
     pub uuid: Uuid,
     pub library: Vec<Song>,
-}
-
-#[test]
-fn library_init() {
-    let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
-    let target_uuid = config.libraries.libraries[0].uuid;
-    let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap();
-    dbg!(a);
+    pub playlists: PlaylistFolder,
+    pub backup_songs: Vec<Song> // maybe move this to the config instead?
 }
 
 impl MusicLibrary {
@@ -669,6 +713,8 @@ impl MusicLibrary {
             name,
             uuid,
             library: Vec::new(),
+            playlists: PlaylistFolder::new(),
+            backup_songs: Vec::new(),
         }
     }
 
@@ -736,8 +782,11 @@ impl MusicLibrary {
             .par_iter()
             .enumerate()
             .try_for_each(|(i, track)| {
-                if path == &track.location {
-                    return std::ops::ControlFlow::Break((track, i));
+                for location in &track.location {
+                    //TODO: check that this works
+                    if path == location {
+                        return std::ops::ControlFlow::Break((track, i));
+                    }
                 }
                 Continue(())
             });
@@ -773,7 +822,7 @@ impl MusicLibrary {
     fn query_path(&self, path: PathBuf) -> Option<Vec<&Song>> {
         let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
         self.library.par_iter().for_each(|track| {
-            if path == track.location.path() {
+            if path == track.primary_uri().unwrap().0.path() { //TODO: make this also not unwrap
                 result.clone().lock().unwrap().push(track);
             }
         });
@@ -885,13 +934,14 @@ impl MusicLibrary {
     }
 
     pub fn add_song(&mut self, new_song: Song) -> Result<(), Box<dyn Error>> {
-        if self.query_uri(&new_song.location).is_some() {
-            return Err(format!("URI already in database: {:?}", new_song.location).into());
+        let location = new_song.primary_uri()?.0;
+        if self.query_uri(location).is_some() {
+            return Err(format!("URI already in database: {:?}", location).into());
         }
 
-        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())
+        match location {
+            URI::Local(_) if self.query_path(location.path()).is_some() => {
+                return Err(format!("Location exists for {:?}", location).into())
             }
             _ => (),
         }
@@ -914,17 +964,18 @@ impl MusicLibrary {
     }
 
     /// Scan the song by a location and update its tags
+    // TODO: change this to work with multiple uris
     pub fn update_uri(
         &mut self,
         target_uri: &URI,
         new_tags: Vec<Tag>,
     ) -> Result<(), Box<dyn std::error::Error>> {
-        let target_song = match self.query_uri(target_uri) {
+        let (target_song, _) = match self.query_uri(target_uri) {
             Some(song) => song,
             None => return Err("URI not in database!".to_string().into()),
         };
 
-        println!("{:?}", target_song.0.location);
+        println!("{:?}", target_song.location);
 
         for tag in new_tags {
             println!("{:?}", tag);
@@ -1142,10 +1193,12 @@ impl MusicLibrary {
 
 #[cfg(test)]
 mod test {
-    use std::{path::Path, thread::sleep, time::{Duration, Instant}};
+    use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}, thread::sleep, time::{Duration, Instant}};
 
     use tempfile::TempDir;
 
+    use crate::{config::config::Config, music_storage::library::MusicLibrary};
+
     use super::Song;
 
 
@@ -1161,4 +1214,12 @@ mod test {
 
         sleep(Duration::from_secs(20));
     }
+
+    #[test]
+    fn library_init() {
+        let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
+        let target_uuid = config.libraries.libraries[0].uuid;
+        let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap();
+        dbg!(a);
+    }
 }
\ No newline at end of file
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 783a293..eb4fef4 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,17 +1,43 @@
-use std::{fs::File, io::{Read, Error}};
+use std::{fs::File, io::{Error, Read}, time::Duration};
 
-use chrono::Duration;
+// use chrono::Duration;
+use serde::{Deserialize, Serialize};
 use uuid::Uuid;
 use super::library::{AlbumArt, Song, Tag};
 
 use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2};
+use nestify::nest;
 
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Deserialize, Serialize)]
 pub enum SortOrder {
     Manual,
     Tag(Tag)
 }
-#[derive(Debug, Clone)]
+
+nest! {
+    #[derive(Debug, Clone, Deserialize, Serialize)]*
+    pub struct PlaylistFolder {
+        name: String,
+        items: Vec<
+            pub enum PlaylistFolderItem {
+                Folder(PlaylistFolder),
+                List(Playlist)
+            }
+        >
+    }
+}
+
+impl PlaylistFolder {
+    pub fn new() -> Self {
+        PlaylistFolder {
+            name: String::new(),
+            items: Vec::new(),
+        }
+    }
+}
+
+
+#[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct Playlist {
     uuid: Uuid,
     title: String,
@@ -28,7 +54,7 @@ impl Playlist {
     pub fn play_count(&self) -> i32 {
         self.play_count
     }
-    pub fn play_time(&self) -> chrono::Duration {
+    pub fn play_time(&self) -> Duration {
         self.play_time
     }
     pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
@@ -71,7 +97,7 @@ impl Playlist {
                 |track| {
 
                     MediaSegment {
-                        uri: track.location.to_string().into(),
+                        uri: track.primary_uri().unwrap().0.to_string().into(), // TODO: error handle this better
                         duration: track.duration.as_millis() as f32,
                         title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.into()),
                         ..Default::default()
@@ -148,7 +174,7 @@ impl Default for Playlist {
             tracks: Vec::default(),
             sort_order: SortOrder::Manual,
             play_count: 0,
-            play_time: Duration::zero(),
+            play_time: Duration::from_secs(0),
         }
     }
 }

From a75081d4fc4f10e08141a1abc59c85cc214a1546 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 5 Apr 2024 02:09:15 -0400
Subject: [PATCH 114/136] added m3u8 to playlist function and fixed playlist to
 m3u8 function

---
 src/config/config.rs          |  64 +++++++-------
 src/music_storage/library.rs  |   1 +
 src/music_storage/playlist.rs | 151 ++++++++++++++++++++++++----------
 3 files changed, 142 insertions(+), 74 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index 76c59cc..5a209ae 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -69,10 +69,10 @@ impl ConfigLibraries {
     }
 
     pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
-        dbg!(&uuid);
+
         for library in &self.libraries {
+            // dbg!(&library.uuid, &uuid);
             if &library.uuid == uuid {
-                dbg!(&library.uuid);
                 return Ok(library.to_owned())
             }
         }
@@ -148,6 +148,13 @@ impl Config {
         let config: Config = serde_json::from_str::<Config>(&bun)?;
         Ok(config)
     }
+
+    pub fn push_library(&mut self, lib: ConfigLibrary) {
+        if self.libraries.libraries.is_empty() {
+            self.libraries.default_library = lib.uuid;
+        }
+        self.libraries.libraries.push(lib);
+    }
 }
 
 #[derive(Error, Debug)]
@@ -165,45 +172,42 @@ pub enum ConfigError {
 }
 
 #[cfg(test)]
-mod tests {
+pub mod tests {
     use std::{path::PathBuf, sync::{Arc, RwLock}};
     use crate::music_storage::library::MusicLibrary;
     use super::{Config, ConfigLibraries, ConfigLibrary};
 
-    #[test]
-    fn config_test() {
-        let lib_a = ConfigLibrary::new(PathBuf::from("test-config/library1"), String::from("library1"), None);
-        let lib_b = ConfigLibrary::new(PathBuf::from("test-config/library2"), String::from("library2"), None);
-        let lib_c = ConfigLibrary::new(PathBuf::from("test-config/library3"), String::from("library3"), None);
-        let config = Config {
+    pub fn new_config_lib() -> (Config, MusicLibrary) {
+        let lib = ConfigLibrary::new(PathBuf::from("test-config/library"), String::from("library"), None);
+        let mut config = Config {
             path: PathBuf::from("test-config/config_test.json"),
-            libraries: ConfigLibraries {
-                libraries: vec![
-                    lib_a.clone(),
-                    lib_b.clone(),
-                    lib_c.clone(),
-                ],
-                ..Default::default()
-            },
             ..Default::default()
         };
-        config.write_file();
-        let arc = Arc::new(RwLock::from(config));
-        MusicLibrary::init(arc.clone(), lib_a.uuid).unwrap();
-        MusicLibrary::init(arc.clone(), lib_b.uuid).unwrap();
-        MusicLibrary::init(arc.clone(), lib_c.uuid).unwrap();
 
-    }
+        config.push_library(lib);
+        config.write_file().unwrap();
 
-    #[test]
-    fn test2() {
-        let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
-        let uuid = config.libraries.get_default().unwrap().uuid;
-        let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
+        let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), dbg!(config.libraries.default_library)).unwrap();
         lib.scan_folder("test-config/music/").unwrap();
         lib.save(config.clone()).unwrap();
-        dbg!(&lib);
-        dbg!(&config);
+
+        (config, lib)
+    }
+
+    pub fn read_config_lib() -> (Config, MusicLibrary) {
+        let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
+
+        // dbg!(&config);
+
+        let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config.libraries.get_default().unwrap().uuid).unwrap();
+
+
+        lib.scan_folder("test-config/music/").unwrap();
+
+        lib.save(config.clone()).unwrap();
+
+
+        (config, lib)
     }
 
     #[test]
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 1afc991..22452a9 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -686,6 +686,7 @@ impl MusicLibrary {
             false => {
                 // If the library does not exist, re-create it
                 let lib = MusicLibrary::new(String::new(), uuid);
+
                 write_file(&lib, path)?;
                 lib
             }
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 783a293..bd40b6a 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,8 +1,9 @@
-use std::{fs::File, io::{Read, Error}};
+use std::{fs::File, io::Read, path:: PathBuf, sync::{Arc, RwLock}};
+use std::error::Error;
 
 use chrono::Duration;
 use uuid::Uuid;
-use super::library::{AlbumArt, Song, Tag};
+use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI};
 
 use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2};
 
@@ -34,16 +35,14 @@ impl Playlist {
     pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
         self.tracks = tracks;
     }
-    pub fn add_track(&mut self, track: Uuid) -> Result<(), Error> {
+    pub fn add_track(&mut self, track: Uuid) {
         self.tracks.push(track);
-        Ok(())
     }
-    pub fn remove_track(&mut self, index: i32) -> Result<(), Error> {
+    pub fn remove_track(&mut self, index: i32) {
         let index = index as usize;
         if (self.tracks.len() - 1) >= index {
             self.tracks.remove(index);
         }
-        Ok(())
     }
     // pub fn get_index(&self, song_name: &str) -> Option<usize> {
     //     let mut index = 0;
@@ -58,26 +57,41 @@ impl Playlist {
     //     }
     //     None
     // }
-    pub fn contains_value(&self, tag: &Tag, value: &str) -> bool {
-        &self.tracks.iter().for_each(|track| {
+    pub fn contains_value(&self, tag: &Tag, value: &String, lib: Arc<RwLock<MusicLibrary>>) -> bool {
+        let lib = lib.read().unwrap();
+        let items = match lib.query_tracks(value, &vec![tag.to_owned()], &vec![tag.to_owned()]) {
+            Some(e) => e,
+            None => return false
+        };
+
+        for item in items {
+            for uuid in &self.tracks {
+                if uuid == &item.uuid {
+                    return true;
+                }
+            }
+        }
 
-        });
         false
     }
-    pub fn to_m3u8(&mut self, tracks: Vec<Song>) {
-        let seg = tracks
-            .iter()
-            .map({
-                |track| {
 
-                    MediaSegment {
-                        uri: track.location.to_string().into(),
-                        duration: track.duration.as_millis() as f32,
-                        title: Some(track.tags.get_key_value(&Tag::Title).unwrap().1.into()),
-                        ..Default::default()
-                    }
+    pub fn to_m3u8(&mut self, lib: Arc<RwLock<MusicLibrary>>, location: &str) -> Result<(), Box<dyn Error>> {
+        let lib = lib.read().unwrap();
+        let seg = self.tracks
+            .iter()
+            .filter_map( |uuid| {
+                    if let Some((track, _)) = lib.query_uuid(uuid) {
+                        if let URI::Local(_) = track.location {
+                            Some(MediaSegment {
+                                uri: track.location.to_string(),
+                                duration: track.duration.as_millis() as f32,
+                                title: track.tags.get_key_value(&Tag::Title).map(|tag| tag.1.into()),
+                                ..Default::default()
+                            })
+                        }else { None }
+                    }else { None }
                 }
-            })
+            )
             .collect::<Vec<MediaSegment>>();
 
         let m3u8 = MediaPlaylist {
@@ -90,19 +104,21 @@ impl Playlist {
             segments: seg.clone(),
             ..Default::default()
         };
-        //TODO: change this to put in a real file path
+
         let mut file = std::fs::OpenOptions::new()
             .read(true)
             .create(true)
+            .truncate(true)
             .write(true)
-            .open("F:\\Dango Music Player\\playlist.m3u8")
-            .unwrap();
-        m3u8.write_to(&mut file).unwrap();
+            .open(location)?;
+        m3u8.write_to(&mut file)?;
+        Ok(())
     }
-    pub fn from_m3u8(path: &str) -> Result<Playlist, Error> {
+
+    pub fn from_m3u8(path: &str, lib: Arc<RwLock<MusicLibrary>>) -> Result<Playlist, Box<dyn Error>> {
         let mut file = match File::open(path) {
             Ok(file) => file,
-            Err(e) => return Err(e),
+            Err(e) => return Err(e.into()),
         };
         let mut bytes = Vec::new();
         file.read_to_end(&mut bytes).unwrap();
@@ -110,18 +126,53 @@ impl Playlist {
         let parsed = m3u8_rs::parse_playlist(&bytes);
 
         let playlist = match parsed {
-            Result::Ok((i, playlist)) => playlist,
+            Result::Ok((_, playlist)) => playlist,
             Result::Err(e) => panic!("Parsing error: \n{}", e),
         };
 
         match playlist {
-            List2::MasterPlaylist(_) => panic!(),
-            List2::MediaPlaylist(pl) => {
-                let values = pl.segments.iter().map(|seg| seg.uri.to_owned() ).collect::<Vec<String>>();
+            List2::MasterPlaylist(_) => Err("This is a Master Playlist!\nPlase input a Media Playlist".into()),
+            List2::MediaPlaylist(playlist_) => {
+                let mut uuids = Vec::new();
+                for seg in playlist_.segments {
+                    let path_ = PathBuf::from(seg.uri.to_owned());
+                    let mut lib = lib.write().unwrap();
+
+                    let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(path_.clone())) {
+                        song.uuid
+                    }else {
+                        let song_ = Song::from_file(&path_)?;
+                        let uuid = song_.uuid.to_owned();
+                        lib.add_song(song_)?;
+                        uuid
+                    };
+                    uuids.push(uuid);
+                }
+                let mut playlist = Playlist::new();
+
+                #[cfg(target_family = "windows")]
+                {
+                    playlist.title = path.split("\\")
+                    .last()
+                    .unwrap_or_default()
+                    .strip_suffix(".m3u8")
+                    .unwrap_or_default()
+                    .to_string();
+                }
+                #[cfg(target_family = "unix")]
+                {
+                    playlist.title = path.split("/")
+                    .last()
+                    .unwrap_or_default()
+                    .strip_suffix(".m3u8")
+                    .unwrap_or_default()
+                    .to_string();
+                }
+
+                playlist.set_tracks(uuids);
+                Ok(playlist)
             }
         }
-
-        todo!()
     }
     fn title(&self) -> &String {
         &self.title
@@ -153,14 +204,26 @@ impl Default for Playlist {
     }
 }
 
-// #[test]
-// fn list_to_m3u8() {
-//     let lib = ITunesLibrary::from_file(Path::new(
-//         "F:\\Music\\Mp3\\Music Main\\iTunes Music Library.xml",
-//     ));
-//     let mut a = Playlist::new();
-//     let c = lib.to_songs();
-//     let mut b = c.iter().map(|song| song.to_owned()).collect::<Vec<Song>>();
-//     a.tracks.append(&mut b);
-//     a.to_m3u8()
-// }
+#[cfg(test)]
+mod test_super {
+    use super::*;
+    use crate::config::config::tests::read_config_lib;
+
+    #[test]
+    fn list_to_m3u8() {
+        let (_, lib) = read_config_lib();
+        let mut playlist = Playlist::new();
+        let tracks = lib.library.iter().map(|track| track.uuid ).collect();
+        playlist.set_tracks(tracks);
+
+        _ = playlist.to_m3u8(Arc::new(RwLock::from(lib)), ".\\test-config\\playlists\\playlist.m3u8");
+    }
+
+    #[test]
+    fn m3u8_to_list() {
+        let (_, lib) = read_config_lib();
+        let arc = Arc::new(RwLock::from(lib));
+        let playlist = Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap();
+        dbg!(playlist);
+    }
+}

From ab529f4c9b82e1a376d93fbccaf86ec5dad0a5d6 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 5 Apr 2024 20:26:13 -0400
Subject: [PATCH 115/136] added `out_queue` function, rudamentary listenbrainz
 scrobbling and other small changes

---
 Cargo.toml                          |   3 +-
 src/config/config.rs                |  15 +++-
 src/music_controller/connections.rs |  93 +++++++++++++++++++-
 src/music_controller/controller.rs  |  47 +++++-----
 src/music_controller/queue.rs       |   8 +-
 src/music_storage/playlist.rs       | 132 +++++++++++++++++++++++-----
 6 files changed, 244 insertions(+), 54 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 2d106a4..49ac4e7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -36,4 +36,5 @@ uuid = { version = "1.6.1", features = ["v4", "serde"] }
 serde_json = "1.0.111"
 deunicode = "1.4.2"
 opener = { version = "0.7.0", features = ["reveal"] }
-tempfile = "3.10.1"
\ No newline at end of file
+tempfile = "3.10.1"
+listenbrainz = "0.7.0"
diff --git a/src/config/config.rs b/src/config/config.rs
index 5a209ae..1c19b15 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -90,11 +90,18 @@ impl ConfigLibraries {
 }
 
 #[derive(Debug, Default, Serialize, Deserialize, Clone)]
+pub struct ConfigConnections {
+    pub listenbrainz_token: Option<String>
+}
+
+#[derive(Debug, Default, Serialize, Deserialize, Clone)]
+#[serde(default)]
 pub struct Config {
     pub path: PathBuf,
     pub backup_folder: Option<PathBuf>,
     pub libraries: ConfigLibraries,
     pub volume: f32,
+    pub connections: ConfigConnections,
 }
 
 impl Config {
@@ -212,10 +219,10 @@ pub mod tests {
 
     #[test]
     fn test3() {
-        let config = Config::read_file(PathBuf::from("test-config/config_test.json")).unwrap();
-        let uuid = config.libraries.get_default().unwrap().uuid;
-        let lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), uuid).unwrap();
+        let (config, lib) = read_config_lib();
 
-        dbg!(lib);
+        _ = config.write_file();
+
+        dbg!(config);
     }
 }
diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs
index ac4abd9..dec723c 100644
--- a/src/music_controller/connections.rs
+++ b/src/music_controller/connections.rs
@@ -1,6 +1,95 @@
-use super::controller::Controller;
+use std::{
+    sync::{Arc, RwLock},
+    error::Error,
+};
+
+use listenbrainz::ListenBrainz;
+use uuid::Uuid;
+
+use crate::{
+    config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag}
+};
+
+use super::controller::DatabaseResponse;
+
 
 
 impl Controller {
-    //more stuff goes here
+    pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> {
+        let config = &self.config.read().unwrap();
+        let mut client = ListenBrainz::new();
+
+        let lbz_token = match &config.connections.listenbrainz_token {
+            Some(token) => token,
+            None => todo!("No ListenBrainz token in config")
+        };
+
+        if !client.is_authenticated() {
+            client.authenticate(lbz_token)?;
+        }
+
+        Ok(client)
+    }
+    pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
+        let config = &self.config.read().unwrap();
+
+        &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
+        let res = &self.db_mail.recv()?;
+        let song = match res {
+                DatabaseResponse::Song(song) => song,
+                _ => todo!()
+        };
+        let unknown = &"unknown".to_string();
+        let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
+        let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
+        let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
+
+        client.listen(artist, track, release)?;
+        Ok(())
+    }
+
+    pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
+        let config = &self.config.read().unwrap();
+
+        &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
+        let res = &self.db_mail.recv()?;
+        let song = match res {
+                DatabaseResponse::Song(song) => song,
+                _ => todo!()
+        };
+        let unknown = &"unknown".to_string();
+        let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
+        let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
+        let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
+
+        client.listen(artist, track, release)?;
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod test_super {
+    use std::{thread::sleep, time::Duration};
+
+    use super::*;
+    use crate::config::config::tests::read_config_lib;
+
+    #[test]
+    fn listenbrainz() {
+        let mut c = Controller::start(".\\test-config\\config_test.json").unwrap();
+
+        let client = c.listenbrainz_authenticate().unwrap();
+
+        c.q_new().unwrap();
+        c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap();
+
+        let songs = c.lib_get_songs();
+
+        c.q_enqueue(0, songs[1].location.to_owned()).unwrap();
+        c.q_play(0).unwrap();
+
+
+        sleep(Duration::from_secs(100));
+        c.lbz_scrobble(client, songs[1].uuid).unwrap();
+    }
 }
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index ebcc27f..d13a62f 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -6,12 +6,14 @@ use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use crossbeam_channel::{Sender, Receiver};
 use crossbeam_channel;
+use listenbrainz::ListenBrainz;
 use std::thread::spawn;
 
 use std::error::Error;
 use crossbeam_channel::unbounded;
 use uuid::Uuid;
 
+use crate::music_controller::queue::{QueueItem, QueueItemType};
 use crate::music_storage::library::{Tag, URI};
 use crate::{
     music_storage::library::{MusicLibrary, Song},
@@ -21,27 +23,27 @@ use crate::{
 
 pub struct Controller {
     // queues: Vec<Queue>,
-    config: Arc<RwLock<Config>>,
+    pub config: Arc<RwLock<Config>>,
     // library: MusicLibrary,
-    controller_mail: MailMan<ControllerCmd, ControllerResponse>,
-    db_mail: MailMan<DatabaseCmd, DatabaseResponse>,
-    queue_mail: Vec<MailMan<QueueCmd, QueueResponse>>,
+    pub(super) controller_mail: MailMan<ControllerCmd, ControllerResponse>,
+    pub(super) db_mail: MailMan<DatabaseCmd, DatabaseResponse>,
+    pub(super) queue_mail: Vec<MailMan<QueueCmd, QueueResponse>>,
 }
 #[derive(Debug)]
-pub enum ControllerCmd {
+pub(super)  enum ControllerCmd {
     Default,
     Test
 }
 
 #[derive(Debug)]
-enum ControllerResponse {
+pub(super) enum ControllerResponse {
     Empty,
     QueueMailMan(MailMan<QueueCmd, QueueResponse>),
 
 }
 
 #[derive(Debug)]
-pub enum DatabaseCmd {
+pub(super) enum DatabaseCmd {
     Default,
     Test,
     SaveLibrary,
@@ -49,11 +51,10 @@ pub enum DatabaseCmd {
     QueryUuid(Uuid),
     QueryUuids(Vec<Uuid>),
     ReadFolder(String),
-
 }
 
 #[derive(Debug)]
-enum DatabaseResponse {
+pub(super) enum DatabaseResponse {
     Empty,
     Song(Song),
     Songs(Vec<Song>),
@@ -61,7 +62,7 @@ enum DatabaseResponse {
 }
 
 #[derive(Debug)]
-enum QueueCmd {
+pub(super) enum QueueCmd {
     Default,
     Test,
     Play,
@@ -73,25 +74,26 @@ enum QueueCmd {
 }
 
 #[derive(Debug)]
-enum QueueResponse {
+pub(super) enum QueueResponse {
     Default,
     Test,
     Index(i32),
+    Uuid(Uuid),
 }
 
 #[derive(Debug)]
-struct MailMan<T, U> {
+pub(super) struct MailMan<T: Send, U: Send> {
     pub tx: Sender<T>,
     rx: Receiver<U>
 }
 
-impl<T> MailMan<T, T> {
+impl<T: Send> MailMan<T, T> {
     pub fn new() -> Self {
         let (tx, rx) = unbounded::<T>();
         MailMan { tx, rx }
     }
 }
-impl<T, U> MailMan<T, U> {
+impl<T: Send, U: Send> MailMan<T, U> {
     pub fn double() -> (MailMan<T, U>, MailMan<U, T>) {
         let (tx, rx) = unbounded::<T>();
         let (tx1, rx1) = unbounded::<U>();
@@ -108,14 +110,16 @@ impl<T, U> MailMan<T, U> {
     }
 
     pub fn recv(&self) -> Result<U, Box<dyn Error>> {
-        let u = self.rx.recv().unwrap();
+        let u = self.rx.recv()?;
         Ok(u)
     }
 }
 
 #[allow(unused_variables)]
 impl Controller {
-    pub fn start(config_path: String) -> Result<Self, Box<dyn Error>> {
+    pub fn start<P>(config_path: P) -> Result<Self, Box<dyn Error>>
+    where std::path::PathBuf: std::convert::From<P>
+    {
         let config_path = PathBuf::from(config_path);
         let config = Config::read_file(config_path)?;
         let uuid = config.libraries.get_default()?.uuid;
@@ -185,7 +189,6 @@ impl Controller {
         });
 
 
-
         Ok(
             Controller {
                 // queues: Vec::new(),
@@ -197,7 +200,7 @@ impl Controller {
         )
     }
 
-    fn lib_get_songs(&self) -> Vec<Song> {
+    pub fn lib_get_songs(&self) -> Vec<Song> {
         self.db_mail.send(DatabaseCmd::GetSongs);
         match self.db_mail.recv().unwrap() {
             DatabaseResponse::Songs(songs) => songs,
@@ -205,7 +208,7 @@ impl Controller {
         }
     }
 
-    fn lib_scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
+    pub fn lib_scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
         let mail = &self.db_mail;
         mail.send(DatabaseCmd::ReadFolder(folder))?;
         dbg!(mail.recv()?);
@@ -259,14 +262,14 @@ impl Controller {
         Ok(self.queue_mail.len() - 1)
     }
 
-    fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> {
+    pub fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
         mail.send(QueueCmd::Play)?;
         dbg!(mail.recv()?);
         Ok(())
     }
 
-    fn q_pause(&self, index: usize) -> Result<(), Box<dyn Error>> {
+    pub fn q_pause(&self, index: usize) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
         mail.send(QueueCmd::Pause)?;
         dbg!(mail.recv()?);
@@ -286,7 +289,7 @@ impl Controller {
     //     Ok(())
     // }
 
-    fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
+    pub fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
         let mail = &self.queue_mail[index];
         mail.send(QueueCmd::Enqueue(uri))?;
         // dbg!(mail.recv()?);
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 6180e9d..483010f 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -89,10 +89,10 @@ pub enum PlayerLocation {
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
 pub struct QueueItem<'a> {
-    item: QueueItemType<'a>,
-    state: QueueState,
-    source: PlayerLocation,
-    by_human: bool
+    pub(super) item: QueueItemType<'a>,
+    pub(super) state: QueueState,
+    pub(super) source: PlayerLocation,
+    pub(super) by_human: bool
 }
 impl QueueItem<'_> {
     fn new() -> Self {
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index bd40b6a..63876fb 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,18 +1,21 @@
 use std::{fs::File, io::Read, path:: PathBuf, sync::{Arc, RwLock}};
 use std::error::Error;
 
-use chrono::Duration;
+use std::time::Duration;
+use serde::{Deserialize, Serialize};
 use uuid::Uuid;
 use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI};
 
 use m3u8_rs::{MediaPlaylist, MediaPlaylistType, MediaSegment, Playlist as List2};
 
-#[derive(Debug, Clone)]
+use rayon::prelude::*;
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub enum SortOrder {
     Manual,
-    Tag(Tag)
+    Tag(Vec<Tag>)
 }
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct Playlist {
     uuid: Uuid,
     title: String,
@@ -29,9 +32,24 @@ impl Playlist {
     pub fn play_count(&self) -> i32 {
         self.play_count
     }
-    pub fn play_time(&self) -> chrono::Duration {
+    pub fn play_time(&self) -> Duration {
         self.play_time
     }
+
+    fn title(&self) -> &String {
+        &self.title
+    }
+
+    fn cover(&self) -> Option<&AlbumArt> {
+        match &self.cover {
+            Some(e) => Some(e),
+            None => None,
+        }
+    }
+
+    fn tracks(&self) -> Vec<Uuid> {
+        self.tracks.to_owned()
+    }
     pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
         self.tracks = tracks;
     }
@@ -75,6 +93,15 @@ impl Playlist {
         false
     }
 
+    pub fn to_file(&self, path: &str) -> Result<(), Box<dyn Error>> {
+        super::utils::write_file(self, PathBuf::from(path))?;
+        Ok(())
+    }
+
+    pub fn from_file(path: &str) -> Result<Playlist, Box<dyn Error>> {
+        super::utils::read_file(PathBuf::from(path))
+    }
+
     pub fn to_m3u8(&mut self, lib: Arc<RwLock<MusicLibrary>>, location: &str) -> Result<(), Box<dyn Error>> {
         let lib = lib.read().unwrap();
         let seg = self.tracks
@@ -174,22 +201,72 @@ impl Playlist {
             }
         }
     }
-    fn title(&self) -> &String {
-        &self.title
-    }
-    fn cover(&self) -> Option<&AlbumArt> {
-        match &self.cover {
-            Some(e) => Some(e),
-            None => None,
+
+
+    pub fn out_tracks(&self, lib: Arc<RwLock<MusicLibrary>>) -> (Vec<Song>, Vec<&Uuid>) {
+        let lib = lib.read().unwrap();
+        let mut songs = vec![];
+        let mut invalid_uuids = vec![];
+
+        for uuid in &self.tracks {
+            if let Some((track, _)) = lib.query_uuid(uuid) {
+                songs.push(track.to_owned());
+            }else {
+                invalid_uuids.push(uuid);
+            }
         }
-    }
-    fn tracks(&self) -> Vec<Uuid> {
-        self.tracks.to_owned()
+
+        if let SortOrder::Tag(sort_by) = &self.sort_order {
+            println!("sorting by: {:?}", sort_by);
+
+            songs.par_sort_by(|a, b| {
+                for (i, sort_option) in sort_by.iter().enumerate() {
+                    dbg!(&i);
+                    let tag_a = match sort_option {
+                        Tag::Field(field_selection) => match a.get_field(field_selection.as_str()) {
+                            Some(field_value) => field_value.to_string(),
+                            None => continue,
+                        },
+                        _ => match a.get_tag(sort_option) {
+                            Some(tag_value) => tag_value.to_owned(),
+                            None => continue,
+                        },
+                    };
+
+                    let tag_b = match sort_option {
+                        Tag::Field(field_selection) => match b.get_field(field_selection) {
+                            Some(field_value) => field_value.to_string(),
+                            None => continue,
+                        },
+                        _ => match b.get_tag(sort_option) {
+                            Some(tag_value) => tag_value.to_owned(),
+                            None => continue,
+                        },
+                    };
+                    dbg!(&i);
+
+                    if let (Ok(num_a), Ok(num_b)) = (tag_a.parse::<i32>(), tag_b.parse::<i32>()) {
+                        // If parsing succeeds, compare as numbers
+                        return dbg!(num_a.cmp(&num_b));
+                    } else {
+                        // If parsing fails, compare as strings
+                        return dbg!(tag_a.cmp(&tag_b));
+                    }
+                }
+
+                // If all tags are equal, sort by Track number
+                let path_a = PathBuf::from(a.get_field("location").unwrap().to_string());
+                let path_b = PathBuf::from(b.get_field("location").unwrap().to_string());
+
+                path_a.file_name().cmp(&path_b.file_name())
+            })
+        }
+
+        (songs, invalid_uuids)
     }
 }
 
 
-
 impl Default for Playlist {
     fn default() -> Self {
         Playlist {
@@ -199,7 +276,7 @@ impl Default for Playlist {
             tracks: Vec::default(),
             sort_order: SortOrder::Manual,
             play_count: 0,
-            play_time: Duration::zero(),
+            play_time: Duration::from_millis(0),
         }
     }
 }
@@ -207,7 +284,7 @@ impl Default for Playlist {
 #[cfg(test)]
 mod test_super {
     use super::*;
-    use crate::config::config::tests::read_config_lib;
+    use crate::{config::config::tests::read_config_lib, music_storage::playlist};
 
     #[test]
     fn list_to_m3u8() {
@@ -219,11 +296,24 @@ mod test_super {
         _ = playlist.to_m3u8(Arc::new(RwLock::from(lib)), ".\\test-config\\playlists\\playlist.m3u8");
     }
 
-    #[test]
-    fn m3u8_to_list() {
+
+    fn m3u8_to_list() -> Playlist {
         let (_, lib) = read_config_lib();
         let arc = Arc::new(RwLock::from(lib));
         let playlist = Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap();
-        dbg!(playlist);
+
+        playlist.to_file(".\\test-config\\playlists\\playlist");
+        dbg!(playlist)
+    }
+
+    #[test]
+    fn out_queue_sort() {
+        let (_, lib) = read_config_lib();
+        let mut list = m3u8_to_list();
+        list.sort_order = SortOrder::Tag(vec![Tag::Album]);
+
+        let songs = &list.out_tracks(Arc::new(RwLock::from(lib)));
+
+        dbg!(songs);
     }
 }

From 94e6c2521901baac363a2776f8055c3e3bc562d4 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Tue, 7 May 2024 18:28:03 -0400
Subject: [PATCH 116/136] made a few minor changes

---
 Cargo.toml                          | 1 +
 src/music_controller/connections.rs | 8 ++++++++
 src/music_controller/queue.rs       | 2 +-
 src/music_storage/utils.rs          | 6 +++---
 4 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 49ac4e7..e3f236f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,3 +38,4 @@ deunicode = "1.4.2"
 opener = { version = "0.7.0", features = ["reveal"] }
 tempfile = "3.10.1"
 listenbrainz = "0.7.0"
+discord-rpc-client = "0.4.0"
diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs
index dec723c..bf5e688 100644
--- a/src/music_controller/connections.rs
+++ b/src/music_controller/connections.rs
@@ -3,6 +3,7 @@ use std::{
     error::Error,
 };
 
+use discord_rpc_client::Client;
 use listenbrainz::ListenBrainz;
 use uuid::Uuid;
 
@@ -65,6 +66,13 @@ impl Controller {
         client.listen(artist, track, release)?;
         Ok(())
     }
+
+    pub fn discord_song_change(client: &mut Client,song: Song) {
+        client.set_activity(|a| {
+            a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap()))
+            .into()
+        });
+    }
 }
 
 #[cfg(test)]
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 483010f..0559696 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -66,7 +66,7 @@ impl QueueItemType<'_> {
                 if !shuffled {
                     Some(album.track(*disc as usize, *index as usize).unwrap().location.clone())
                 }else {
-                    todo!()
+                    todo!() //what to do for non shuffled album
                 }
             },
             ExternalSong(uri) => { Some(uri.clone()) },
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index 1f68a8c..2f04d4d 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -25,12 +25,12 @@ pub(super) fn normalize(input_string: &str) -> String {
 
 /// Write any data structure which implements [serde::Serialize]
 /// out to a [bincode] encoded file compressed using [snap]
-pub(super) fn write_file<T: serde::Serialize>(
+pub(super) fn write_file<T: serde::Serialize, U: std::convert::AsRef<Path>+std::convert::AsRef<std::ffi::OsStr>+Clone>(
     library: T,
-    path: PathBuf,
+    path: U,
 ) -> Result<(), Box<dyn Error>> {
     // Create a temporary name for writing out
-    let mut writer_name = path.clone();
+    let mut writer_name = PathBuf::from(&path);
     writer_name.set_extension("tmp");
 
     // Create a new BufWriter on the file and a snap frame encoder

From 56040bfd28fcff01cfdb8a1f4d0a076d5af22da6 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 19 May 2024 18:48:29 -0400
Subject: [PATCH 117/136] Updated Controller and added Player Trait

---
 src/lib.rs                                    |   7 +-
 src/music_controller/connections.rs           | 158 +++++-----
 src/music_controller/controller.rs            | 281 +++---------------
 src/music_controller/queue.rs                 | 224 ++------------
 .../gstreamer.rs}                             |  44 +--
 src/music_player/kira.rs                      |   0
 src/music_player/player.rs                    |  57 ++++
 7 files changed, 226 insertions(+), 545 deletions(-)
 rename src/{music_player.rs => music_player/gstreamer.rs} (94%)
 create mode 100644 src/music_player/kira.rs
 create mode 100644 src/music_player/player.rs

diff --git a/src/lib.rs b/src/lib.rs
index 387a488..9f6bb0a 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,13 +8,16 @@ pub mod music_storage {
     pub mod db_reader;
 }
 
-pub mod music_controller{
+pub mod music_controller {
     pub mod controller;
     pub mod connections;
     pub mod queue;
 }
 
-pub mod music_player;
+pub mod music_player {
+    pub mod gstreamer;
+    pub mod player;
+}
 #[allow(clippy::module_inception)]
 pub mod config {
     pub mod config;
diff --git a/src/music_controller/connections.rs b/src/music_controller/connections.rs
index bf5e688..b2ceb85 100644
--- a/src/music_controller/connections.rs
+++ b/src/music_controller/connections.rs
@@ -1,103 +1,103 @@
-use std::{
-    sync::{Arc, RwLock},
-    error::Error,
-};
+// use std::{
+//     sync::{Arc, RwLock},
+//     error::Error,
+// };
 
-use discord_rpc_client::Client;
-use listenbrainz::ListenBrainz;
-use uuid::Uuid;
+// use discord_rpc_client::Client;
+// use listenbrainz::ListenBrainz;
+// use uuid::Uuid;
 
-use crate::{
-    config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag}
-};
+// use crate::{
+//     config::config::Config, music_controller::controller::{Controller, QueueCmd, QueueResponse}, music_storage::library::{MusicLibrary, Song, Tag}
+// };
 
-use super::controller::DatabaseResponse;
+// use super::controller::DatabaseResponse;
 
 
 
-impl Controller {
-    pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> {
-        let config = &self.config.read().unwrap();
-        let mut client = ListenBrainz::new();
+// impl Controller {
+//     pub fn listenbrainz_authenticate(&mut self) -> Result<ListenBrainz, Box<dyn Error>> {
+//         let config = &self.config.read().unwrap();
+//         let mut client = ListenBrainz::new();
 
-        let lbz_token = match &config.connections.listenbrainz_token {
-            Some(token) => token,
-            None => todo!("No ListenBrainz token in config")
-        };
+//         let lbz_token = match &config.connections.listenbrainz_token {
+//             Some(token) => token,
+//             None => todo!("No ListenBrainz token in config")
+//         };
 
-        if !client.is_authenticated() {
-            client.authenticate(lbz_token)?;
-        }
+//         if !client.is_authenticated() {
+//             client.authenticate(lbz_token)?;
+//         }
 
-        Ok(client)
-    }
-    pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
-        let config = &self.config.read().unwrap();
+//         Ok(client)
+//     }
+//     pub fn lbz_scrobble(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
+//         let config = &self.config.read().unwrap();
 
-        &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
-        let res = &self.db_mail.recv()?;
-        let song = match res {
-                DatabaseResponse::Song(song) => song,
-                _ => todo!()
-        };
-        let unknown = &"unknown".to_string();
-        let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
-        let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
-        let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
+//         &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
+//         let res = &self.db_mail.recv()?;
+//         let song = match res {
+//                 DatabaseResponse::Song(song) => song,
+//                 _ => todo!()
+//         };
+//         let unknown = &"unknown".to_string();
+//         let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
+//         let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
+//         let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
 
-        client.listen(artist, track, release)?;
-        Ok(())
-    }
+//         client.listen(artist, track, release)?;
+//         Ok(())
+//     }
 
-    pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
-        let config = &self.config.read().unwrap();
+//     pub fn lbz_now_playing(&self, client: ListenBrainz, uuid: Uuid) -> Result<(), Box<dyn Error>> {
+//         let config = &self.config.read().unwrap();
 
-        &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
-        let res = &self.db_mail.recv()?;
-        let song = match res {
-                DatabaseResponse::Song(song) => song,
-                _ => todo!()
-        };
-        let unknown = &"unknown".to_string();
-        let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
-        let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
-        let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
+//         &self.db_mail.send(super::controller::DatabaseCmd::QueryUuid(uuid));
+//         let res = &self.db_mail.recv()?;
+//         let song = match res {
+//                 DatabaseResponse::Song(song) => song,
+//                 _ => todo!()
+//         };
+//         let unknown = &"unknown".to_string();
+//         let artist = song.get_tag(&Tag::Artist).unwrap_or(unknown);
+//         let track = song.get_tag(&Tag::Title).unwrap_or(unknown);
+//         let release = song.get_tag(&Tag::Album).map(|rel| rel.as_str());
 
-        client.listen(artist, track, release)?;
-        Ok(())
-    }
+//         client.listen(artist, track, release)?;
+//         Ok(())
+//     }
 
-    pub fn discord_song_change(client: &mut Client,song: Song) {
-        client.set_activity(|a| {
-            a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap()))
-            .into()
-        });
-    }
-}
+//     pub fn discord_song_change(client: &mut Client,song: Song) {
+//         client.set_activity(|a| {
+//             a.state(format!("Listening to {}", song.get_tag(&Tag::Title).unwrap()))
+//             .into()
+//         });
+//     }
+// }
 
-#[cfg(test)]
-mod test_super {
-    use std::{thread::sleep, time::Duration};
+// #[cfg(test)]
+// mod test_super {
+//     use std::{thread::sleep, time::Duration};
 
-    use super::*;
-    use crate::config::config::tests::read_config_lib;
+//     use super::*;
+//     use crate::config::config::tests::read_config_lib;
 
-    #[test]
-    fn listenbrainz() {
-        let mut c = Controller::start(".\\test-config\\config_test.json").unwrap();
+//     #[test]
+//     fn listenbrainz() {
+//         let mut c = Controller::start(".\\test-config\\config_test.json").unwrap();
 
-        let client = c.listenbrainz_authenticate().unwrap();
+//         let client = c.listenbrainz_authenticate().unwrap();
 
-        c.q_new().unwrap();
-        c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap();
+//         c.q_new().unwrap();
+//         c.queue_mail[0].send(QueueCmd::SetVolume(0.04)).unwrap();
 
-        let songs = c.lib_get_songs();
+//         let songs = c.lib_get_songs();
 
-        c.q_enqueue(0, songs[1].location.to_owned()).unwrap();
-        c.q_play(0).unwrap();
+//         c.q_enqueue(0, songs[1].location.to_owned()).unwrap();
+//         c.q_play(0).unwrap();
 
 
-        sleep(Duration::from_secs(100));
-        c.lbz_scrobble(client, songs[1].uuid).unwrap();
-    }
-}
+//         sleep(Duration::from_secs(100));
+//         c.lbz_scrobble(client, songs[1].uuid).unwrap();
+//     }
+// }
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index d13a62f..8efb630 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -13,7 +13,8 @@ use std::error::Error;
 use crossbeam_channel::unbounded;
 use uuid::Uuid;
 
-use crate::music_controller::queue::{QueueItem, QueueItemType};
+use crate::music_controller::queue::QueueItem;
+use crate::music_player::gstreamer::GStreamer;
 use crate::music_storage::library::{Tag, URI};
 use crate::{
     music_storage::library::{MusicLibrary, Song},
@@ -22,63 +23,10 @@ use crate::{
 };
 
 pub struct Controller {
-    // queues: Vec<Queue>,
+    pub queue: Queue,
     pub config: Arc<RwLock<Config>>,
-    // library: MusicLibrary,
-    pub(super) controller_mail: MailMan<ControllerCmd, ControllerResponse>,
-    pub(super) db_mail: MailMan<DatabaseCmd, DatabaseResponse>,
-    pub(super) queue_mail: Vec<MailMan<QueueCmd, QueueResponse>>,
-}
-#[derive(Debug)]
-pub(super)  enum ControllerCmd {
-    Default,
-    Test
-}
-
-#[derive(Debug)]
-pub(super) enum ControllerResponse {
-    Empty,
-    QueueMailMan(MailMan<QueueCmd, QueueResponse>),
-
-}
-
-#[derive(Debug)]
-pub(super) enum DatabaseCmd {
-    Default,
-    Test,
-    SaveLibrary,
-    GetSongs,
-    QueryUuid(Uuid),
-    QueryUuids(Vec<Uuid>),
-    ReadFolder(String),
-}
-
-#[derive(Debug)]
-pub(super) enum DatabaseResponse {
-    Empty,
-    Song(Song),
-    Songs(Vec<Song>),
-    Library(MusicLibrary),
-}
-
-#[derive(Debug)]
-pub(super) enum QueueCmd {
-    Default,
-    Test,
-    Play,
-    Pause,
-    // SetSongs(Vec<QueueItem<QueueState>>),
-    // SetLocation(URI),
-    Enqueue(URI),
-    SetVolume(f64),
-}
-
-#[derive(Debug)]
-pub(super) enum QueueResponse {
-    Default,
-    Test,
-    Index(i32),
-    Uuid(Uuid),
+    pub library: MusicLibrary,
+    player_mail: MailMan<PlayerCmd, PlayerRes>
 }
 
 #[derive(Debug)]
@@ -115,227 +63,72 @@ impl<T: Send, U: Send> MailMan<T, U> {
     }
 }
 
+enum PlayerCmd {
+    Test(URI)
+}
+
+enum PlayerRes {
+    Test
+}
+
 #[allow(unused_variables)]
 impl Controller {
     pub fn start<P>(config_path: P) -> Result<Self, Box<dyn Error>>
     where std::path::PathBuf: std::convert::From<P>
     {
         let config_path = PathBuf::from(config_path);
+
         let config = Config::read_file(config_path)?;
         let uuid = config.libraries.get_default()?.uuid;
 
         let config_ = Arc::new(RwLock::from(config));
-        let mut lib = MusicLibrary::init(config_.clone(), uuid)?;
+        let library = MusicLibrary::init(config_.clone(), uuid)?;
 
-        let config = config_.clone();
-        let (out_thread_controller, in_thread) = MailMan::double();
-        let monitor_thread = spawn(move || {
-            use ControllerCmd::*;
-            loop {
-                let command = in_thread.recv().unwrap();
+        let (player_mail, in_thread) = MailMan::<PlayerCmd, PlayerRes>::double();
 
-                match command {
-                    Default => (),
-                    Test => {
-                        in_thread.send(ControllerResponse::Empty).unwrap();
-                    },
-                }
-            }
-        });
+        spawn(move || {
+            let mut player = GStreamer::new().unwrap();
 
-        let config = config_.clone();
-        let (out_thread_db, in_thread) = MailMan::double();
-        let db_monitor = spawn(move || {
-            use DatabaseCmd::*;
-            loop {
-                let command = in_thread.recv().unwrap();
-
-                match command {
-                    Default => {},
-                    Test => {
-                        in_thread.send(DatabaseResponse::Empty).unwrap();
-                    },
-                    GetSongs => {
-                        let songs = lib.query_tracks(&String::from(""), &(vec![Tag::Title]), &(vec![Tag::Title])).unwrap().iter().cloned().cloned().collect();
-                        in_thread.send(DatabaseResponse::Songs(songs)).unwrap();
-                    },
-                    SaveLibrary => {
-                        //TODO: make this send lib ref to the function to save instead
-                        lib.save(config.read().unwrap().to_owned()).unwrap();
-                    },
-                    QueryUuid(uuid) => {
-                        match lib.query_uuid(&uuid) {
-                            Some(song) => in_thread.send(DatabaseResponse::Song(song.0.clone())).unwrap(),
-                            None => in_thread.send(DatabaseResponse::Empty).unwrap(),
-                        }
-                    },
-                    QueryUuids(uuids) => {
-                        let mut vec = Vec::new();
-                        for uuid in uuids {
-                            match lib.query_uuid(&uuid) {
-                                Some(song) => vec.push(song.0.clone()),
-                                None => unimplemented!()
-                            }
-                        }
-                        in_thread.send(DatabaseResponse::Songs(vec)).unwrap();
-                    },
-                    ReadFolder(folder) => {
-                        lib.scan_folder(&folder).unwrap();
-                        in_thread.send(DatabaseResponse::Empty).unwrap();
+            while true {
+                match in_thread.recv().unwrap() {
+                    PlayerCmd::Test(uri) => {
+                        &player.set_volume(0.04);
+                        _ = &player.enqueue_next(&uri).unwrap();
+                        _ = &player.play();
+                        in_thread.send(PlayerRes::Test).unwrap();
                     }
-
                 }
             }
+
         });
 
 
         Ok(
             Controller {
-                // queues: Vec::new(),
+                queue: Queue::new(),
                 config: config_.clone(),
-                controller_mail: out_thread_controller,
-                db_mail: out_thread_db,
-                queue_mail: Vec::new(),
+                library,
+                player_mail
             }
         )
     }
 
-    pub fn lib_get_songs(&self) -> Vec<Song> {
-        self.db_mail.send(DatabaseCmd::GetSongs);
-        match self.db_mail.recv().unwrap() {
-            DatabaseResponse::Songs(songs) => songs,
-            _ => Vec::new()
-        }
+    pub fn q_add(&self, item: Uuid, source:super::queue::PlayerLocation , by_human: bool) {
+        self.queue.add_item(item, source, by_human)
     }
 
-    pub fn lib_scan_folder(&self, folder: String) -> Result<(), Box<dyn Error>> {
-        let mail = &self.db_mail;
-        mail.send(DatabaseCmd::ReadFolder(folder))?;
-        dbg!(mail.recv()?);
-        Ok(())
-    }
-
-    pub fn lib_save(&self) -> Result<(), Box<dyn Error>> {
-        self.db_mail.send(DatabaseCmd::SaveLibrary);
-        Ok(())
-    }
-
-    pub fn q_new(&mut self) -> Result<usize, Box<dyn Error>> {
-        let (out_thread_queue, in_thread) = MailMan::<QueueCmd, QueueResponse>::double();
-        let queues_monitor =  spawn(move || {
-            use QueueCmd::*;
-            let mut queue = Queue::new().unwrap();
-            loop {
-                let command = in_thread.recv().unwrap();
-                match command {
-                    Default => {},
-                    Test => { in_thread.send(QueueResponse::Test).unwrap() },
-                    Play => {
-                        match queue.player.play() {
-                            Ok(_) => in_thread.send(QueueResponse::Default).unwrap(),
-                            Err(_) => todo!()
-                        };
-
-                    },
-                    Pause => {
-                        match queue.player.pause() {
-                            Ok(_) => in_thread.send(QueueResponse::Default).unwrap(),
-                            Err(_) => todo!()
-                        }
-                    },
-                    // SetSongs(songs) => {
-                    //     queue.set_tracks(songs);
-                    //     in_thread.send(QueueResponse::Default).unwrap();
-                    // },
-                    Enqueue(uri) => {
-                        queue.player.enqueue_next(&uri).unwrap();
-
-                        // in_thread.send(QueueResponse::Default).unwrap();
-                    },
-                    SetVolume(vol) => {
-                        queue.player.set_volume(vol);
-                    }
-                }
-            }
-        });
-        self.queue_mail.push(out_thread_queue);
-        Ok(self.queue_mail.len() - 1)
-    }
-
-    pub fn q_play(&self, index: usize) -> Result<(), Box<dyn Error>> {
-        let mail = &self.queue_mail[index];
-        mail.send(QueueCmd::Play)?;
-        dbg!(mail.recv()?);
-        Ok(())
-    }
-
-    pub fn q_pause(&self, index: usize) -> Result<(), Box<dyn Error>> {
-        let mail = &self.queue_mail[index];
-        mail.send(QueueCmd::Pause)?;
-        dbg!(mail.recv()?);
-        Ok(())
-    }
-
-    pub fn q_set_volume(&self, index: usize, volume: f64) -> Result<(), Box<dyn Error>> {
-        let mail = &self.queue_mail[index];
-        mail.send(QueueCmd::SetVolume(volume))?;
-        Ok(())
-    }
-
-    // fn q_set_songs(&self, index: usize, songs: Vec<QueueItem<QueueState>>) -> Result<(), Box<dyn Error>> {
-    //     let mail = &self.queue_mail[index];
-    //     mail.send(QueueCmd::SetSongs(songs))?;
-    //     dbg!(mail.recv()?);
-    //     Ok(())
-    // }
-
-    pub fn q_enqueue(&self, index: usize, uri: URI) -> Result<(), Box<dyn Error>> {
-        let mail = &self.queue_mail[index];
-        mail.send(QueueCmd::Enqueue(uri))?;
-        // dbg!(mail.recv()?);
-        Ok(())
-    }
-
-
 }
 
 #[cfg(test)]
-mod tests {
+mod test_super {
     use std::{thread::sleep, time::Duration};
 
-    use super::Controller;
-
-    #[test]
-    fn play_test() {
-        let mut a = match Controller::start("test-config/config_test.json".to_string()) {
-            Ok(c) => c,
-            Err(e) => panic!("{e}")
-        };
-        sleep(Duration::from_millis(500));
-
-        let i = a.q_new().unwrap();
-        a.q_set_volume(i, 0.04);
-        // a.new_queue();
-        let songs = a.lib_get_songs();
-        a.q_enqueue(i, songs[2].location.clone());
-        // a.enqueue(1, songs[2].location.clone());
-        a.q_play(i).unwrap();
-        // a.play(1).unwrap();
-
-        sleep(Duration::from_secs(10));
-        a.q_pause(i);
-        sleep(Duration::from_secs(10));
-        a.q_play(i);
-        sleep(Duration::from_secs(1000));
-    }
+    use super::*;
 
     #[test]
     fn test_() {
-        let a = match Controller::start("test-config/config_test.json".to_string()) {
-            Ok(c) => c,
-            Err(e) => panic!("{e}")
-        };
-        a.lib_scan_folder("F:/Music/Mp3".to_string());
-        a.lib_save();
+        let c = Controller::start("F:\\Dangoware\\Dango Music Player\\dmp-core\\test-config\\config_test.json").unwrap();
+
+        sleep(Duration::from_secs(60));
     }
-}
+}
\ No newline at end of file
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 0559696..39736e6 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,8 +1,5 @@
 use uuid::Uuid;
-use crate::{
-    music_player::{Player, PlayerError},
-    music_storage::library::{Album, MusicLibrary, URI}
-};
+use crate::music_storage::library::{MusicLibrary, Song, URI};
 use std::{
     error::Error,
     sync::{Arc, RwLock}
@@ -27,54 +24,6 @@ pub enum QueueState {
     NoState,
 }
 
-#[derive(Debug, Clone, PartialEq)]
-#[non_exhaustive]
-pub enum QueueItemType<'a> {
-    Song(Uuid),
-    ExternalSong(URI),
-    Album{
-        album: Album<'a>,
-        shuffled: bool,
-        order: Option<Vec<Uuid>>,
-        // disc #, track #
-        current: (i32, i32)
-    },
-    Playlist {
-        uuid: Uuid,
-        shuffled: bool,
-        order: Option<Vec<Uuid>>,
-        current: Uuid
-    },
-    None,
-    Test
-}
-
-impl QueueItemType<'_> {
-    fn get_uri(&self, lib: Arc<RwLock<MusicLibrary>>) -> Option<URI> {
-        use QueueItemType::*;
-
-        let lib = lib.read().unwrap();
-        match self {
-            Song(uuid) => {
-                if let Some((song, _))  = lib.query_uuid(uuid) {
-                    Some(song.location.clone())
-                }else {
-                    Option::None
-                }
-            },
-            Album{album, shuffled, current: (disc, index), ..} => {
-                if !shuffled {
-                    Some(album.track(*disc as usize, *index as usize).unwrap().location.clone())
-                }else {
-                    todo!() //what to do for non shuffled album
-                }
-            },
-            ExternalSong(uri) => { Some(uri.clone()) },
-            _ => { Option::None }
-        }
-    }
-}
-
 // TODO: move this to a different location to be used elsewhere
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
@@ -88,16 +37,16 @@ pub enum PlayerLocation {
 
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
-pub struct QueueItem<'a> {
-    pub(super) item: QueueItemType<'a>,
+pub struct QueueItem {
+    pub(super) item: Song,
     pub(super) state: QueueState,
     pub(super) source: PlayerLocation,
     pub(super) by_human: bool
 }
-impl QueueItem<'_> {
-    fn new() -> Self {
+impl From<Song> for QueueItem {
+    fn from(song: Song) -> Self {
         QueueItem {
-            item: QueueItemType::None,
+            item: song,
             state: QueueState::NoState,
             source: PlayerLocation::Library,
             by_human: false
@@ -107,15 +56,14 @@ impl QueueItem<'_> {
 
 
 #[derive(Debug)]
-pub struct Queue<'a> {
-    pub player: Player,
-    pub name: String,
-    pub items: Vec<QueueItem<'a>>,
-    pub played: Vec<QueueItem<'a>>,
-    pub loop_: bool
+pub struct Queue {
+    pub items: Vec<QueueItem>,
+    pub played: Vec<QueueItem>,
+    pub loop_: bool,
+    pub shuffle: bool
 }
 
-impl<'a> Queue<'a> {
+impl Queue {
     fn has_addhere(&self) -> bool {
         for item in &self.items {
             if item.state == QueueState::AddHere {
@@ -126,28 +74,26 @@ impl<'a> Queue<'a> {
     }
 
     fn dbg_items(&self) {
-        dbg!(self.items.iter().map(|item| item.item.clone() ).collect::<Vec<QueueItemType>>(), self.items.len());
+        dbg!(self.items.iter().map(|item| item.item.clone() ).collect::<Vec<Song>>(), self.items.len());
     }
 
-    pub fn new() -> Result<Self, PlayerError> {
-        Ok(
-            Queue {
-            player: Player::new()?,
-            name: String::new(),
-            items: Vec::new(),
-            played: Vec::new(),
-            loop_: false,
-            }
-        )
+    pub fn new() -> Self {
+        //TODO: Make the queue take settings from config/state if applicable
+        Queue {
+        items: Vec::new(),
+        played: Vec::new(),
+        loop_: false,
+        shuffle: false,
+        }
     }
 
-    pub fn set_items(&mut self, tracks: Vec<QueueItem<'a>>) {
+    pub fn set_items(&mut self, tracks: Vec<QueueItem>) {
         let mut tracks = tracks;
         self.items.clear();
         self.items.append(&mut tracks);
     }
 
-    pub fn add_item(&mut self, item: QueueItemType<'a>, source: PlayerLocation, by_human: bool) {
+    pub fn add_item(&mut self, item: Song, source: PlayerLocation, by_human: bool) {
         let mut i: usize = 0;
 
         self.items = self.items.iter().enumerate().map(|(j, item_)| {
@@ -168,7 +114,7 @@ impl<'a> Queue<'a> {
         });
     }
 
-    pub fn add_item_next(&mut self, item: QueueItemType<'a>, source: PlayerLocation) {
+    pub fn add_item_next(&mut self, item: Song, source: PlayerLocation) {
         use QueueState::*;
         let empty = self.items.is_empty();
 
@@ -176,14 +122,14 @@ impl<'a> Queue<'a> {
             (if empty { 0 } else { 1 }),
             QueueItem {
                 item,
-                state: if (self.items.get(1).is_none() || (!self.has_addhere() && self.items.get(1).is_some()) || empty) { AddHere } else { NoState },
+                state: if (self.items.get(1).is_none() || !self.has_addhere() && self.items.get(1).is_some()) || empty { AddHere } else { NoState },
                 source,
                 by_human: true
             }
         )
     }
 
-    pub fn add_multi(&mut self, items: Vec<QueueItemType>, source: PlayerLocation, by_human: bool) {
+    pub fn add_multi(&mut self, items: Vec<Song>, source: PlayerLocation, by_human: bool) {
 
     }
 
@@ -239,14 +185,10 @@ impl<'a> Queue<'a> {
         use QueueState::*;
 
         let empty = self.items.is_empty();
-        let nothing_error = Err(QueueError::EmptyQueue);
-        let index = if !empty { index } else { return nothing_error; };
+
+        let index = if !empty { index } else { return Err(QueueError::EmptyQueue); };
 
         if !empty && index < self.items.len() {
-            let position = self.player.position();
-            if position.is_some_and(|dur| !dur.is_zero() ) {
-                self.played.push(self.items[0].clone());
-            }
 
             let to_item = self.items[index].clone();
 
@@ -263,13 +205,13 @@ impl<'a> Queue<'a> {
                     }
                 // dbg!(&to_item.item, &self.items[ind].item);
                 }else if empty {
-                    return nothing_error;
+                    return Err(QueueError::EmptyQueue);
                 }else {
                     break;
                 }
             }
         }else {
-            return Err(QueueError::EmptyQueue.into());
+            return Err(QueueError::EmptyQueue);
         }
         Ok(())
     }
@@ -287,9 +229,7 @@ impl<'a> Queue<'a> {
     }
 
     #[allow(clippy::should_implement_trait)]
-    pub fn next(&mut self, lib: Arc<RwLock<MusicLibrary>>) -> Result<OutQueue, Box<dyn Error>> {
-
-
+    pub fn next(&mut self) -> Result<&QueueItem, Box<dyn Error>> {
 
         if self.items.is_empty() {
             if self.loop_ {
@@ -300,119 +240,21 @@ impl<'a> Queue<'a> {
         }
         // TODO: add an algorithm to detect if the song should be skipped
         let item = self.items[0].clone();
-        let uri: URI = match &self.items[1].item {
-            QueueItemType::Song(uuid) => {
-                // TODO:  Refactor later for  multiple URIs
-                match &lib.read().unwrap().query_uuid(uuid) {
-                    Some(song) => song.0.location.clone(),
-                    None => return Err("Uuid does not exist!".into()),
-                }
-            },
-            QueueItemType::Album { album, current, ..} => {
-                let (disc, track) = (current.0 as usize, current.1 as usize);
-                match album.track(disc, track) {
-                    Some(track) => track.location.clone(),
-                    None => return Err(format!("Track in Album {} at disc {} track {} does not exist!", album.title(), disc, track).into())
-                }
-            },
-            QueueItemType::Playlist { current, .. } => {
-                // TODO:  Refactor later for  multiple URIs
-                match &lib.read().unwrap().query_uuid(current) {
-                    Some(song) => song.0.location.clone(),
-                    None => return Err("Uuid does not exist!".into()),
-                }
-            },
-            _ => todo!()
-        };
-        if !self.player.is_paused() {
-            self.player.enqueue_next(&uri)?;
-            self.player.play()?
-        }
+
         if self.items[0].state == QueueState::AddHere || !self.has_addhere() {
             self.items[1].state = QueueState::AddHere;
         }
         self.played.push(item);
         self.items.remove(0);
 
-        Ok(todo!())
+        Ok(&self.items[1])
     }
 
     pub fn prev() {}
 
-    pub fn enqueue_item(&mut self, item: QueueItem, lib: Arc<RwLock<MusicLibrary>>) -> Result<(), Box<dyn Error>> {
-        if let Some(uri) = item.item.get_uri(lib) {
-            self.player.enqueue_next(&uri)?;
-        }else {
-            return Err("this item does not exist!".into());
-        }
-        Ok(())
-    }
     pub fn check_played(&mut self) {
         while self.played.len() > 50 {
             self.played.remove(0);
         }
     }
 }
-
-pub struct OutQueue {
-
-}
-
-pub enum OutQueueItem {
-
-}
-
-
-#[test]
-fn item_add_test() {
-    let mut q = Queue::new().unwrap();
-
-    for _ in 0..5 {
-        // dbg!("tick!");
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
-        // dbg!(&q.items, &q.items.len());
-    }
-
-    for _ in 0..1 {
-    q.remove_item(0).inspect_err(|e| println!("{e:?}"));
-    }
-    for _ in 0..2 {
-        q.items.push(QueueItem { item: QueueItemType::Test, state: QueueState::NoState, source: PlayerLocation::Library, by_human: false });
-    }
-    dbg!(5);
-
-    q.add_item_next(QueueItemType::Test, PlayerLocation::Test);
-    dbg!(6);
-
-    dbg!(&q.items, &q.items.len());
-}
-
-#[test]
-fn test_() {
-    let mut q = Queue::new().unwrap();
-    for _ in 0..400 {
-        q.items.push(QueueItem { item: QueueItemType::Song(Uuid::new_v4()), state: QueueState::NoState, source: PlayerLocation::File, by_human: false });
-    }
-    for _ in 0..50000 {
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
-    }
-    // q.add_item_next(QueueItemType::Test, PlayerLocation::File);
-
-    // dbg!(&q.items, &q.items.len());
-
-}
-
-#[test]
-fn move_test() {
-    let mut q = Queue::new().unwrap();
-
-    for _ in 0..5 {
-        q.add_item(QueueItemType::Song(Uuid::new_v4()), PlayerLocation::Library, true);
-    }
-    // q.add_item(QueueItemType::Test, QueueSource::Library, true).unwrap();
-    dbg!(&q.items, &q.items.len());
-
-    q.move_to(3).inspect_err(|e| {dbg!(e);});
-    dbg!(&q.items, &q.items.len());
-    // q.dbg_items();
-}
diff --git a/src/music_player.rs b/src/music_player/gstreamer.rs
similarity index 94%
rename from src/music_player.rs
rename to src/music_player/gstreamer.rs
index e310403..91abfde 100644
--- a/src/music_player.rs
+++ b/src/music_player/gstreamer.rs
@@ -15,8 +15,10 @@ use gstreamer::prelude::*;
 use chrono::Duration;
 use thiserror::Error;
 
+use super::player::{Player, PlayerError};
+
 #[derive(Debug)]
-pub enum PlayerCmd {
+pub enum GstCmd {
     Play,
     Pause,
     Eos,
@@ -24,7 +26,7 @@ pub enum PlayerCmd {
 }
 
 #[derive(Debug, PartialEq, Eq)]
-pub enum PlayerState {
+pub enum GstState {
     Playing,
     Paused,
     Ready,
@@ -33,7 +35,7 @@ pub enum PlayerState {
     VoidPending,
 }
 
-impl From<gst::State> for PlayerState {
+impl From<gst::State> for GstState {
     fn from(value: gst::State) -> Self {
         match value {
             gst::State::VoidPending => Self::VoidPending,
@@ -45,7 +47,7 @@ impl From<gst::State> for PlayerState {
     }
 }
 
-impl TryInto<gst::State> for PlayerState {
+impl TryInto<gst::State> for GstState {
     fn try_into(self) -> Result<gst::State, Box<dyn Error>> {
         match self {
             Self::VoidPending => Ok(gst::State::VoidPending),
@@ -60,24 +62,6 @@ impl TryInto<gst::State> for PlayerState {
     type Error = Box<dyn Error>;
 }
 
-#[derive(Error, Debug)]
-pub enum PlayerError {
-    #[error("player initialization failed")]
-    Init(#[from] glib::Error),
-    #[error("element factory failed to create playbin3")]
-    Factory(#[from] glib::BoolError),
-    #[error("could not change playback state")]
-    StateChange(#[from] gst::StateChangeError),
-    #[error("the file or source is not found")]
-    NotFound,
-    #[error("failed to build gstreamer item")]
-    Build,
-    #[error("poison error")]
-    Poison,
-    #[error("general player error")]
-    General,
-}
-
 #[derive(Debug, PartialEq, Eq)]
 enum PlaybackStats {
     Idle,
@@ -91,10 +75,10 @@ enum PlaybackStats {
 
 /// An instance of a music player with a GStreamer backend
 #[derive(Debug)]
-pub struct Player {
+pub struct GStreamer {
     source:     Option<URI>,
     //pub message_tx: Sender<PlayerCmd>,
-    pub message_rx: crossbeam::channel::Receiver<PlayerCmd>,
+    pub message_rx: crossbeam::channel::Receiver<GstCmd>,
 
     playback_tx: crossbeam::channel::Sender<PlaybackStats>,
     playbin:    Arc<RwLock<Element>>,
@@ -105,7 +89,7 @@ pub struct Player {
     position:   Arc<RwLock<Option<Duration>>>,
 }
 
-impl Player {
+impl GStreamer {
     pub fn new() -> Result<Self, PlayerError> {
         // Initialize GStreamer, maybe figure out how to nicely fail here
         gst::init()?;
@@ -162,14 +146,14 @@ impl Player {
                         // Check if the current playback position is close to the end
                         let finish_point = end - Duration::milliseconds(250);
                         if pos_temp.unwrap() >= end {
-                            let _ = playback_tx.try_send(PlayerCmd::Eos);
+                            let _ = playback_tx.try_send(GstCmd::Eos);
                             playbin_arc
                                 .write()
                                 .unwrap()
                                 .set_state(gst::State::Ready)
                                 .expect("Unable to set the pipeline state");
                         } else if pos_temp.unwrap() >= finish_point {
-                            let _ = playback_tx.try_send(PlayerCmd::AboutToFinish);
+                            let _ = playback_tx.try_send(GstCmd::AboutToFinish);
                         }
 
                         // This has to be done AFTER the current time in the file
@@ -468,7 +452,7 @@ impl Player {
     }
 
     /// Get the current state of the playback
-    pub fn state(&mut self) -> PlayerState {
+    pub fn state(&mut self) -> GstState {
         self.playbin().unwrap().current_state().into()
         /*
         match *self.buffer.read().unwrap() {
@@ -498,7 +482,9 @@ impl Player {
     }
 }
 
-impl Drop for Player {
+// impl Player for GStreamer {}
+
+impl Drop for GStreamer {
     /// Cleans up the `GStreamer` pipeline and the monitoring
     /// thread when [Player] is dropped.
     fn drop(&mut self) {
diff --git a/src/music_player/kira.rs b/src/music_player/kira.rs
new file mode 100644
index 0000000..e69de29
diff --git a/src/music_player/player.rs b/src/music_player/player.rs
new file mode 100644
index 0000000..a30b2a6
--- /dev/null
+++ b/src/music_player/player.rs
@@ -0,0 +1,57 @@
+use chrono::Duration;
+use thiserror::Error;
+use gstreamer as gst;
+
+use crate::music_storage::library::URI;
+
+
+#[derive(Error, Debug)]
+pub enum PlayerError {
+    #[error("player initialization failed")]
+    Init(#[from] glib::Error),
+    #[error("element factory failed to create playbin3")]
+    Factory(#[from] glib::BoolError),
+    #[error("could not change playback state")]
+    StateChange(#[from] gst::StateChangeError),
+    #[error("the file or source is not found")]
+    NotFound,
+    #[error("failed to build gstreamer item")]
+    Build,
+    #[error("poison error")]
+    Poison,
+    #[error("general player error")]
+    General,
+}
+
+pub trait Player {
+    fn source(&self) -> &Option<URI>;
+
+    fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
+
+    fn set_volume(&mut self, volume: f64);
+
+    fn volume(&mut self) -> f64;
+
+    fn ready(&mut self) -> Result<(), PlayerError>;
+
+    fn play(&mut self) -> Result<(), PlayerError>;
+
+    fn resume(&mut self) -> Result<(), PlayerError>;
+
+    fn pause(&mut self) -> Result<(), PlayerError>;
+
+    fn stop(&mut self) -> Result<(), PlayerError>;
+
+    fn is_paused(&mut self) -> bool;
+
+    fn position(&mut self) -> Option<Duration>;
+
+    fn duration(&mut self) -> Option<Duration>;
+
+    fn raw_duration(&self) -> Option<Duration>;
+
+    fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
+
+    fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
+
+}
\ No newline at end of file

From 9457c5c9965e44c256ea91f9182d99608dacf883 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 19 May 2024 22:29:30 -0400
Subject: [PATCH 118/136] fixed a function and removed Album Art function

---
 src/music_controller/controller.rs |   3 +-
 src/music_storage/library.rs       | 126 +++++++----------------------
 2 files changed, 33 insertions(+), 96 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 525fd9f..2ab8811 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -107,7 +107,8 @@ impl Controller {
         })
     }
 
-    pub fn q_add(&self, item: Uuid, source: super::queue::PlayerLocation, by_human: bool) {
+    pub fn q_add(&mut self, item: &Uuid, source: super::queue::PlayerLocation, by_human: bool) {
+        let item = self.library.query_uuid(item).unwrap().0.to_owned();
         self.queue.add_item(item, source, by_human)
     }
 }
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index a64101f..4818324 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -9,16 +9,15 @@ use std::error::Error;
 use std::io::Write;
 use std::ops::ControlFlow::{Break, Continue};
 
-
 // Files
 use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
-use uuid::Uuid;
 use std::fs::{self, File};
-use tempfile::TempDir;
 use std::path::{Path, PathBuf};
+use tempfile::TempDir;
+use uuid::Uuid;
 use walkdir::WalkDir;
 
 // Time
@@ -152,7 +151,7 @@ pub enum SongType {
     Main,
     Instrumental,
     Remix,
-    Custom(String)
+    Custom(String),
 }
 
 impl Default for SongType {
@@ -182,10 +181,9 @@ pub struct Song {
     pub date_modified: Option<DateTime<Utc>>,
     pub album_art: Vec<AlbumArt>,
     pub tags: BTreeMap<Tag, String>,
-    pub internal_tags: Vec<InternalTag>
+    pub internal_tags: Vec<InternalTag>,
 }
 
-
 impl Song {
     /// Get a tag's value
     ///
@@ -342,7 +340,6 @@ impl Song {
         for file in cue_data.files.iter() {
             let audio_location = &parent_dir.join(file.file.clone());
 
-
             if !audio_location.exists() {
                 continue;
             }
@@ -447,80 +444,12 @@ impl Song {
                     date_modified: Some(chrono::offset::Utc::now()),
                     tags,
                     album_art,
-                    internal_tags: Vec::new()
+                    internal_tags: Vec::new(),
                 };
                 tracks.push((new_song, audio_location.clone()));
             }
         }
-     Ok(tracks)
-    }
-
-    /// Takes the AlbumArt[index] and opens it in the native file viewer
-
-    pub fn open_album_art(&self, index: usize, temp_dir: &TempDir) -> Result<(), Box<dyn Error>> {
-        use opener::open;
-        use urlencoding::decode;
-
-        if index >= self.album_art.len() {
-            return Err("Index out of bounds".into());
-        }
-
-        let uri = match &self.album_art[index] {
-            AlbumArt::External(uri) => {
-                PathBuf::from(decode(match uri.as_uri().strip_prefix("file:///") {
-                    Some(e) => e,
-                    None => return Err("Invalid path?".into())
-                })?.to_owned().to_string())
-            },
-            AlbumArt::Embedded(_) => {
-                let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
-                let blank_tag = &lofty::Tag::new(TagType::Id3v2);
-                let tagged_file: lofty::TaggedFile;
-
-                // TODO: add support for other URI types... or don't
-                #[cfg(target_family = "windows")]
-                let uri = urlencoding::decode(
-                    match self.primary_uri()?.0.as_uri().strip_prefix("file:///") {
-                        Some(str) => str,
-                        None => return Err("invalid path.. again?".into())
-                })?.into_owned();
-
-                #[cfg(target_family = "unix")]
-                let uri = urlencoding::decode(
-                    match self.primary_uri()?.as_uri().strip_prefix("file://") {
-                        Some(str) => str,
-                        None => return Err("invalid path.. again?".into())
-                })?.into_owned();
-
-                let tag = match Probe::open(uri)?.options(normal_options).read() {
-                    Ok(file) => {
-                        tagged_file = file;
-
-                        match tagged_file.primary_tag() {
-                            Some(primary_tag) => primary_tag,
-
-                            None => match tagged_file.first_tag() {
-                                Some(first_tag) => first_tag,
-                                None => blank_tag,
-                            },
-                        }
-                    }
-
-                    Err(_) => blank_tag,
-                };
-
-                let data = tag.pictures()[index].data();
-
-                let fmt = FileFormat::from_bytes(data);
-                let file_path = temp_dir.path().join(format!("{}_{index}.{}", self.uuid, fmt.extension()));
-
-                File::create(&file_path)?.write_all(data)?;
-
-                file_path
-            },
-        };
-        dbg!(open(dbg!(uri))?);
-        Ok(())
+        Ok(tracks)
     }
 
     /// Returns a reference to the first valid URI in the song, and any invalid URIs that come before it, or errors if there are no valid URIs
@@ -533,13 +462,20 @@ impl Song {
             if uri.exists()? {
                 valid_uri = Some(uri);
                 break;
-            }else {
+            } else {
                 invalid_uris.push(uri);
             }
         }
         match valid_uri {
-            Some(uri) => Ok((uri, if !invalid_uris.is_empty() { Some(invalid_uris) } else { None } )),
-            None => Err("No valid URIs for this song".into())
+            Some(uri) => Ok((
+                uri,
+                if !invalid_uris.is_empty() {
+                    Some(invalid_uris)
+                } else {
+                    None
+                },
+            )),
+            None => Err("No valid URIs for this song".into()),
         }
     }
 }
@@ -610,7 +546,7 @@ impl URI {
     pub fn as_path(&self) -> Result<&PathBuf, Box<dyn Error>> {
         if let Self::Local(path) = self {
             Ok(path)
-        }else {
+        } else {
             Err("This URI is not local!".into())
         }
     }
@@ -618,7 +554,7 @@ impl URI {
     pub fn exists(&self) -> Result<bool, std::io::Error> {
         match self {
             URI::Local(loc) => loc.try_exists(),
-            URI::Cue {location, ..} => location.try_exists(),
+            URI::Cue { location, .. } => location.try_exists(),
             URI::Remote(_, _loc) => todo!(),
         }
     }
@@ -703,7 +639,7 @@ pub struct MusicLibrary {
     pub uuid: Uuid,
     pub library: Vec<Song>,
     pub playlists: PlaylistFolder,
-    pub backup_songs: Vec<Song> // maybe move this to the config instead?
+    pub backup_songs: Vec<Song>, // maybe move this to the config instead?
 }
 
 impl MusicLibrary {
@@ -823,7 +759,8 @@ impl MusicLibrary {
     fn query_path(&self, path: PathBuf) -> Option<Vec<&Song>> {
         let result: Arc<Mutex<Vec<&Song>>> = Arc::new(Mutex::new(Vec::new()));
         self.library.par_iter().for_each(|track| {
-            if path == track.primary_uri().unwrap().0.path() { //TODO: make this also not unwrap
+            if path == track.primary_uri().unwrap().0.path() {
+                //TODO: make this also not unwrap
                 result.clone().lock().unwrap().push(track);
             }
         });
@@ -835,10 +772,7 @@ impl MusicLibrary {
     }
 
     /// Finds all the audio files within a specified folder
-    pub fn scan_folder(
-        &mut self,
-        target_path: &str,
-    ) -> Result<i32, Box<dyn std::error::Error>> {
+    pub fn scan_folder(&mut self, target_path: &str) -> Result<i32, Box<dyn std::error::Error>> {
         let mut total = 0;
         let mut errors = 0;
         for target_file in WalkDir::new(target_path)
@@ -901,7 +835,6 @@ impl MusicLibrary {
     }
 
     pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> {
-
         let new_song = Song::from_file(target_file)?;
         match self.add_song(new_song) {
             Ok(_) => (),
@@ -917,14 +850,13 @@ impl MusicLibrary {
         let tracks = Song::from_cue(cuesheet)?;
         let mut tracks_added = tracks.len() as i32;
 
-
         for (new_song, location) in tracks {
             // Try to remove the original audio file from the db if it exists
             if self.remove_uri(&URI::Local(location.clone())).is_ok() {
                 tracks_added -= 1
             }
             match self.add_song(new_song) {
-                Ok(_) => {},
+                Ok(_) => {}
                 Err(_error) => {
                     //println!("{}", _error);
                     continue;
@@ -1194,7 +1126,12 @@ impl MusicLibrary {
 
 #[cfg(test)]
 mod test {
-    use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}, thread::sleep, time::{Duration, Instant}};
+    use std::{
+        path::{Path, PathBuf},
+        sync::{Arc, RwLock},
+        thread::sleep,
+        time::{Duration, Instant},
+    };
 
     use tempfile::TempDir;
 
@@ -1202,7 +1139,6 @@ mod test {
 
     use super::Song;
 
-
     #[test]
     fn get_art_test() {
         let s = Song::from_file(Path::new("")).unwrap();
@@ -1211,7 +1147,7 @@ mod test {
         let now = Instant::now();
         _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}"));
         _ = s.open_album_art(1, dir).inspect_err(|e| println!("{e:?}"));
-        println!("{}ms", now.elapsed().as_millis() );
+        println!("{}ms", now.elapsed().as_millis());
 
         sleep(Duration::from_secs(20));
     }
@@ -1223,4 +1159,4 @@ mod test {
         let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap();
         dbg!(a);
     }
-}
\ No newline at end of file
+}

From c8c00a765bf1645545a08982443ace4c1ee4c142 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 19 May 2024 22:42:47 -0400
Subject: [PATCH 119/136] removed a broken test function

---
 src/music_storage/library.rs | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 4818324..6fc3b36 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -1139,19 +1139,6 @@ mod test {
 
     use super::Song;
 
-    #[test]
-    fn get_art_test() {
-        let s = Song::from_file(Path::new("")).unwrap();
-        let dir = &TempDir::new().unwrap();
-
-        let now = Instant::now();
-        _ = s.open_album_art(0, dir).inspect_err(|e| println!("{e:?}"));
-        _ = s.open_album_art(1, dir).inspect_err(|e| println!("{e:?}"));
-        println!("{}ms", now.elapsed().as_millis());
-
-        sleep(Duration::from_secs(20));
-    }
-
     #[test]
     fn library_init() {
         let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();

From f79bf8d477caf940927b512fe849d4acedbb0f02 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Mon, 20 May 2024 00:00:06 -0400
Subject: [PATCH 120/136] cleaned some warnings and updated player trait

---
 src/music_controller/controller.rs | 38 +++++++-----------------------
 src/music_player/player.rs         |  7 +++---
 src/music_storage/library.rs       | 21 ++++-------------
 3 files changed, 16 insertions(+), 50 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 2ab8811..f84545c 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -4,29 +4,24 @@
 
 use crossbeam_channel;
 use crossbeam_channel::{Receiver, Sender};
-use listenbrainz::ListenBrainz;
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
-use std::thread::spawn;
 
 use crossbeam_channel::unbounded;
 use std::error::Error;
 use uuid::Uuid;
 
-use crate::music_controller::queue::QueueItem;
-use crate::music_player::gstreamer::GStreamer;
-use crate::music_storage::library::{Tag, URI};
+use crate::music_player::player::Player;
+use crate::music_storage::library::URI;
 use crate::{
-    config::config::Config,
-    music_controller::queue::Queue,
-    music_storage::library::{MusicLibrary, Song},
+    config::config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
 };
 
-pub struct Controller {
+pub struct Controller<P: Player> {
     pub queue: Queue,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
-    player_mail: MailMan<PlayerCmd, PlayerRes>,
+    pub player: P,
 }
 
 #[derive(Debug)]
@@ -69,10 +64,10 @@ enum PlayerRes {
 }
 
 #[allow(unused_variables)]
-impl Controller {
-    pub fn start<P>(config_path: P) -> Result<Self, Box<dyn Error>>
+impl<P> Controller<P> {
+    pub fn start<T>(config_path: T) -> Result<Self, Box<dyn Error>>
     where
-        std::path::PathBuf: std::convert::From<P>,
+        std::path::PathBuf: std::convert::From<T>,
     {
         let config_path = PathBuf::from(config_path);
 
@@ -84,26 +79,11 @@ impl Controller {
 
         let (player_mail, in_thread) = MailMan::<PlayerCmd, PlayerRes>::double();
 
-        spawn(move || {
-            let mut player = GStreamer::new().unwrap();
-
-            while true {
-                match in_thread.recv().unwrap() {
-                    PlayerCmd::Test(uri) => {
-                        &player.set_volume(0.04);
-                        _ = &player.enqueue_next(&uri).unwrap();
-                        _ = &player.play();
-                        in_thread.send(PlayerRes::Test).unwrap();
-                    }
-                }
-            }
-        });
-
         Ok(Controller {
             queue: Queue::new(),
             config: config_.clone(),
             library,
-            player_mail,
+            player: P::new(),
         })
     }
 
diff --git a/src/music_player/player.rs b/src/music_player/player.rs
index a30b2a6..ddf79c4 100644
--- a/src/music_player/player.rs
+++ b/src/music_player/player.rs
@@ -1,10 +1,9 @@
 use chrono::Duration;
-use thiserror::Error;
 use gstreamer as gst;
+use thiserror::Error;
 
 use crate::music_storage::library::URI;
 
-
 #[derive(Error, Debug)]
 pub enum PlayerError {
     #[error("player initialization failed")]
@@ -24,6 +23,7 @@ pub enum PlayerError {
 }
 
 pub trait Player {
+    fn new() -> Self;
     fn source(&self) -> &Option<URI>;
 
     fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
@@ -53,5 +53,4 @@ pub trait Player {
     fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
 
     fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
-
-}
\ No newline at end of file
+}
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 6fc3b36..344dd3a 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -6,7 +6,6 @@ use crate::config::config::Config;
 // Various std things
 use std::collections::BTreeMap;
 use std::error::Error;
-use std::io::Write;
 use std::ops::ControlFlow::{Break, Continue};
 
 // Files
@@ -14,9 +13,8 @@ use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
-use std::fs::{self, File};
+use std::fs;
 use std::path::{Path, PathBuf};
-use tempfile::TempDir;
 use uuid::Uuid;
 use walkdir::WalkDir;
 
@@ -144,22 +142,17 @@ pub enum DoNotTrack {
     Discord,
 }
 
-#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
 #[non_exhaustive]
 pub enum SongType {
     // TODO: add MORE?! song types
+    #[default]
     Main,
     Instrumental,
     Remix,
     Custom(String),
 }
 
-impl Default for SongType {
-    fn default() -> Self {
-        SongType::Main
-    }
-}
-
 /// Stores information about a single song
 #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
 pub struct Song {
@@ -1127,18 +1120,12 @@ impl MusicLibrary {
 #[cfg(test)]
 mod test {
     use std::{
-        path::{Path, PathBuf},
+        path::PathBuf,
         sync::{Arc, RwLock},
-        thread::sleep,
-        time::{Duration, Instant},
     };
 
-    use tempfile::TempDir;
-
     use crate::{config::config::Config, music_storage::library::MusicLibrary};
 
-    use super::Song;
-
     #[test]
     fn library_init() {
         let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();

From 0fa97c27d00b147d63c3e5561ba7713e3bfcbce2 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Mon, 20 May 2024 00:23:45 -0400
Subject: [PATCH 121/136] Cleaned up many warnings

---
 src/config/config.rs               | 63 ++++++++++++++++++++----------
 src/music_controller/controller.rs | 45 +++------------------
 src/music_controller/queue.rs      | 21 ++--------
 src/music_storage/library.rs       |  2 +-
 src/music_storage/playlist.rs      | 17 +++-----
 5 files changed, 58 insertions(+), 90 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index 1c19b15..9b8ede8 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -1,7 +1,7 @@
 use std::{
+    fs::{self, File, OpenOptions},
+    io::{Error, Read, Write},
     path::PathBuf,
-    fs::{File, OpenOptions, self},
-    io::{Error, Write, Read},
 };
 
 use serde::{Deserialize, Serialize};
@@ -41,7 +41,7 @@ impl ConfigLibrary {
     pub fn open(&self) -> Result<File, Error> {
         match File::open(self.path.as_path()) {
             Ok(ok) => Ok(ok),
-            Err(e) => Err(e)
+            Err(e) => Err(e),
         }
     }
 }
@@ -62,18 +62,17 @@ impl ConfigLibraries {
     pub fn get_default(&self) -> Result<&ConfigLibrary, ConfigError> {
         for library in &self.libraries {
             if library.uuid == self.default_library {
-                return Ok(library)
+                return Ok(library);
             }
         }
         Err(ConfigError::NoDefaultLibrary)
     }
 
     pub fn get_library(&self, uuid: &Uuid) -> Result<ConfigLibrary, ConfigError> {
-
         for library in &self.libraries {
             // dbg!(&library.uuid, &uuid);
             if &library.uuid == uuid {
-                return Ok(library.to_owned())
+                return Ok(library.to_owned());
             }
         }
         Err(ConfigError::NoConfigLibrary(*uuid))
@@ -82,7 +81,7 @@ impl ConfigLibraries {
     pub fn uuid_exists(&self, uuid: &Uuid) -> bool {
         for library in &self.libraries {
             if &library.uuid == uuid {
-                return true
+                return true;
             }
         }
         false
@@ -91,7 +90,7 @@ impl ConfigLibraries {
 
 #[derive(Debug, Default, Serialize, Deserialize, Clone)]
 pub struct ConfigConnections {
-    pub listenbrainz_token: Option<String>
+    pub listenbrainz_token: Option<String>,
 }
 
 #[derive(Debug, Default, Serialize, Deserialize, Clone)]
@@ -122,7 +121,12 @@ impl Config {
     pub fn write_file(&self) -> Result<(), Error> {
         let mut writer = self.path.clone();
         writer.set_extension("tmp");
-        let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(&writer)?;
+        let mut file = OpenOptions::new()
+            .create(true)
+            .truncate(true)
+            .read(true)
+            .write(true)
+            .open(&writer)?;
         let config = to_string_pretty(self)?;
         // dbg!(&config);
 
@@ -136,15 +140,20 @@ impl Config {
             Some(path) => {
                 let mut writer = path.clone();
                 writer.set_extension("tmp");
-                let mut file = OpenOptions::new().create(true).truncate(true).read(true).write(true).open(&writer)?;
+                let mut file = OpenOptions::new()
+                    .create(true)
+                    .truncate(true)
+                    .read(true)
+                    .write(true)
+                    .open(&writer)?;
                 let config = to_string_pretty(self)?;
                 // dbg!(&config);
 
                 file.write_all(config.as_bytes())?;
                 fs::rename(writer, self.path.as_path())?;
                 Ok(())
-            },
-            None => Err(ConfigError::NoBackupLibrary.into())
+            }
+            None => Err(ConfigError::NoBackupLibrary.into()),
         }
     }
 
@@ -175,17 +184,23 @@ pub enum ConfigError {
     BadPlaylist,
     #[error("No backup Config folder present")]
     NoBackupLibrary,
-
 }
 
 #[cfg(test)]
 pub mod tests {
-    use std::{path::PathBuf, sync::{Arc, RwLock}};
+    use super::{Config, ConfigLibrary};
     use crate::music_storage::library::MusicLibrary;
-    use super::{Config, ConfigLibraries, ConfigLibrary};
+    use std::{
+        path::PathBuf,
+        sync::{Arc, RwLock},
+    };
 
     pub fn new_config_lib() -> (Config, MusicLibrary) {
-        let lib = ConfigLibrary::new(PathBuf::from("test-config/library"), String::from("library"), None);
+        let lib = ConfigLibrary::new(
+            PathBuf::from("test-config/library"),
+            String::from("library"),
+            None,
+        );
         let mut config = Config {
             path: PathBuf::from("test-config/config_test.json"),
             ..Default::default()
@@ -194,7 +209,11 @@ pub mod tests {
         config.push_library(lib);
         config.write_file().unwrap();
 
-        let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), dbg!(config.libraries.default_library)).unwrap();
+        let mut lib = MusicLibrary::init(
+            Arc::new(RwLock::from(config.clone())),
+            dbg!(config.libraries.default_library),
+        )
+        .unwrap();
         lib.scan_folder("test-config/music/").unwrap();
         lib.save(config.clone()).unwrap();
 
@@ -206,20 +225,22 @@ pub mod tests {
 
         // dbg!(&config);
 
-        let mut lib = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config.libraries.get_default().unwrap().uuid).unwrap();
-
+        let mut lib = MusicLibrary::init(
+            Arc::new(RwLock::from(config.clone())),
+            config.libraries.get_default().unwrap().uuid,
+        )
+        .unwrap();
 
         lib.scan_folder("test-config/music/").unwrap();
 
         lib.save(config.clone()).unwrap();
 
-
         (config, lib)
     }
 
     #[test]
     fn test3() {
-        let (config, lib) = read_config_lib();
+        let (config, _) = read_config_lib();
 
         _ = config.write_file();
 
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index f84545c..5e0ea55 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -12,7 +12,6 @@ use std::error::Error;
 use uuid::Uuid;
 
 use crate::music_player::player::Player;
-use crate::music_storage::library::URI;
 use crate::{
     config::config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
 };
@@ -21,7 +20,7 @@ pub struct Controller<P: Player> {
     pub queue: Queue,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
-    pub player: P,
+    pub player: Box<P>,
 }
 
 #[derive(Debug)]
@@ -55,19 +54,12 @@ impl<T: Send, U: Send> MailMan<T, U> {
     }
 }
 
-enum PlayerCmd {
-    Test(URI),
-}
-
-enum PlayerRes {
-    Test,
-}
-
 #[allow(unused_variables)]
-impl<P> Controller<P> {
+impl<P: Player> Controller<P> {
     pub fn start<T>(config_path: T) -> Result<Self, Box<dyn Error>>
     where
         std::path::PathBuf: std::convert::From<T>,
+        P: Player,
     {
         let config_path = PathBuf::from(config_path);
 
@@ -77,13 +69,11 @@ impl<P> Controller<P> {
         let config_ = Arc::new(RwLock::from(config));
         let library = MusicLibrary::init(config_.clone(), uuid)?;
 
-        let (player_mail, in_thread) = MailMan::<PlayerCmd, PlayerRes>::double();
-
         Ok(Controller {
-            queue: Queue::new(),
+            queue: Queue::default(),
             config: config_.clone(),
             library,
-            player: P::new(),
+            player: Box::new(P::new()),
         })
     }
 
@@ -94,27 +84,4 @@ impl<P> Controller<P> {
 }
 
 #[cfg(test)]
-mod test_super {
-    use std::{thread::sleep, time::Duration};
-
-    use super::Controller;
-
-    #[test]
-    fn play_test() {
-        let mut a = match Controller::start("test-config/config_test.json".to_string()) {
-            Ok(c) => c,
-            Err(e) => panic!("{e}"),
-        };
-        sleep(Duration::from_millis(500));
-    }
-
-    #[test]
-    fn test_() {
-        let c = Controller::start(
-            "F:\\Dangoware\\Dango Music Player\\dmp-core\\test-config\\config_test.json",
-        )
-        .unwrap();
-
-        sleep(Duration::from_secs(60));
-    }
-}
+mod test_super {}
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 380d157..6c533c3 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,8 +1,5 @@
-use crate::music_storage::library::{MusicLibrary, Song, URI};
-use std::{
-    error::Error,
-    sync::{Arc, RwLock},
-};
+use crate::music_storage::library::Song;
+use std::error::Error;
 use uuid::Uuid;
 
 use thiserror::Error;
@@ -53,7 +50,7 @@ impl From<Song> for QueueItem {
     }
 }
 
-#[derive(Debug)]
+#[derive(Debug, Default)]
 pub struct Queue {
     pub items: Vec<QueueItem>,
     pub played: Vec<QueueItem>,
@@ -81,16 +78,6 @@ impl Queue {
         );
     }
 
-    pub fn new() -> Self {
-        //TODO: Make the queue take settings from config/state if applicable
-        Queue {
-            items: Vec::new(),
-            played: Vec::new(),
-            loop_: false,
-            shuffle: false,
-        }
-    }
-
     pub fn set_items(&mut self, tracks: Vec<QueueItem>) {
         let mut tracks = tracks;
         self.items.clear();
@@ -131,7 +118,7 @@ impl Queue {
         let empty = self.items.is_empty();
 
         self.items.insert(
-            (if empty { 0 } else { 1 }),
+            if empty { 0 } else { 1 },
             QueueItem {
                 item,
                 state: if (self.items.get(1).is_none()
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 344dd3a..e3e660e 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -642,7 +642,7 @@ impl MusicLibrary {
             name,
             uuid,
             library: Vec::new(),
-            playlists: PlaylistFolder::new(),
+            playlists: PlaylistFolder::default(),
             backup_songs: Vec::new(),
         }
     }
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index e7ae61b..788e59b 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,3 +1,4 @@
+use std::default;
 use std::error::Error;
 use std::{
     fs::File,
@@ -26,6 +27,7 @@ pub enum SortOrder {
 
 nest! {
     #[derive(Debug, Clone, Deserialize, Serialize)]*
+    #[derive(Default)]
     pub struct PlaylistFolder {
         name: String,
         items: Vec<
@@ -37,15 +39,6 @@ nest! {
     }
 }
 
-impl PlaylistFolder {
-    pub fn new() -> Self {
-        PlaylistFolder {
-            name: String::new(),
-            items: Vec::new(),
-        }
-    }
-}
-
 #[derive(Debug, Clone, Deserialize, Serialize)]
 pub struct Playlist {
     uuid: Uuid,
@@ -233,7 +226,7 @@ impl Playlist {
                 #[cfg(target_family = "windows")]
                 {
                     playlist.title = path
-                        .split("\\")
+                        .split('\\')
                         .last()
                         .unwrap_or_default()
                         .strip_suffix(".m3u8")
@@ -339,7 +332,7 @@ impl Default for Playlist {
 #[cfg(test)]
 mod test_super {
     use super::*;
-    use crate::{config::config::tests::read_config_lib, music_storage::playlist};
+    use crate::config::config::tests::read_config_lib;
 
     #[test]
     fn list_to_m3u8() {
@@ -360,7 +353,7 @@ mod test_super {
         let playlist =
             Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap();
 
-        playlist.to_file(".\\test-config\\playlists\\playlist");
+        _ = playlist.to_file(".\\test-config\\playlists\\playlist");
         dbg!(playlist)
     }
 

From 9fac9ef777b3317ef2bd4b72bbe59c8b751de4a7 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Mon, 20 May 2024 00:39:51 -0400
Subject: [PATCH 122/136] updated Playlist functions

---
 src/music_storage/playlist.rs | 53 ++++++++++++-----------------------
 1 file changed, 18 insertions(+), 35 deletions(-)

diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 788e59b..1f04860 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -1,4 +1,3 @@
-use std::default;
 use std::error::Error;
 use std::{
     fs::File,
@@ -60,66 +59,50 @@ impl Playlist {
         self.play_time
     }
 
-    fn title(&self) -> &String {
+    pub fn title(&self) -> &String {
         &self.title
     }
 
-    fn cover(&self) -> Option<&AlbumArt> {
+    pub fn cover(&self) -> Option<&AlbumArt> {
         match &self.cover {
             Some(e) => Some(e),
             None => None,
         }
     }
 
-    fn tracks(&self) -> Vec<Uuid> {
+    pub fn tracks(&self) -> Vec<Uuid> {
         self.tracks.to_owned()
     }
+
     pub fn set_tracks(&mut self, tracks: Vec<Uuid>) {
         self.tracks = tracks;
     }
+
     pub fn add_track(&mut self, track: Uuid) {
         self.tracks.push(track);
     }
+
     pub fn remove_track(&mut self, index: i32) {
         let index = index as usize;
         if (self.tracks.len() - 1) >= index {
             self.tracks.remove(index);
         }
     }
-    // pub fn get_index(&self, song_name: &str) -> Option<usize> {
-    //     let mut index = 0;
-    //     if self.contains_value(&Tag::Title, song_name) {
-    //         for track in &self.tracks {
-    //             index += 1;
-    //             if song_name == track.tags.get_key_value(&Tag::Title).unwrap().1 {
-    //                 dbg!("Index gotted! ", index);
-    //                 return Some(index);
-    //             }
-    //         }
-    //     }
-    //     None
-    // }
-    pub fn contains_value(
-        &self,
-        tag: &Tag,
-        value: &String,
-        lib: Arc<RwLock<MusicLibrary>>,
-    ) -> bool {
-        let lib = lib.read().unwrap();
-        let items = match lib.query_tracks(value, &vec![tag.to_owned()], &vec![tag.to_owned()]) {
-            Some(e) => e,
-            None => return false,
-        };
-
-        for item in items {
-            for uuid in &self.tracks {
-                if uuid == &item.uuid {
-                    return true;
+    pub fn get_index(&self, uuid: Uuid) -> Option<usize> {
+        let mut i = 0;
+        if self.contains(uuid) {
+            for track in &self.tracks {
+                i += 1;
+                if &uuid == track {
+                    dbg!("Index gotted! ", i);
+                    return Some(i);
                 }
             }
         }
-
-        false
+        None
+    }
+    pub fn contains(&self, uuid: Uuid) -> bool {
+        self.get_index(uuid).is_some()
     }
 
     pub fn to_file(&self, path: &str) -> Result<(), Box<dyn Error>> {

From dd7f447d460bb6c15ce498857dc0b1a299b6d8ab Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 20 May 2024 02:32:37 -0500
Subject: [PATCH 123/136] Implemented  for the GStreamer backend

---
 src/music_controller/controller.rs |   2 +-
 src/music_player/gstreamer.rs      | 478 +++++++++++++++--------------
 src/music_player/player.rs         |  39 ++-
 3 files changed, 279 insertions(+), 240 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 5e0ea55..df4fa20 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -73,7 +73,7 @@ impl<P: Player> Controller<P> {
             queue: Queue::default(),
             config: config_.clone(),
             library,
-            player: Box::new(P::new()),
+            player: Box::new(P::new()?),
         })
     }
 
diff --git a/src/music_player/gstreamer.rs b/src/music_player/gstreamer.rs
index 91abfde..ff5d25c 100644
--- a/src/music_player/gstreamer.rs
+++ b/src/music_player/gstreamer.rs
@@ -1,7 +1,7 @@
 // Crate things
 //use crate::music_controller::config::Config;
 use crate::music_storage::library::URI;
-use crossbeam_channel::unbounded;
+use crossbeam_channel::{unbounded, Receiver, Sender};
 use std::error::Error;
 use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
 
@@ -13,29 +13,10 @@ use gstreamer::prelude::*;
 
 // Extra things
 use chrono::Duration;
-use thiserror::Error;
 
-use super::player::{Player, PlayerError};
+use super::player::{Player, PlayerCommand, PlayerError, PlayerState};
 
-#[derive(Debug)]
-pub enum GstCmd {
-    Play,
-    Pause,
-    Eos,
-    AboutToFinish,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub enum GstState {
-    Playing,
-    Paused,
-    Ready,
-    Buffering(u8),
-    Null,
-    VoidPending,
-}
-
-impl From<gst::State> for GstState {
+impl From<gst::State> for PlayerState {
     fn from(value: gst::State) -> Self {
         match value {
             gst::State::VoidPending => Self::VoidPending,
@@ -47,7 +28,7 @@ impl From<gst::State> for GstState {
     }
 }
 
-impl TryInto<gst::State> for GstState {
+impl TryInto<gst::State> for PlayerState {
     fn try_into(self) -> Result<gst::State, Box<dyn Error>> {
         match self {
             Self::VoidPending => Ok(gst::State::VoidPending),
@@ -63,7 +44,7 @@ impl TryInto<gst::State> for GstState {
 }
 
 #[derive(Debug, PartialEq, Eq)]
-enum PlaybackStats {
+enum PlaybackInfo {
     Idle,
     Switching,
     Playing{
@@ -77,10 +58,10 @@ enum PlaybackStats {
 #[derive(Debug)]
 pub struct GStreamer {
     source:     Option<URI>,
-    //pub message_tx: Sender<PlayerCmd>,
-    pub message_rx: crossbeam::channel::Receiver<GstCmd>,
 
-    playback_tx: crossbeam::channel::Sender<PlaybackStats>,
+    message_rx: crossbeam::channel::Receiver<PlayerCommand>,
+    playback_tx: crossbeam::channel::Sender<PlaybackInfo>,
+
     playbin:    Arc<RwLock<Element>>,
     volume:     f64,
     start:      Option<Duration>,
@@ -89,16 +70,163 @@ pub struct GStreamer {
     position:   Arc<RwLock<Option<Duration>>>,
 }
 
+impl From<gst::StateChangeError> for PlayerError {
+    fn from(value: gst::StateChangeError) -> Self {
+        PlayerError::StateChange(value.to_string())
+    }
+}
+
+impl From<glib::BoolError> for PlayerError {
+    fn from(value: glib::BoolError) -> Self {
+        PlayerError::General(value.to_string())
+    }
+}
+
 impl GStreamer {
-    pub fn new() -> Result<Self, PlayerError> {
+    /// Set the playback URI
+    fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> {
+        if !source.exists().is_ok_and(|x| x) {
+            // If the source doesn't exist, gstreamer will crash!
+            return Err(PlayerError::NotFound)
+        }
+
+        // Make sure the playback tracker knows the stuff is stopped
+        self.playback_tx.send(PlaybackInfo::Switching).unwrap();
+
+        let uri = self.playbin.read().unwrap().property_value("current-uri");
+        self.source = Some(source.clone());
+        match source {
+            URI::Cue { start, end, .. } => {
+                self.playbin
+                    .write()
+                    .unwrap()
+                    .set_property("uri", source.as_uri());
+
+                // Set the start and end positions of the CUE file
+                self.start = Some(Duration::from_std(*start).unwrap());
+                self.end = Some(Duration::from_std(*end).unwrap());
+
+                // Send the updated position to the tracker
+                self.playback_tx.send(PlaybackInfo::Playing{
+                    start: self.start.unwrap(),
+                    end: self.end.unwrap()
+                }).unwrap();
+
+                // Wait for it to be ready, and then move to the proper position
+                self.play().unwrap();
+                let now = std::time::Instant::now();
+                while now.elapsed() < std::time::Duration::from_millis(20) {
+                    if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
+                        return Ok(());
+                    }
+                    std::thread::sleep(std::time::Duration::from_millis(1));
+                }
+                //panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
+                return Err(PlayerError::StateChange("Could not seek to beginning of CUE track".into()))
+            }
+            _ => {
+                self.playbin
+                    .write()
+                    .unwrap()
+                    .set_property("uri", source.as_uri());
+
+                self.play().unwrap();
+
+                while uri.get::<&str>().unwrap_or("")
+                    == self.property("current-uri").get::<&str>().unwrap_or("")
+                    || self.position().is_none()
+                {
+                    std::thread::sleep(std::time::Duration::from_millis(10));
+                }
+
+                self.start = Some(Duration::seconds(0));
+                self.end = self.raw_duration();
+
+                // Send the updated position to the tracker
+                self.playback_tx.send(PlaybackInfo::Playing{
+                    start: self.start.unwrap(),
+                    end: self.end.unwrap()
+                }).unwrap();
+            }
+        }
+
+        Ok(())
+    }
+
+    /// Gets a mutable reference to the playbin element
+    fn playbin_mut(
+        &mut self,
+    ) -> Result<RwLockWriteGuard<gst::Element>, std::sync::PoisonError<RwLockWriteGuard<'_, Element>>>
+    {
+        let element = match self.playbin.write() {
+            Ok(element) => element,
+            Err(err) => return Err(err),
+        };
+        Ok(element)
+    }
+
+    /// Gets a read-only reference to the playbin element
+    fn playbin(
+        &self,
+    ) -> Result<RwLockReadGuard<gst::Element>, std::sync::PoisonError<RwLockReadGuard<'_, Element>>>
+    {
+        let element = match self.playbin.read() {
+            Ok(element) => element,
+            Err(err) => return Err(err),
+        };
+        Ok(element)
+    }
+
+    /// Set volume of the internal playbin player, can be
+    /// used to bypass the main volume control for seeking
+    fn set_gstreamer_volume(&mut self, volume: f64) {
+        self.playbin_mut().unwrap().set_property("volume", volume)
+    }
+
+    fn set_state(&mut self, state: gst::State) -> Result<(), gst::StateChangeError> {
+        self.playbin_mut().unwrap().set_state(state)?;
+
+        Ok(())
+    }
+
+    fn raw_duration(&self) -> Option<Duration> {
+        self.playbin()
+            .unwrap()
+            .query_duration::<ClockTime>()
+            .map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
+    }
+
+    /// Get the current state of the playback
+    fn state(&mut self) -> PlayerState {
+        self.playbin().unwrap().current_state().into()
+        /*
+        match *self.buffer.read().unwrap() {
+            None => self.playbin().unwrap().current_state().into(),
+            Some(value) => PlayerState::Buffering(value),
+        }
+        */
+    }
+
+    fn property(&self, property: &str) -> glib::Value {
+        self.playbin().unwrap().property_value(property)
+    }
+}
+
+impl Player for GStreamer {
+    fn new() -> Result<Self, PlayerError> {
         // Initialize GStreamer, maybe figure out how to nicely fail here
-        gst::init()?;
+        if let Err(err) = gst::init() {
+            return Err(PlayerError::Init(err.to_string()))
+        };
         let ctx = glib::MainContext::default();
         let _guard = ctx.acquire();
         let mainloop = glib::MainLoop::new(Some(&ctx), false);
 
         let playbin_arc = Arc::new(RwLock::new(
-            gst::ElementFactory::make("playbin3").build()?,
+            match gst::ElementFactory::make("playbin3").build() {
+                Ok(playbin) => playbin,
+                Err(error) => return Err(PlayerError::Init(error.to_string())),
+            }
         ));
 
         let playbin = playbin_arc.clone();
@@ -124,53 +252,11 @@ impl GStreamer {
 
         // Set up the thread to monitor the position
         let (playback_tx, playback_rx) = unbounded();
-        let (stat_tx, stat_rx) = unbounded::<PlaybackStats>();
+        let (status_tx, status_rx) = unbounded::<PlaybackInfo>();
         let position_update = Arc::clone(&position);
-        let _playback_monitor = std::thread::spawn(move || { //TODO: Figure out how to return errors nicely in threads
-            let mut stats = PlaybackStats::Idle;
-            let mut pos_temp;
-            loop {
-                // Check for new messages or updates about how to proceed
-                if let Ok(res) = stat_rx.recv_timeout(std::time::Duration::from_millis(100)) {
-                    stats = res
-                }
 
-                pos_temp = playbin_arc
-                    .read()
-                    .unwrap()
-                    .query_position::<ClockTime>()
-                    .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
-
-                match stats {
-                    PlaybackStats::Playing{start, end} if pos_temp.is_some() => {
-                        // Check if the current playback position is close to the end
-                        let finish_point = end - Duration::milliseconds(250);
-                        if pos_temp.unwrap() >= end {
-                            let _ = playback_tx.try_send(GstCmd::Eos);
-                            playbin_arc
-                                .write()
-                                .unwrap()
-                                .set_state(gst::State::Ready)
-                                .expect("Unable to set the pipeline state");
-                        } else if pos_temp.unwrap() >= finish_point {
-                            let _ = playback_tx.try_send(GstCmd::AboutToFinish);
-                        }
-
-                        // This has to be done AFTER the current time in the file
-                        // is calculated, or everything else is wrong
-                        pos_temp = Some(pos_temp.unwrap() - start)
-                    },
-                    PlaybackStats::Finished => {
-                        *position_update.write().unwrap() = None;
-                        break
-                    },
-                    PlaybackStats::Idle | PlaybackStats::Switching => {},
-                    _ => ()
-                }
-
-                *position_update.write().unwrap() = pos_temp;
-            }
-        });
+        let _playback_monitor =
+            std::thread::spawn(|| playback_monitor(playbin_arc, status_rx, playback_tx, position_update));
 
         // Set up the thread to monitor bus messages
         let playbin_bus_ctrl = Arc::clone(&playbin);
@@ -231,7 +317,7 @@ impl GStreamer {
             source,
             playbin,
             message_rx: playback_rx,
-            playback_tx: stat_tx,
+            playback_tx: status_tx,
             volume: 1.0,
             start: None,
             end: None,
@@ -240,163 +326,65 @@ impl GStreamer {
         })
     }
 
-    pub fn source(&self) -> &Option<URI> {
+    fn source(&self) -> &Option<URI> {
         &self.source
     }
 
-    pub fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
+    /// Insert a new track to be played. This method should be called at the
+    /// beginning to start playback of something, and once the [PlayerCommand]
+    /// indicates the track is about to finish to enqueue gaplessly.
+    fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
         self.set_source(next_track)
     }
 
-    /// Set the playback URI
-    fn set_source(&mut self, source: &URI) -> Result<(), PlayerError> {
-        if !source.exists().is_ok_and(|x| x) {
-            // If the source doesn't exist, gstreamer will crash!
-            return Err(PlayerError::NotFound)
-        }
-
-        // Make sure the playback tracker knows the stuff is stopped
-        self.playback_tx.send(PlaybackStats::Switching).unwrap();
-
-        let uri = self.playbin.read().unwrap().property_value("current-uri");
-        self.source = Some(source.clone());
-        match source {
-            URI::Cue { start, end, .. } => {
-                self.playbin
-                    .write()
-                    .unwrap()
-                    .set_property("uri", source.as_uri());
-
-                // Set the start and end positions of the CUE file
-                self.start = Some(Duration::from_std(*start).unwrap());
-                self.end = Some(Duration::from_std(*end).unwrap());
-
-                // Send the updated position to the tracker
-                self.playback_tx.send(PlaybackStats::Playing{
-                    start: self.start.unwrap(),
-                    end: self.end.unwrap()
-                }).unwrap();
-
-                // Wait for it to be ready, and then move to the proper position
-                self.play().unwrap();
-                let now = std::time::Instant::now();
-                while now.elapsed() < std::time::Duration::from_millis(20) {
-                    if self.seek_to(Duration::from_std(*start).unwrap()).is_ok() {
-                        return Ok(());
-                    }
-                    std::thread::sleep(std::time::Duration::from_millis(1));
-                }
-                panic!("Couldn't seek to beginning of cue track in reasonable time (>20ms)");
-            }
-            _ => {
-                self.playbin
-                    .write()
-                    .unwrap()
-                    .set_property("uri", source.as_uri());
-
-                self.play().unwrap();
-
-                while uri.get::<&str>().unwrap_or("")
-                    == self.property("current-uri").get::<&str>().unwrap_or("")
-                    || self.position().is_none()
-                {
-                    std::thread::sleep(std::time::Duration::from_millis(10));
-                }
-
-                self.start = Some(Duration::seconds(0));
-                self.end = self.raw_duration();
-
-                // Send the updated position to the tracker
-                self.playback_tx.send(PlaybackStats::Playing{
-                    start: self.start.unwrap(),
-                    end: self.end.unwrap()
-                }).unwrap();
-            }
-        }
-
-        Ok(())
-    }
-
-    /// Gets a mutable reference to the playbin element
-    fn playbin_mut(
-        &mut self,
-    ) -> Result<RwLockWriteGuard<gst::Element>, std::sync::PoisonError<RwLockWriteGuard<'_, Element>>>
-    {
-        let element = match self.playbin.write() {
-            Ok(element) => element,
-            Err(err) => return Err(err),
-        };
-        Ok(element)
-    }
-
-    /// Gets a read-only reference to the playbin element
-    fn playbin(
-        &self,
-    ) -> Result<RwLockReadGuard<gst::Element>, std::sync::PoisonError<RwLockReadGuard<'_, Element>>>
-    {
-        let element = match self.playbin.read() {
-            Ok(element) => element,
-            Err(err) => return Err(err),
-        };
-        Ok(element)
-    }
-
     /// Set the playback volume, accepts a float from 0 to 1
-    pub fn set_volume(&mut self, volume: f64) {
+    fn set_volume(&mut self, volume: f64) {
         self.volume = volume.clamp(0.0, 1.0);
         self.set_gstreamer_volume(self.volume);
     }
 
-    /// Set volume of the internal playbin player, can be
-    /// used to bypass the main volume control for seeking
-    fn set_gstreamer_volume(&mut self, volume: f64) {
-        self.playbin_mut().unwrap().set_property("volume", volume)
-    }
-
     /// Returns the current volume level, a float from 0 to 1
-    pub fn volume(&mut self) -> f64 {
+    fn volume(&mut self) -> f64 {
         self.volume
     }
 
-    fn set_state(&mut self, state: gst::State) -> Result<(), gst::StateChangeError> {
-        self.playbin_mut().unwrap().set_state(state)?;
-
+    fn ready(&mut self) -> Result<(), PlayerError> {
+        self.set_state(gst::State::Ready)?;
         Ok(())
     }
 
-    pub fn ready(&mut self) -> Result<(), gst::StateChangeError> {
-        self.set_state(gst::State::Ready)
-    }
-
     /// If the player is paused or stopped, starts playback
-    pub fn play(&mut self) -> Result<(), gst::StateChangeError> {
-        self.set_state(gst::State::Playing)
+    fn play(&mut self) -> Result<(), PlayerError> {
+        self.set_state(gst::State::Playing)?;
+        Ok(())
     }
 
     /// Pause, if playing
-    pub fn pause(&mut self) -> Result<(), gst::StateChangeError> {
-        //*self.paused.write().unwrap() = true;
-        self.set_state(gst::State::Paused)
+    fn pause(&mut self) -> Result<(), PlayerError> {
+        //self.paused = true;
+        self.set_state(gst::State::Paused)?;
+        Ok(())
     }
 
     /// Resume from being paused
-    pub fn resume(&mut self) -> Result<(), gst::StateChangeError> {
-        //*self.paused.write().unwrap() = false;
-        self.set_state(gst::State::Playing)
+    fn resume(&mut self) -> Result<(), PlayerError> {
+        //self.paused = false;
+        self.set_state(gst::State::Playing)?;
+        Ok(())
     }
 
     /// Check if playback is paused
-    pub fn is_paused(&mut self) -> bool {
+    fn is_paused(&mut self) -> bool {
         self.playbin().unwrap().current_state() == gst::State::Paused
     }
 
     /// Get the current playback position of the player
-    pub fn position(&mut self) -> Option<Duration> {
+    fn position(&mut self) -> Option<Duration> {
         *self.position.read().unwrap()
     }
 
     /// Get the duration of the currently playing track
-    pub fn duration(&mut self) -> Option<Duration> {
+    fn duration(&mut self) -> Option<Duration> {
         if self.end.is_some() && self.start.is_some() {
             Some(self.end.unwrap() - self.start.unwrap())
         } else {
@@ -404,18 +392,11 @@ impl GStreamer {
         }
     }
 
-    pub fn raw_duration(&self) -> Option<Duration> {
-        self.playbin()
-            .unwrap()
-            .query_duration::<ClockTime>()
-            .map(|pos| Duration::nanoseconds(pos.nseconds() as i64))
-    }
-
     /// Seek relative to the current position
-    pub fn seek_by(&mut self, seek_amount: Duration) -> Result<(), Box<dyn Error>> {
+    fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError> {
         let time_pos = match *self.position.read().unwrap() {
             Some(pos) => pos,
-            None => return Err("No position".into()),
+            None => return Err(PlayerError::Seek("No position".into())),
         };
         let seek_pos = time_pos + seek_amount;
 
@@ -424,15 +405,15 @@ impl GStreamer {
     }
 
     /// Seek absolutely
-    pub fn seek_to(&mut self, target_pos: Duration) -> Result<(), Box<dyn Error>> {
+    fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError> {
         let start = if self.start.is_none() {
-            return Err("Failed to seek: No START time".into());
+            return Err(PlayerError::Seek("No START time".into()));
         } else {
             self.start.unwrap()
         };
 
         let end = if self.end.is_none() {
-            return Err("Failed to seek: No END time".into());
+            return Err(PlayerError::Seek("No END time".into()));
         } else {
             self.end.unwrap()
         };
@@ -451,28 +432,13 @@ impl GStreamer {
         Ok(())
     }
 
-    /// Get the current state of the playback
-    pub fn state(&mut self) -> GstState {
-        self.playbin().unwrap().current_state().into()
-        /*
-        match *self.buffer.read().unwrap() {
-            None => self.playbin().unwrap().current_state().into(),
-            Some(value) => PlayerState::Buffering(value),
-        }
-        */
-    }
-
-    pub fn property(&self, property: &str) -> glib::Value {
-        self.playbin().unwrap().property_value(property)
-    }
-
     /// Stop the playback entirely
-    pub fn stop(&mut self) -> Result<(), gst::StateChangeError> {
+    fn stop(&mut self) -> Result<(), PlayerError> {
         self.pause()?;
         self.ready()?;
 
         // Send the updated position to the tracker
-        self.playback_tx.send(PlaybackStats::Idle).unwrap();
+        self.playback_tx.send(PlaybackInfo::Idle).unwrap();
 
         // Set all positions to none
         *self.position.write().unwrap() = None;
@@ -480,9 +446,12 @@ impl GStreamer {
         self.end = None;
         Ok(())
     }
-}
 
-// impl Player for GStreamer {}
+    /// Return a reference to the player message channel
+    fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand> {
+        &self.message_rx
+    }
+}
 
 impl Drop for GStreamer {
     /// Cleans up the `GStreamer` pipeline and the monitoring
@@ -492,6 +461,57 @@ impl Drop for GStreamer {
             .unwrap()
             .set_state(gst::State::Null)
             .expect("Unable to set the pipeline to the `Null` state");
-        let _ = self.playback_tx.send(PlaybackStats::Finished);
+        let _ = self.playback_tx.send(PlaybackInfo::Finished);
+    }
+}
+
+fn playback_monitor(
+    playbin: Arc<RwLock<Element>>,
+    status_rx: Receiver<PlaybackInfo>,
+    playback_tx: Sender<PlayerCommand>,
+    position: Arc<RwLock<Option<Duration>>>,
+) {
+    let mut stats = PlaybackInfo::Idle;
+    let mut pos_temp;
+    loop {
+        // Check for new messages to decide how to proceed
+        if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(100)) {
+            stats = result
+        }
+
+        pos_temp = playbin
+            .read()
+            .unwrap()
+            .query_position::<ClockTime>()
+            .map(|pos| Duration::nanoseconds(pos.nseconds() as i64));
+
+        match stats {
+            PlaybackInfo::Playing{start, end} if pos_temp.is_some() => {
+                // Check if the current playback position is close to the end
+                let finish_point = end - Duration::milliseconds(250);
+                if pos_temp.unwrap() >= end {
+                    let _ = playback_tx.try_send(PlayerCommand::EndOfStream);
+                    playbin
+                        .write()
+                        .unwrap()
+                        .set_state(gst::State::Ready)
+                        .expect("Unable to set the pipeline state");
+                } else if pos_temp.unwrap() >= finish_point {
+                    let _ = playback_tx.try_send(PlayerCommand::AboutToFinish);
+                }
+
+                // This has to be done AFTER the current time in the file
+                // is calculated, or everything else is wrong
+                pos_temp = Some(pos_temp.unwrap() - start)
+            },
+            PlaybackInfo::Finished => {
+                *position.write().unwrap() = None;
+                break
+            },
+            PlaybackInfo::Idle | PlaybackInfo::Switching => {},
+            _ => ()
+        }
+
+        *position.write().unwrap() = pos_temp;
     }
 }
diff --git a/src/music_player/player.rs b/src/music_player/player.rs
index ddf79c4..c2e24d9 100644
--- a/src/music_player/player.rs
+++ b/src/music_player/player.rs
@@ -1,17 +1,16 @@
 use chrono::Duration;
-use gstreamer as gst;
 use thiserror::Error;
 
 use crate::music_storage::library::URI;
 
 #[derive(Error, Debug)]
 pub enum PlayerError {
-    #[error("player initialization failed")]
-    Init(#[from] glib::Error),
-    #[error("element factory failed to create playbin3")]
-    Factory(#[from] glib::BoolError),
+    #[error("player initialization failed: {0}")]
+    Init(String),
     #[error("could not change playback state")]
-    StateChange(#[from] gst::StateChangeError),
+    StateChange(String),
+    #[error("seeking failed: {0}")]
+    Seek(String),
     #[error("the file or source is not found")]
     NotFound,
     #[error("failed to build gstreamer item")]
@@ -19,11 +18,31 @@ pub enum PlayerError {
     #[error("poison error")]
     Poison,
     #[error("general player error")]
-    General,
+    General(String),
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum PlayerState {
+    Playing,
+    Paused,
+    Ready,
+    Buffering(u8),
+    Null,
+    VoidPending,
+}
+
+#[derive(Debug)]
+pub enum PlayerCommand {
+    Play,
+    Pause,
+    EndOfStream,
+    AboutToFinish,
 }
 
 pub trait Player {
-    fn new() -> Self;
+    /// Create a new player
+    fn new() -> Result<Self, PlayerError> where Self: Sized;
+
     fn source(&self) -> &Option<URI>;
 
     fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
@@ -48,9 +67,9 @@ pub trait Player {
 
     fn duration(&mut self) -> Option<Duration>;
 
-    fn raw_duration(&self) -> Option<Duration>;
-
     fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
 
     fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
+
+    fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand>;
 }

From 48c7e29ecaf436a31d8cd719fed88b5a43da21c5 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Mon, 20 May 2024 04:03:38 -0500
Subject: [PATCH 124/136] Fixed buffering for GStreamer

---
 src/music_player/gstreamer.rs | 57 ++++++++++++++++++++---------------
 src/music_player/player.rs    |  2 +-
 src/music_storage/library.rs  |  8 ++---
 src/music_storage/utils.rs    |  1 -
 4 files changed, 37 insertions(+), 31 deletions(-)

diff --git a/src/music_player/gstreamer.rs b/src/music_player/gstreamer.rs
index ff5d25c..db35c03 100644
--- a/src/music_player/gstreamer.rs
+++ b/src/music_player/gstreamer.rs
@@ -51,7 +51,10 @@ enum PlaybackInfo {
         start: Duration,
         end:   Duration,
     },
-    Finished // When this is sent, the thread will die!
+
+    /// When this is sent, the thread will die! Use it when the [Player] is
+    /// done playing
+    Finished
 }
 
 /// An instance of a music player with a GStreamer backend
@@ -66,7 +69,7 @@ pub struct GStreamer {
     volume:     f64,
     start:      Option<Duration>,
     end:        Option<Duration>,
-    paused:     bool,
+    paused:     Arc<RwLock<bool>>,
     position:   Arc<RwLock<Option<Duration>>>,
 }
 
@@ -260,6 +263,8 @@ impl Player for GStreamer {
 
         // Set up the thread to monitor bus messages
         let playbin_bus_ctrl = Arc::clone(&playbin);
+        let paused = Arc::new(RwLock::new(false));
+        let bus_paused = Arc::clone(&paused);
         let bus_watch = playbin
             .read()
             .unwrap()
@@ -267,23 +272,18 @@ impl Player for GStreamer {
             .expect("Failed to get GStreamer message bus")
             .add_watch(move |_bus, msg| {
                 match msg.view() {
-                    gst::MessageView::Eos(_) => {}
+                    gst::MessageView::Eos(_) => println!("End of stream"),
                     gst::MessageView::StreamStart(_) => println!("Stream start"),
-                    gst::MessageView::Error(_) => {
-                        playbin_bus_ctrl
-                            .write()
-                            .unwrap()
-                            .set_state(gst::State::Ready)
-                            .unwrap();
-
-                        playbin_bus_ctrl
-                            .write()
-                            .unwrap()
-                            .set_state(gst::State::Playing)
-                            .unwrap();
+                    gst::MessageView::Error(err) => {
+                        println!("Error recieved: {}", err);
+                        return glib::ControlFlow::Break
                     }
-                    /* TODO: Fix buffering!!
                     gst::MessageView::Buffering(buffering) => {
+                        if *bus_paused.read().unwrap() == true {
+                            return glib::ControlFlow::Continue
+                        }
+
+                        // If the player is not paused, pause it
                         let percent = buffering.percent();
                         if percent < 100 {
                             playbin_bus_ctrl
@@ -291,7 +291,8 @@ impl Player for GStreamer {
                                 .unwrap()
                                 .set_state(gst::State::Paused)
                                 .unwrap();
-                        } else if !(buffering) {
+                        } else if percent >= 100 {
+                            println!("Finished buffering");
                             playbin_bus_ctrl
                                 .write()
                                 .unwrap()
@@ -299,7 +300,6 @@ impl Player for GStreamer {
                                 .unwrap();
                         }
                     }
-                    */
                     _ => (),
                 }
                 glib::ControlFlow::Continue
@@ -321,7 +321,7 @@ impl Player for GStreamer {
             volume: 1.0,
             start: None,
             end: None,
-            paused: false,
+            paused,
             position,
         })
     }
@@ -355,20 +355,21 @@ impl Player for GStreamer {
 
     /// If the player is paused or stopped, starts playback
     fn play(&mut self) -> Result<(), PlayerError> {
+        *self.paused.write().unwrap() = false;
         self.set_state(gst::State::Playing)?;
         Ok(())
     }
 
     /// Pause, if playing
     fn pause(&mut self) -> Result<(), PlayerError> {
-        //self.paused = true;
+        *self.paused.write().unwrap() = true;
         self.set_state(gst::State::Paused)?;
         Ok(())
     }
 
     /// Resume from being paused
     fn resume(&mut self) -> Result<(), PlayerError> {
-        //self.paused = false;
+        *self.paused.write().unwrap() = false;
         self.set_state(gst::State::Playing)?;
         Ok(())
     }
@@ -473,9 +474,10 @@ fn playback_monitor(
 ) {
     let mut stats = PlaybackInfo::Idle;
     let mut pos_temp;
+    let mut sent_atf = false;
     loop {
         // Check for new messages to decide how to proceed
-        if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(100)) {
+        if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(10)) {
             stats = result
         }
 
@@ -489,15 +491,18 @@ fn playback_monitor(
             PlaybackInfo::Playing{start, end} if pos_temp.is_some() => {
                 // Check if the current playback position is close to the end
                 let finish_point = end - Duration::milliseconds(250);
-                if pos_temp.unwrap() >= end {
+                if pos_temp.unwrap().num_microseconds() >= end.num_microseconds() {
                     let _ = playback_tx.try_send(PlayerCommand::EndOfStream);
                     playbin
                         .write()
                         .unwrap()
                         .set_state(gst::State::Ready)
                         .expect("Unable to set the pipeline state");
-                } else if pos_temp.unwrap() >= finish_point {
+                    sent_atf = false
+                } else if pos_temp.unwrap().num_microseconds() >= finish_point.num_microseconds()
+                    && !sent_atf {
                     let _ = playback_tx.try_send(PlayerCommand::AboutToFinish);
+                    sent_atf = true;
                 }
 
                 // This has to be done AFTER the current time in the file
@@ -508,7 +513,9 @@ fn playback_monitor(
                 *position.write().unwrap() = None;
                 break
             },
-            PlaybackInfo::Idle | PlaybackInfo::Switching => {},
+            PlaybackInfo::Idle | PlaybackInfo::Switching => {
+                sent_atf = false
+            },
             _ => ()
         }
 
diff --git a/src/music_player/player.rs b/src/music_player/player.rs
index c2e24d9..3a9e76e 100644
--- a/src/music_player/player.rs
+++ b/src/music_player/player.rs
@@ -31,7 +31,7 @@ pub enum PlayerState {
     VoidPending,
 }
 
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq)]
 pub enum PlayerCommand {
     Play,
     Pause,
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index e3e660e..1dd63b5 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -218,8 +218,8 @@ impl Song {
         self.tags.remove(target_key);
     }
 
-    /// Creates a `Song` from a song file
-    pub fn from_file(target_file: &Path) -> Result<Self, Box<dyn Error>> {
+    /// Creates a `Song` from a music file
+    pub fn from_file<P: ?Sized + AsRef<Path>>(target_file: &P) -> Result<Self, Box<dyn Error>> {
         let normal_options = ParseOptions::new().parsing_mode(lofty::ParsingMode::Relaxed);
 
         let blank_tag = &lofty::Tag::new(TagType::Id3v2);
@@ -283,7 +283,7 @@ impl Song {
         }
 
         // Find images around the music file that can be used
-        let mut found_images = find_images(target_file).unwrap();
+        let mut found_images = find_images(target_file.as_ref()).unwrap();
         album_art.append(&mut found_images);
 
         // Get the format as a string
@@ -548,7 +548,7 @@ impl URI {
         match self {
             URI::Local(loc) => loc.try_exists(),
             URI::Cue { location, .. } => location.try_exists(),
-            URI::Remote(_, _loc) => todo!(),
+            URI::Remote(_, _loc) => Ok(true), // TODO: Investigate a way to do this?
         }
     }
 }
diff --git a/src/music_storage/utils.rs b/src/music_storage/utils.rs
index c9347c4..e29a6e0 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -84,7 +84,6 @@ pub fn find_images(song_path: &Path) -> Result<Vec<AlbumArt>, Box<dyn Error>> {
         .filter(|e| e.depth() < 3)
     // Don't recurse very deep
     {
-        println!("{:?}", target_file);
         let path = target_file.path();
         if !path.is_file() || !path.exists() {
             continue;

From 5b94c7950ff9670bfc0b78acdc65c0b311d324f4 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Mon, 20 May 2024 23:51:44 -0400
Subject: [PATCH 125/136] Fixed `move_to()` function and added more queue
 functions. also created ControllerError

---
 src/config/config.rs               |   1 -
 src/music_controller/controller.rs |  16 ++-
 src/music_controller/queue.rs      | 206 ++++++++++++++++++++++++-----
 3 files changed, 187 insertions(+), 36 deletions(-)

diff --git a/src/config/config.rs b/src/config/config.rs
index 9b8ede8..18ab894 100644
--- a/src/config/config.rs
+++ b/src/config/config.rs
@@ -54,7 +54,6 @@ pub struct ConfigLibraries {
 }
 
 impl ConfigLibraries {
-    //TODO: Add new function for test tube
     pub fn set_default(mut self, uuid: &Uuid) {
         self.default_library = *uuid;
     }
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index df4fa20..e3f1e68 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -6,16 +6,20 @@ use crossbeam_channel;
 use crossbeam_channel::{Receiver, Sender};
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
+use thiserror::Error;
 
 use crossbeam_channel::unbounded;
 use std::error::Error;
 use uuid::Uuid;
 
-use crate::music_player::player::Player;
+use crate::config::config::ConfigError;
+use crate::music_player::player::{Player, PlayerError};
 use crate::{
     config::config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
 };
 
+use super::queue::QueueError;
+
 pub struct Controller<P: Player> {
     pub queue: Queue,
     pub config: Arc<RwLock<Config>>,
@@ -23,6 +27,16 @@ pub struct Controller<P: Player> {
     pub player: Box<P>,
 }
 
+#[derive(Error, Debug)]
+pub enum ControllerError {
+    #[error("{0:?}")]
+    QueueError(#[from] QueueError),
+    #[error("{0:?}")]
+    PlayerError(#[from] PlayerError),
+    #[error("{0:?}")]
+    ConfigError(#[from] ConfigError),
+}
+
 #[derive(Debug)]
 pub(super) struct MailMan<T: Send, U: Send> {
     pub tx: Sender<T>,
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 6c533c3..85675ff 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,4 +1,5 @@
 use crate::music_storage::library::Song;
+use chrono::format::Item;
 use std::error::Error;
 use uuid::Uuid;
 
@@ -6,10 +7,12 @@ use thiserror::Error;
 
 #[derive(Error, Debug)]
 pub enum QueueError {
-    #[error("Index out of bounds! Index {0} is over len {1}")]
-    OutOfBounds(usize, usize),
+    #[error("Index out of bounds! Index {index} is over len {len}")]
+    OutOfBounds { index: usize, len: usize },
     #[error("The Queue is empty!")]
     EmptyQueue,
+    #[error("There are no past played songs!")]
+    EmptyPlayed,
 }
 
 #[derive(Debug, PartialEq, Clone, Copy)]
@@ -21,7 +24,7 @@ pub enum QueueState {
 }
 
 // TODO: move this to a different location to be used elsewhere
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
 #[non_exhaustive]
 pub enum PlayerLocation {
     Test,
@@ -39,6 +42,7 @@ pub struct QueueItem {
     pub(super) source: PlayerLocation,
     pub(super) by_human: bool,
 }
+
 impl From<Song> for QueueItem {
     fn from(song: Song) -> Self {
         QueueItem {
@@ -58,6 +62,7 @@ pub struct Queue {
     pub shuffle: bool,
 }
 
+// TODO: HAndle the First QueueState
 impl Queue {
     fn has_addhere(&self) -> bool {
         for item in &self.items {
@@ -72,8 +77,8 @@ impl Queue {
         dbg!(
             self.items
                 .iter()
-                .map(|item| item.item.clone())
-                .collect::<Vec<Song>>(),
+                .map(|item| (item.item.uuid, item.state))
+                .collect::<Vec<(Uuid, QueueState)>>(),
             self.items.len()
         );
     }
@@ -84,6 +89,7 @@ impl Queue {
         self.items.append(&mut tracks);
     }
 
+    /// Inserts an item after the AddHere item
     pub fn add_item(&mut self, item: Song, source: PlayerLocation, by_human: bool) {
         let mut i: usize = 0;
 
@@ -113,6 +119,7 @@ impl Queue {
         );
     }
 
+    /// Inserts an item after the currently playing item
     pub fn add_item_next(&mut self, item: Song, source: PlayerLocation) {
         use QueueState::*;
         let empty = self.items.is_empty();
@@ -135,9 +142,70 @@ impl Queue {
         )
     }
 
-    pub fn add_multi(&mut self, items: Vec<Song>, source: PlayerLocation, by_human: bool) {}
+    pub fn add_multi(&mut self, items: Vec<Song>, source: PlayerLocation, by_human: bool) {
+        let mut i: usize = 0;
 
-    pub fn remove_item(&mut self, remove_index: usize) -> Result<(), QueueError> {
+        self.items = self
+            .items
+            .iter()
+            .enumerate()
+            .map(|(j, item_)| {
+                let mut item_ = item_.to_owned();
+                // get the index of the current AddHere item and give it to i
+                if item_.state == QueueState::AddHere {
+                    i = j;
+                    item_.state = QueueState::NoState;
+                }
+                item_
+            })
+            .collect::<Vec<QueueItem>>();
+
+        let empty = self.items.is_empty();
+
+        let len = items.len();
+        for item in items {
+            self.items.insert(
+                i + if empty { 0 } else { 1 },
+                QueueItem {
+                    item,
+                    state: QueueState::NoState,
+                    source,
+                    by_human,
+                },
+            );
+        }
+        self.items[i + len - if empty { 1 } else { 0 }].state = QueueState::AddHere;
+    }
+
+    /// Add multiple Songs after the currently playing Song
+    pub fn add_multi_next(&mut self, items: Vec<Song>, source: PlayerLocation, by_human: bool) {
+        use QueueState::*;
+        let empty = self.items.is_empty();
+
+        let add_here = (self.items.get(1).is_none()
+            || !self.has_addhere() && self.items.get(1).is_some())
+            || empty;
+
+        let len = items.len();
+
+        for item in items {
+            self.items.insert(
+                if empty { 0 } else { 1 },
+                QueueItem {
+                    item,
+                    state: NoState,
+                    source,
+                    by_human: true,
+                },
+            )
+        }
+
+        if add_here {
+            self.items[len - if empty { 1 } else { 0 }].state = QueueState::AddHere;
+        }
+    }
+
+    pub fn remove_item(&mut self, remove_index: usize) -> Result<QueueItem, QueueError> {
         // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]);
 
         if remove_index < self.items.len() {
@@ -145,19 +213,36 @@ impl Queue {
             if self.items.get(remove_index + 1).is_some() {
                 self.items[remove_index + 1].state = self.items[remove_index].state;
             }
-            self.items[remove_index].state = QueueState::NoState;
-            self.items.remove(remove_index);
-            Ok(())
+            Ok(self.items.remove(remove_index))
         } else {
             Err(QueueError::EmptyQueue)
         }
     }
 
+    pub fn insert<T>(&mut self, index: usize, new_item: T, addhere: bool)
+    where
+        QueueItem: std::convert::From<T>,
+    {
+        if addhere {
+            let mut new_item = QueueItem::from(new_item);
+            for item in &mut self.items {
+                if item.state == QueueState::AddHere {
+                    item.state = QueueState::NoState
+                }
+            }
+            new_item.state = QueueState::AddHere;
+            self.items.insert(index, new_item);
+        } else {
+            let new_item = QueueItem::from(new_item);
+            self.items.insert(index, new_item);
+        }
+    }
+
     pub fn clear(&mut self) {
         self.items.clear();
     }
 
-    pub fn clear_except(&mut self, index: usize) -> Result<(), Box<dyn Error>> {
+    pub fn clear_except(&mut self, index: usize) -> Result<(), QueueError> {
         use QueueState::*;
         let empty = self.items.is_empty();
 
@@ -166,9 +251,12 @@ impl Queue {
             self.items.retain(|item| *item == i);
             self.items[0].state = AddHere;
         } else if empty {
-            return Err("Queue is empty!".into());
+            return Err(QueueError::EmptyQueue);
         } else {
-            return Err("index out of bounds!".into());
+            return Err(QueueError::OutOfBounds {
+                index,
+                len: self.items.len(),
+            });
         }
         Ok(())
     }
@@ -182,8 +270,7 @@ impl Queue {
         self.played.clear();
     }
 
-    // TODO: uh, fix this?
-    fn move_to(&mut self, index: usize) -> Result<(), QueueError> {
+    pub fn move_to(&mut self, index: usize) -> Result<(), QueueError> {
         use QueueState::*;
 
         let empty = self.items.is_empty();
@@ -194,22 +281,20 @@ impl Queue {
             return Err(QueueError::EmptyQueue);
         };
 
-        if !empty && index < self.items.len() {
+        if !empty && dbg!(index < self.items.len()) {
             let to_item = self.items[index].clone();
 
             loop {
-                let empty = !self.items.is_empty();
+                let empty = self.items.is_empty();
                 let item = self.items[0].item.to_owned();
 
                 if item != to_item.item && !empty {
                     if self.items[0].state == AddHere && self.items.get(1).is_some() {
                         self.items[1].state = AddHere;
                     }
-                    if let Err(e) = self.remove_item(0) {
-                        dbg!(&e);
-                        self.dbg_items();
-                        return Err(e);
-                    }
+                    let item = self.items.remove(0);
+                    self.played.push(item);
+
                 // dbg!(&to_item.item, &self.items[ind].item);
                 } else if empty {
                     return Err(QueueError::EmptyQueue);
@@ -227,35 +312,52 @@ impl Queue {
         self.items.swap(a, b)
     }
 
-    pub fn move_item(&mut self, a: usize, b: usize) {
-        let item = self.items[a].to_owned();
-        if a != b {
-            self.items.remove(a);
+    pub fn move_item(&mut self, from: usize, to: usize) {
+        let item = self.items[from].to_owned();
+        if from != to {
+            self.items.remove(from);
         }
-        self.items.insert(b, item);
+        self.items.insert(to, item);
     }
 
     #[allow(clippy::should_implement_trait)]
-    pub fn next(&mut self) -> Result<&QueueItem, Box<dyn Error>> {
+    pub fn next(&mut self) -> Result<&QueueItem, QueueError> {
         if self.items.is_empty() {
             if self.loop_ {
-                return Err(QueueError::EmptyQueue.into()); // TODO: add function to loop the queue
+                unimplemented!() // TODO: add function to loop the queue
             } else {
-                return Err(QueueError::EmptyQueue.into());
+                return Err(QueueError::EmptyQueue);
             }
         }
-        // TODO: add an algorithm to detect if the song should be skipped
-        let item = self.items[0].clone();
+
+        let item = self.items.remove(0);
         if self.items[0].state == QueueState::AddHere || !self.has_addhere() {
             self.items[1].state = QueueState::AddHere;
         }
         self.played.push(item);
-        self.items.remove(0);
 
         Ok(&self.items[1])
     }
 
-    pub fn prev() {}
+    pub fn prev(&mut self) -> Result<&QueueItem, QueueError> {
+        if self.items[0].state == QueueState::First && self.loop_ {
+            todo!()
+        }
+        if let Some(item) = self.played.pop() {
+            self.items.insert(0, item);
+            Ok(&self.items[0])
+        } else {
+            Err(QueueError::EmptyPlayed)
+        }
+    }
+
+    pub fn now_playing(&self) -> Result<&QueueItem, QueueError> {
+        if !self.items.is_empty() {
+            Ok(&self.items[0])
+        } else {
+            Err(QueueError::EmptyQueue)
+        }
+    }
 
     pub fn check_played(&mut self) {
         while self.played.len() > 50 {
@@ -263,3 +365,39 @@ impl Queue {
         }
     }
 }
+
+#[cfg(test)]
+mod test_super {
+    #![allow(unused)]
+    use crate::{
+        config::config::tests::{new_config_lib, read_config_lib},
+        music_storage::library,
+    };
+
+    use super::*;
+
+    #[test]
+    fn move_test() {
+        let (_, library) = read_config_lib();
+        let mut q = Queue::default();
+        q.add_multi(library.library.clone(), PlayerLocation::Library, true);
+        q.add_multi_next(library.library, PlayerLocation::Library, true);
+
+
+        q.dbg_items();
+        dbg!(&q.played);
+
+        // q.move_to(2).inspect_err(|e| println!("{e:?}"));
+        // q.dbg_items();
+        // dbg!(&q.played.iter().map(|i| i.item.uuid).collect::<Vec<_>>());
+
+        // let a = q
+        //     .prev()
+        //     .inspect_err(|e| println!("{e:?}"))
+        //     .unwrap()
+        //     .item
+        //     .uuid;
+        // q.dbg_items();
+        // dbg!(a, &q.played.iter().map(|i| i.item.uuid).collect::<Vec<_>>());
+    }
+}

From 32c0cf31054f16dd4ba525e2f22fb06445b3576b Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Wed, 22 May 2024 00:45:35 -0400
Subject: [PATCH 126/136] Tested and adjusted queue functions

---
 src/music_controller/queue.rs | 83 ++++++++++++++++++++---------------
 1 file changed, 48 insertions(+), 35 deletions(-)

diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 85675ff..c26ae05 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,6 +1,4 @@
 use crate::music_storage::library::Song;
-use chrono::format::Item;
-use std::error::Error;
 use uuid::Uuid;
 
 use thiserror::Error;
@@ -13,6 +11,8 @@ pub enum QueueError {
     EmptyQueue,
     #[error("There are no past played songs!")]
     EmptyPlayed,
+    #[error("There is no item after this in the Queue")]
+    NoNext,
 }
 
 #[derive(Debug, PartialEq, Clone, Copy)]
@@ -43,12 +43,12 @@ pub struct QueueItem {
     pub(super) by_human: bool,
 }
 
-impl From<Song> for QueueItem {
-    fn from(song: Song) -> Self {
+impl QueueItem {
+    fn from_song(song: Song, source: PlayerLocation) -> Self {
         QueueItem {
             item: song,
             state: QueueState::NoState,
-            source: PlayerLocation::Library,
+            source,
             by_human: false,
         }
     }
@@ -62,7 +62,7 @@ pub struct Queue {
     pub shuffle: bool,
 }
 
-// TODO: HAndle the First QueueState
+// TODO: HAndle the First QueueState[looping] and shuffle
 impl Queue {
     fn has_addhere(&self) -> bool {
         for item in &self.items {
@@ -73,6 +73,7 @@ impl Queue {
         false
     }
 
+    #[allow(unused)]
     fn dbg_items(&self) {
         dbg!(
             self.items
@@ -163,7 +164,7 @@ impl Queue {
         let empty = self.items.is_empty();
 
         let len = items.len();
-        for item in items {
+        for item in items.into_iter().rev() {
             self.items.insert(
                 i + if empty { 0 } else { 1 },
                 QueueItem {
@@ -178,7 +179,7 @@ impl Queue {
     }
 
     /// Add multiple Songs after the currently playing Song
-    pub fn add_multi_next(&mut self, items: Vec<Song>, source: PlayerLocation, by_human: bool) {
+    pub fn add_multi_next(&mut self, items: Vec<Song>, source: PlayerLocation) {
         use QueueState::*;
         let empty = self.items.is_empty();
 
@@ -219,12 +220,24 @@ impl Queue {
         }
     }
 
-    pub fn insert<T>(&mut self, index: usize, new_item: T, addhere: bool)
-    where
-        QueueItem: std::convert::From<T>,
-    {
+    pub fn insert(
+        &mut self,
+        index: usize,
+        new_item: Song,
+        source: PlayerLocation,
+        addhere: bool,
+    ) -> Result<(), QueueError> {
+        if self.items.get_mut(index).is_none()
+            && index > 0
+            && self.items.get_mut(index - 1).is_none()
+        {
+            return Err(QueueError::OutOfBounds {
+                index,
+                len: self.items.len(),
+            });
+        }
         if addhere {
-            let mut new_item = QueueItem::from(new_item);
+            let mut new_item = QueueItem::from_song(new_item, source);
             for item in &mut self.items {
                 if item.state == QueueState::AddHere {
                     item.state = QueueState::NoState
@@ -233,9 +246,10 @@ impl Queue {
             new_item.state = QueueState::AddHere;
             self.items.insert(index, new_item);
         } else {
-            let new_item = QueueItem::from(new_item);
+            let new_item = QueueItem::from_song(new_item, source);
             self.items.insert(index, new_item);
         }
+        Ok(())
     }
 
     pub fn clear(&mut self) {
@@ -330,20 +344,27 @@ impl Queue {
             }
         }
 
-        let item = self.items.remove(0);
         if self.items[0].state == QueueState::AddHere || !self.has_addhere() {
-            self.items[1].state = QueueState::AddHere;
+            self.items[0].state = QueueState::NoState;
+            if self.items.get_mut(1).is_some() {
+                self.items[1].state = QueueState::AddHere;
+            }
         }
+        let item = self.items.remove(0);
         self.played.push(item);
 
-        Ok(&self.items[1])
+        if self.items.is_empty() {
+            Err(QueueError::NoNext)
+        } else {
+            Ok(&self.items[0])
+        }
     }
 
     pub fn prev(&mut self) -> Result<&QueueItem, QueueError> {
-        if self.items[0].state == QueueState::First && self.loop_ {
-            todo!()
-        }
         if let Some(item) = self.played.pop() {
+            if item.state == QueueState::First && self.loop_ {
+                todo!()
+            }
             self.items.insert(0, item);
             Ok(&self.items[0])
         } else {
@@ -380,24 +401,16 @@ mod test_super {
     fn move_test() {
         let (_, library) = read_config_lib();
         let mut q = Queue::default();
-        q.add_multi(library.library.clone(), PlayerLocation::Library, true);
-        q.add_multi_next(library.library, PlayerLocation::Library, true);
-
 
+        q.insert(0, library.library[2].to_owned(), PlayerLocation::File, true)
+            .inspect_err(|e| println!("{e}"));
+        q.insert(1, library.library[2].to_owned(), PlayerLocation::File, true)
+            .inspect_err(|e| println!("{e}"));
+        // q.next();
+        // q.clear();
         q.dbg_items();
-        dbg!(&q.played);
+        dbg!(&q.played.len());
 
-        // q.move_to(2).inspect_err(|e| println!("{e:?}"));
         // q.dbg_items();
-        // dbg!(&q.played.iter().map(|i| i.item.uuid).collect::<Vec<_>>());
-
-        // let a = q
-        //     .prev()
-        //     .inspect_err(|e| println!("{e:?}"))
-        //     .unwrap()
-        //     .item
-        //     .uuid;
-        // q.dbg_items();
-        // dbg!(a, &q.played.iter().map(|i| i.item.uuid).collect::<Vec<_>>());
     }
 }

From 42ff5b20a8447f775d6c57623753da69be60f0a9 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 28 May 2024 23:12:42 -0500
Subject: [PATCH 127/136] Polished some things

---
 src/music_player/gstreamer.rs | 25 ++++++++++++++-----------
 src/music_storage/library.rs  | 31 +++++++++++++++++--------------
 2 files changed, 31 insertions(+), 25 deletions(-)

diff --git a/src/music_player/gstreamer.rs b/src/music_player/gstreamer.rs
index db35c03..a503ae6 100644
--- a/src/music_player/gstreamer.rs
+++ b/src/music_player/gstreamer.rs
@@ -94,6 +94,7 @@ impl GStreamer {
         }
 
         // Make sure the playback tracker knows the stuff is stopped
+        println!("Beginning switch");
         self.playback_tx.send(PlaybackInfo::Switching).unwrap();
 
         let uri = self.playbin.read().unwrap().property_value("current-uri");
@@ -133,12 +134,11 @@ impl GStreamer {
                     .unwrap()
                     .set_property("uri", source.as_uri());
 
-                self.play().unwrap();
+                if self.state() != PlayerState::Playing {
+                    self.play().unwrap();
+                }
 
-                while uri.get::<&str>().unwrap_or("")
-                    == self.property("current-uri").get::<&str>().unwrap_or("")
-                    || self.position().is_none()
-                {
+                while self.raw_duration().is_none() {
                     std::thread::sleep(std::time::Duration::from_millis(10));
                 }
 
@@ -249,7 +249,7 @@ impl Player for GStreamer {
             .ok_or(PlayerError::Build)?;
 
         playbin.write().unwrap().set_property_from_value("flags", &flags);
-        playbin.write().unwrap().set_property("instant-uri", true);
+        //playbin.write().unwrap().set_property("instant-uri", true);
 
         let position = Arc::new(RwLock::new(None));
 
@@ -258,8 +258,7 @@ impl Player for GStreamer {
         let (status_tx, status_rx) = unbounded::<PlaybackInfo>();
         let position_update = Arc::clone(&position);
 
-        let _playback_monitor =
-            std::thread::spawn(|| playback_monitor(playbin_arc, status_rx, playback_tx, position_update));
+        std::thread::spawn(|| playback_monitor(playbin_arc, status_rx, playback_tx, position_update));
 
         // Set up the thread to monitor bus messages
         let playbin_bus_ctrl = Arc::clone(&playbin);
@@ -477,7 +476,7 @@ fn playback_monitor(
     let mut sent_atf = false;
     loop {
         // Check for new messages to decide how to proceed
-        if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(10)) {
+        if let Ok(result) = status_rx.recv_timeout(std::time::Duration::from_millis(50)) {
             stats = result
         }
 
@@ -490,8 +489,9 @@ fn playback_monitor(
         match stats {
             PlaybackInfo::Playing{start, end} if pos_temp.is_some() => {
                 // Check if the current playback position is close to the end
-                let finish_point = end - Duration::milliseconds(250);
+                let finish_point = end - Duration::milliseconds(2000);
                 if pos_temp.unwrap().num_microseconds() >= end.num_microseconds() {
+                    println!("MONITOR: End of stream");
                     let _ = playback_tx.try_send(PlayerCommand::EndOfStream);
                     playbin
                         .write()
@@ -500,7 +500,9 @@ fn playback_monitor(
                         .expect("Unable to set the pipeline state");
                     sent_atf = false
                 } else if pos_temp.unwrap().num_microseconds() >= finish_point.num_microseconds()
-                    && !sent_atf {
+                    && !sent_atf
+                {
+                    println!("MONITOR: About to finish");
                     let _ = playback_tx.try_send(PlayerCommand::AboutToFinish);
                     sent_atf = true;
                 }
@@ -510,6 +512,7 @@ fn playback_monitor(
                 pos_temp = Some(pos_temp.unwrap() - start)
             },
             PlaybackInfo::Finished => {
+                println!("MONITOR: Shutting down");
                 *position.write().unwrap() = None;
                 break
             },
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 1dd63b5..a2fed42 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -283,8 +283,8 @@ impl Song {
         }
 
         // Find images around the music file that can be used
-        let mut found_images = find_images(target_file.as_ref()).unwrap();
-        album_art.append(&mut found_images);
+        let found_images = find_images(target_file.as_ref()).unwrap();
+        album_art.extend_from_slice(&found_images);
 
         // Get the format as a string
         let format: Option<FileFormat> = match FileFormat::from_file(target_file) {
@@ -624,8 +624,6 @@ impl Album<'_> {
     }
 }
 
-const BLOCKED_EXTENSIONS: [&str; 4] = ["vob", "log", "txt", "sf2"];
-
 #[derive(Debug, Serialize, Deserialize)]
 pub struct MusicLibrary {
     pub name: String,
@@ -636,6 +634,8 @@ pub struct MusicLibrary {
 }
 
 impl MusicLibrary {
+    const BLOCKED_EXTENSIONS: &'static [&'static str] = &["vob", "log", "txt", "sf2"];
+
     /// Create a new library from a name and [Uuid]
     fn new(name: String, uuid: Uuid) -> Self {
         MusicLibrary {
@@ -654,7 +654,7 @@ impl MusicLibrary {
     /// the [MusicLibrary] Vec
     pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
         let global_config = &*config.read().unwrap();
-        let path = global_config.libraries.get_library(&uuid)?.path;
+        let path = global_config.libraries.get_library(&uuid)?.path.clone();
 
         let library: MusicLibrary = match path.exists() {
             true => read_file(path)?,
@@ -671,7 +671,8 @@ impl MusicLibrary {
     }
 
     //#[cfg(debug_assertions)] // We probably wouldn't want to use this for real, but maybe it would have some utility?
-    pub fn from_path(path: PathBuf) -> Result<Self, Box<dyn Error>> {
+    pub fn from_path<P: ?Sized + AsRef<Path>>(path: &P) -> Result<Self, Box<dyn Error>> {
+        let path: PathBuf = path.as_ref().to_path_buf();
         let library: MusicLibrary = match path.exists() {
             true => read_file(path)?,
             false => {
@@ -684,8 +685,8 @@ impl MusicLibrary {
     }
 
     /// Serializes the database out to the file specified in the config
-    pub fn save(&self, config: Config) -> Result<(), Box<dyn Error>> {
-        let path = config.libraries.get_library(&self.uuid)?.path;
+    pub fn save(&self, config: Arc<RwLock<Config>>) -> Result<(), Box<dyn Error>> {
+        let path = config.read().unwrap().libraries.get_library(&self.uuid)?.path.clone();
         match path.try_exists() {
             Ok(_) => write_file(self, path)?,
             Err(error) => return Err(error.into()),
@@ -715,7 +716,7 @@ impl MusicLibrary {
                 for location in &track.location {
                     //TODO: check that this works
                     if path == location {
-                        return std::ops::ControlFlow::Break((track, i));
+                        return Break((track, i));
                     }
                 }
                 Continue(())
@@ -765,7 +766,7 @@ impl MusicLibrary {
     }
 
     /// Finds all the audio files within a specified folder
-    pub fn scan_folder(&mut self, target_path: &str) -> Result<i32, Box<dyn std::error::Error>> {
+    pub fn scan_folder<P: ?Sized + AsRef<Path>>(&mut self, target_path: &P) -> Result<i32, Box<dyn std::error::Error>> {
         let mut total = 0;
         let mut errors = 0;
         for target_file in WalkDir::new(target_path)
@@ -803,27 +804,29 @@ impl MusicLibrary {
             // 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)
-                && !BLOCKED_EXTENSIONS.contains(&extension.as_str())
+                && !Self::BLOCKED_EXTENSIONS.contains(&extension.as_str())
             {
                 match self.add_file(target_file.path()) {
                     Ok(_) => total += 1,
                     Err(_error) => {
                         errors += 1;
-                        //println!("{}, {:?}: {}", format, target_file.file_name(), _error)
+                        println!("{:?}: {}", target_file.file_name(), _error)
                     } // TODO: Handle more of these errors
                 };
             } else if extension == "cue" {
                 total += match self.add_cuesheet(target_file.path()) {
                     Ok(added) => added,
-                    Err(error) => {
+                    Err(_error) => {
                         errors += 1;
-                        //println!("{}", error);
+                        println!("{:?}: {}", target_file.file_name(), _error);
                         0
                     }
                 }
             }
         }
 
+        println!("Total scanning errors: {}", errors);
+
         Ok(total)
     }
 

From 407a1b16bdeb143511f802cf18dfd322e32aa60d Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Tue, 28 May 2024 23:58:56 -0500
Subject: [PATCH 128/136] Modified method on `Player` trait, improved
 documentation

---
 src/music_player/gstreamer.rs | 45 ++++++++++++-----------------------
 src/music_player/player.rs    | 40 ++++++++++++++++++++++++-------
 2 files changed, 46 insertions(+), 39 deletions(-)

diff --git a/src/music_player/gstreamer.rs b/src/music_player/gstreamer.rs
index a503ae6..cefd74e 100644
--- a/src/music_player/gstreamer.rs
+++ b/src/music_player/gstreamer.rs
@@ -213,6 +213,11 @@ impl GStreamer {
     fn property(&self, property: &str) -> glib::Value {
         self.playbin().unwrap().property_value(property)
     }
+
+    fn ready(&mut self) -> Result<(), PlayerError> {
+        self.set_state(gst::State::Ready)?;
+        Ok(())
+    }
 }
 
 impl Player for GStreamer {
@@ -329,62 +334,46 @@ impl Player for GStreamer {
         &self.source
     }
 
-    /// Insert a new track to be played. This method should be called at the
-    /// beginning to start playback of something, and once the [PlayerCommand]
-    /// indicates the track is about to finish to enqueue gaplessly.
     fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
         self.set_source(next_track)
     }
 
-    /// Set the playback volume, accepts a float from 0 to 1
     fn set_volume(&mut self, volume: f64) {
         self.volume = volume.clamp(0.0, 1.0);
         self.set_gstreamer_volume(self.volume);
     }
 
-    /// Returns the current volume level, a float from 0 to 1
-    fn volume(&mut self) -> f64 {
+    fn volume(&self) -> f64 {
         self.volume
     }
 
-    fn ready(&mut self) -> Result<(), PlayerError> {
-        self.set_state(gst::State::Ready)?;
-        Ok(())
-    }
-
-    /// If the player is paused or stopped, starts playback
     fn play(&mut self) -> Result<(), PlayerError> {
+        if self.state() == PlayerState::Playing {
+            return Ok(())
+        }
         *self.paused.write().unwrap() = false;
         self.set_state(gst::State::Playing)?;
         Ok(())
     }
 
-    /// Pause, if playing
     fn pause(&mut self) -> Result<(), PlayerError> {
+        if self.state() == PlayerState::Paused || *self.paused.read().unwrap() {
+            return Ok(())
+        }
         *self.paused.write().unwrap() = true;
         self.set_state(gst::State::Paused)?;
         Ok(())
     }
 
-    /// Resume from being paused
-    fn resume(&mut self) -> Result<(), PlayerError> {
-        *self.paused.write().unwrap() = false;
-        self.set_state(gst::State::Playing)?;
-        Ok(())
-    }
-
-    /// Check if playback is paused
-    fn is_paused(&mut self) -> bool {
+    fn is_paused(&self) -> bool {
         self.playbin().unwrap().current_state() == gst::State::Paused
     }
 
-    /// Get the current playback position of the player
-    fn position(&mut self) -> Option<Duration> {
+    fn position(&self) -> Option<Duration> {
         *self.position.read().unwrap()
     }
 
-    /// Get the duration of the currently playing track
-    fn duration(&mut self) -> Option<Duration> {
+    fn duration(&self) -> Option<Duration> {
         if self.end.is_some() && self.start.is_some() {
             Some(self.end.unwrap() - self.start.unwrap())
         } else {
@@ -392,7 +381,6 @@ impl Player for GStreamer {
         }
     }
 
-    /// Seek relative to the current position
     fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError> {
         let time_pos = match *self.position.read().unwrap() {
             Some(pos) => pos,
@@ -404,7 +392,6 @@ impl Player for GStreamer {
         Ok(())
     }
 
-    /// Seek absolutely
     fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError> {
         let start = if self.start.is_none() {
             return Err(PlayerError::Seek("No START time".into()));
@@ -432,7 +419,6 @@ impl Player for GStreamer {
         Ok(())
     }
 
-    /// Stop the playback entirely
     fn stop(&mut self) -> Result<(), PlayerError> {
         self.pause()?;
         self.ready()?;
@@ -447,7 +433,6 @@ impl Player for GStreamer {
         Ok(())
     }
 
-    /// Return a reference to the player message channel
     fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand> {
         &self.message_rx
     }
diff --git a/src/music_player/player.rs b/src/music_player/player.rs
index 3a9e76e..1bfe18c 100644
--- a/src/music_player/player.rs
+++ b/src/music_player/player.rs
@@ -40,36 +40,58 @@ pub enum PlayerCommand {
 }
 
 pub trait Player {
-    /// Create a new player
+    /// Create a new player.
     fn new() -> Result<Self, PlayerError> where Self: Sized;
 
+    /// Get the currently playing [URI] from the player.
     fn source(&self) -> &Option<URI>;
 
+    /// Insert a new [`URI`] to be played. This method should be called at the
+    /// beginning to start playback of something, and once the [`PlayerCommand`]
+    /// indicates the track is about to finish to enqueue gaplessly.
+    ///
+    /// For backends which do not support gapless playback, `AboutToFinish`
+    /// will not be called, and the next [`URI`] should be enqueued once `Eos`
+    /// occurs.
     fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError>;
 
+    /// Set the playback volume, accepts a float from `0` to `1`.
+    ///
+    /// Values outside the range of `0` to `1` will be capped.
     fn set_volume(&mut self, volume: f64);
 
-    fn volume(&mut self) -> f64;
-
-    fn ready(&mut self) -> Result<(), PlayerError>;
+    /// Returns the current volume level, a float from `0` to `1`.
+    fn volume(&self) -> f64;
 
+    /// If the player is paused or stopped, starts playback.
     fn play(&mut self) -> Result<(), PlayerError>;
 
-    fn resume(&mut self) -> Result<(), PlayerError>;
-
+    /// If the player is playing, pause playback.
     fn pause(&mut self) -> Result<(), PlayerError>;
 
+    /// Stop the playback entirely, removing the current [`URI`] from the player.
     fn stop(&mut self) -> Result<(), PlayerError>;
 
-    fn is_paused(&mut self) -> bool;
+    /// Convenience function to check if playback is paused.
+    fn is_paused(&self) -> bool;
 
-    fn position(&mut self) -> Option<Duration>;
+    /// Get the current playback position of the player.
+    fn position(&self) -> Option<Duration>;
 
-    fn duration(&mut self) -> Option<Duration>;
+    /// Get the duration of the currently playing track.
+    fn duration(&self) -> Option<Duration>;
 
+    /// Seek relative to the current position.
+    ///
+    /// The position is capped at the duration of the song, and zero.
     fn seek_by(&mut self, seek_amount: Duration) -> Result<(), PlayerError>;
 
+    /// Seek absolutely within the song.
+    ///
+    /// The position is capped at the duration of the song, and zero.
     fn seek_to(&mut self, target_pos: Duration) -> Result<(), PlayerError>;
 
+    /// Return a reference to the player message channel, which can be cloned
+    /// in order to monitor messages from the player.
     fn message_channel(&self) -> &crossbeam::channel::Receiver<PlayerCommand>;
 }

From 54704260babbb81eeb79bafc89a4ff99a8effe9a Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Wed, 29 May 2024 01:09:00 -0400
Subject: [PATCH 129/136] Added controller & queue changes

---
 src/music_controller/controller.rs           | 31 +++++++++++++++-----
 src/music_controller/queue.rs                |  2 +-
 src/music_storage/db_reader/itunes/reader.rs |  2 +-
 3 files changed, 26 insertions(+), 9 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index e3f1e68..5664121 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -6,6 +6,7 @@ use crossbeam_channel;
 use crossbeam_channel::{Receiver, Sender};
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
+use std::thread::spawn;
 use thiserror::Error;
 
 use crossbeam_channel::unbounded;
@@ -13,18 +14,18 @@ use std::error::Error;
 use uuid::Uuid;
 
 use crate::config::config::ConfigError;
-use crate::music_player::player::{Player, PlayerError};
+use crate::music_player::player::{Player, PlayerCommand, PlayerError};
 use crate::{
     config::config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
 };
 
 use super::queue::QueueError;
 
-pub struct Controller<P: Player> {
+pub struct Controller<P: Player + Send + Sync> {
     pub queue: Queue,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
-    pub player: Box<P>,
+    pub player: Arc<RwLock<Box<P>>>,
 }
 
 #[derive(Error, Debug)]
@@ -69,7 +70,7 @@ impl<T: Send, U: Send> MailMan<T, U> {
 }
 
 #[allow(unused_variables)]
-impl<P: Player> Controller<P> {
+impl<P: Player + Send + Sync> Controller<P> {
     pub fn start<T>(config_path: T) -> Result<Self, Box<dyn Error>>
     where
         std::path::PathBuf: std::convert::From<T>,
@@ -83,12 +84,28 @@ impl<P: Player> Controller<P> {
         let config_ = Arc::new(RwLock::from(config));
         let library = MusicLibrary::init(config_.clone(), uuid)?;
 
-        Ok(Controller {
+        let controller = Controller {
             queue: Queue::default(),
             config: config_.clone(),
             library,
-            player: Box::new(P::new()?),
-        })
+            player: Arc::new(RwLock::new(Box::new(P::new()?))),
+        };
+
+
+        let player = controller.player.clone();
+        let controler_thread = spawn(move || {
+            match player.read().unwrap().message_channel().recv().unwrap() {
+                PlayerCommand::AboutToFinish => {},
+                PlayerCommand::EndOfStream => {
+
+                    player.write().unwrap().enqueue_next(todo!());
+                },
+                _ => {}
+            }
+        });
+
+
+        Ok(controller)
     }
 
     pub fn q_add(&mut self, item: &Uuid, source: super::queue::PlayerLocation, by_human: bool) {
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index c26ae05..519f8c6 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -59,7 +59,7 @@ pub struct Queue {
     pub items: Vec<QueueItem>,
     pub played: Vec<QueueItem>,
     pub loop_: bool,
-    pub shuffle: bool,
+    pub shuffle: Option<Vec<usize>>,
 }
 
 // TODO: HAndle the First QueueState[looping] and shuffle
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index f6032db..a900fa8 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -353,6 +353,6 @@ mod tests {
         songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
 
         config.write_file().unwrap();
-        library.save(config).unwrap();
+        library.save(Arc::new(RwLock::from(config))).unwrap();
     }
 }

From 0513109de819ac7f4c27291daaec7f9be780a767 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Wed, 29 May 2024 18:40:32 -0500
Subject: [PATCH 130/136] Moved `config` to a `mod.rs` file, made controller
 Player static

---
 src/config/{config.rs => mod.rs}             |  6 +++--
 src/lib.rs                                   |  7 ++---
 src/music_controller/controller.rs           | 28 +++++++++++++-------
 src/music_controller/queue.rs                |  2 +-
 src/music_storage/db_reader/itunes/reader.rs |  2 +-
 src/music_storage/library.rs                 |  4 +--
 src/music_storage/playlist.rs                |  2 +-
 7 files changed, 30 insertions(+), 21 deletions(-)
 rename src/config/{config.rs => mod.rs} (97%)

diff --git a/src/config/config.rs b/src/config/mod.rs
similarity index 97%
rename from src/config/config.rs
rename to src/config/mod.rs
index 18ab894..431498f 100644
--- a/src/config/config.rs
+++ b/src/config/mod.rs
@@ -1,3 +1,5 @@
+pub mod other_settings;
+
 use std::{
     fs::{self, File, OpenOptions},
     io::{Error, Read, Write},
@@ -214,7 +216,7 @@ pub mod tests {
         )
         .unwrap();
         lib.scan_folder("test-config/music/").unwrap();
-        lib.save(config.clone()).unwrap();
+        lib.save(Arc::new(RwLock::new(config.clone()))).unwrap();
 
         (config, lib)
     }
@@ -232,7 +234,7 @@ pub mod tests {
 
         lib.scan_folder("test-config/music/").unwrap();
 
-        lib.save(config.clone()).unwrap();
+        lib.save(Arc::new(RwLock::new(config.clone()))).unwrap();
 
         (config, lib)
     }
diff --git a/src/lib.rs b/src/lib.rs
index 9f6bb0a..6f93ec1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -18,8 +18,5 @@ pub mod music_player {
     pub mod gstreamer;
     pub mod player;
 }
-#[allow(clippy::module_inception)]
-pub mod config {
-    pub mod config;
-    pub mod other_settings;
-}
+
+pub mod config;
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 5664121..9d5c0b4 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -13,10 +13,10 @@ use crossbeam_channel::unbounded;
 use std::error::Error;
 use uuid::Uuid;
 
-use crate::config::config::ConfigError;
+use crate::config::ConfigError;
 use crate::music_player::player::{Player, PlayerCommand, PlayerError};
 use crate::{
-    config::config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
+    config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
 };
 
 use super::queue::QueueError;
@@ -25,7 +25,7 @@ pub struct Controller<P: Player + Send + Sync> {
     pub queue: Queue,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
-    pub player: Arc<RwLock<Box<P>>>,
+    pub player: Arc<RwLock<P>>,
 }
 
 #[derive(Error, Debug)]
@@ -70,7 +70,7 @@ impl<T: Send, U: Send> MailMan<T, U> {
 }
 
 #[allow(unused_variables)]
-impl<P: Player + Send + Sync> Controller<P> {
+impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
     pub fn start<T>(config_path: T) -> Result<Self, Box<dyn Error>>
     where
         std::path::PathBuf: std::convert::From<T>,
@@ -88,12 +88,11 @@ impl<P: Player + Send + Sync> Controller<P> {
             queue: Queue::default(),
             config: config_.clone(),
             library,
-            player: Arc::new(RwLock::new(Box::new(P::new()?))),
+            player: Arc::new(RwLock::new(P::new()?)),
         };
 
-
-        let player = controller.player.clone();
-        let controler_thread = spawn(move || {
+        let player = Arc::clone(&controller.player);
+        let controller_thread = spawn(move || {
             match player.read().unwrap().message_channel().recv().unwrap() {
                 PlayerCommand::AboutToFinish => {},
                 PlayerCommand::EndOfStream => {
@@ -115,4 +114,15 @@ impl<P: Player + Send + Sync> Controller<P> {
 }
 
 #[cfg(test)]
-mod test_super {}
+mod test_super {
+    use crate::{config::tests::read_config_lib, music_player::gstreamer::GStreamer};
+
+    use super::Controller;
+
+    #[test]
+    fn construct_controller() {
+        let config = read_config_lib();
+
+        let controller = Controller::<GStreamer>::start("test-config/config_test.json").unwrap();
+    }
+}
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 519f8c6..a2965c8 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -391,7 +391,7 @@ impl Queue {
 mod test_super {
     #![allow(unused)]
     use crate::{
-        config::config::tests::{new_config_lib, read_config_lib},
+        config::tests::{new_config_lib, read_config_lib},
         music_storage::library,
     };
 
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index a900fa8..98ea1da 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -336,7 +336,7 @@ impl ITunesSong {
 mod tests {
     use std::{path::{Path, PathBuf}, sync::{Arc, RwLock}};
 
-    use crate::{config::config::{Config, ConfigLibrary}, music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary}};
+    use crate::{config::{Config, ConfigLibrary}, music_storage::{db_reader::extern_library::ExternalLibrary, library::MusicLibrary}};
 
     use super::ITunesLibrary;
 
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index a2fed42..ae189f4 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -1,7 +1,7 @@
 use super::playlist::PlaylistFolder;
 // Crate things
 use super::utils::{find_images, normalize, read_file, write_file};
-use crate::config::config::Config;
+use crate::config::Config;
 
 // Various std things
 use std::collections::BTreeMap;
@@ -1127,7 +1127,7 @@ mod test {
         sync::{Arc, RwLock},
     };
 
-    use crate::{config::config::Config, music_storage::library::MusicLibrary};
+    use crate::{config::{tests::new_config_lib, Config}, music_storage::library::MusicLibrary};
 
     #[test]
     fn library_init() {
diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs
index 1f04860..c4c0d8c 100644
--- a/src/music_storage/playlist.rs
+++ b/src/music_storage/playlist.rs
@@ -315,7 +315,7 @@ impl Default for Playlist {
 #[cfg(test)]
 mod test_super {
     use super::*;
-    use crate::config::config::tests::read_config_lib;
+    use crate::config::tests::read_config_lib;
 
     #[test]
     fn list_to_m3u8() {

From 683b695bc6d033c5cd7f9ff74156a58fdf40a138 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Thu, 30 May 2024 01:20:04 -0400
Subject: [PATCH 131/136] added controller changes

---
 .gitignore                         |  1 +
 src/music_controller/controller.rs | 80 +++++++++++++++++++++++-------
 src/music_controller/queue.rs      |  2 +-
 src/music_player/gstreamer.rs      |  1 +
 4 files changed, 66 insertions(+), 18 deletions(-)

diff --git a/.gitignore b/.gitignore
index 7a0594f..b2d0597 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ music_database*
 *.json
 *.zip
 *.xml
+
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 9d5c0b4..22109cc 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -5,8 +5,9 @@
 use crossbeam_channel;
 use crossbeam_channel::{Receiver, Sender};
 use std::path::PathBuf;
-use std::sync::{Arc, RwLock};
-use std::thread::spawn;
+use std::sync::{Arc, Mutex, RwLock};
+use std::thread::{sleep, spawn};
+use std::time::Duration;
 use thiserror::Error;
 
 use crossbeam_channel::unbounded;
@@ -22,10 +23,10 @@ use crate::{
 use super::queue::QueueError;
 
 pub struct Controller<P: Player + Send + Sync> {
-    pub queue: Queue,
+    pub queue: Arc<RwLock<Queue>>,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
-    pub player: Arc<RwLock<P>>,
+    pub player: Arc<Mutex<P>>,
 }
 
 #[derive(Error, Debug)]
@@ -85,22 +86,43 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
         let library = MusicLibrary::init(config_.clone(), uuid)?;
 
         let controller = Controller {
-            queue: Queue::default(),
+            queue: Arc::new(RwLock::from(Queue::default())),
             config: config_.clone(),
             library,
-            player: Arc::new(RwLock::new(P::new()?)),
+            player: Arc::new(Mutex::new(P::new()?)),
         };
 
-        let player = Arc::clone(&controller.player);
-        let controller_thread = spawn(move || {
-            match player.read().unwrap().message_channel().recv().unwrap() {
-                PlayerCommand::AboutToFinish => {},
-                PlayerCommand::EndOfStream => {
 
-                    player.write().unwrap().enqueue_next(todo!());
-                },
-                _ => {}
+        let player = controller.player.clone();
+        let queue = controller.queue.clone();
+        let controller_thread = spawn(move || {
+            loop {
+                let signal = { player.lock().unwrap().message_channel().recv().unwrap() };
+                match signal {
+                    PlayerCommand::AboutToFinish => {
+                        println!("Switching songs!");
+
+                        let mut queue = queue.write().unwrap();
+
+                        let uri = queue
+                                .next()
+                                .unwrap()
+                                .clone();
+
+                        player
+                            .lock()
+                            .unwrap()
+                            .enqueue_next(uri.item
+                                .primary_uri()
+                                .unwrap()
+                                .0)
+                            .unwrap();
+                    },
+                    PlayerCommand::EndOfStream => {dbg!()}
+                    _ => {}
+                }
             }
+
         });
 
 
@@ -109,20 +131,44 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
 
     pub fn q_add(&mut self, item: &Uuid, source: super::queue::PlayerLocation, by_human: bool) {
         let item = self.library.query_uuid(item).unwrap().0.to_owned();
-        self.queue.add_item(item, source, by_human)
+        self.queue.write().unwrap().add_item(item, source, by_human)
     }
 }
 
 #[cfg(test)]
 mod test_super {
-    use crate::{config::tests::read_config_lib, music_player::gstreamer::GStreamer};
+    use std::{thread::sleep, time::Duration};
+
+    use crate::{config::tests::read_config_lib, music_controller::queue::PlayerLocation, music_player::{gstreamer::GStreamer, player::Player}};
 
     use super::Controller;
 
     #[test]
     fn construct_controller() {
+        println!("starto!");
         let config = read_config_lib();
 
-        let controller = Controller::<GStreamer>::start("test-config/config_test.json").unwrap();
+        let next = config.1.library[2].clone();
+        {
+            let controller = Controller::<GStreamer>::start("test-config/config_test.json").unwrap();
+            {
+                let mut queue = controller.queue.write().unwrap();
+                for x in config.1.library {
+                    queue.add_item(x, PlayerLocation::Library, true);
+                }
+            }
+            {
+                controller.player.lock().unwrap().enqueue_next(next.primary_uri().unwrap().0).unwrap();
+            }
+            {
+                controller.player.lock().unwrap().set_volume(0.2);
+            }
+            {
+                controller.player.lock().unwrap().play().unwrap();
+            }
+            println!("I'm a tire");
+        }
+        sleep(Duration::from_secs(600))
+
     }
 }
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index a2965c8..3acdd7a 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -74,7 +74,7 @@ impl Queue {
     }
 
     #[allow(unused)]
-    fn dbg_items(&self) {
+    pub(super) fn dbg_items(&self) {
         dbg!(
             self.items
                 .iter()
diff --git a/src/music_player/gstreamer.rs b/src/music_player/gstreamer.rs
index cefd74e..5fa44bc 100644
--- a/src/music_player/gstreamer.rs
+++ b/src/music_player/gstreamer.rs
@@ -335,6 +335,7 @@ impl Player for GStreamer {
     }
 
     fn enqueue_next(&mut self, next_track: &URI) -> Result<(), PlayerError> {
+        println!("enqueuing in fn");
         self.set_source(next_track)
     }
 

From 81ccab01f1cb4c029b81a37413aa1f8b1d8d7154 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 2 Jun 2024 23:51:59 -0400
Subject: [PATCH 132/136] implemented Queue changes with kushi crate

---
 Cargo.toml                         |  1 +
 src/lib.rs                         |  2 +-
 src/music_controller/controller.rs | 58 +++++++++++++++++++++---------
 src/music_controller/queue.rs      | 11 +-----
 src/music_storage/library.rs       |  4 +++
 5 files changed, 48 insertions(+), 28 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 08c25dc..c12abb7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,3 +48,4 @@ tempfile = "3.10.1"
 listenbrainz = "0.7.0"
 discord-rpc-client = "0.4.0"
 nestify = "0.3.3"
+kushi = "0.1.1"
diff --git a/src/lib.rs b/src/lib.rs
index 6f93ec1..d25f68f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,7 +11,7 @@ pub mod music_storage {
 pub mod music_controller {
     pub mod controller;
     pub mod connections;
-    pub mod queue;
+    // pub mod queue;
 }
 
 pub mod music_player {
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 22109cc..8c64baa 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -4,6 +4,9 @@
 
 use crossbeam_channel;
 use crossbeam_channel::{Receiver, Sender};
+use kushi::error::QueueError;
+use kushi::traits::Location;
+use kushi::{Queue, QueueItemType};
 use std::path::PathBuf;
 use std::sync::{Arc, Mutex, RwLock};
 use std::thread::{sleep, spawn};
@@ -16,14 +19,13 @@ use uuid::Uuid;
 
 use crate::config::ConfigError;
 use crate::music_player::player::{Player, PlayerCommand, PlayerError};
+use crate::music_storage::library::{Album, Song};
 use crate::{
-    config::Config, music_controller::queue::Queue, music_storage::library::MusicLibrary,
+    config::Config, music_storage::library::MusicLibrary,
 };
 
-use super::queue::QueueError;
-
-pub struct Controller<P: Player + Send + Sync> {
-    pub queue: Arc<RwLock<Queue>>,
+pub struct Controller<'a, P: Player + Send + Sync> {
+    pub queue: Arc<RwLock<Queue<Song, Album<'a>, PlayerLocation>>>,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
     pub player: Arc<Mutex<P>>,
@@ -39,6 +41,19 @@ pub enum ControllerError {
     ConfigError(#[from] ConfigError),
 }
 
+// TODO: move this to a different location to be used elsewhere
+#[derive(Debug, Clone, Copy, PartialEq)]
+#[non_exhaustive]
+pub enum PlayerLocation {
+    Test,
+    Library,
+    Playlist(Uuid),
+    File,
+    Custom,
+}
+
+impl Location for PlayerLocation {}
+
 #[derive(Debug)]
 pub(super) struct MailMan<T: Send, U: Send> {
     pub tx: Sender<T>,
@@ -71,8 +86,8 @@ impl<T: Send, U: Send> MailMan<T, U> {
 }
 
 #[allow(unused_variables)]
-impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
-    pub fn start<T>(config_path: T) -> Result<Self, Box<dyn Error>>
+impl<P: Player + Send + Sync + Sized + 'static> Controller<'static, P> {
+    pub fn start<T>(config_path: T) -> Result <Self, Box<dyn Error>>
     where
         std::path::PathBuf: std::convert::From<T>,
         P: Player,
@@ -85,8 +100,15 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
         let config_ = Arc::new(RwLock::from(config));
         let library = MusicLibrary::init(config_.clone(), uuid)?;
 
+        let queue: Queue<Song, Album, PlayerLocation> = Queue {
+            items: Vec::new(),
+            played: Vec::new(),
+            loop_: false,
+            shuffle: None
+        };
+
         let controller = Controller {
-            queue: Arc::new(RwLock::from(Queue::default())),
+            queue: Arc::new(RwLock::from(queue)),
             config: config_.clone(),
             library,
             player: Arc::new(Mutex::new(P::new()?)),
@@ -112,10 +134,12 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
                         player
                             .lock()
                             .unwrap()
-                            .enqueue_next(uri.item
-                                .primary_uri()
-                                .unwrap()
-                                .0)
+                            .enqueue_next(&{
+                                match uri.item {
+                                    QueueItemType::Single(song) => song.primary_uri().unwrap().0.clone(),
+                                    _ => unimplemented!()
+                                }
+                            })
                             .unwrap();
                     },
                     PlayerCommand::EndOfStream => {dbg!()}
@@ -129,7 +153,7 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
         Ok(controller)
     }
 
-    pub fn q_add(&mut self, item: &Uuid, source: super::queue::PlayerLocation, by_human: bool) {
+    pub fn q_add(&mut self, item: &Uuid, source: Option<PlayerLocation>, by_human: bool) {
         let item = self.library.query_uuid(item).unwrap().0.to_owned();
         self.queue.write().unwrap().add_item(item, source, by_human)
     }
@@ -139,7 +163,7 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
 mod test_super {
     use std::{thread::sleep, time::Duration};
 
-    use crate::{config::tests::read_config_lib, music_controller::queue::PlayerLocation, music_player::{gstreamer::GStreamer, player::Player}};
+    use crate::{config::tests::read_config_lib, music_controller::controller::PlayerLocation, music_player::{gstreamer::GStreamer, player::Player}};
 
     use super::Controller;
 
@@ -154,21 +178,21 @@ mod test_super {
             {
                 let mut queue = controller.queue.write().unwrap();
                 for x in config.1.library {
-                    queue.add_item(x, PlayerLocation::Library, true);
+                    queue.add_item(x, Some(PlayerLocation::Library), true);
                 }
             }
             {
                 controller.player.lock().unwrap().enqueue_next(next.primary_uri().unwrap().0).unwrap();
             }
             {
-                controller.player.lock().unwrap().set_volume(0.2);
+                controller.player.lock().unwrap().set_volume(0.1);
             }
             {
                 controller.player.lock().unwrap().play().unwrap();
             }
             println!("I'm a tire");
         }
-        sleep(Duration::from_secs(600))
+        sleep(Duration::from_secs(10))
 
     }
 }
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index 3acdd7a..d13ab35 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -23,16 +23,7 @@ pub enum QueueState {
     NoState,
 }
 
-// TODO: move this to a different location to be used elsewhere
-#[derive(Debug, Clone, Copy, PartialEq)]
-#[non_exhaustive]
-pub enum PlayerLocation {
-    Test,
-    Library,
-    Playlist(Uuid),
-    File,
-    Custom,
-}
+
 
 #[derive(Debug, Clone, PartialEq)]
 #[non_exhaustive]
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index ae189f4..022e1de 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -11,6 +11,7 @@ use std::ops::ControlFlow::{Break, Continue};
 // Files
 use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
+use kushi::traits::TrackGroup;
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
 use std::fs;
@@ -624,6 +625,9 @@ impl Album<'_> {
     }
 }
 
+impl TrackGroup for Album<'_> {}
+
+
 #[derive(Debug, Serialize, Deserialize)]
 pub struct MusicLibrary {
     pub name: String,

From 47483127ed4830053f38757080997b4bbb900640 Mon Sep 17 00:00:00 2001
From: G2-Games <ke0bhogsg@gmail.com>
Date: Fri, 28 Jun 2024 22:20:13 -0500
Subject: [PATCH 133/136] Made albums own their contents

---
 src/music_controller/controller.rs |   9 +-
 src/music_storage/library.rs       | 140 ++++++++++++++---------------
 src/music_storage/utils.rs         |  12 +--
 3 files changed, 78 insertions(+), 83 deletions(-)

diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 8c64baa..caf6581 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -9,8 +9,7 @@ use kushi::traits::Location;
 use kushi::{Queue, QueueItemType};
 use std::path::PathBuf;
 use std::sync::{Arc, Mutex, RwLock};
-use std::thread::{sleep, spawn};
-use std::time::Duration;
+use std::thread::spawn;
 use thiserror::Error;
 
 use crossbeam_channel::unbounded;
@@ -24,8 +23,8 @@ use crate::{
     config::Config, music_storage::library::MusicLibrary,
 };
 
-pub struct Controller<'a, P: Player + Send + Sync> {
-    pub queue: Arc<RwLock<Queue<Song, Album<'a>, PlayerLocation>>>,
+pub struct Controller<P: Player + Send + Sync> {
+    pub queue: Arc<RwLock<Queue<Song, Album, PlayerLocation>>>,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
     pub player: Arc<Mutex<P>>,
@@ -86,7 +85,7 @@ impl<T: Send, U: Send> MailMan<T, U> {
 }
 
 #[allow(unused_variables)]
-impl<P: Player + Send + Sync + Sized + 'static> Controller<'static, P> {
+impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
     pub fn start<T>(config_path: T) -> Result <Self, Box<dyn Error>>
     where
         std::path::PathBuf: std::convert::From<T>,
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 022e1de..84867df 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -4,7 +4,7 @@ use super::utils::{find_images, normalize, read_file, write_file};
 use crate::config::Config;
 
 // Various std things
-use std::collections::BTreeMap;
+use std::collections::{BTreeMap, HashMap};
 use std::error::Error;
 use std::ops::ControlFlow::{Break, Continue};
 
@@ -320,7 +320,6 @@ impl Song {
     }
 
     /// creates a `Vec<Song>` from a cue file
-
     pub fn from_cue(cuesheet: &Path) -> Result<Vec<(Self, PathBuf)>, Box<dyn Error>> {
         let mut tracks = Vec::new();
 
@@ -574,43 +573,43 @@ pub enum Service {
 }
 
 #[derive(Clone, Debug, PartialEq)]
-pub struct Album<'a> {
-    title: &'a String,
-    artist: Option<&'a String>,
-    cover: Option<&'a AlbumArt>,
-    discs: BTreeMap<usize, Vec<&'a Song>>,
+pub struct Album {
+    title: String,
+    artist: Option<String>,
+    cover: Option<AlbumArt>,
+    discs: BTreeMap<u16, Vec<Uuid>>,
 }
 
 #[allow(clippy::len_without_is_empty)]
-impl Album<'_> {
+impl Album {
     //returns the Album title
     pub fn title(&self) -> &String {
-        self.title
+        &self.title
     }
 
     /// Returns the album cover as an AlbumArt struct, if it exists
-    fn cover(&self) -> Option<&AlbumArt> {
-        self.cover
+    fn cover(&self) -> &Option<AlbumArt> {
+        &self.cover
     }
 
     /// Returns the Album Artist, if they exist
-    pub fn artist(&self) -> Option<&String> {
-        self.artist
+    pub fn artist(&self) -> &Option<String> {
+        &self.artist
     }
 
-    pub fn discs(&self) -> &BTreeMap<usize, Vec<&Song>> {
+    pub fn discs(&self) -> &BTreeMap<u16, Vec<Uuid>> {
         &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])
+    pub fn track(&self, disc: u16, index: usize) -> Option<&Uuid> {
+        self.discs.get(&disc)?.get(index)
     }
 
-    fn tracks(&self) -> Vec<&Song> {
+    fn tracks(&self) -> Vec<Uuid> {
         let mut songs = Vec::new();
-        for disc in &self.discs {
-            songs.append(&mut disc.1.clone())
+        for disc in self.discs.values() {
+            songs.extend_from_slice(&disc)
         }
         songs
     }
@@ -618,15 +617,14 @@ impl Album<'_> {
     /// 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();
+        for disc in self.discs.values() {
+            total += disc.len();
         }
         total
     }
 }
 
-impl TrackGroup for Album<'_> {}
-
+impl TrackGroup for Album {}
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct MusicLibrary {
@@ -688,6 +686,17 @@ impl MusicLibrary {
         Ok(library)
     }
 
+    /// Serializes the database out to the file specified in the config
+    pub fn save_path<P: ?Sized + AsRef<Path>>(&self, path: &P) -> Result<(), Box<dyn Error>> {
+        let path = path.as_ref();
+        match path.try_exists() {
+            Ok(_) => write_file(self, path)?,
+            Err(error) => return Err(error.into()),
+        }
+
+        Ok(())
+    }
+
     /// Serializes the database out to the file specified in the config
     pub fn save(&self, config: Arc<RwLock<Config>>) -> Result<(), Box<dyn Error>> {
         let path = config.read().unwrap().libraries.get_library(&self.uuid)?.path.clone();
@@ -711,6 +720,7 @@ impl MusicLibrary {
 
     /// Queries for a [Song] by its [URI], returning a single `Song`
     /// with the `URI` that matches along with its position in the library
+    #[inline(always)]
     pub fn query_uri(&self, path: &URI) -> Option<(&Song, usize)> {
         let result = self
             .library
@@ -759,9 +769,10 @@ impl MusicLibrary {
         self.library.par_iter().for_each(|track| {
             if path == track.primary_uri().unwrap().0.path() {
                 //TODO: make this also not unwrap
-                result.clone().lock().unwrap().push(track);
+                Arc::clone(&result).lock().unwrap().push(track);
             }
         });
+
         if result.lock().unwrap().len() > 0 {
             Some(Arc::try_unwrap(result).unwrap().into_inner().unwrap())
         } else {
@@ -786,19 +797,11 @@ 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;
             }
 
-            // Save periodically while scanning
-            i += 1;
-            if i % 500 == 0 {
-                self.save(config).unwrap();
-            }
-            */
-
             let format = FileFormat::from_file(path)?;
             let extension = match path.extension() {
                 Some(ext) => ext.to_string_lossy().to_ascii_lowercase(),
@@ -834,6 +837,22 @@ impl MusicLibrary {
         Ok(total)
     }
 
+    pub fn remove_missing(&mut self) {
+        let target_removals = Arc::new(Mutex::new(Vec::new()));
+        self.library.par_iter().for_each(|t|{
+            for location in &t.location {
+                if !location.exists().unwrap() {
+                    Arc::clone(&target_removals).lock().unwrap().push(location.clone());
+                }
+            }
+        });
+
+        let target_removals = Arc::try_unwrap(target_removals).unwrap().into_inner().unwrap();
+        for location in target_removals {
+            self.remove_uri(&location).unwrap();
+        }
+    }
+
     pub fn add_file(&mut self, target_file: &Path) -> Result<(), Box<dyn Error>> {
         let new_song = Song::from_file(target_file)?;
         match self.add_song(new_song) {
@@ -1038,65 +1057,42 @@ impl MusicLibrary {
     /// 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,
+        for song in &self.library {
+            let album_title = match song.get_tag(&Tag::Album) {
+                Some(title) => title.clone(),
                 None => continue,
             };
-            let norm_title = normalize(title);
+            //let norm_title = normalize(&album_title);
 
-            let disc_num = result
+            let disc_num = song
                 .get_tag(&Tag::Disk)
                 .unwrap_or(&"".to_string())
-                .parse::<usize>()
+                .parse::<u16>()
                 .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
+            match albums.get_mut(&album_title) {
+                // If the album is in the list, add the track to the appropriate disc within the album
                 Some(album) => match album.discs.get_mut(&disc_num) {
-                    Some(disc) => disc.push(result),
+                    Some(disc) => disc.push(song.uuid),
                     None => {
-                        album.discs.insert(disc_num, vec![result]);
+                        album.discs.insert(disc_num, vec![song.uuid]);
                     }
                 },
-                // If the album is not in the list, make a new one and add it
+                // If the album is not in the list, make it new one and add it
                 None => {
-                    let album_art = result.album_art.first();
+                    let album_art = song.album_art.first();
 
                     let new_album = Album {
-                        title,
-                        artist: result.get_tag(&Tag::AlbumArtist),
-                        discs: BTreeMap::from([(disc_num, vec![result])]),
-                        cover: album_art,
+                        title: album_title.clone(),
+                        artist: song.get_tag(&Tag::AlbumArtist).cloned(),
+                        discs: BTreeMap::from([(disc_num, vec![song.uuid])]),
+                        cover: album_art.cloned(),
                     };
-                    albums.insert(norm_title, new_album);
+                    albums.insert(album_title, new_album);
                 }
             }
         }
 
-        // 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| {
-                    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().to_string());
-                        let path_b = PathBuf::from(b.get_field("location").unwrap().to_string());
-
-                        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 e29a6e0..b1171da 100644
--- a/src/music_storage/utils.rs
+++ b/src/music_storage/utils.rs
@@ -37,13 +37,13 @@ pub(super) fn write_file<
     writer_name.set_extension("tmp");
 
     // Create a new BufWriter on the file and a snap frame encoder
-    let writer = BufWriter::new(File::create(&writer_name)?);
-    let mut e = snap::write::FrameEncoder::new(writer);
+    let mut writer = BufWriter::new(File::create(&writer_name)?);
+    //let mut e = snap::write::FrameEncoder::new(writer);
 
     // Write out the data
     bincode::serde::encode_into_std_write(
         library,
-        &mut e,
+        &mut writer,
         bincode::config::standard()
             .with_little_endian()
             .with_variable_int_encoding(),
@@ -59,12 +59,12 @@ pub(super) fn read_file<T: for<'de> serde::Deserialize<'de>>(
     path: PathBuf,
 ) -> Result<T, Box<dyn Error>> {
     // Create a new snap reader over the file
-    let file_reader = BufReader::new(File::open(path)?);
-    let mut d = snap::read::FrameDecoder::new(file_reader);
+    let mut file_reader = BufReader::new(File::open(path)?);
+    //let mut d = snap::read::FrameDecoder::new(file_reader);
 
     // Decode the library from the serialized data into the vec
     let library: T = bincode::serde::decode_from_std_read(
-        &mut d,
+        &mut file_reader,
         bincode::config::standard()
             .with_little_endian()
             .with_variable_int_encoding(),

From 67f2385c9d782153c00ea1b61b3f9b12ff71fc8d Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sat, 29 Jun 2024 17:12:50 -0400
Subject: [PATCH 134/136] Updated the Queue, added IntoIterator and sorting for
 Albums

---
 Cargo.toml                         |   2 +-
 src/music_controller/controller.rs |   2 +-
 src/music_storage/library.rs       | 116 ++++++++++++++++++++++++++---
 3 files changed, 108 insertions(+), 12 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index c12abb7..d810a1e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,4 +48,4 @@ tempfile = "3.10.1"
 listenbrainz = "0.7.0"
 discord-rpc-client = "0.4.0"
 nestify = "0.3.3"
-kushi = "0.1.1"
+kushi = "0.1.2"
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index caf6581..97af6e1 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -5,7 +5,7 @@
 use crossbeam_channel;
 use crossbeam_channel::{Receiver, Sender};
 use kushi::error::QueueError;
-use kushi::traits::Location;
+use kushi::Location;
 use kushi::{Queue, QueueItemType};
 use std::path::PathBuf;
 use std::sync::{Arc, Mutex, RwLock};
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index 84867df..f7d3e81 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -3,15 +3,17 @@ use super::playlist::PlaylistFolder;
 use super::utils::{find_images, normalize, read_file, write_file};
 use crate::config::Config;
 
+use std::cmp::Ordering;
 // Various std things
 use std::collections::{BTreeMap, HashMap};
 use std::error::Error;
 use std::ops::ControlFlow::{Break, Continue};
+use std::vec::IntoIter;
 
 // Files
 use file_format::{FileFormat, Kind};
 use glib::filename_to_uri;
-use kushi::traits::TrackGroup;
+
 use lofty::{AudioFile, ItemKey, ItemValue, ParseOptions, Probe, TagType, TaggedFileExt};
 use rcue::parser::parse_from_file;
 use std::fs;
@@ -577,7 +579,7 @@ pub struct Album {
     title: String,
     artist: Option<String>,
     cover: Option<AlbumArt>,
-    discs: BTreeMap<u16, Vec<Uuid>>,
+    discs: BTreeMap<u16, Vec<(u16, Uuid)>>,
 }
 
 #[allow(clippy::len_without_is_empty)]
@@ -597,16 +599,16 @@ impl Album {
         &self.artist
     }
 
-    pub fn discs(&self) -> &BTreeMap<u16, Vec<Uuid>> {
+    pub fn discs(&self) -> &BTreeMap<u16, Vec<(u16, Uuid)>> {
         &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: u16, index: usize) -> Option<&Uuid> {
+    pub fn track(&self, disc: u16, index: usize) -> Option<&(u16, Uuid)> {
         self.discs.get(&disc)?.get(index)
     }
 
-    fn tracks(&self) -> Vec<Uuid> {
+    fn tracks(&self) -> Vec<(u16, Uuid)> {
         let mut songs = Vec::new();
         for disc in self.discs.values() {
             songs.extend_from_slice(&disc)
@@ -624,7 +626,50 @@ impl Album {
     }
 }
 
-impl TrackGroup for Album {}
+impl IntoIterator for Album {
+    type Item = AlbumTrack;
+    type IntoIter = IntoIter<Self::Item>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        let mut vec = vec![];
+
+        for (disc, mut tracks) in self.discs {
+            tracks.par_sort_by(|a, b| a.0.cmp(&b.0));
+
+            let mut tracks = tracks.into_iter()
+                .map(|(track, uuid)|
+                AlbumTrack {
+                    disc,
+                    track,
+                    uuid
+                })
+                .collect::<Vec<_>>();
+
+                vec.append(&mut tracks);
+        }
+        vec.into_iter()
+    }
+}
+
+pub struct AlbumTrack {
+    disc: u16,
+    track: u16,
+    uuid: Uuid
+}
+
+impl AlbumTrack {
+    pub fn disc(&self) -> &u16 {
+        &self.disc
+    }
+
+    pub fn track(&self) -> &u16 {
+        &self.track
+    }
+
+    pub fn uuid(&self) -> &Uuid {
+        &self.uuid
+    }
+}
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct MusicLibrary {
@@ -1056,6 +1101,8 @@ impl MusicLibrary {
 
     /// Generates all albums from the track list
     pub fn albums(&self) -> BTreeMap<String, Album> {
+        let mut paths = BTreeMap::new();
+
         let mut albums: BTreeMap<String, Album> = BTreeMap::new();
         for song in &self.library {
             let album_title = match song.get_tag(&Tag::Album) {
@@ -1073,26 +1120,75 @@ impl MusicLibrary {
             match albums.get_mut(&album_title) {
                 // If the album is in the list, add the track to the appropriate disc within the album
                 Some(album) => match album.discs.get_mut(&disc_num) {
-                    Some(disc) => disc.push(song.uuid),
+                    Some(disc) => disc.push((
+                        song.get_tag(&Tag::Track)
+                            .unwrap_or(&String::new())
+                            .parse::<u16>()
+                            .unwrap_or_default(),
+                        song.uuid
+                    )),
                     None => {
-                        album.discs.insert(disc_num, vec![song.uuid]);
+                        album.discs.insert(disc_num, vec![(
+                            song.get_tag(&Tag::Track)
+                                .unwrap_or(&String::new())
+                                .parse::<u16>()
+                                .unwrap_or_default(),
+                            song.uuid
+                        )]);
                     }
                 },
                 // If the album is not in the list, make it new one and add it
                 None => {
                     let album_art = song.album_art.first();
-
                     let new_album = Album {
                         title: album_title.clone(),
                         artist: song.get_tag(&Tag::AlbumArtist).cloned(),
-                        discs: BTreeMap::from([(disc_num, vec![song.uuid])]),
+                        discs: BTreeMap::from([(
+                            disc_num,
+                            vec![(
+                                song.get_tag(&Tag::Track)
+                                    .unwrap_or(&String::new())
+                                    .parse::<u16>()
+                                    .unwrap_or_default(),
+                                song.uuid
+                            )])]),
                         cover: album_art.cloned(),
                     };
                     albums.insert(album_title, new_album);
                 }
+
             }
+            paths.insert(song.uuid, song.primary_uri().unwrap());
         }
 
+        // Sort the tracks in each disk in each album
+        albums.par_iter_mut().for_each(|album| {
+            for disc in &mut album.1.discs {
+                disc.1.sort_by(|a, b| {
+                    let num_a = a.0;
+                    let num_b = b.0;
+
+                    if (num_a, num_b) != (0,0)
+                    {
+                        // If parsing the track numbers succeeds, compare as numbers
+                        num_a.cmp(&num_b)
+                    } else {
+                        // If parsing doesn't succeed, compare the locations
+                        let a = match paths.get_key_value(&a.1) {
+                            Some((_, (uri, _))) => uri,
+                            None => return Ordering::Equal
+                        };
+                        let b = match paths.get_key_value(&b.1) {
+                            Some((_, (uri, _))) => uri,
+                            None => return Ordering::Equal
+                        };
+
+                        a.as_uri().cmp(&b.as_uri())
+                    }
+                });
+            }
+        });
+
         // Return the albums!
         albums
     }

From e97075401d02e970d0d6ce59523796ccad362a2d Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Sun, 7 Jul 2024 19:27:13 -0400
Subject: [PATCH 135/136] updated Kushi, mode changes to the moved location
 field to wrapper structs rather than as part of the queue

---
 Cargo.toml                         |   2 +-
 src/lib.rs                         |   2 +-
 src/music_controller/controller.rs |  23 +-
 src/music_controller/queue.rs      | 412 ++---------------------------
 4 files changed, 28 insertions(+), 411 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index d810a1e..b4ab8d0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,4 +48,4 @@ tempfile = "3.10.1"
 listenbrainz = "0.7.0"
 discord-rpc-client = "0.4.0"
 nestify = "0.3.3"
-kushi = "0.1.2"
+kushi = "0.1.3"
diff --git a/src/lib.rs b/src/lib.rs
index d25f68f..6f93ec1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -11,7 +11,7 @@ pub mod music_storage {
 pub mod music_controller {
     pub mod controller;
     pub mod connections;
-    // pub mod queue;
+    pub mod queue;
 }
 
 pub mod music_player {
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 97af6e1..74c3c62 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -4,8 +4,7 @@
 
 use crossbeam_channel;
 use crossbeam_channel::{Receiver, Sender};
-use kushi::error::QueueError;
-use kushi::Location;
+use kushi::QueueError;
 use kushi::{Queue, QueueItemType};
 use std::path::PathBuf;
 use std::sync::{Arc, Mutex, RwLock};
@@ -18,13 +17,15 @@ use uuid::Uuid;
 
 use crate::config::ConfigError;
 use crate::music_player::player::{Player, PlayerCommand, PlayerError};
-use crate::music_storage::library::{Album, Song};
 use crate::{
     config::Config, music_storage::library::MusicLibrary,
 };
 
+use super::queue::{QueueAlbum, QueueSong};
+
+
 pub struct Controller<P: Player + Send + Sync> {
-    pub queue: Arc<RwLock<Queue<Song, Album, PlayerLocation>>>,
+    pub queue: Arc<RwLock<Queue<QueueSong, QueueAlbum>>>,
     pub config: Arc<RwLock<Config>>,
     pub library: MusicLibrary,
     pub player: Arc<Mutex<P>>,
@@ -51,8 +52,6 @@ pub enum PlayerLocation {
     Custom,
 }
 
-impl Location for PlayerLocation {}
-
 #[derive(Debug)]
 pub(super) struct MailMan<T: Send, U: Send> {
     pub tx: Sender<T>,
@@ -99,7 +98,7 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
         let config_ = Arc::new(RwLock::from(config));
         let library = MusicLibrary::init(config_.clone(), uuid)?;
 
-        let queue: Queue<Song, Album, PlayerLocation> = Queue {
+        let queue: Queue<QueueSong, QueueAlbum> = Queue {
             items: Vec::new(),
             played: Vec::new(),
             loop_: false,
@@ -135,7 +134,7 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
                             .unwrap()
                             .enqueue_next(&{
                                 match uri.item {
-                                    QueueItemType::Single(song) => song.primary_uri().unwrap().0.clone(),
+                                    QueueItemType::Single(song) => song.song.primary_uri().unwrap().0.clone(),
                                     _ => unimplemented!()
                                 }
                             })
@@ -152,9 +151,9 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
         Ok(controller)
     }
 
-    pub fn q_add(&mut self, item: &Uuid, source: Option<PlayerLocation>, by_human: bool) {
+    pub fn q_add(&mut self, item: &Uuid, source: PlayerLocation, by_human: bool) {
         let item = self.library.query_uuid(item).unwrap().0.to_owned();
-        self.queue.write().unwrap().add_item(item, source, by_human)
+        self.queue.write().unwrap().add_item(QueueSong { song: item, location: source }, by_human)
     }
 }
 
@@ -162,7 +161,7 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
 mod test_super {
     use std::{thread::sleep, time::Duration};
 
-    use crate::{config::tests::read_config_lib, music_controller::controller::PlayerLocation, music_player::{gstreamer::GStreamer, player::Player}};
+    use crate::{config::tests::read_config_lib, music_controller::controller::{PlayerLocation, QueueSong}, music_player::{gstreamer::GStreamer, player::Player}};
 
     use super::Controller;
 
@@ -177,7 +176,7 @@ mod test_super {
             {
                 let mut queue = controller.queue.write().unwrap();
                 for x in config.1.library {
-                    queue.add_item(x, Some(PlayerLocation::Library), true);
+                    queue.add_item(QueueSong { song: x, location: PlayerLocation::Library }, true);
                 }
             }
             {
diff --git a/src/music_controller/queue.rs b/src/music_controller/queue.rs
index d13ab35..27f8075 100644
--- a/src/music_controller/queue.rs
+++ b/src/music_controller/queue.rs
@@ -1,407 +1,25 @@
-use crate::music_storage::library::Song;
-use uuid::Uuid;
-
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum QueueError {
-    #[error("Index out of bounds! Index {index} is over len {len}")]
-    OutOfBounds { index: usize, len: usize },
-    #[error("The Queue is empty!")]
-    EmptyQueue,
-    #[error("There are no past played songs!")]
-    EmptyPlayed,
-    #[error("There is no item after this in the Queue")]
-    NoNext,
-}
-
-#[derive(Debug, PartialEq, Clone, Copy)]
-pub enum QueueState {
-    Played,
-    First,
-    AddHere,
-    NoState,
-}
+use std::vec::IntoIter;
 
+use crate::music_storage::library::{Album, AlbumTrack, Song};
 
+use super::controller::PlayerLocation;
 
 #[derive(Debug, Clone, PartialEq)]
-#[non_exhaustive]
-pub struct QueueItem {
-    pub(super) item: Song,
-    pub(super) state: QueueState,
-    pub(super) source: PlayerLocation,
-    pub(super) by_human: bool,
+pub struct QueueSong {
+    pub song: Song,
+    pub location: PlayerLocation,
 }
 
-impl QueueItem {
-    fn from_song(song: Song, source: PlayerLocation) -> Self {
-        QueueItem {
-            item: song,
-            state: QueueState::NoState,
-            source,
-            by_human: false,
-        }
-    }
+#[derive(Debug, Clone, PartialEq)]
+pub struct QueueAlbum {
+    pub album: Album,
+    pub location: PlayerLocation,
 }
 
-#[derive(Debug, Default)]
-pub struct Queue {
-    pub items: Vec<QueueItem>,
-    pub played: Vec<QueueItem>,
-    pub loop_: bool,
-    pub shuffle: Option<Vec<usize>>,
-}
-
-// TODO: HAndle the First QueueState[looping] and shuffle
-impl Queue {
-    fn has_addhere(&self) -> bool {
-        for item in &self.items {
-            if item.state == QueueState::AddHere {
-                return true;
-            }
-        }
-        false
-    }
-
-    #[allow(unused)]
-    pub(super) fn dbg_items(&self) {
-        dbg!(
-            self.items
-                .iter()
-                .map(|item| (item.item.uuid, item.state))
-                .collect::<Vec<(Uuid, QueueState)>>(),
-            self.items.len()
-        );
-    }
-
-    pub fn set_items(&mut self, tracks: Vec<QueueItem>) {
-        let mut tracks = tracks;
-        self.items.clear();
-        self.items.append(&mut tracks);
-    }
-
-    /// Inserts an item after the AddHere item
-    pub fn add_item(&mut self, item: Song, source: PlayerLocation, by_human: bool) {
-        let mut i: usize = 0;
-
-        self.items = self
-            .items
-            .iter()
-            .enumerate()
-            .map(|(j, item_)| {
-                let mut item_ = item_.to_owned();
-                // get the index of the current AddHere item and give it to i
-                if item_.state == QueueState::AddHere {
-                    i = j;
-                    item_.state = QueueState::NoState;
-                }
-                item_
-            })
-            .collect::<Vec<QueueItem>>();
-
-        self.items.insert(
-            i + if self.items.is_empty() { 0 } else { 1 },
-            QueueItem {
-                item,
-                state: QueueState::AddHere,
-                source,
-                by_human,
-            },
-        );
-    }
-
-    /// Inserts an item after the currently playing item
-    pub fn add_item_next(&mut self, item: Song, source: PlayerLocation) {
-        use QueueState::*;
-        let empty = self.items.is_empty();
-
-        self.items.insert(
-            if empty { 0 } else { 1 },
-            QueueItem {
-                item,
-                state: if (self.items.get(1).is_none()
-                    || !self.has_addhere() && self.items.get(1).is_some())
-                    || empty
-                {
-                    AddHere
-                } else {
-                    NoState
-                },
-                source,
-                by_human: true,
-            },
-        )
-    }
-
-    pub fn add_multi(&mut self, items: Vec<Song>, source: PlayerLocation, by_human: bool) {
-        let mut i: usize = 0;
-
-        self.items = self
-            .items
-            .iter()
-            .enumerate()
-            .map(|(j, item_)| {
-                let mut item_ = item_.to_owned();
-                // get the index of the current AddHere item and give it to i
-                if item_.state == QueueState::AddHere {
-                    i = j;
-                    item_.state = QueueState::NoState;
-                }
-                item_
-            })
-            .collect::<Vec<QueueItem>>();
-
-        let empty = self.items.is_empty();
-
-        let len = items.len();
-        for item in items.into_iter().rev() {
-            self.items.insert(
-                i + if empty { 0 } else { 1 },
-                QueueItem {
-                    item,
-                    state: QueueState::NoState,
-                    source,
-                    by_human,
-                },
-            );
-        }
-        self.items[i + len - if empty { 1 } else { 0 }].state = QueueState::AddHere;
-    }
-
-    /// Add multiple Songs after the currently playing Song
-    pub fn add_multi_next(&mut self, items: Vec<Song>, source: PlayerLocation) {
-        use QueueState::*;
-        let empty = self.items.is_empty();
-
-        let add_here = (self.items.get(1).is_none()
-            || !self.has_addhere() && self.items.get(1).is_some())
-            || empty;
-
-        let len = items.len();
-
-        for item in items {
-            self.items.insert(
-                if empty { 0 } else { 1 },
-                QueueItem {
-                    item,
-                    state: NoState,
-                    source,
-                    by_human: true,
-                },
-            )
-        }
-
-        if add_here {
-            self.items[len - if empty { 1 } else { 0 }].state = QueueState::AddHere;
-        }
-    }
-
-    pub fn remove_item(&mut self, remove_index: usize) -> Result<QueueItem, QueueError> {
-        // dbg!(/*&remove_index, self.current_index(), &index,*/ &self.items[remove_index]);
-
-        if remove_index < self.items.len() {
-            // update the state of the next item to replace the item being removed
-            if self.items.get(remove_index + 1).is_some() {
-                self.items[remove_index + 1].state = self.items[remove_index].state;
-            }
-            Ok(self.items.remove(remove_index))
-        } else {
-            Err(QueueError::EmptyQueue)
-        }
-    }
-
-    pub fn insert(
-        &mut self,
-        index: usize,
-        new_item: Song,
-        source: PlayerLocation,
-        addhere: bool,
-    ) -> Result<(), QueueError> {
-        if self.items.get_mut(index).is_none()
-            && index > 0
-            && self.items.get_mut(index - 1).is_none()
-        {
-            return Err(QueueError::OutOfBounds {
-                index,
-                len: self.items.len(),
-            });
-        }
-        if addhere {
-            let mut new_item = QueueItem::from_song(new_item, source);
-            for item in &mut self.items {
-                if item.state == QueueState::AddHere {
-                    item.state = QueueState::NoState
-                }
-            }
-            new_item.state = QueueState::AddHere;
-            self.items.insert(index, new_item);
-        } else {
-            let new_item = QueueItem::from_song(new_item, source);
-            self.items.insert(index, new_item);
-        }
-        Ok(())
-    }
-
-    pub fn clear(&mut self) {
-        self.items.clear();
-    }
-
-    pub fn clear_except(&mut self, index: usize) -> Result<(), QueueError> {
-        use QueueState::*;
-        let empty = self.items.is_empty();
-
-        if !empty && index < self.items.len() {
-            let i = self.items[index].clone();
-            self.items.retain(|item| *item == i);
-            self.items[0].state = AddHere;
-        } else if empty {
-            return Err(QueueError::EmptyQueue);
-        } else {
-            return Err(QueueError::OutOfBounds {
-                index,
-                len: self.items.len(),
-            });
-        }
-        Ok(())
-    }
-
-    pub fn clear_played(&mut self) {
-        self.played.clear();
-    }
-
-    pub fn clear_all(&mut self) {
-        self.items.clear();
-        self.played.clear();
-    }
-
-    pub fn move_to(&mut self, index: usize) -> Result<(), QueueError> {
-        use QueueState::*;
-
-        let empty = self.items.is_empty();
-
-        let index = if !empty {
-            index
-        } else {
-            return Err(QueueError::EmptyQueue);
-        };
-
-        if !empty && dbg!(index < self.items.len()) {
-            let to_item = self.items[index].clone();
-
-            loop {
-                let empty = self.items.is_empty();
-                let item = self.items[0].item.to_owned();
-
-                if item != to_item.item && !empty {
-                    if self.items[0].state == AddHere && self.items.get(1).is_some() {
-                        self.items[1].state = AddHere;
-                    }
-                    let item = self.items.remove(0);
-                    self.played.push(item);
-
-                // dbg!(&to_item.item, &self.items[ind].item);
-                } else if empty {
-                    return Err(QueueError::EmptyQueue);
-                } else {
-                    break;
-                }
-            }
-        } else {
-            return Err(QueueError::EmptyQueue);
-        }
-        Ok(())
-    }
-
-    pub fn swap(&mut self, a: usize, b: usize) {
-        self.items.swap(a, b)
-    }
-
-    pub fn move_item(&mut self, from: usize, to: usize) {
-        let item = self.items[from].to_owned();
-        if from != to {
-            self.items.remove(from);
-        }
-        self.items.insert(to, item);
-    }
-
-    #[allow(clippy::should_implement_trait)]
-    pub fn next(&mut self) -> Result<&QueueItem, QueueError> {
-        if self.items.is_empty() {
-            if self.loop_ {
-                unimplemented!() // TODO: add function to loop the queue
-            } else {
-                return Err(QueueError::EmptyQueue);
-            }
-        }
-
-        if self.items[0].state == QueueState::AddHere || !self.has_addhere() {
-            self.items[0].state = QueueState::NoState;
-            if self.items.get_mut(1).is_some() {
-                self.items[1].state = QueueState::AddHere;
-            }
-        }
-        let item = self.items.remove(0);
-        self.played.push(item);
-
-        if self.items.is_empty() {
-            Err(QueueError::NoNext)
-        } else {
-            Ok(&self.items[0])
-        }
-    }
-
-    pub fn prev(&mut self) -> Result<&QueueItem, QueueError> {
-        if let Some(item) = self.played.pop() {
-            if item.state == QueueState::First && self.loop_ {
-                todo!()
-            }
-            self.items.insert(0, item);
-            Ok(&self.items[0])
-        } else {
-            Err(QueueError::EmptyPlayed)
-        }
-    }
-
-    pub fn now_playing(&self) -> Result<&QueueItem, QueueError> {
-        if !self.items.is_empty() {
-            Ok(&self.items[0])
-        } else {
-            Err(QueueError::EmptyQueue)
-        }
-    }
-
-    pub fn check_played(&mut self) {
-        while self.played.len() > 50 {
-            self.played.remove(0);
-        }
-    }
-}
-
-#[cfg(test)]
-mod test_super {
-    #![allow(unused)]
-    use crate::{
-        config::tests::{new_config_lib, read_config_lib},
-        music_storage::library,
-    };
-
-    use super::*;
-
-    #[test]
-    fn move_test() {
-        let (_, library) = read_config_lib();
-        let mut q = Queue::default();
-
-        q.insert(0, library.library[2].to_owned(), PlayerLocation::File, true)
-            .inspect_err(|e| println!("{e}"));
-        q.insert(1, library.library[2].to_owned(), PlayerLocation::File, true)
-            .inspect_err(|e| println!("{e}"));
-        // q.next();
-        // q.clear();
-        q.dbg_items();
-        dbg!(&q.played.len());
-
-        // q.dbg_items();
+impl IntoIterator for QueueAlbum {
+    type Item = AlbumTrack;
+    type IntoIter = IntoIter<Self::Item>;
+    fn into_iter(self) -> Self::IntoIter {
+        self.album.into_iter()
     }
 }

From be9f28e38ffece66c60bc8b15490bb2d268ef167 Mon Sep 17 00:00:00 2001
From: MrDulfin <mrdulfin@mrdulfin.com>
Date: Fri, 30 Aug 2024 23:35:28 -0400
Subject: [PATCH 136/136] removed config passing in library functions

---
 src/config/mod.rs                            |  8 ++++----
 src/music_controller/controller.rs           |  3 ++-
 src/music_player/gstreamer.rs                |  1 -
 src/music_storage/db_reader/itunes/reader.rs |  4 ++--
 src/music_storage/library.rs                 | 12 +++---------
 5 files changed, 11 insertions(+), 17 deletions(-)

diff --git a/src/config/mod.rs b/src/config/mod.rs
index 431498f..6371afb 100644
--- a/src/config/mod.rs
+++ b/src/config/mod.rs
@@ -211,12 +211,12 @@ pub mod tests {
         config.write_file().unwrap();
 
         let mut lib = MusicLibrary::init(
-            Arc::new(RwLock::from(config.clone())),
+            config.libraries.get_default().unwrap().path.clone(),
             dbg!(config.libraries.default_library),
         )
         .unwrap();
         lib.scan_folder("test-config/music/").unwrap();
-        lib.save(Arc::new(RwLock::new(config.clone()))).unwrap();
+        lib.save(config.libraries.get_default().unwrap().path.clone()).unwrap();
 
         (config, lib)
     }
@@ -227,14 +227,14 @@ pub mod tests {
         // dbg!(&config);
 
         let mut lib = MusicLibrary::init(
-            Arc::new(RwLock::from(config.clone())),
+            config.libraries.get_default().unwrap().path.clone(),
             config.libraries.get_default().unwrap().uuid,
         )
         .unwrap();
 
         lib.scan_folder("test-config/music/").unwrap();
 
-        lib.save(Arc::new(RwLock::new(config.clone()))).unwrap();
+        lib.save(config.libraries.get_default().unwrap().path.clone()).unwrap();
 
         (config, lib)
     }
diff --git a/src/music_controller/controller.rs b/src/music_controller/controller.rs
index 74c3c62..3c9dcb3 100644
--- a/src/music_controller/controller.rs
+++ b/src/music_controller/controller.rs
@@ -95,8 +95,9 @@ impl<P: Player + Send + Sync + Sized + 'static> Controller<P> {
         let config = Config::read_file(config_path)?;
         let uuid = config.libraries.get_default()?.uuid;
 
+        let library = MusicLibrary::init(config.libraries.get_default()?.path.clone(), uuid)?;
         let config_ = Arc::new(RwLock::from(config));
-        let library = MusicLibrary::init(config_.clone(), uuid)?;
+
 
         let queue: Queue<QueueSong, QueueAlbum> = Queue {
             items: Vec::new(),
diff --git a/src/music_player/gstreamer.rs b/src/music_player/gstreamer.rs
index 5fa44bc..a6915aa 100644
--- a/src/music_player/gstreamer.rs
+++ b/src/music_player/gstreamer.rs
@@ -1,5 +1,4 @@
 // Crate things
-//use crate::music_controller::config::Config;
 use crate::music_storage::library::URI;
 use crossbeam_channel::{unbounded, Receiver, Sender};
 use std::error::Error;
diff --git a/src/music_storage/db_reader/itunes/reader.rs b/src/music_storage/db_reader/itunes/reader.rs
index 98ea1da..6ff383c 100644
--- a/src/music_storage/db_reader/itunes/reader.rs
+++ b/src/music_storage/db_reader/itunes/reader.rs
@@ -348,11 +348,11 @@ mod tests {
 
         let songs = ITunesLibrary::from_file(Path::new("test-config\\iTunesLib.xml")).to_songs();
 
-        let mut library = MusicLibrary::init(Arc::new(RwLock::from(config.clone())), config_lib.uuid).unwrap();
+        let mut library = MusicLibrary::init(config.libraries.get_default().unwrap().path.clone(), config_lib.uuid).unwrap();
 
         songs.iter().for_each(|song| library.add_song(song.to_owned()).unwrap());
 
         config.write_file().unwrap();
-        library.save(Arc::new(RwLock::from(config))).unwrap();
+        library.save(config.libraries.get_default().unwrap().path.clone()).unwrap();
     }
 }
diff --git a/src/music_storage/library.rs b/src/music_storage/library.rs
index f7d3e81..010fed3 100644
--- a/src/music_storage/library.rs
+++ b/src/music_storage/library.rs
@@ -699,21 +699,16 @@ impl MusicLibrary {
     /// 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 [MusicLibrary] Vec
-    pub fn init(config: Arc<RwLock<Config>>, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
-        let global_config = &*config.read().unwrap();
-        let path = global_config.libraries.get_library(&uuid)?.path.clone();
-
+    pub fn init(path: PathBuf, uuid: Uuid) -> Result<Self, Box<dyn Error>> {
         let library: MusicLibrary = match path.exists() {
             true => read_file(path)?,
             false => {
                 // If the library does not exist, re-create it
                 let lib = MusicLibrary::new(String::new(), uuid);
-
                 write_file(&lib, path)?;
                 lib
             }
         };
-
         Ok(library)
     }
 
@@ -743,8 +738,7 @@ impl MusicLibrary {
     }
 
     /// Serializes the database out to the file specified in the config
-    pub fn save(&self, config: Arc<RwLock<Config>>) -> Result<(), Box<dyn Error>> {
-        let path = config.read().unwrap().libraries.get_library(&self.uuid)?.path.clone();
+    pub fn save(&self, path: PathBuf) -> Result<(), Box<dyn Error>> {
         match path.try_exists() {
             Ok(_) => write_file(self, path)?,
             Err(error) => return Err(error.into()),
@@ -1229,7 +1223,7 @@ mod test {
     fn library_init() {
         let config = Config::read_file(PathBuf::from("test_config/config_test.json")).unwrap();
         let target_uuid = config.libraries.libraries[0].uuid;
-        let a = MusicLibrary::init(Arc::new(RwLock::from(config)), target_uuid).unwrap();
+        let a = MusicLibrary::init(config.libraries.get_default().unwrap().path.clone(), target_uuid).unwrap();
         dbg!(a);
     }
 }