feat: add basic types and scanning

This commit is contained in:
skybldev
2026-10-23 04:11:31 -04:00
parent f46209c200
commit 9b89ea803b
4 changed files with 243 additions and 1 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
/target
/test/fixtures/

78
src/library_scan.rs Normal file
View File

@@ -0,0 +1,78 @@
use std::error::Error;
use std::fmt;
use std::fs;
use std::fs::File;
use std::io;
use std::path::{Path, PathBuf};
use lofty::file;
use lofty::prelude::*;
use lofty::read_from;
use crate::types::errors::{ScanError, TrackScanError};
use crate::types::library::ScannedTrack;
use crate::types::library::ScannedTrackMetadata;
pub fn scan(path: &Path) -> Result<Vec<ScannedTrack>, ScanError> {
println!("Scanning library...");
if !path.is_dir() {
return Err(ScanError::NotDirectory(path.to_path_buf()));
}
let entries = fs::read_dir(path).map_err(|source| ScanError::ReadDir {
path: path.to_path_buf(),
source,
})?;
let mut tracks: Vec<ScannedTrack> = Vec::new();
// iterate through entries in directory (depth of one)
for entry in entries {
// validate if entry is even valid (this annoys me)
if let Ok(entry) = entry {
let entry_path = entry.path();
// try to read file and come up with metadata
let metadata = || -> Result<ScannedTrackMetadata, TrackScanError> {
let mut file =
File::open(&entry_path).map_err(|source| TrackScanError::FileOpen {
path: entry_path.clone(),
source,
})?;
let tagged_file =
read_from(&mut file).map_err(|source| TrackScanError::FileParse {
path: entry_path.clone(),
source,
})?;
if let Some(tags) = tagged_file.primary_tag() {
Ok(ScannedTrackMetadata {
artist: tags.artist().as_deref().map(|tag| String::from(tag)),
collection: tags.album().as_deref().map(|tag| String::from(tag)),
title: tags.title().as_deref().map(|tag| String::from(tag)),
})
} else {
Ok(ScannedTrackMetadata {
artist: None,
collection: None,
title: None,
})
}
}();
tracks.push(ScannedTrack {
path: entry_path,
metadata,
});
} else {
eprintln!(
"there was an error reading some file in {}",
path.to_str().unwrap()
);
}
}
Ok(tracks)
}

View File

@@ -1,3 +1,64 @@
fn main() {
use std::{fs::metadata, path::Path};
use lofty::ogg::tag;
use rusqlite::{Connection, Result};
mod library_scan;
mod types;
fn main() -> Result<()> {
println!("Hello, world!");
let conn = Connection::open_in_memory()?;
println!("Initializing database...");
conn.execute(
"CREATE TABLE artist (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)",
(),
)?;
conn.execute(
"CREATE TABLE collection (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)",
(),
)?;
conn.execute(
"CREATE TABLE track (
id INTEGER PRIMARY KEY,
artist INTEGER,
collection INTEGER,
title TEXT
)",
(),
)?;
println!("Database initialized.");
for track in library_scan::scan(Path::new("./test/fixtures")).expect("oh no") {
println!("-- {}", track.path.into_string().unwrap());
match track.metadata {
Ok(metadata) => {
println!("artist: {}", metadata.artist.unwrap_or("unknown".into()));
println!(
"collection: {}",
metadata.collection.unwrap_or("unknown".into())
);
println!("title: {}", metadata.title.unwrap_or("unknown".into()));
}
Err(e) => {
eprint!("{e}");
}
}
println!("");
}
Ok(())
}

102
src/types.rs Normal file
View File

@@ -0,0 +1,102 @@
pub mod library {
use std::path::PathBuf;
use crate::types::errors;
#[derive(Debug)]
struct Artist {
id: u32,
name: String,
}
#[derive(Debug)]
struct Collection {
id: u32,
name: String,
}
#[derive(Debug)]
struct Track {
id: u32,
artist: Option<Artist>,
collection: Option<Collection>,
title: Option<String>,
}
#[derive(Debug)]
pub struct ScannedTrackMetadata {
pub artist: Option<String>,
pub collection: Option<String>,
pub title: Option<String>,
}
#[derive(Debug)]
pub struct ScannedTrack {
pub path: PathBuf,
pub metadata: Result<ScannedTrackMetadata, errors::TrackScanError>,
}
}
pub mod errors {
use std::{error::Error, fmt, io, path::PathBuf};
#[derive(Debug)]
pub enum ScanError {
NotDirectory(PathBuf),
ReadDir { path: PathBuf, source: io::Error },
}
impl fmt::Display for ScanError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotDirectory(path) => write!(f, "{} is not a directory", path.display()),
Self::ReadDir { path, source } => {
write!(f, "could not read directory {}: {source}", path.display())
}
}
}
}
impl Error for ScanError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::ReadDir { source, .. } => Some(source),
Self::NotDirectory(_) => None,
}
}
}
#[derive(Debug)]
pub enum TrackScanError {
FileOpen {
path: PathBuf,
source: io::Error,
},
FileParse {
path: PathBuf,
source: lofty::error::FileParseError,
},
}
impl fmt::Display for TrackScanError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FileOpen { path, source } => {
write!(f, "could not open file {}: {source}", path.display())
}
Self::FileParse { path, source } => {
write!(f, "could not parse file {}: {source}", path.display())
}
}
}
}
impl Error for TrackScanError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::FileOpen { path, source } => Some(source),
Self::FileParse { path, source } => Some(source),
}
}
}
}