Skip to content

Commit

Permalink
Dependency inversion for visit_file
Browse files Browse the repository at this point in the history
  • Loading branch information
nukeop committed Nov 11, 2023
1 parent b48cc12 commit 41c85db
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 82 deletions.
1 change: 1 addition & 0 deletions packages/scanner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exclude = ["index.node"]
crate-type = ["cdylib"]

[dependencies]
derive_builder = "0.12.0"
id3 = "1.7.0"
md5 = "0.7.0"
metaflac = "0.2.5"
Expand Down
16 changes: 16 additions & 0 deletions packages/scanner/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ pub struct ScannerError {

impl Error for ScannerError {}

impl ScannerError {
pub fn new(message: &str) -> ScannerError {
ScannerError {
message: message.to_string(),
}
}
}

impl fmt::Display for ScannerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ScannerError: {}", self.message)
Expand All @@ -19,6 +27,14 @@ pub struct MetadataError {
pub message: String,
}

impl MetadataError {
pub fn new(message: &str) -> MetadataError {
MetadataError {
message: message.to_string(),
}
}
}

impl Error for MetadataError {}

impl fmt::Display for MetadataError {
Expand Down
5 changes: 3 additions & 2 deletions packages/scanner/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#![forbid(unsafe_code)]

mod error;
mod js;
mod local_track;
Expand All @@ -8,7 +9,7 @@ mod thumbnails;
use id3::Tag;
use js::{set_optional_field_str, set_optional_field_u32};
use neon::prelude::*;
use scanner::{visit_directory, visit_file};
use scanner::{extractor_from_path, visit_directory, visit_file};
use std::collections::LinkedList;
use thumbnails::create_thumbnails_dir;

Expand Down Expand Up @@ -71,7 +72,7 @@ fn scan_folders(mut cx: FunctionContext) -> JsResult<JsArray> {
// Scan the file
let track = visit_file(
file.clone(),
|path| Tag::read_from_path(path),
extractor_from_path,
thumbnails_dir_str.as_str(),
);

Expand Down
4 changes: 3 additions & 1 deletion packages/scanner/src/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use derive_builder::Builder;
use id3::TagLike;
use metaflac;
use neon::meta;

use crate::{
error::MetadataError,
thumbnails::ThumbnailGenerator,
thumbnails::{FlacThumbnailGenerator, Mp3ThumbnailGenerator},
};

#[derive(Default, Debug, Clone, Builder)]
#[builder(setter(strip_option))]
pub struct AudioMetadata {
pub artist: Option<String>,
pub title: Option<String>,
Expand Down
152 changes: 74 additions & 78 deletions packages/scanner/src/scanner.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,48 @@
use id3::{Error, Tag, TagLike};
use id3::{Error, Tag};
use std::collections::LinkedList;
use std::ffi::OsStr;
use std::path::Path;
use uuid::Uuid;

use crate::error::{MetadataError, ScannerError};
use crate::local_track::LocalTrack;
use crate::metadata::AudioMetadata;
use crate::thumbnails::generate_thumbnail;
use crate::metadata::{
AudioMetadata, AudioMetadataBuilder, FlacMetadataExtractor, MetadataExtractor,
Mp3MetadataExtractor,
};

pub trait TagReader {
fn read_from_path(path: impl AsRef<Path>) -> Result<Tag, Error>;
}

fn get_extension(path: &str) -> Option<&str> {
Path::new(path).extension().and_then(OsStr::to_str)
}

pub fn extractor_from_path(path: &str) -> Option<Box<dyn MetadataExtractor>> {
match get_extension(path) {
Some("mp3") => Some(Box::new(Mp3MetadataExtractor)),
Some("flac") => Some(Box::new(FlacMetadataExtractor)),
_ => None,
}
}

pub fn visit_file<F>(
path: String,
metadata_reader: F,
extractor_provider: F,
thumbnails_dir: &str,
) -> Result<LocalTrack, ScannerError>
where
F: FnOnce(&str) -> Result<AudioMetadata, MetadataError>,
F: Fn(&str) -> Option<Box<dyn MetadataExtractor>>,
{
let meta = metadata_reader(&path);
let extractor: Box<dyn MetadataExtractor> = extractor_provider(&path)
.ok_or_else(|| ScannerError::new(&format!("Unsupported file format: {}", path)))?;
let metadata = extractor.extract_metadata(&path, thumbnails_dir);

match tag {
Ok(tag) => Ok(LocalTrack {
match metadata {
Ok(metadata) => Ok(LocalTrack {
uuid: Uuid::new_v4().to_string(),
artist: tag.artist().map(|s| s.to_string()),
title: tag.title().map(|s| s.to_string()),
album: tag.album().map(|s| s.to_string()),
duration: tag.duration().unwrap_or(0),
thumbnail: generate_thumbnail(&path, thumbnails_dir),
position: tag.track(),
disc: tag.disc(),
year: tag.year().map(|s| s as u32),
metadata: metadata,
filename: path.split("/").last().map(|s| s.to_string()).unwrap(),
path: path.clone(),
}),
Expand Down Expand Up @@ -67,73 +77,59 @@ pub fn visit_directory(

#[cfg(test)]
mod tests {
use id3::{
frame::{Picture, PictureType},
Content, Frame,
};

use super::*;

#[test]
fn test_visit_file_with_valid_file() {
// With mocked tag
let path = String::from("path/to/valid/file.mp3");
let result = visit_file(path.clone(), |_inner_path| {
let mut tag = Tag::new();
tag.set_artist("Artist");
tag.set_title("Title");
tag.set_album("Album");
tag.set_duration(123);
tag.set_track(1);
tag.set_year(2020);
let picture = Picture {
mime_type: String::new(),
picture_type: PictureType::CoverFront,
description: String::new(),
data: vec![1, 2, 3],
};
tag.add_frame(Frame::with_content(
"APIC",
Content::Picture(picture.clone()),
));
Ok(tag)
});

if let Some(track) = result.ok() {
//check uuid format
assert_eq!(
track.uuid,
Uuid::parse_str(&track.uuid).unwrap().to_string()
);
assert_eq!(track.artist, Some(String::from("Artist")));
assert_eq!(track.title, Some(String::from("Title")));
assert_eq!(track.album, Some(String::from("Album")));
assert_eq!(track.duration, 123);
assert_eq!(track.position, Some(1));
assert_eq!(track.year, Some(2020));
assert_eq!(track.filename, String::from("file.mp3"));
assert_eq!(track.path, path);
assert_eq!(
track.thumbnail,
Some("file://path/to/valid/file.webp".to_string())
);
} else {
panic!("Result is not ok");
#[derive(Debug, Clone, Default)]
struct TestMetadataExtractor {
pub test_metadata: AudioMetadata,
}
impl TestMetadataExtractor {
pub fn new() -> TestMetadataExtractor {
TestMetadataExtractor {
test_metadata: AudioMetadata::new(),
}
}
}
impl MetadataExtractor for TestMetadataExtractor {
fn extract_metadata(
&self,
_path: &str,
_thumbnails_dir: &str,
) -> Result<AudioMetadata, MetadataError> {
Ok(AudioMetadataBuilder::default()
.artist("Test Artist".to_string())
.title("Test Title".to_string())
.album("Test Album".to_string())
.duration(10)
.position(1)
.disc(1)
.year(2020)
.thumbnail("http://localhost:8080/thumbnails/0b/0b0b0b0b0b0b0b0b.webp".to_string())
.build()
.unwrap())
}
}

#[test]
fn test_visit_file_with_no_tags() {
// With mocked tag
let path = String::from("path/to/invalid/file.mp3");
let result = visit_file(path.clone(), |_inner_path| {
Err(id3::Error::new(id3::ErrorKind::NoTag, ""))
});
pub fn test_extractor_from_path(_path: &str) -> Option<Box<dyn MetadataExtractor>> {
Some(Box::new(TestMetadataExtractor::new()))
}

if let Some(error) = result.err() {
assert_eq!(error.message, String::from("Error reading file: NoTag"));
} else {
panic!("Result is not err");
}
#[test]
fn test_visit_file() {
let path = "tests/test.mp3".to_string();
let thumbnails_dir = "tests/thumbnails".to_string();
let local_track = visit_file(path, test_extractor_from_path, &thumbnails_dir).unwrap();
assert_eq!(local_track.filename, "test.mp3");
assert_eq!(local_track.metadata.artist, Some("Test Artist".to_string()));
assert_eq!(local_track.metadata.title, Some("Test Title".to_string()));
assert_eq!(local_track.metadata.album, Some("Test Album".to_string()));
assert_eq!(local_track.metadata.duration, 10);
assert_eq!(local_track.metadata.position, Some(1));
assert_eq!(local_track.metadata.disc, Some(1));
assert_eq!(local_track.metadata.year, Some(2020));
assert_eq!(
local_track.metadata.thumbnail,
Some("http://localhost:8080/thumbnails/0b/0b0b0b0b0b0b0b0b.webp".to_string())
);
}
}
2 changes: 1 addition & 1 deletion packages/scanner/src/thumbnails.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn hash_thumb_filename(path: &str) -> Result<String, io::Error> {
Ok(format!("{:x}.webp", hash))
}

fn create_thumbnails_dir(thumbnails_dir: &str) -> io::Result<()> {
pub fn create_thumbnails_dir(thumbnails_dir: &str) -> io::Result<()> {
let thumbnails_dir_path = Path::new(thumbnails_dir);

if !thumbnails_dir_path.exists() {
Expand Down

0 comments on commit 41c85db

Please sign in to comment.