Files
nomina/crates/nomina-core/src/scanner.rs
lashman 6f5b862234 pipeline cards, context menus, presets, settings overhaul
rewrote pipeline as draggable card strip with per-rule config popovers,
added right-click menus to pipeline cards, sidebar tree, and file list,
preset import/export with BRU format support, new rules (hash, swap,
truncate, sanitize, padding, randomize, text editor, folder name,
transliterate), settings dialog with all sections, overlay collision
containment, tooltips on icon buttons, empty pipeline default
2026-03-14 19:04:35 +02:00

123 lines
3.2 KiB
Rust

use std::path::{Path, PathBuf};
use chrono::{DateTime, Utc};
use walkdir::WalkDir;
use crate::filter::FilterConfig;
use crate::FileEntry;
pub struct FileScanner {
pub root: PathBuf,
pub filters: FilterConfig,
}
impl FileScanner {
pub fn new(root: PathBuf, filters: FilterConfig) -> Self {
Self { root, filters }
}
pub fn scan(&self) -> Vec<FileEntry> {
let max_depth = self.filters.subfolder_depth.map(|d| d + 1).unwrap_or(usize::MAX);
let walker = WalkDir::new(&self.root)
.max_depth(max_depth)
.follow_links(false);
let mut entries: Vec<FileEntry> = Vec::new();
for result in walker {
let dir_entry = match result {
Ok(e) => e,
Err(_) => continue,
};
// skip the root directory itself
if dir_entry.path() == self.root {
continue;
}
let metadata = match dir_entry.metadata() {
Ok(m) => m,
Err(_) => continue,
};
let path = dir_entry.path().to_path_buf();
let name = dir_entry.file_name().to_string_lossy().to_string();
let is_dir = metadata.is_dir();
let is_hidden = is_hidden_file(&path);
let (stem, extension) = if is_dir {
(name.clone(), String::new())
} else {
split_filename(&name)
};
let created = file_created(&metadata);
let modified = file_modified(&metadata);
let accessed = file_accessed(&metadata);
let entry = FileEntry {
path,
name,
stem,
extension,
size: metadata.len(),
is_dir,
is_hidden,
created,
modified,
accessed,
};
if self.filters.matches(&entry) {
entries.push(entry);
}
}
// natural sort
entries.sort_by(|a, b| natord::compare(&a.name, &b.name));
entries
}
}
fn split_filename(name: &str) -> (String, String) {
match name.rsplit_once('.') {
Some((stem, ext)) if !stem.is_empty() => (stem.to_string(), ext.to_string()),
_ => (name.to_string(), String::new()),
}
}
fn is_hidden_file(path: &Path) -> bool {
if path.file_name()
.map(|n| n.to_string_lossy().starts_with('.'))
.unwrap_or(false)
{
return true;
}
#[cfg(target_os = "windows")]
{
use std::os::windows::fs::MetadataExt;
if let Ok(meta) = std::fs::metadata(path) {
const FILE_ATTRIBUTE_HIDDEN: u32 = 0x2;
if meta.file_attributes() & FILE_ATTRIBUTE_HIDDEN != 0 {
return true;
}
}
}
false
}
fn file_created(meta: &std::fs::Metadata) -> Option<DateTime<Utc>> {
meta.created().ok().map(|t| DateTime::<Utc>::from(t))
}
fn file_modified(meta: &std::fs::Metadata) -> Option<DateTime<Utc>> {
meta.modified().ok().map(|t| DateTime::<Utc>::from(t))
}
fn file_accessed(meta: &std::fs::Metadata) -> Option<DateTime<Utc>> {
meta.accessed().ok().map(|t| DateTime::<Utc>::from(t))
}