feat: add basic types and scanning
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1 +1,2 @@
|
||||
/target
|
||||
/test/fixtures/
|
||||
78
src/library_scan.rs
Normal file
78
src/library_scan.rs
Normal 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)
|
||||
}
|
||||
63
src/main.rs
63
src/main.rs
@@ -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
102
src/types.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user