use std::path::{Path, PathBuf};
use std::time::Instant;
use oinkie::birthmarks::{AnalysisType, Birthmark, BirthmarkType};
use oinkie::compare::{Aggregator, Comparator, PairingStrategy};
use oinkie::extract::Extractor;
use oinkie::{Error, Program, Result};
use rayon::prelude::*;
use rmcp::schemars;
use serde::Serialize;
use crate::CompareResult;
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub struct Score {
pub index: usize,
pub left: String,
pub right: String,
pub similarity: f64,
pub duration_ms: u64,
}
#[derive(Debug, Serialize, schemars::JsonSchema)]
pub struct Extracted {
pub input: String,
pub output: String,
pub elements: usize,
pub duration_ms: u64,
}
fn ms(d: std::time::Duration) -> u64 {
d.as_millis() as u64
}
fn scores(results: &[CompareResult]) -> Vec<Score> {
results
.iter()
.map(|r| Score {
index: r.index,
left: r.path1.display().to_string(),
right: r.path2.display().to_string(),
similarity: r.similarity,
duration_ms: ms(r.duration),
})
.collect()
}
fn store(results: Vec<CompareResult>, dest: &Path, start: Instant) -> Result<()> {
crate::store_and_get_durations(results, &results_file(dest), start).map(|_| ())
}
pub fn pair_file(dest: &Path, index: usize) -> PathBuf {
dest.join(format!("{index:05}.csv"))
}
pub fn results_file(dest: &Path) -> PathBuf {
dest.join("results.csv")
}
pub fn score_files(dest: &Path, pairs: usize) -> impl Iterator<Item = PathBuf> + '_ {
(0..pairs)
.map(move |i| pair_file(dest, i))
.chain(std::iter::once(results_file(dest)))
}
pub fn extracted_files(inputs: &[PathBuf], dir: &Path) -> Result<Vec<PathBuf>> {
let inputs = inputs.iter().collect::<Vec<_>>();
Ok(crate::unique_destinations(&inputs, dir)?
.into_iter()
.map(|(_, out)| out)
.collect())
}
fn prepare(dest: Option<&Path>) -> Result<()> {
match dest {
Some(d) => std::fs::create_dir_all(d).map_err(|e| Error::Io(d.to_path_buf(), e)),
None => Ok(()),
}
}
pub fn extract(
inputs: &[PathBuf],
birthmark_type: &BirthmarkType,
dest: &Path,
skip: bool,
) -> Result<Vec<Extracted>> {
std::fs::create_dir_all(dest).map_err(|e| Error::Io(dest.to_path_buf(), e))?;
let extractor = Extractor::new(birthmark_type.clone());
let inputs = inputs.iter().collect::<Vec<_>>();
let done = crate::unique_destinations(&inputs, dest)?
.par_iter()
.map(|(input, out)| {
let start = Instant::now();
if out.exists() && skip {
let birthmark: Birthmark = out.clone().try_into()?;
return Ok(Extracted {
input: input.display().to_string(),
output: out.display().to_string(),
elements: birthmark.len(),
duration_ms: ms(start.elapsed()),
});
}
let program = Program::load(input)?;
let birthmark = extractor.extract(&program)?;
let json = serde_json::to_string_pretty(&birthmark)
.map_err(|e| Error::Json(out.clone(), e))?;
std::fs::write(out, json).map_err(|e| Error::Io(out.clone(), e))?;
Ok(Extracted {
input: input.display().to_string(),
output: out.display().to_string(),
elements: birthmark.len(),
duration_ms: ms(start.elapsed()),
})
})
.collect::<Vec<_>>();
Error::vec_result_to_result_vec(done)
}
pub fn birthmarks_dir(dest: &Path) -> PathBuf {
dest.join("birthmarks")
}
pub fn birthmark_files(inputs: &[PathBuf], dest: &Path) -> Result<Vec<PathBuf>> {
extracted_files(inputs, &birthmarks_dir(dest))
}
pub fn run(
inputs: &[PathBuf],
analysis: &AnalysisType,
comparator: &Comparator,
strategy: &PairingStrategy,
aggregator: &Aggregator,
dest: Option<&Path>,
) -> Result<Vec<Score>> {
let start = Instant::now();
prepare(dest)?;
let extractor = Extractor::new(analysis.birthmark().clone());
let by_input = match dest {
Some(d) => {
let dir = birthmarks_dir(d);
std::fs::create_dir_all(&dir).map_err(|e| Error::Io(dir.clone(), e))?;
crate::extract_all(inputs, &dir, &extractor, false, || ())?
.into_iter()
.map(|(input, e)| (input, e.birthmark))
.collect::<rustc_hash::FxHashMap<_, _>>()
}
None => {
let mut unique = inputs.iter().collect::<Vec<_>>();
unique.sort();
unique.dedup();
let done = unique
.par_iter()
.map(|input| Ok(((*input).clone(), extractor.extract(&Program::load(input)?)?)))
.collect::<Vec<_>>();
Error::vec_result_to_result_vec(done)?
.into_iter()
.collect::<rustc_hash::FxHashMap<_, _>>()
}
};
let results = strategy
.pairs(inputs)
.enumerate()
.par_bridge()
.map(|(i, (left, right))| {
let (b1, b2) = (&by_input[left], &by_input[right]);
let comparison = comparator.compare_birthmarks(b1, b2, aggregator)?;
if let Some(d) = dest {
crate::score_csv::store(&comparison, pair_file(d, i))?;
}
Ok(CompareResult::new(
i,
comparison.similarity(),
b1.path().to_path_buf(),
b2.path().to_path_buf(),
comparison.duration(),
))
})
.collect::<Vec<_>>();
finish(results, dest, start)
}
pub fn compare(
inputs: &[PathBuf],
comparator: &Comparator,
strategy: &PairingStrategy,
aggregator: &Aggregator,
dest: Option<&Path>,
) -> Result<Vec<Score>> {
let start = Instant::now();
prepare(dest)?;
let results = strategy
.pairs(inputs)
.enumerate()
.par_bridge()
.map(|(i, (left, right))| {
let mut b1: Birthmark = left.clone().try_into()?;
b1.set_json_path(left.clone());
let mut b2: Birthmark = right.clone().try_into()?;
b2.set_json_path(right.clone());
let comparison = comparator.compare_birthmarks(&b1, &b2, aggregator)?;
if let Some(d) = dest {
crate::score_csv::store(&comparison, pair_file(d, i))?;
}
Ok(CompareResult::new(
i,
comparison.similarity(),
b1.path().to_path_buf(),
b2.path().to_path_buf(),
comparison.duration(),
))
})
.collect::<Vec<_>>();
finish(results, dest, start)
}
fn finish(
results: Vec<Result<CompareResult>>,
dest: Option<&Path>,
start: Instant,
) -> Result<Vec<Score>> {
let mut results = Error::vec_result_to_result_vec(results)?;
results.sort_by_key(|r| r.index);
let out = scores(&results);
if let Some(d) = dest {
store(results, d, start)?;
}
Ok(out)
}