use ferinth::Ferinth;
use furse::{cf_fingerprint, Furse};
use futures_util::{try_join, TryFutureExt};
use sha1::{Digest, Sha1};
use std::{
collections::HashMap,
fs::{read, read_dir},
path::Path,
};
type Result<T> = std::result::Result<T, Error>;
#[derive(thiserror::Error, Debug)]
#[error(transparent)]
pub enum Error {
IOError(#[from] std::io::Error),
ModrinthError(#[from] ferinth::Error),
CurseForgeError(#[from] furse::Error),
}
pub async fn scan(
modrinth: &Ferinth,
curseforge: &Furse,
dir_path: impl AsRef<Path>,
hashing_complete: impl Fn(),
) -> Result<Vec<(String, Option<String>, Option<i32>)>> {
let mut filenames = HashMap::new();
let mut mr_hashes = vec![];
let mut cf_hashes = vec![];
for entry in read_dir(dir_path)? {
let path = entry?.path();
if path.is_file()
&& path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("jar"))
{
let bytes = read(&path)?;
let cf_hash = cf_fingerprint(&bytes);
if let Some(filename) = path.file_name() {
if filenames.insert(cf_hash, filename.to_owned()).is_none() {
mr_hashes.push(format!("{:x}", Sha1::digest(&bytes)));
cf_hashes.push(cf_hash);
}
}
}
}
hashing_complete();
let (mr_results, cf_results) = try_join!(
modrinth
.get_versions_from_hashes(mr_hashes.clone())
.map_err(Error::from),
curseforge
.get_fingerprint_matches(cf_hashes.clone())
.map_err(Error::from),
)?;
let mut mr_results =
HashMap::<_, _>::from_iter(mr_results.into_iter().map(|(k, v)| (k, v.project_id)));
let mut cf_results = HashMap::<_, _>::from_iter(
cf_results
.exact_fingerprints
.into_iter()
.zip(cf_results.exact_matches.into_iter().map(|m| m.id)),
);
Ok(mr_hashes
.into_iter()
.zip(cf_hashes)
.map(|(mr, cf)| {
(
filenames
.remove(&cf)
.expect("Missing filename in hashmap")
.to_string_lossy()
.into_owned(),
mr_results.remove(&mr),
cf_results.remove(&cf),
)
})
.collect())
}