use crate::song::Song;
use bincode::{decode_from_std_read, encode_into_std_write};
use id3::{v1v2, Tag, TagLike};
use itertools::Itertools;
use std::{
fs::File,
io::{BufReader, BufWriter},
path::PathBuf,
};
fn get_meta(tag: &Tag, field: &str) -> Option<String> {
let frame = tag.get(field)?;
let text = frame.content().text()?;
Some(String::from(text))
}
fn parse_file(path: PathBuf) -> Option<Song> {
let tag = v1v2::read_from_path(&path).ok()?;
Some(Song {
path: path,
title: get_meta(&tag, "TIT2").unwrap_or_else(|| String::from("unknown")),
album: get_meta(&tag, "TALB").unwrap_or_else(|| String::from("unknown")),
artists: get_meta(&tag, "TPE1")
.unwrap_or_else(|| String::from("unknown"))
.split(",")
.map(|artist| String::from(artist.trim()))
.collect(),
genre: get_meta(&tag, "TCON").unwrap_or_else(|| String::from("unknown")),
year: get_meta(&tag, "TDRC").unwrap_or_else(|| String::from("unknown")),
})
}
fn explore_directory(base_path: &PathBuf) -> std::io::Result<Vec<Song>> {
let mut songs: Vec<Song> = Vec::new();
let entries = base_path.read_dir()?;
let entries = entries.sorted_by(|left, right| {
Ord::cmp(
&left.as_ref().ok().map(|entry| entry.path()),
&right.as_ref().ok().map(|entry| entry.path()),
)
});
for entry in entries {
let path = entry?.path();
if path.is_dir() {
if let Ok(mut sub_paths) = explore_directory(&path) {
songs.append(&mut sub_paths);
}
} else if let Some(song) = parse_file(path) {
songs.push(song);
}
}
Ok(songs)
}
fn write(path: &PathBuf, songs: &Vec<Song>) -> std::io::Result<()> {
let file = File::create(path.join(".tagdex"))?;
let mut writer = BufWriter::new(file);
encode_into_std_write(songs, &mut writer, bincode::config::standard())
.expect("Could not encode index.");
Ok(())
}
pub fn get(path: &PathBuf) -> std::io::Result<Vec<Song>> {
let file = File::open(path.join(".tagdex"))?;
let mut reader = BufReader::new(file);
let songs = decode_from_std_read::<Vec<Song>, _, _>(&mut reader, bincode::config::standard())
.expect("Could not decode index.");
Ok(songs)
}
pub fn populate(path: &PathBuf) -> std::io::Result<()> {
let songs = explore_directory(&path)?;
write(&path, &songs)
}