1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
use std::slice::Iter;

#[cfg(feature = "daemon")]
use {
    rust_utils::logging::Log,
    std::{
        env, fs,
        collections::HashSet
    },
    lofty::{read_from_path, AudioFile, Accessor, ItemKey, TaggedFileExt}
};

mod album;
mod artist;
mod playlist;
mod song;

pub use self::{
    song::Song,
    album::Album,
    artist::Artist,
    playlist::{Playlist, PlaylistStore, PlaylistAddable}
};

/// Music library object
#[derive(Clone, serde_derive::Serialize, serde_derive::Deserialize)]
pub struct Library(Vec<Artist>);

impl Library {
    /// Find a song by path
    pub fn find_song(&self, path: &str) -> Option<Song> {
        for artist in self.iter() {
            for album in &artist.albums {
                for song in &album.songs {
                    if song.get_path() == path {
                        return Some(song.clone());
                    }
                }
            }
        }

        None
    }

    /// Iterate through all the artists in this music library
    pub fn iter(&self) -> Iter<Artist> {
        self.0.iter()
    }

    /// Get an artist by its index
    ///
    /// Hint: artists are in alphabetical order
    pub fn get_artist(&self, index: usize) -> Option<&Artist> {
        self.0.get(index)
    }
}

#[cfg(feature = "daemon")]
impl Library {
    /// Load in the library database or create a new one if it does not exist
    pub fn load(first_load: bool, rescan: bool, music_dir: &str, log: &Log) -> Self {
        fs::create_dir_all(format!("{music_dir}/.mucl")).unwrap_or(());

        // reload the library?
        if rescan {
            fs::remove_file(format!("{music_dir}/.mucl/library")).unwrap();
            return Self::gen_lib(music_dir, log);
        }

        let encoded = if let Ok(l) = fs::read(format!("{music_dir}/.mucl/library")) {
            l
        }
        else {
            return Self::gen_lib(music_dir, log);
        };

        if let Ok(l) = bincode::deserialize(&encoded) {
            if first_load {
                log.line_basic("Refreshing playlists...", true);
                let mut playlists = PlaylistStore::load(&music_dir, &l);
                playlists.gen_thumbnails();
            }

            l
        }
        else {
            Self::gen_lib(music_dir, log)
        }
    }

    // generate a new music library
    fn gen_lib(music_dir: &str, log: &Log) -> Self {
        log.line_basic("Scanning music library...", true);
        let mut songs: Vec<(Song, u32)> = Vec::new();
        let mut albums: Vec<Album> = Vec::new();
        let mut artists: Vec<Artist> = Vec::new();
        let mut file_list = Vec::new();
        env::set_current_dir(music_dir).expect("What went wrong?!");

        // get a recursive listing of all the music files in the music directory
        for entry in glob::glob("*/*/*.*").unwrap() {
            file_list.push(format!("{}", entry.unwrap().display()));
        }

        // get the raw metadata from each song file
        for entry in file_list {
            if let Ok(song_file) = read_from_path(&entry) {
                let tag = &song_file.tags()[0];
                let title = tag.title();
                let album = tag.album();
                let artist = tag.artist();
                let year: u32 = tag.get_string(&ItemKey::RecordingDate).unwrap_or("0").parse().unwrap_or(0);
                let t_num_raw = tag.get_string(&ItemKey::TrackNumber).unwrap_or("0");
                let track_num: u32 = if t_num_raw.contains('/') {
                    t_num_raw.split('/').next().unwrap().parse().unwrap_or(0)
                }
                else {
                    t_num_raw.parse().unwrap_or(0)
                };

                // get the song duration
                let properties = song_file.properties();
                let duration = properties.duration().as_secs();
                let path = format!("{music_dir}/{entry}");
                songs.push((
                    Song::new(
                        title.as_deref().unwrap_or("<unknown>"),
                        album.as_deref().unwrap_or("<unknown>"),
                        artist.as_deref().unwrap_or("<unknown>"),
                        duration + 1, track_num, &path
                    ),
                    year
                ));
            }
        }

        // create album list
        let mut album_names = HashSet::new();
        for (song, year) in &songs {
            if album_names.insert(&song.album) {
                albums.push(Album::new(&song.album, &song.artist, *year));
            }
        }

        // add songs to the albums
        for (song, _) in &songs {
            for album in &mut albums {
                if song.album == album.name {
                    album.add_song(song);
                }
            }
        }

        // sort the songs by their track numbers
        for album in &mut albums {
            album.sort_songs();
        }

        // create artist list
        let mut artist_names = HashSet::new();
        for album in &albums {
            if artist_names.insert(&album.artist) {
                artists.push(Artist::new(&album.artist));
            }
        }

        // group all the albums by artist
        for album in &albums {
            for artist in &mut artists {
                if album.artist == artist.name {
                    artist.add_album(album);
                }
            }
        }

        // sort each artist's albums by year and reverse the list
        for artist in &mut artists {
            artist.sort_albums();
            artist.albums.reverse();
        }

        let lib = Library(artists);

        // save the new music library to disk
        let encoded = bincode::serialize(&lib).expect("What went wrong?!");
        fs::write(format!("{}/.mucl/library", music_dir), encoded).expect("What went wrong?!");

        // refresh the playlists
        log.line_basic("Refreshing playlists...", true);
        let mut playlists = PlaylistStore::load(music_dir, &lib);
        playlists.gen_thumbnails();

        lib
    }
}