Extractor refactor, S3 support
Some checks failed
CI / Typos (push) Successful in 1m5s
CI / Clippy (push) Failing after 1m50s
CI / Build and test (push) Successful in 3m1s

This commit is contained in:
2026-03-06 17:49:12 -08:00
parent 77b3125af4
commit aecc84233b
31 changed files with 2676 additions and 675 deletions

View File

@@ -1,5 +1,5 @@
use pile_config::Label;
use std::{collections::HashMap, rc::Rc};
use std::{collections::HashMap, sync::Arc};
mod flac;
pub use flac::*;
@@ -13,59 +13,73 @@ pub use fs::*;
mod pdf;
pub use pdf::*;
mod sidecar;
pub use sidecar::*;
mod toml;
pub use toml::*;
mod map;
pub use map::*;
mod sidecar;
pub use sidecar::*;
use crate::Item;
/// An attachment that extracts metadata from an [Item].
///
/// Metadata is exposed as an immutable map of {label: value},
/// much like a json object.
pub trait Extractor<I: crate::Item> {
#[async_trait::async_trait]
pub trait Extractor: Send + Sync {
/// Get the field at `name` from `item`.
/// - returns `None` if `name` is not a valid field
/// - returns `Some(Null)` if `name` is not available
fn field<'a>(
async fn field<'a>(
&'a self,
name: &pile_config::Label,
) -> Result<Option<&'a crate::PileValue<'a, I>>, std::io::Error>;
) -> Result<Option<&'a crate::PileValue<'a>>, std::io::Error>;
/// Return all fields in this extractor.
/// `Self::field` must return [Some] for all these keys
/// and [None] for all others.
fn fields(&self) -> Result<Vec<Label>, std::io::Error>;
async fn fields(&self) -> Result<Vec<Label>, std::io::Error>;
}
pub struct MetaExtractor<'a, I: crate::Item> {
inner: MapExtractor<'a, I>,
pub struct MetaExtractor<'a> {
inner: MapExtractor<'a>,
}
impl<'a> MetaExtractor<'a, crate::FileItem> {
//
// MARK: file
//
impl<'a> MetaExtractor<'a> {
#[expect(clippy::unwrap_used)]
pub fn new(item: &'a crate::FileItem) -> Self {
pub fn new(item: &'a Item) -> Self {
let inner = MapExtractor {
inner: HashMap::from([
(
Label::new("flac").unwrap(),
crate::PileValue::Extractor(Rc::new(FlacExtractor::new(item))),
crate::PileValue::Extractor(Arc::new(FlacExtractor::new(item))),
),
(
Label::new("id3").unwrap(),
crate::PileValue::Extractor(Rc::new(Id3Extractor::new(item))),
crate::PileValue::Extractor(Arc::new(Id3Extractor::new(item))),
),
(
Label::new("fs").unwrap(),
crate::PileValue::Extractor(Rc::new(FsExtractor::new(item))),
crate::PileValue::Extractor(Arc::new(FsExtractor::new(item))),
),
(
Label::new("pdf").unwrap(),
crate::PileValue::Extractor(Rc::new(PdfExtractor::new(item))),
crate::PileValue::Extractor(Arc::new(PdfExtractor::new(item))),
),
(
Label::new("toml").unwrap(),
crate::PileValue::Extractor(Arc::new(TomlExtractor::new(item))),
),
(
Label::new("sidecar").unwrap(),
crate::PileValue::Extractor(Rc::new(SidecarExtractor::new(item))),
crate::PileValue::Extractor(Arc::new(SidecarExtractor::new(item))),
),
]),
};
@@ -74,16 +88,17 @@ impl<'a> MetaExtractor<'a, crate::FileItem> {
}
}
impl Extractor<crate::FileItem> for MetaExtractor<'_, crate::FileItem> {
fn field<'a>(
#[async_trait::async_trait]
impl Extractor for MetaExtractor<'_> {
async fn field<'a>(
&'a self,
name: &pile_config::Label,
) -> Result<Option<&'a crate::PileValue<'a, crate::FileItem>>, std::io::Error> {
self.inner.field(name)
) -> Result<Option<&'a crate::PileValue<'a>>, std::io::Error> {
self.inner.field(name).await
}
#[expect(clippy::unwrap_used)]
fn fields(&self) -> Result<Vec<Label>, std::io::Error> {
async fn fields(&self) -> Result<Vec<Label>, std::io::Error> {
return Ok(vec![
Label::new("flac").unwrap(),
Label::new("id3").unwrap(),
@@ -92,4 +107,4 @@ impl Extractor<crate::FileItem> for MetaExtractor<'_, crate::FileItem> {
Label::new("sidecar").unwrap(),
]);
}
}
}