Refactor grouping

This commit is contained in:
2026-03-28 11:20:16 -07:00
parent 9967e066bb
commit 5527b61d39
40 changed files with 466 additions and 630 deletions

View File

@@ -0,0 +1,115 @@
use pile_config::Label;
use pile_io::SyncReadBridge;
use std::{
collections::HashMap,
io::BufReader,
sync::{Arc, OnceLock},
};
use tracing::trace;
use crate::{
extract::traits::{ExtractState, ObjectExtractor},
value::{BinaryPileValue, PileValue},
};
pub struct ExifExtractor {
item: BinaryPileValue,
output: OnceLock<HashMap<Label, PileValue>>,
}
impl ExifExtractor {
pub fn new(item: &BinaryPileValue) -> Self {
Self {
item: item.clone(),
output: OnceLock::new(),
}
}
async fn get_inner(&self) -> Result<&HashMap<Label, PileValue>, std::io::Error> {
if let Some(x) = self.output.get() {
return Ok(x);
}
let reader = SyncReadBridge::new_current(self.item.read().await?);
let raw_fields = tokio::task::spawn_blocking(move || {
let mut br = BufReader::new(reader);
let exif = exif::Reader::new().read_from_container(&mut br)?;
let fields: Vec<(String, String)> = exif
.fields()
.map(|f| {
(
f.tag.to_string(),
f.display_value().with_unit(&exif).to_string(),
)
})
.collect();
Ok::<_, exif::Error>(fields)
})
.await?;
let raw_fields = match raw_fields {
Ok(x) => x,
Err(exif::Error::Io(x)) => return Err(x),
Err(error) => {
trace!(message = "Could not process exif", ?error, item = ?self.item);
return Ok(self.output.get_or_init(HashMap::new));
}
};
let mut output: HashMap<Label, PileValue> = HashMap::new();
for (tag_name, value) in raw_fields {
let Some(label) = tag_to_label(&tag_name) else {
continue;
};
// First occurrence wins (PRIMARY IFD comes before THUMBNAIL)
output
.entry(label)
.or_insert_with(|| PileValue::String(Arc::new(value.into())));
}
return Ok(self.output.get_or_init(|| output));
}
}
fn tag_to_label(tag: &str) -> Option<Label> {
let sanitized: String = tag
.chars()
.map(|c| if c == ' ' { '_' } else { c })
.filter(|c| Label::VALID_CHARS.contains(*c))
.collect();
Label::new(sanitized)
}
#[async_trait::async_trait]
impl ObjectExtractor for ExifExtractor {
async fn field(
&self,
state: &ExtractState,
name: &Label,
args: Option<&str>,
) -> Result<Option<PileValue>, std::io::Error> {
trace!(
?args,
item = ?self.item,
"Getting field {name:?} from ExifExtractor",
);
if args.is_some() {
return Ok(None);
}
if !state.ignore_mime && self.item.mime().type_() != mime::IMAGE {
return Ok(None);
}
Ok(self.get_inner().await?.get(name).cloned())
}
async fn fields(&self) -> Result<Vec<Label>, std::io::Error> {
Ok(self.get_inner().await?.keys().cloned().collect())
}
}