feat: implement robust format identification, plugin availability checks, and automatic conversion path rerouting on failure
Release / check-release (push) Successful in 16s
Release / build_gnu (push) Skipped
Release / build_musl (push) Skipped
Release / build_windows (push) Skipped
Release / package_gnu (deb) (push) Skipped
Release / package_gnu (rpm) (push) Skipped
Release / package_musl (deb) (push) Skipped
Release / package_musl (rpm) (push) Skipped
Release / publish-release (push) Skipped
CI / Test (push) Successful in 1m52s
CI / Build Linux (push) Successful in 2m0s
Release / check-release (push) Successful in 16s
Release / build_gnu (push) Skipped
Release / build_musl (push) Skipped
Release / build_windows (push) Skipped
Release / package_gnu (deb) (push) Skipped
Release / package_gnu (rpm) (push) Skipped
Release / package_musl (deb) (push) Skipped
Release / package_musl (rpm) (push) Skipped
Release / publish-release (push) Skipped
CI / Test (push) Successful in 1m52s
CI / Build Linux (push) Successful in 2m0s
This commit is contained in:
1 parent
a372937854
commit
d3b7d7a18f
8 files changed
+244
-97
No files matched your search
+21
-6
@@ -15,14 +15,29 @@
|
||||
use crate::plugin::Plugin;
|
||||
use std::path::Path;
|
||||
|
||||
pub fn identify_format(path: &str, plugins: &[Box<dyn Plugin>]) -> Option<&'static str> {
|
||||
pub fn identify_format(path: &str, input: &[u8], plugins: &[Box<dyn Plugin>]) -> Option<&'static str> {
|
||||
let mut ext_str_opt = None;
|
||||
|
||||
if let Some(kind) = infer::get(input) {
|
||||
ext_str_opt = Some(kind.extension());
|
||||
}
|
||||
|
||||
let p = Path::new(path);
|
||||
let ext = p
|
||||
let mut ext = p
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
.unwrap_or(path)
|
||||
.to_lowercase();
|
||||
let ext_str = match ext.as_str() {
|
||||
.map(|s| s.to_lowercase());
|
||||
|
||||
if ext.is_none() {
|
||||
ext = Some(path.to_lowercase());
|
||||
}
|
||||
|
||||
let mut ext_str = ext.as_deref().unwrap_or("");
|
||||
if ext_str_opt.is_some() {
|
||||
ext_str = ext_str_opt.unwrap();
|
||||
}
|
||||
|
||||
let ext_str = match ext_str {
|
||||
"jpg" => "jpeg",
|
||||
other => other,
|
||||
};
|
||||
@@ -61,7 +76,7 @@ mod tests {
|
||||
fn $name() {
|
||||
let mock = Box::new(MockPlugin);
|
||||
let plugins: Vec<Box<dyn Plugin>> = vec![mock];
|
||||
assert_eq!(identify_format($ext, &plugins), $expected);
|
||||
assert_eq!(identify_format($ext, &[], &plugins), $expected);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
+27
-22
@@ -21,7 +21,7 @@ pub mod runner;
|
||||
include!(concat!(env!("OUT_DIR"), "/plugins_gen.rs"));
|
||||
|
||||
use args::Args;
|
||||
use clap::{Parser, CommandFactory, FromArgMatches};
|
||||
use clap::{CommandFactory, FromArgMatches};
|
||||
use std::fs;
|
||||
use std::io::{self, IsTerminal, Write};
|
||||
use std::process;
|
||||
@@ -80,7 +80,7 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
let from_format = match identifier::identify_format(input_path, &plugins) {
|
||||
let from_format = match identifier::identify_format(input_path, &input_bytes, &plugins) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
tracing::error!("Unknown input format.");
|
||||
@@ -89,7 +89,7 @@ fn main() {
|
||||
};
|
||||
|
||||
let to_format = match &args.output_path {
|
||||
Some(out) => match identifier::identify_format(out, &plugins) {
|
||||
Some(out) => match identifier::identify_format(out, &[], &plugins) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
tracing::error!("Unknown output format.");
|
||||
@@ -115,27 +115,32 @@ fn main() {
|
||||
}
|
||||
};
|
||||
|
||||
let path = match pathfinder::find_best_path(&plugins, from_format, to_format, &args.priority) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
tracing::error!("No conversion path found from {} to {}.", from_format, to_format);
|
||||
process::exit(1);
|
||||
let mut banned_plugins: Vec<&str> = Vec::new();
|
||||
|
||||
let output_bytes = loop {
|
||||
let path = match pathfinder::find_best_path(&plugins, from_format, to_format, &args.priority, &banned_plugins) {
|
||||
Some(p) => p,
|
||||
None => {
|
||||
tracing::error!("No conversion path found from {} to {}.", from_format, to_format);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if args.test {
|
||||
let path_str: Vec<_> = path.iter().map(|(p, _, _)| p.name()).collect();
|
||||
tracing::info!("Test successful. Path: {}", path_str.join(" -> "));
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if args.test {
|
||||
let path_str: Vec<_> = path.iter().map(|(p, _, _)| p.name()).collect();
|
||||
tracing::info!("Test successful. Path: {}", path_str.join(" -> "));
|
||||
return;
|
||||
}
|
||||
|
||||
let result = runner::run_conversion(&path, &input_bytes, &args.temp_dir);
|
||||
|
||||
let output_bytes = match result {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
tracing::error!("Conversion error: {}", e);
|
||||
process::exit(1);
|
||||
match runner::run_conversion(&path, &input_bytes, &args.temp_dir) {
|
||||
Ok(output_bytes) => {
|
||||
break output_bytes;
|
||||
}
|
||||
Err((e, plugin_name)) => {
|
||||
tracing::warn!("Conversion failed at plugin '{}': {}", plugin_name, e);
|
||||
tracing::warn!("Rerouting and trying alternative paths...");
|
||||
banned_plugins.push(plugin_name);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+9
-5
@@ -45,9 +45,13 @@ pub fn find_best_path<'a>(
|
||||
from_format: &'a str,
|
||||
to_format: &str,
|
||||
priority: &str,
|
||||
banned_plugins: &[&str],
|
||||
) -> Option<Vec<(&'a dyn Plugin, &'a str, &'a str)>> {
|
||||
let mut adj_list: HashMap<&str, Vec<&'a dyn Plugin>> = HashMap::new();
|
||||
for p in plugins {
|
||||
if banned_plugins.contains(&p.name()) || !p.is_available() {
|
||||
continue;
|
||||
}
|
||||
for &from in &p.from_formats() {
|
||||
adj_list.entry(from).or_default().push(p.as_ref());
|
||||
}
|
||||
@@ -243,7 +247,7 @@ mod tests {
|
||||
}),
|
||||
];
|
||||
|
||||
let path = find_best_path(&plugins, "jpeg", "png", "fqs").unwrap();
|
||||
let path = find_best_path(&plugins, "jpeg", "png", "fqs", &[]).unwrap();
|
||||
assert_eq!(path.len(), 1);
|
||||
assert_eq!(path[0].0.name(), "A");
|
||||
}
|
||||
@@ -269,10 +273,10 @@ mod tests {
|
||||
}),
|
||||
];
|
||||
|
||||
let path = find_best_path(&plugins, "jpeg", "png", "fqs").unwrap();
|
||||
let path = find_best_path(&plugins, "jpeg", "png", "fqs", &[]).unwrap();
|
||||
assert_eq!(path[0].0.name(), "B");
|
||||
|
||||
let path = find_best_path(&plugins, "jpeg", "png", "qfs").unwrap();
|
||||
let path = find_best_path(&plugins, "jpeg", "png", "qfs", &[]).unwrap();
|
||||
assert_eq!(path[0].0.name(), "A");
|
||||
}
|
||||
|
||||
@@ -339,7 +343,7 @@ mod tests {
|
||||
}),
|
||||
];
|
||||
|
||||
let path = find_best_path(&plugins, $from, $to, $priority);
|
||||
let path = find_best_path(&plugins, $from, $to, $priority, &[]);
|
||||
if $expected_len == 0 {
|
||||
assert!(path.is_none());
|
||||
} else {
|
||||
@@ -408,7 +412,7 @@ mod tests {
|
||||
s: 10,
|
||||
}),
|
||||
];
|
||||
let path = find_best_path(&plugins, $from, $to, $priority);
|
||||
let path = find_best_path(&plugins, $from, $to, $priority, &[]);
|
||||
if $expected_len == 0 {
|
||||
assert!(path.is_none());
|
||||
} else {
|
||||
|
||||
@@ -19,6 +19,11 @@ pub trait Plugin: Send + Sync {
|
||||
fn from_formats(&self) -> Vec<&'static str>;
|
||||
fn to_formats(&self) -> Vec<&'static str>;
|
||||
|
||||
/// Checks if the plugin is available to run on this system (e.g. required binaries are installed).
|
||||
fn is_available(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Score from 1 to 255. Higher is better.
|
||||
fn familiarity(&self, from_format: &str, to_format: &str) -> u8;
|
||||
fn quality(&self, from_format: &str, to_format: &str) -> u8;
|
||||
|
||||
+5
-2
@@ -20,7 +20,7 @@ pub fn run_conversion(
|
||||
path: &[(&dyn Plugin, &str, &str)],
|
||||
input: &[u8],
|
||||
temp_dir: &Path,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
) -> Result<Vec<u8>, (String, &'static str)> {
|
||||
let path_str: Vec<_> = path.iter().map(|(p, _, _)| p.name()).collect();
|
||||
tracing::info!("Path taken: {}", path_str.join(" -> "));
|
||||
|
||||
@@ -45,7 +45,10 @@ pub fn run_conversion(
|
||||
);
|
||||
|
||||
let start_time = Instant::now();
|
||||
current_data = plugin.convert(¤t_data, from_format, to_format, temp_dir)?;
|
||||
current_data = match plugin.convert(¤t_data, from_format, to_format, temp_dir) {
|
||||
Ok(data) => data,
|
||||
Err(e) => return Err((e, plugin.name())),
|
||||
};
|
||||
let elapsed = start_time.elapsed();
|
||||
|
||||
tracing::info!(
|
||||
|
||||
Reference in new issue
Block a user