use super::foreign::ForeignFormat;
use std::{
collections::HashSet,
path::{Path, PathBuf},
};
use tracing::warn;
pub(super) const MAX_RECURSE_DEPTH: usize = 32;
#[must_use]
fn is_importable_design(path: &Path) -> bool {
path.extension()
.is_some_and(|e| e.eq_ignore_ascii_case("asc"))
|| ForeignFormat::from_path(path).is_some()
}
pub(super) fn collect_design_files_recursive(
dir: &Path,
recurse: bool,
depth: usize,
max_depth: usize,
visited: &mut HashSet<PathBuf>,
out: &mut Vec<PathBuf>,
) {
if depth > max_depth {
warn!(
"Import: not descending into '{}' -- exceeded max recursion depth ({max_depth})",
dir.display()
);
return;
}
let canon = std::fs::canonicalize(dir).unwrap_or_else(|_| dir.to_path_buf());
if !visited.insert(canon) {
warn!(
"Import: skipping '{}' -- already visited (symlink loop?)",
dir.display()
);
return;
}
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let p = entry.path();
if p.is_file() && is_importable_design(&p) {
out.push(p);
} else if recurse && p.is_dir() {
collect_design_files_recursive(&p, recurse, depth + 1, max_depth, visited, out);
}
}
}
pub(super) fn collect_import_candidates(
path: &Path,
recurse: bool,
) -> Result<Vec<PathBuf>, String> {
let mut candidates: Vec<PathBuf> = Vec::new();
if path.is_dir() {
match std::fs::read_dir(path) {
Ok(entries) => {
let mut visited: HashSet<PathBuf> = HashSet::new();
for entry in entries.flatten() {
let p = entry.path();
if p.is_file() && is_importable_design(&p) {
candidates.push(p);
} else if recurse && p.is_dir() {
collect_design_files_recursive(
&p,
recurse,
1,
MAX_RECURSE_DEPTH,
&mut visited,
&mut candidates,
);
}
}
}
Err(e) => return Err(format!("Could not read folder '{}': {e}", path.display())),
}
} else if path.is_file() {
if indicatrix_cut_core::native::asc_path_for_native(path).is_some() {
return Err(format!(
"'{}' is an Indicatrix native design file, not a .asc -- open it with \"Open \
Native\" in the Edit tab instead of Import.",
path.display()
));
}
candidates.push(path.to_path_buf());
} else {
return Err(format!("'{}' is not a file or folder.", path.display()));
}
if candidates.is_empty() {
return Err(format!(
"No .asc, .gem or .gcs files found at '{}'.",
path.display()
));
}
Ok(candidates)
}
pub(super) fn find_native_sidecar(asc_path: &Path) -> Option<(String, Vec<u8>)> {
let mut current = asc_path.to_path_buf();
current.set_extension(indicatrix_cut_core::native::NATIVE_EXTENSION_SUFFIX);
let mut legacy = asc_path.to_path_buf();
legacy.set_extension(indicatrix_cut_core::native::LEGACY_NATIVE_EXTENSION_SUFFIX);
[current, legacy].into_iter().find_map(|candidate| {
let bytes = std::fs::read(&candidate).ok()?;
let name = candidate.file_name()?.to_string_lossy().into_owned();
Some((name, bytes))
})
}