Route dr-ui's decoding through the Decoder trait

With the trait in place the claim still meant nothing while every caller
named dr_decode's free functions: a second decoder would have had to be
threaded through the scan, the thumbnail ladder, import, the viewer,
export, merge and repairs at the moment it arrived.

Each of those now takes a &dyn Decoder and reads headers, previews,
orientation and sensor data through it, including the header budget a
remote fetch asks for (header_bytes) and where it finds the embedded
preview (locate_preview). Only the places that start a job name
dr_decode::default(): the thumbnail, sweep and thumbnail-sweep threads,
the viewer's open handlers, and the request structs a job is handed
(BatchRequest, MergeRequest, the import Request, the repairs Toolkit),
so a caller can be given another decoder by changing what it is handed.

The default is rawler through the same free functions as before, so
nothing a user sees changes. The trait gains Debug as a supertrait so
request structs that derive Debug can carry one.
This commit is contained in:
2026-09-24 21:33:14 -04:00
parent d8fb382ce9
commit 414094bd38
10 changed files with 214 additions and 129 deletions
+24 -5
View File
@@ -517,6 +517,10 @@ pub struct BatchRequest {
/// rendered at all — reported per image rather than refused up front, so
/// the reason lands in the same place every other failure does.
pub gpu: Option<dr_gpu::GpuContext>,
/// TRACES: FR-RAW-2
/// What reads the header and decodes the sensor data, chosen by whoever
/// assembled the batch; the worker never names one.
pub decoder: &'static dyn dr_decode::Decoder,
}
/// TRACES: NFR-ARCH-4
@@ -750,6 +754,22 @@ type RenderedItem = (
Option<dr_export::SourceMetadata>,
);
/// TRACES: FR-RAW-2 | FR-EXP-8
/// Read a fetched photograph's header and open it for rendering, through
/// whichever decoder the batch was given.
///
/// Split from [`render_from_library`] so the half that decodes can be run
/// without a server: the stub-decoder test drives it directly.
pub(crate) fn open_for_export(
gpu: &dr_gpu::GpuContext,
decoder: &dyn dr_decode::Decoder,
bytes: &[u8],
) -> Result<(dr_decode::Metadata, crate::DevelopSession), String> {
let meta = decoder.metadata(bytes).unwrap_or_default();
let session = crate::open_session(gpu, decoder, bytes, &meta)?;
Ok((meta, session))
}
/// Fetch a photograph, apply its stored edit, and render it at full size.
///
/// `None` means the work was cancelled, which is not a failure and has no
@@ -802,13 +822,11 @@ fn render_from_library(
Waited::Silent => None,
};
let meta = dr_decode::metadata(&bytes).unwrap_or_default();
let (date, carried) = from_header(&meta);
let mut session = match crate::open_session(gpu, &bytes, &meta) {
Ok(session) => session,
let (meta, mut session) = match open_for_export(gpu, request.decoder, &bytes) {
Ok(opened) => opened,
Err(e) => return Some(Err(ItemError::Open(e))),
};
let (date, carried) = from_header(&meta);
// TRACES: FR-CAT-8
// The stored edit. FR-EXP-9 is about resolution; this is the other half of
@@ -1400,6 +1418,7 @@ mod tests {
sidecar_cache: std::env::temp_dir().join("dr-batch-test-sidecars"),
offline: true,
gpu: None,
decoder: dr_decode::default(),
}
}
+30 -10
View File
@@ -92,6 +92,9 @@ pub struct Request {
/// Where the photographs are going. An import without one has nowhere to
/// put anything, and the page refuses to start.
pub upload: Upload,
/// TRACES: FR-RAW-2
/// What reads each candidate's header and cuts its thumbnail.
pub decoder: &'static dyn dr_decode::Decoder,
}
/// TRACES: FR-NC-7a | FR-NC-7b
@@ -283,7 +286,7 @@ fn run(request: Request, cancel: &Cancel, tx: &Sender<Message>) -> Result<(), St
let report = ingest.run(
&candidates,
&|c| probe(&card, c),
&|c| probe(&card, request.decoder, c),
&|key| is_duplicate(catalog.connection(), key),
&|| cancel.load(Ordering::Relaxed),
&mut |p| {
@@ -303,7 +306,14 @@ fn run(request: Request, cancel: &Cancel, tx: &Sender<Message>) -> Result<(), St
// photograph — only a transfer.
{
let upload = &request.upload;
let (sent, thumbnails) = upload_all(upload, &library, &report.imported, cancel, tx);
let (sent, thumbnails) = upload_all(
upload,
request.decoder,
&library,
&report.imported,
cancel,
tx,
);
outcome.thumbnails = thumbnails;
outcome.uploaded = sent.iter().filter(|s| s.transferred).count();
outcome.already_on_server = sent.len() - outcome.uploaded;
@@ -370,6 +380,7 @@ fn run(request: Request, cancel: &Cancel, tx: &Sender<Message>) -> Result<(), St
/// kept and a card emptied.
fn upload_all(
upload: &Upload,
decoder: &dyn dr_decode::Decoder,
library: &LocalStorage,
imported: &[Imported],
cancel: &Cancel,
@@ -418,7 +429,7 @@ fn upload_all(
// Before the body is handed over: `upload_original` takes it by
// value, and re-reading the file to make a thumbnail afterwards
// would be a second full read of an 80 MB original.
let thumbnail = make_thumbnail(&bytes);
let thumbnail = make_thumbnail(decoder, &bytes);
// The same folder segments the local copy went into, so the two
// libraries have the same shape (FR-NC-7a).
@@ -473,10 +484,15 @@ struct Sent {
/// than the full preview's. An embedded preview is written in the sensor's
/// orientation, so without this every portrait frame lies on its side — and
/// the store is keyed by file and size alone, so it would stay that way.
fn make_thumbnail(bytes: &[u8]) -> Option<dr_thumbs::Thumbnail> {
let mut preview = dr_decode::extract_preview(bytes, dr_decode::PreviewSize::Thumbnail).ok()?;
pub(crate) fn make_thumbnail(
decoder: &dyn dr_decode::Decoder,
bytes: &[u8],
) -> Option<dr_thumbs::Thumbnail> {
let mut preview = decoder
.preview(bytes, dr_decode::PreviewSize::Thumbnail)
.ok()?;
preview.downscale_to(dr_thumbs::ThumbSize::Grid.edge());
preview.apply_orientation(dr_decode::orientation(bytes).unwrap_or_default());
preview.apply_orientation(decoder.orientation(bytes).unwrap_or_default());
let encoded = dr_thumbs::encode_rgba(preview.width, preview.height, &preview.rgba).ok()?;
Some(dr_thumbs::Thumbnail {
width: preview.width,
@@ -676,14 +692,18 @@ pub fn survey(
/// Read one candidate's capture metadata.
///
/// A header read rather than the whole file: `dr_decode::HEADER_BYTES` is
/// A header read rather than the whole file: the decoder's `header_bytes` is
/// enough for EXIF on every format in the tree, and reading 80 MB per file to
/// learn a date would take longer than the import itself.
fn probe(card: &dyn Storage, candidate: &Candidate) -> Option<Shot> {
fn probe(
card: &dyn Storage,
decoder: &dyn dr_decode::Decoder,
candidate: &Candidate,
) -> Option<Shot> {
let header = card
.read_range(&candidate.source, 0..dr_decode::HEADER_BYTES)
.read_range(&candidate.source, 0..decoder.header_bytes())
.ok()?;
let md = dr_decode::metadata(&header).ok()?;
let md = decoder.metadata(&header).ok()?;
Some(Shot {
captured_at: md.captured_at,
captured_offset: md.captured_offset,
+1
View File
@@ -589,6 +589,7 @@ fn start(
filter: context.filter.clone(),
options: ctl.ingest_options(),
upload,
decoder: dr_decode::default(),
};
let cancel: import::Cancel = Default::default();
+25 -12
View File
@@ -71,7 +71,7 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
use anyhow::Result;
use dr_decode::{Metadata, PreviewSize};
use dr_decode::{Decoder, Metadata, PreviewSize};
use slint::ComponentHandle as _;
pub use develop::DevelopSession;
@@ -198,7 +198,11 @@ struct Loaded {
/// (FR-NC-3) fetches only a byte range for *browsing*, which is why the
/// preview API is separate — this is the develop path, and it is expected to
/// be expensive.
fn load(ctx: Option<&dr_gpu::GpuContext>, path: &Path) -> Result<Loaded, String> {
fn load(
ctx: Option<&dr_gpu::GpuContext>,
decoder: &dyn Decoder,
path: &Path,
) -> Result<Loaded, String> {
// A VFS placeholder holds one byte and reading it triggers no fetch
// (ARCH §9.0). Say so plainly rather than reporting a decode failure.
if path
@@ -210,7 +214,7 @@ fn load(ctx: Option<&dr_gpu::GpuContext>, path: &Path) -> Result<Loaded, String>
}
let bytes = std::fs::read(path).map_err(|e| e.to_string())?;
load_bytes(ctx, &bytes)
load_bytes(ctx, decoder, &bytes)
}
/// Open already-fetched bytes.
@@ -218,8 +222,12 @@ fn load(ctx: Option<&dr_gpu::GpuContext>, path: &Path) -> Result<Loaded, String>
/// Split from [`load`] because a library image has no local file: it arrives
/// as a WebDAV response body, and writing it to disk purely to read it back
/// would be a round-trip for nothing.
fn load_bytes(ctx: Option<&dr_gpu::GpuContext>, bytes: &[u8]) -> Result<Loaded, String> {
let meta = dr_decode::metadata(bytes).unwrap_or_default();
fn load_bytes(
ctx: Option<&dr_gpu::GpuContext>,
decoder: &dyn Decoder,
bytes: &[u8],
) -> Result<Loaded, String> {
let meta = decoder.metadata(bytes).unwrap_or_default();
// How the file stored its pixels, for the preview path below — the develop
// path takes it from the same header inside `open_session`. A file that
@@ -227,7 +235,7 @@ fn load_bytes(ctx: Option<&dr_gpu::GpuContext>, bytes: &[u8]) -> Result<Loaded,
let orientation = meta.orientation.unwrap_or_default();
if let Some(ctx) = ctx {
match open_session(ctx, bytes, &meta) {
match open_session(ctx, decoder, bytes, &meta) {
Ok(session) => {
let (width, height) = session.source_size();
return Ok(Loaded {
@@ -244,8 +252,9 @@ fn load_bytes(ctx: Option<&dr_gpu::GpuContext>, bytes: &[u8]) -> Result<Loaded,
// Fall back to the embedded preview: no GPU, or a file neither decoder
// could open for editing. Read-only, and the adjust panel is disabled.
let mut preview =
dr_decode::extract_preview(bytes, PreviewSize::Screen).map_err(|e| e.to_string())?;
let mut preview = decoder
.preview(bytes, PreviewSize::Screen)
.map_err(|e| e.to_string())?;
preview.downscale_to(MAX_DISPLAY_DIM);
// No graph here to carry the baseline, so the pixels are turned instead.
// Cheaper than it sounds after the downscale, and this path is the one a
@@ -283,12 +292,13 @@ fn load_bytes(ctx: Option<&dr_gpu::GpuContext>, bytes: &[u8]) -> Result<Loaded,
/// construction rather than by everybody remembering to say so.
pub(crate) fn open_session(
ctx: &dr_gpu::GpuContext,
decoder: &dyn Decoder,
bytes: &[u8],
meta: &Metadata,
) -> Result<DevelopSession, String> {
// How the file stored its pixels. A file that says nothing is taken as
// upright — see `Orientation::from_exif`.
let mut session = open_pixels(ctx, bytes, meta.orientation.unwrap_or_default())?;
let mut session = open_pixels(ctx, decoder, bytes, meta.orientation.unwrap_or_default())?;
// TRACES: FR-EXP-8
// The header goes with the session rather than being read again later,
@@ -308,6 +318,7 @@ pub(crate) fn open_session(
/// the pixels came from.
fn open_pixels(
ctx: &dr_gpu::GpuContext,
decoder: &dyn Decoder,
bytes: &[u8],
orientation: dr_types::Orientation,
) -> Result<DevelopSession, String> {
@@ -338,7 +349,8 @@ fn open_pixels(
// A failure here is expected for bodies rawler does not know, and must not
// stop the image displaying (FR-RAW-4).
dr_decode::decode(bytes)
decoder
.decode(bytes)
.map_err(|e| e.to_string())
.and_then(|raw| DevelopSession::open(ctx, &raw, orientation))
}
@@ -676,6 +688,7 @@ fn batch_request(
sidecar_cache: library.sidecar_cache_dir().unwrap_or_default(),
offline: library.is_offline(),
gpu: gpu.cloned(),
decoder: dr_decode::default(),
}
}
@@ -2539,7 +2552,7 @@ fn build_show(
window.set_filename(name.clone().into());
window.set_index(i as i32);
match load(gpu.as_ref(), path) {
match load(gpu.as_ref(), dr_decode::default(), path) {
Ok(l) => {
window.set_load_error("".into());
let capture = window.global::<Capture>();
@@ -2802,7 +2815,7 @@ fn wire_remote_open(
// transfers are under way while the UI thread is busy.
prefetch_neighbours(&library, &prefetch, &path);
match load_bytes(gpu.as_ref(), &bytes) {
match load_bytes(gpu.as_ref(), dr_decode::default(), &bytes) {
Ok(l) => {
w.set_load_error("".into());
let capture = w.global::<Capture>();
+16 -8
View File
@@ -69,8 +69,9 @@ pub(super) fn flush_metadata(
/// otherwise tell: a header that carried no EXIF and a fetch that never
/// happened both leave `found` untouched, and recording the second as "this
/// image has no date" would let one lock mark it dateless for good.
pub(super) async fn read_metadata_only(
pub(crate) async fn read_metadata_only(
backend: &dyn RemoteBackend,
decoder: &dyn dr_decode::Decoder,
req: &ThumbnailRequest,
found: &mut Vec<MetadataFound>,
) -> bool {
@@ -86,9 +87,9 @@ pub(super) async fn read_metadata_only(
// three round trips before the sweep moves on.
const ATTEMPTS: usize = 3;
for attempt in 1..=ATTEMPTS {
match backend.get(&id, Some(0..dr_decode::HEADER_BYTES)).await {
match backend.get(&id, Some(0..decoder.header_bytes())).await {
Ok(header) => {
collect_metadata(backend, &id, &header, req, found).await;
collect_metadata(backend, decoder, &id, &header, req, found).await;
return true;
}
Err(e) if e.is_transient() && attempt < ATTEMPTS => {
@@ -275,6 +276,9 @@ pub(crate) const SWEEP_MAX_ORIGINAL_BYTES: u64 = 256 * 1024 * 1024;
/// and its chunked commits keep write transactions short.
pub fn spawn_sweep(conn: Connection, catalog_path: PathBuf) -> Receiver<SweepMessage> {
let (tx, rx) = std::sync::mpsc::channel();
// TRACES: FR-RAW-2
// The one place this job names a decoder; everything below takes it.
let decoder = dr_decode::default();
std::thread::spawn(move || {
let catalog = match Catalog::open(&catalog_path) {
@@ -355,7 +359,7 @@ pub fn spawn_sweep(conn: Connection, catalog_path: PathBuf) -> Receiver<SweepMes
let mut found = Vec::new();
let mut reached = Vec::new();
for req in lane {
if read_metadata_only(backend, req, &mut found).await {
if read_metadata_only(backend, decoder, req, &mut found).await {
reached.push(req.image_id);
}
}
@@ -511,7 +515,7 @@ pub(super) fn flush_sweep(catalog: &Catalog, found: &mut Vec<MetadataFound>) {
/// ways and comparing the crops — see `examples/face_native.rs`. A tool that
/// had to reimplement the render would be measuring its own reimplementation.
pub fn render_native(gpu: &dr_gpu::GpuContext, bytes: &[u8]) -> Result<dr_export::Frame, String> {
open_native(gpu, bytes)?.render_for_export(dr_types::ColourSpace::Srgb)
open_native(gpu, dr_decode::default(), bytes)?.render_for_export(dr_types::ColourSpace::Srgb)
}
/// The session behind [`render_native`], crate-private because `DevelopSession`
@@ -520,13 +524,14 @@ pub fn render_native(gpu: &dr_gpu::GpuContext, bytes: &[u8]) -> Result<dr_export
/// opening the file twice (`repairs::Fetched`).
pub(crate) fn open_native(
gpu: &dr_gpu::GpuContext,
decoder: &dyn dr_decode::Decoder,
bytes: &[u8],
) -> Result<crate::develop::DevelopSession, String> {
// Whatever the header says, or an empty one for a file that has none: the
// session takes its orientation from it, and remembers the rest for
// anything that later exports from this session (FR-EXP-8).
let meta = dr_decode::metadata(bytes).unwrap_or_default();
crate::open_session(gpu, bytes, &meta)
let meta = decoder.metadata(bytes).unwrap_or_default();
crate::open_session(gpu, decoder, bytes, &meta)
}
/// The class the whole-library pass fills.
@@ -593,6 +598,9 @@ pub fn spawn_thumbnail_sweep(
store_dir: PathBuf,
) -> Receiver<ThumbSweepMessage> {
let (tx, rx) = std::sync::mpsc::channel();
// TRACES: FR-RAW-2
// The one place this job names a decoder; everything below takes it.
let decoder = dr_decode::default();
std::thread::spawn(move || {
let finish_empty = |tx: &Sender<ThumbSweepMessage>| {
@@ -727,7 +735,7 @@ pub fn spawn_thumbnail_sweep(
}
};
match fetch_preview(backend, req, &mut found).await {
match fetch_preview(backend, decoder, req, &mut found).await {
PreviewOutcome::Ready(preview) => {
match encode_preview(file_id, &preview) {
Some(thumb) => made.push((file_id, thumb)),
+17 -11
View File
@@ -97,6 +97,9 @@ pub fn spawn_thumbnails(
catalog_path: PathBuf,
) -> Receiver<ThumbnailMessage> {
let (tx, rx) = std::sync::mpsc::channel();
// TRACES: FR-RAW-2
// The one place this job names a decoder; everything below takes it.
let decoder = dr_decode::default();
std::thread::spawn(move || {
let mut store = match ThumbStore::open(&store_dir) {
@@ -222,7 +225,7 @@ pub fn spawn_thumbnails(
let mut offline = false;
for req in to_fetch {
let msg = fetch_one(&*backend, store.as_mut(), &req, &mut found).await;
let msg = fetch_one(&*backend, decoder, store.as_mut(), &req, &mut found).await;
offline = matches!(msg, ThumbnailMessage::Offline { .. });
// A closed channel means the window went away mid-fetch.
if tx.send(msg).is_err() || offline {
@@ -247,7 +250,7 @@ pub fn spawn_thumbnails(
if tx.send(ThumbnailMessage::DateProgress).is_err() {
break;
}
read_metadata_only(&*backend, &req, &mut found).await;
read_metadata_only(&*backend, decoder, &req, &mut found).await;
if found.len() >= FLUSH_EVERY {
flush_metadata(&catalog_path, &mut found, &tx);
@@ -267,11 +270,12 @@ pub fn spawn_thumbnails(
pub(super) async fn fetch_one(
backend: &dyn RemoteBackend,
decoder: &dyn dr_decode::Decoder,
store: Option<&mut ThumbStore>,
req: &ThumbnailRequest,
found_metadata: &mut Vec<MetadataFound>,
) -> ThumbnailMessage {
let preview = match fetch_preview(backend, req, found_metadata).await {
let preview = match fetch_preview(backend, decoder, req, found_metadata).await {
PreviewOutcome::Ready(p) => p,
PreviewOutcome::Unavailable(reason) => {
return ThumbnailMessage::Unavailable {
@@ -308,7 +312,7 @@ pub(super) async fn fetch_one(
/// ([`spawn_thumbnail_sweep`]) fetches on several lanes at once and stores the
/// results on the one thread that owns the store, so it needs the pixels
/// *before* anything is written or addressed to a cell.
pub(super) enum PreviewOutcome {
pub(crate) enum PreviewOutcome {
Ready(dr_decode::Preview),
/// This image has no usable preview. The batch continues past it.
Unavailable(String),
@@ -322,8 +326,9 @@ pub(super) enum PreviewOutcome {
/// was wrong in a way that looked like corruption: fetching a fixed prefix cut
/// the embedded JPEG partway through, and decoders render a truncated JPEG as
/// the top fraction of the frame rather than reporting an error.
pub(super) async fn fetch_preview(
pub(crate) async fn fetch_preview(
backend: &dyn RemoteBackend,
decoder: &dyn dr_decode::Decoder,
req: &ThumbnailRequest,
found_metadata: &mut Vec<MetadataFound>,
) -> PreviewOutcome {
@@ -341,7 +346,7 @@ pub(super) async fn fetch_preview(
};
// Stage one: the header, enough to parse the container's IFDs.
let header = match backend.get(&id, Some(0..dr_decode::HEADER_BYTES)).await {
let header = match backend.get(&id, Some(0..decoder.header_bytes())).await {
Ok(b) => b,
Err(e) => return classify(e),
};
@@ -349,15 +354,15 @@ pub(super) async fn fetch_preview(
// The same bytes carry EXIF. Reading it here is free — the alternative is
// a second 256 KB fetch per image over the whole library.
if req.needs_metadata {
collect_metadata(backend, &id, &header, req, found_metadata).await;
collect_metadata(backend, decoder, &id, &header, req, found_metadata).await;
}
// Read unconditionally, unlike the rest of the EXIF above: `needs_metadata`
// is false once an image has been catalogued, but a thumbnail can still be
// regenerated long after that — a cleared cache, a new size — and a
// thumbnail that came out upright the first time must come out upright
// every time. This is a header walk, not a decode; see `dr_decode::orientation`.
let orientation = dr_decode::orientation(&header).unwrap_or_default();
// every time. This is a header walk, not a decode; see `Decoder::orientation`.
let orientation = decoder.orientation(&header).unwrap_or_default();
// A plain JPEG is its own preview; anything else needs locating.
let bytes = if header.starts_with(&[0xFF, 0xD8, 0xFF]) {
@@ -366,7 +371,7 @@ pub(super) async fn fetch_preview(
Err(e) => return classify(e),
}
} else {
let Some(loc) = dr_decode::locate_preview(&header, req.size) else {
let Some(loc) = decoder.locate_preview(&header, req.size) else {
// No locatable preview. Declining beats fetching the whole file:
// that is the 370 GB path FR-NC-3 exists to avoid.
return PreviewOutcome::Unavailable("no locatable embedded preview".into());
@@ -474,12 +479,13 @@ pub(crate) fn store_thumbnail(
/// and the header-only pass for images whose pixels were already cached.
pub(super) async fn collect_metadata(
backend: &dyn RemoteBackend,
decoder: &dyn dr_decode::Decoder,
id: &RemoteId,
header: &[u8],
req: &ThumbnailRequest,
out: &mut Vec<MetadataFound>,
) {
let md = match dr_decode::metadata(header) {
let md = match decoder.metadata(header) {
Ok(md) => md,
Err(first) => {
// A file whose IFDs follow its pixels — the linear DNG a merge
+9 -2
View File
@@ -102,6 +102,9 @@ pub struct MergeRequest {
/// The border filler's weights, if the device has them (FR-MRG-4).
/// `None` and the fill is not offered.
pub inpaint_model: Option<PathBuf>,
/// TRACES: FR-RAW-2
/// What reads and decodes each frame.
pub decoder: &'static dyn dr_decode::Decoder,
}
impl MergeRequest {
@@ -113,6 +116,7 @@ impl MergeRequest {
feather_px: 200.0,
chunk: (2048, 512),
inpaint_model: None,
decoder: dr_decode::default(),
}
}
}
@@ -328,8 +332,11 @@ fn run_inner(
}
progress("Reading", i, request.frames.len());
let bytes = &input.bytes;
let meta = dr_decode::metadata(bytes).unwrap_or_default();
let raw = dr_decode::decode(bytes).map_err(|e| format!("{}: {e}", input.name))?;
let meta = request.decoder.metadata(bytes).unwrap_or_default();
let raw = request
.decoder
.decode(bytes)
.map_err(|e| format!("{}: {e}", input.name))?;
let orientation = meta.orientation.unwrap_or_default();
let mut graph = EditGraph::default_chain();
graph.set_orientation(orientation);
+18 -8
View File
@@ -61,7 +61,7 @@ use crate::library::{FaceModelPaths, MetadataFound};
/// native render serves every face handler and the metadata one besides.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Input {
/// The first `dr_decode::HEADER_BYTES` of the file: EXIF, and the
/// The first `Decoder::header_bytes` of the file: EXIF, and the
/// embedded preview's offset.
Header,
/// The whole original, undecoded.
@@ -165,15 +165,18 @@ pub struct Target {
pub struct Fetched {
bytes: Vec<u8>,
input: Input,
/// What opens [`Self::session`]; the job's, from its [`Toolkit`].
decoder: &'static dyn dr_decode::Decoder,
session: Option<crate::develop::DevelopSession>,
frame: Option<dr_export::Frame>,
}
impl Fetched {
fn new(bytes: Vec<u8>, input: Input) -> Self {
fn new(bytes: Vec<u8>, input: Input, decoder: &'static dyn dr_decode::Decoder) -> Self {
Self {
bytes,
input,
decoder,
session: None,
frame: None,
}
@@ -194,8 +197,10 @@ impl Fetched {
return Err(Failure::Other("only the header was fetched".into()));
}
if self.session.is_none() {
self.session =
Some(crate::library::open_native(gpu, &self.bytes).map_err(Failure::Unreadable)?);
self.session = Some(
crate::library::open_native(gpu, self.decoder, &self.bytes)
.map_err(Failure::Unreadable)?,
);
}
Ok(self.session.as_mut().expect("just opened"))
}
@@ -234,6 +239,9 @@ pub struct Toolkit {
pub model_id: String,
pub store: ThumbStore,
pub options: dr_face::DetectOptions,
/// TRACES: FR-RAW-2
/// What reads headers and opens originals for every handler.
pub decoder: &'static dyn dr_decode::Decoder,
}
impl Toolkit {
@@ -692,12 +700,12 @@ fn crop(
/// marked examined too, so it is not fetched again -- the state the sweep
/// records for the dateless, for the same reason.
fn metadata(
_tk: &mut Toolkit,
tk: &mut Toolkit,
catalog: &Catalog,
target: &Target,
fetched: &mut Fetched,
) -> Result<usize, Failure> {
let found = match dr_decode::metadata(fetched.bytes()) {
let found = match tk.decoder.metadata(fetched.bytes()) {
Ok(md) => MetadataFound {
image_id: target.image.0 as i64,
captured_at: md.captured_at,
@@ -1065,6 +1073,7 @@ pub fn spawn(
model_id: model_id.clone(),
store,
options,
decoder: dr_decode::default(),
};
let repairs = registry(scope, &model_id, detector, caps);
@@ -1144,6 +1153,7 @@ pub fn spawn(
// photograph. Every file borrowed is given back (ARCH §9.0a).
let pool = dr_sync_folder::BorrowPool::new();
let header_bytes = tk.decoder.header_bytes();
for chunk in planned.chunks(LANES) {
let fetched = crate::library::futures_join_all(chunk.iter().map(|p| {
let backend = &*backend;
@@ -1151,7 +1161,7 @@ pub fn spawn(
async move {
let id = RemoteId::Path(RemotePath::new(&p.target.path));
let got = if p.input == Input::Header {
backend.get(&id, Some(0..dr_decode::HEADER_BYTES)).await
backend.get(&id, Some(0..header_bytes)).await
} else {
let held = match pool
.borrow(backend, &RemotePath::new(&p.target.path))
@@ -1185,7 +1195,7 @@ pub fn spawn(
}
};
let mut fetched = Fetched::new(bytes, p.input);
let mut fetched = Fetched::new(bytes, p.input, tk.decoder);
let mut wrote = 0usize;
let mut broke = false;
for (repair, set) in repairs.iter().zip(sets.iter()) {