use std::ffi::OsString;
use std::fs::{self, symlink_metadata};
use std::io;
use std::path::Path;
use std::time::SystemTime;
use crate::formatter::format_attributes;
#[derive(Debug, Clone)]
pub struct FileEntry {
name: OsString,
name_str: String,
lowercase_name: String,
display_name: String,
is_dir: bool,
is_hidden: bool,
is_system: bool,
}
impl FileEntry {
pub fn name(&self) -> &OsString {
&self.name
}
pub fn name_str(&self) -> &str {
&self.name_str
}
pub fn lowercase_name(&self) -> &str {
&self.lowercase_name
}
pub fn display_name(&self) -> &str {
&self.display_name
}
pub fn is_dir(&self) -> bool {
self.is_dir
}
pub fn is_hidden(&self) -> bool {
self.is_hidden
}
pub fn is_system(&self) -> bool {
self.is_system
}
pub fn set_display_name(&mut self, new_name: String) {
self.display_name = new_name;
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum FileType {
File,
Directory,
Symlink,
Other,
}
#[derive(Debug, Clone, PartialEq)]
pub struct FileInfo {
name: OsString,
size: Option<u64>,
modified: Option<SystemTime>,
attributes: String,
file_type: FileType,
}
impl FileInfo {
pub fn name(&self) -> &OsString {
&self.name
}
pub fn size(&self) -> &Option<u64> {
&self.size
}
pub fn modified(&self) -> &Option<SystemTime> {
&self.modified
}
pub fn attributes(&self) -> &str {
&self.attributes
}
pub fn file_type(&self) -> &FileType {
&self.file_type
}
pub fn get_file_info(path: &Path) -> io::Result<FileInfo> {
let metadata = symlink_metadata(path)?;
let file_type = if metadata.is_file() {
FileType::File
} else if metadata.is_dir() {
FileType::Directory
} else if metadata.file_type().is_symlink() {
FileType::Symlink
} else {
FileType::Other
};
Ok(FileInfo {
name: path.file_name().unwrap_or_default().to_os_string(),
size: if metadata.is_file() {
Some(metadata.len())
} else {
None
},
modified: metadata.modified().ok(),
attributes: format_attributes(&metadata),
file_type,
})
}
}
pub fn browse_dir(path: &std::path::Path) -> std::io::Result<Vec<FileEntry>> {
let mut entries = Vec::with_capacity(256);
for entry in fs::read_dir(path)? {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
let name = entry.file_name();
let name_lossy = name.to_string_lossy();
let name_str = name_lossy.to_string();
let lowercase_name = name_lossy.to_lowercase();
let is_dir = entry.file_type().map(|ft| ft.is_dir()).unwrap_or(false);
let display_name = if is_dir {
format!("{}/", name_lossy)
} else {
name_lossy.into_owned()
};
#[cfg(unix)]
let (is_hidden, is_system) = {
use std::os::unix::ffi::OsStrExt;
let is_hidden = name.as_bytes().first() == Some(&b'.');
(is_hidden, false)
};
#[cfg(windows)]
let (is_dir, is_hidden, is_system) = {
use std::os::windows::fs::MetadataExt;
let starts_with_dot = lowercase_name.starts_with('.');
if let Ok(md) = entry.metadata() {
let attrs = md.file_attributes();
(
md.is_dir(),
(attrs & 0x2 != 0) || starts_with_dot,
(attrs & 0x4 != 0),
)
} else {
(is_dir, starts_with_dot, false)
}
};
entries.push(FileEntry {
name,
name_str,
lowercase_name,
display_name,
is_dir,
is_hidden,
is_system,
});
}
Ok(entries)
}