tagdex 1.0.1

An mp3 tag indexer written in Rust.
Documentation
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,
};

// Get a field from a tag by name.
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")),
    // Transform comma-separated list into a vector.
    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();

  // Get a sorted iterator of directory entries.
  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 the path is a directory, recurse.
    if path.is_dir() {
      if let Ok(mut sub_paths) = explore_directory(&path) {
        songs.append(&mut sub_paths);
      }
    // If the path is not a directory, attempt to parse it.
    } else if let Some(song) = parse_file(path) {
      songs.push(song);
    }
  }

  Ok(songs)
}

// Write the list of songs to the index.
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(())
}

// Get the list of songs from the index.
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)
}