use std::path::{Path, PathBuf};
use std::time::UNIX_EPOCH;
use crate::{Error, Result};
fn io_err(op: &'static str, source: std::io::Error) -> Error {
Error::io(op, source)
}
fn sort_paths(paths: &mut [PathBuf]) {
paths.sort_unstable();
}
fn glob_match(pattern: &[u8], name: &[u8]) -> bool {
let mut p = 0usize;
let mut n = 0usize;
let mut star_p = usize::MAX;
let mut star_n = 0usize;
while n < name.len() {
if p < pattern.len() && (pattern[p] == name[n] || pattern[p] == b'?') {
p += 1;
n += 1;
} else if p < pattern.len() && pattern[p] == b'*' {
star_p = p;
p += 1;
star_n = n;
} else if star_p != usize::MAX {
p = star_p + 1;
star_n += 1;
n = star_n;
} else {
return false;
}
}
while p < pattern.len() && pattern[p] == b'*' {
p += 1;
}
p == pattern.len()
}
pub struct Filesystem;
impl Filesystem {
pub fn exists(path: &Path) -> bool {
path.exists()
}
pub fn is_file(path: &Path) -> bool {
path.is_file()
}
pub fn is_directory(path: &Path) -> bool {
path.is_dir()
}
pub fn get_file_size(path: &Path) -> Result<u64> {
let meta = std::fs::metadata(path).map_err(|e| io_err("get_file_size", e))?;
Ok(meta.len())
}
pub fn get_last_modified(path: &Path) -> Result<i64> {
let meta = std::fs::metadata(path).map_err(|e| io_err("get_last_modified", e))?;
let modified = meta
.modified()
.map_err(|e| io_err("get_last_modified", e))?;
let secs = modified
.duration_since(UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or_else(|e| -(e.duration().as_secs() as i64));
Ok(secs)
}
pub fn create_directory(path: &Path) -> Result<()> {
match std::fs::create_dir(path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists && path.is_dir() => Ok(()),
Err(e) => Err(io_err("create_directory", e)),
}
}
pub fn create_directories(path: &Path) -> Result<()> {
std::fs::create_dir_all(path).map_err(|e| io_err("create_directories", e))
}
pub fn remove_file(path: &Path) -> Result<()> {
match std::fs::remove_file(path) {
Ok(()) => Ok(()),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()),
Err(e) => Err(io_err("remove_file", e)),
}
}
pub fn remove_directory(path: &Path, recursive: bool) -> Result<()> {
if !path.exists() {
return Ok(());
}
if recursive {
std::fs::remove_dir_all(path).map_err(|e| io_err("remove_directory", e))
} else {
std::fs::remove_dir(path).map_err(|e| io_err("remove_directory", e))
}
}
pub fn copy(from: &Path, to: &Path) -> Result<()> {
if from.is_dir() {
return Self::create_directories(to);
}
if let Some(parent) = to.parent()
&& !parent.as_os_str().is_empty()
{
Self::create_directories(parent)?;
}
std::fs::copy(from, to)
.map(|_| ())
.map_err(|e| io_err("copy", e))
}
pub fn move_path(from: &Path, to: &Path) -> Result<()> {
if let Some(parent) = to.parent()
&& !parent.as_os_str().is_empty()
{
Self::create_directories(parent)?;
}
std::fs::rename(from, to).map_err(|e| io_err("move_path", e))
}
pub fn list_files(dir: &Path, extension: &str) -> Result<Vec<PathBuf>> {
if !dir.is_dir() {
return Err(Error::not_found(format!(
"directory does not exist: {}",
dir.display()
)));
}
let mut files = Vec::new();
for entry in std::fs::read_dir(dir).map_err(|e| io_err("list_files", e))? {
let entry = entry.map_err(|e| io_err("list_files", e))?;
let path = entry.path();
if !path.is_file() {
continue;
}
if extension.is_empty()
|| path
.extension()
.and_then(|e| e.to_str())
.map(|e| format!(".{e}") == extension)
.unwrap_or(false)
{
files.push(path);
}
}
sort_paths(&mut files);
Ok(files)
}
pub fn list_directories(dir: &Path) -> Result<Vec<PathBuf>> {
if !dir.is_dir() {
return Err(Error::not_found(format!(
"directory does not exist: {}",
dir.display()
)));
}
let mut dirs = Vec::new();
for entry in std::fs::read_dir(dir).map_err(|e| io_err("list_directories", e))? {
let entry = entry.map_err(|e| io_err("list_directories", e))?;
let meta = entry
.metadata()
.map_err(|e| io_err("list_directories", e))?;
if meta.is_dir() && !meta.is_symlink() {
dirs.push(entry.path());
}
}
sort_paths(&mut dirs);
Ok(dirs)
}
pub fn list_all(dir: &Path, recursive: bool) -> Result<Vec<PathBuf>> {
if !dir.is_dir() {
return Err(Error::not_found(format!(
"directory does not exist: {}",
dir.display()
)));
}
let mut entries = Vec::new();
list_all_impl(dir, recursive, &mut entries)?;
sort_paths(&mut entries);
Ok(entries)
}
pub fn read_text(path: &Path) -> Result<String> {
std::fs::read_to_string(path).map_err(|e| io_err("read_text", e))
}
pub fn write_text(path: &Path, content: &str) -> Result<()> {
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
Self::create_directories(parent)?;
}
std::fs::write(path, content.as_bytes()).map_err(|e| io_err("write_text", e))
}
pub fn append_text(path: &Path, content: &str) -> Result<()> {
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
Self::create_directories(parent)?;
}
use std::io::Write as _;
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(path)
.map_err(|e| io_err("append_text", e))?;
file
.write_all(content.as_bytes())
.map_err(|e| io_err("append_text", e))
}
pub fn read_lines(path: &Path) -> Result<Vec<String>> {
let text = Self::read_text(path)?;
if text.is_empty() {
return Ok(Vec::new());
}
let mut lines = Vec::new();
let mut begin = 0usize;
let bytes = text.as_bytes();
for i in 0..bytes.len() {
if bytes[i] == b'\n' {
let end = if i > begin && bytes[i - 1] == b'\r' {
i - 1
} else {
i
};
lines.push(text[begin..end].to_owned());
begin = i + 1;
}
}
if begin < text.len() {
lines.push(text[begin..].to_owned());
}
Ok(lines)
}
pub fn read_binary(path: &Path) -> Result<Vec<u8>> {
std::fs::read(path).map_err(|e| io_err("read_binary", e))
}
pub fn write_binary(path: &Path, data: &[u8]) -> Result<()> {
if let Some(parent) = path.parent()
&& !parent.as_os_str().is_empty()
{
Self::create_directories(parent)?;
}
std::fs::write(path, data).map_err(|e| io_err("write_binary", e))
}
pub fn absolute(path: &Path) -> Result<PathBuf> {
std::path::absolute(path).map_err(|e| io_err("absolute", e))
}
pub fn glob(dir: &Path, pattern: &str) -> Result<Vec<PathBuf>> {
if !dir.is_dir() {
return Err(Error::not_found(format!(
"directory does not exist: {}",
dir.display()
)));
}
let pat = pattern.as_bytes();
let mut matches = Vec::new();
for entry in std::fs::read_dir(dir).map_err(|e| io_err("glob", e))? {
let entry = entry.map_err(|e| io_err("glob", e))?;
let path = entry.path();
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or_default();
if glob_match(pat, name.as_bytes()) {
matches.push(path);
}
}
sort_paths(&mut matches);
Ok(matches)
}
}
fn list_all_impl(dir: &Path, recursive: bool, out: &mut Vec<PathBuf>) -> Result<()> {
for entry in std::fs::read_dir(dir).map_err(|e| io_err("list_all", e))? {
let entry = entry.map_err(|e| io_err("list_all", e))?;
let path = entry.path();
let meta = entry.metadata().map_err(|e| io_err("list_all", e))?;
out.push(path.clone());
if recursive && meta.is_dir() && !meta.is_symlink() {
list_all_impl(&path, recursive, out)?;
}
}
Ok(())
}