v0.2.12 : ligne de statut — temps dexecution de la derniere commande (⚡ ms/s) + ls parallele (tailles de dossiers reparties sur les coeurs, un seul appel de metadonnees par entree) — 130 entrees instantanees
This commit is contained in:
+74
-48
@@ -51,9 +51,8 @@ pub fn dir_size(path: &Path) -> Option<u64> {
|
||||
Some(total)
|
||||
}
|
||||
|
||||
fn modified_str(path: &Path) -> String {
|
||||
std::fs::metadata(path)
|
||||
.and_then(|m| m.modified())
|
||||
fn modified_text(meta: &std::fs::Metadata) -> String {
|
||||
meta.modified()
|
||||
.ok()
|
||||
.map(|t| {
|
||||
let dt: chrono::DateTime<chrono::Local> = t.into();
|
||||
@@ -94,59 +93,86 @@ fn is_executable(path: &Path, meta: &std::fs::Metadata) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
/// One ls row: (sort key, cells). A single symlink_metadata call per entry
|
||||
/// (the modification time comes from the same metadata).
|
||||
fn row_for(p: &Path, color: bool) -> Option<(String, Vec<Cell>)> {
|
||||
let meta = std::fs::symlink_metadata(p).ok()?;
|
||||
let file_type = meta.file_type();
|
||||
let name = p.file_name().and_then(|n| n.to_str())?.to_string();
|
||||
let (kind, style) = if file_type.is_symlink() {
|
||||
("symlink", if p.exists() { 3 } else { 4 })
|
||||
} else if file_type.is_dir() {
|
||||
("dir", 1)
|
||||
} else if is_executable(p, &meta) {
|
||||
("file", 2)
|
||||
} else {
|
||||
("file", 0)
|
||||
};
|
||||
let (size_text, size_bytes) = if file_type.is_dir() {
|
||||
match dir_size(p) {
|
||||
Some(s) => (format_size(s), Some(s)),
|
||||
None => ("—".to_string(), None),
|
||||
}
|
||||
} else {
|
||||
(format_size(meta.len()), Some(meta.len()))
|
||||
};
|
||||
let mut display = name.clone();
|
||||
if file_type.is_dir() {
|
||||
display.push('/');
|
||||
} else if file_type.is_symlink() {
|
||||
if let Ok(target) = std::fs::read_link(p) {
|
||||
display.push_str(&format!(" -> {}", target.display()));
|
||||
}
|
||||
}
|
||||
Some((
|
||||
name.to_lowercase(),
|
||||
vec![
|
||||
Cell::str(paint(&display, color, style)),
|
||||
Cell::str(kind.to_string()),
|
||||
Cell::bytes(size_bytes.unwrap_or(0), size_text),
|
||||
Cell::str(modified_text(&meta)),
|
||||
],
|
||||
))
|
||||
}
|
||||
|
||||
/// Build the ls table (columns: name, type, size, modified). Directories
|
||||
/// come first, then alphabetical order.
|
||||
///
|
||||
/// Rows are built in parallel: recursive directory sizes dominate the cost,
|
||||
/// so a listing with many subdirectories stays fast (like Nushell, which
|
||||
/// also parallelizes its listing).
|
||||
pub fn ls_table(path: &Path, color: bool) -> Result<DataTable> {
|
||||
let rd = std::fs::read_dir(path)
|
||||
.with_context(|| format!("cannot read {}", path.display()))?;
|
||||
let mut rows: Vec<(String, Vec<Cell>)> = Vec::new();
|
||||
for entry in rd.flatten() {
|
||||
let p = entry.path();
|
||||
let meta = match std::fs::symlink_metadata(&p) {
|
||||
Ok(m) => m,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let file_type = meta.file_type();
|
||||
let name = p
|
||||
.file_name()
|
||||
.map(|n| n.to_string_lossy().to_string())
|
||||
.unwrap_or_default();
|
||||
let (kind, style) = if file_type.is_symlink() {
|
||||
("symlink", if p.exists() { 3 } else { 4 })
|
||||
} else if file_type.is_dir() {
|
||||
("dir", 1)
|
||||
} else if is_executable(&p, &meta) {
|
||||
("file", 2)
|
||||
} else {
|
||||
("file", 0)
|
||||
};
|
||||
let (size_text, size_bytes) = if file_type.is_dir() {
|
||||
match dir_size(&p) {
|
||||
Some(s) => (format_size(s), Some(s)),
|
||||
None => ("—".to_string(), None),
|
||||
}
|
||||
} else {
|
||||
(format_size(meta.len()), Some(meta.len()))
|
||||
};
|
||||
let mut display = name.clone();
|
||||
if file_type.is_dir() {
|
||||
display.push('/');
|
||||
} else if file_type.is_symlink() {
|
||||
if let Ok(target) = std::fs::read_link(&p) {
|
||||
display.push_str(&format!(" -> {}", target.display()));
|
||||
let entries: Vec<PathBuf> = rd.flatten().map(|e| e.path()).collect();
|
||||
|
||||
let threads = std::thread::available_parallelism()
|
||||
.map(|n| n.get())
|
||||
.unwrap_or(4)
|
||||
.min(8);
|
||||
let chunk_size = entries.len().div_ceil(threads).max(1);
|
||||
let rows: Vec<(String, Vec<Cell>)> = std::thread::scope(|scope| {
|
||||
let mut handles = Vec::new();
|
||||
for chunk in entries.chunks(chunk_size) {
|
||||
let chunk: Vec<PathBuf> = chunk.to_vec();
|
||||
handles.push(scope.spawn(move || {
|
||||
chunk
|
||||
.into_iter()
|
||||
.filter_map(|p| row_for(&p, color))
|
||||
.collect::<Vec<_>>()
|
||||
}));
|
||||
}
|
||||
let mut out = Vec::new();
|
||||
for handle in handles {
|
||||
if let Ok(mut rows) = handle.join() {
|
||||
out.append(&mut rows);
|
||||
}
|
||||
}
|
||||
rows.push((
|
||||
name.to_lowercase(),
|
||||
vec![
|
||||
Cell::str(paint(&display, color, style)),
|
||||
Cell::str(kind.to_string()),
|
||||
Cell::bytes(size_bytes.unwrap_or(0), size_text),
|
||||
Cell::str(modified_str(&p)),
|
||||
],
|
||||
));
|
||||
}
|
||||
out
|
||||
});
|
||||
|
||||
// Directories first, then alphabetical.
|
||||
let mut rows = rows;
|
||||
rows.sort_by(|a, b| {
|
||||
let a_dir = a.1[1].text == "dir";
|
||||
let b_dir = b.1[1].text == "dir";
|
||||
|
||||
Reference in New Issue
Block a user