use std::collections::HashMap;
use std::ffi::OsString;
use std::path::{Path, PathBuf};
pub const SUPPORTED_DIST_EXTENSIONS: &[&str] = &[
".tar.gz",
".tgz",
".tar.bz2",
".tar.xz",
".tar.lzma",
".tbz2",
".tar",
".zip",
];
pub fn supported_dist_file(file: &Path) -> bool {
SUPPORTED_DIST_EXTENSIONS
.iter()
.any(|&ext| file.ends_with(ext))
}
pub struct DistCatcher {
existing_files: Option<HashMap<PathBuf, HashMap<PathBuf, std::fs::DirEntry>>>,
directories: Vec<PathBuf>,
files: std::sync::Mutex<Vec<PathBuf>>,
start_time: std::time::SystemTime,
}
impl DistCatcher {
pub fn new(directories: Vec<PathBuf>) -> Self {
Self {
directories: directories
.iter()
.map(|d| d.canonicalize().unwrap())
.collect(),
files: std::sync::Mutex::new(Vec::new()),
start_time: std::time::SystemTime::now(),
existing_files: None,
}
}
pub fn default(directory: &Path) -> Self {
Self::new(vec![
directory.join("dist"),
directory.to_path_buf(),
directory.join(".."),
])
}
pub fn start(&mut self) {
self.existing_files = Some(
self.directories
.iter()
.map(|d| {
let mut map = HashMap::new();
for entry in d.read_dir().unwrap() {
let entry = entry.unwrap();
map.insert(entry.path(), entry);
}
(d.clone(), map)
})
.collect(),
);
}
pub fn find_files(&self) -> Option<PathBuf> {
let existing_files = self.existing_files.as_ref().unwrap();
let mut files = self.files.lock().unwrap();
for directory in &self.directories {
let old_files = existing_files.get(directory).unwrap();
let mut possible_new = Vec::new();
let mut possible_updated = Vec::new();
if !directory.is_dir() {
continue;
}
for entry in directory.read_dir().unwrap() {
let entry = entry.unwrap();
if !entry.file_type().unwrap().is_file() || !supported_dist_file(&entry.path()) {
continue;
}
let old_entry = old_files.get(&entry.path());
if old_entry.is_none() {
possible_new.push(entry);
continue;
}
if entry.metadata().unwrap().modified().unwrap() > self.start_time {
possible_updated.push(entry);
continue;
}
}
if possible_new.len() == 1 {
let entry = possible_new[0].path();
log::info!("Found new tarball {:?} in {:?}", entry, directory);
files.push(entry.clone());
return Some(entry);
} else if possible_new.len() > 1 {
log::warn!(
"Found multiple tarballs {:?} in {:?}",
possible_new.iter().map(|e| e.path()).collect::<Vec<_>>(),
directory
);
files.extend(possible_new.iter().map(|e| e.path()));
return Some(possible_new[0].path());
}
if possible_updated.len() == 1 {
let entry = possible_updated[0].path();
log::info!("Found updated tarball {:?} in {:?}", entry, directory);
files.push(entry.clone());
return Some(entry);
}
}
None
}
pub fn copy_single(&self, target_dir: &Path) -> Result<Option<OsString>, std::io::Error> {
for path in self.files.lock().unwrap().iter() {
match std::fs::copy(path, target_dir.join(path.file_name().unwrap())) {
Ok(_) => return Ok(Some(path.file_name().unwrap().into())),
Err(e) => {
if e.kind() == std::io::ErrorKind::AlreadyExists {
continue;
}
return Err(e);
}
}
}
log::info!("No tarball created :(");
Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"No tarball found",
))
}
}