use crate::core::format_attributes;
use std::borrow::Cow;
use std::ffi::OsString;
use std::fs::{self, symlink_metadata};
use std::io;
use std::path::Path;
use std::time::SystemTime;
#[derive(Debug, Clone)]
pub struct FileEntry {
name: OsString,
lowercase_name: Box<str>,
flags: u8,
}
impl FileEntry {
const IS_DIR: u8 = 1 << 0;
const IS_HIDDEN: u8 = 1 << 1;
const IS_SYSTEM: u8 = 1 << 2;
const IS_SYMLINK: u8 = 1 << 3;
fn new(name: OsString, flags: u8) -> Self {
let lowercase_name = name.to_string_lossy().to_lowercase().into_boxed_str();
FileEntry {
name,
lowercase_name,
flags,
}
}
pub fn name(&self) -> &OsString {
&self.name
}
pub fn name_str(&self) -> Cow<'_, str> {
self.name.to_string_lossy()
}
pub fn lowercase_name(&self) -> &str {
&self.lowercase_name
}
pub fn is_dir(&self) -> bool {
self.flags & Self::IS_DIR != 0
}
pub fn is_hidden(&self) -> bool {
self.flags & Self::IS_HIDDEN != 0
}
pub fn is_system(&self) -> bool {
self.flags & Self::IS_SYSTEM != 0
}
pub fn is_symlink(&self) -> bool {
self.flags & Self::IS_SYMLINK != 0
}
pub fn extension(&self) -> Option<String> {
Path::new(&self.name)
.extension()
.and_then(|s| s.to_str())
.map(|s| s.to_ascii_lowercase())
}
}
#[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: &Path) -> 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 ft = match entry.file_type() {
Ok(ft) => ft,
Err(_) => continue,
};
let mut flags = 0u8;
if ft.is_dir() {
flags |= FileEntry::IS_DIR;
}
if ft.is_symlink() {
flags |= FileEntry::IS_SYMLINK;
}
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
if name.as_bytes().first() == Some(&b'.') {
flags |= FileEntry::IS_HIDDEN;
}
}
#[cfg(windows)]
{
use std::os::windows::fs::MetadataExt;
if let Ok(md) = entry.metadata() {
let attrs = md.file_attributes();
if attrs & 0x2 != 0 {
flags |= FileEntry::IS_HIDDEN;
}
if attrs & 0x4 != 0 {
flags |= FileEntry::IS_SYSTEM;
}
}
if flags & FileEntry::IS_HIDDEN == 0 && name.to_string_lossy().starts_with('.') {
flags |= FileEntry::IS_HIDDEN;
}
}
entries.push(FileEntry::new(name, flags));
}
Ok(entries)
}