use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::fs::File;
use std::hash::Hasher;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::RwLock;
use super::RuntimeError;
use crate::version::{Version, VersionError};
#[derive(Debug)]
struct Entry {
size: u64,
hash: u64,
version: Version,
}
lazy_static! {
static ref CACHE: RwLock<HashMap<PathBuf, Entry>> = HashMap::new().into();
}
pub fn version<P: AsRef<Path>>(binary: P) -> Result<Version, RuntimeError> {
let binary: PathBuf = binary.as_ref().canonicalize()?;
let (size, hash) = {
let mut file = File::open(&binary)?;
let size = file.metadata()?.len();
let hash = {
let mut hasher = DefaultHasher::new();
let mut buffer = [0u8; 16384]; loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break; }
hasher.write(&buffer[..bytes_read]);
}
hasher.finish()
};
(size, hash)
};
if let Ok(cache) = CACHE.read() {
if let Some(entry) = cache.get(&binary) {
if entry.size == size && entry.hash == hash {
return Ok(entry.version);
}
}
}
let version = version_from_binary(&binary)?;
if let Ok(mut cache) = CACHE.write() {
cache.insert(binary, Entry { size, hash, version });
}
Ok(version)
}
fn version_from_binary<P: AsRef<Path>>(binary: P) -> Result<Version, RuntimeError> {
let output = Command::new(binary.as_ref()).arg("--version").output()?;
if output.status.success() {
let version_string = String::from_utf8_lossy(&output.stdout);
Ok(version_string.parse()?)
} else {
Err(VersionError::Missing)?
}
}