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] 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