use std::{fs::Metadata, path::PathBuf};
use rquickjs::{
atom::PredefinedAtom, prelude::Opt, Array, Class, Ctx, IntoJs, Object, Result, Value,
};
#[cfg(unix)]
use std::os::unix::fs::FileTypeExt;
use crate::{
modules::path::{is_absolute, CURRENT_DIR_STR},
utils::io::DirectoryWalker,
};
#[derive(rquickjs::class::Trace)]
#[rquickjs::class]
pub struct Dirent {
#[qjs(skip_trace)]
metadata: Metadata,
}
#[rquickjs::methods(rename_all = "camelCase")]
impl Dirent {
pub fn is_file(&self) -> bool {
self.metadata.is_file()
}
pub fn is_directory(&self) -> bool {
self.metadata.is_dir()
}
pub fn is_symbolic_link(&self) -> bool {
self.metadata.is_symlink()
}
#[qjs(rename = "isFIFO")]
pub fn is_fifo(&self) -> bool {
#[cfg(unix)]
{
self.metadata.file_type().is_fifo()
}
#[cfg(not(unix))]
{
false
}
}
pub fn is_block_device(&self) -> bool {
#[cfg(unix)]
{
self.metadata.file_type().is_block_device()
}
#[cfg(not(unix))]
{
false
}
}
pub fn is_character_device(&self) -> bool {
#[cfg(unix)]
{
self.metadata.file_type().is_char_device()
}
#[cfg(not(unix))]
{
false
}
}
pub fn is_socket(&self) -> bool {
#[cfg(unix)]
{
self.metadata.file_type().is_socket()
}
#[cfg(not(unix))]
{
false
}
}
}
struct ReadDirItem {
name: String,
metadata: Option<Metadata>,
}
pub struct ReadDir {
items: Vec<ReadDirItem>,
root: String,
}
impl<'js> IntoJs<'js> for ReadDir {
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
let arr = Array::new(ctx.clone())?;
for (index, item) in self.items.into_iter().enumerate() {
if let Some(metadata) = item.metadata {
let dirent = Dirent { metadata };
let dirent = Class::instance(ctx.clone(), dirent)?;
dirent.set(PredefinedAtom::Name, item.name)?;
dirent.set("parentPath", &self.root)?;
arr.set(index, dirent)?;
} else {
arr.set(index, item.name)?;
}
}
arr.into_js(ctx)
}
}
pub async fn read_dir<'js>(
_ctx: Ctx<'js>,
mut path: String,
options: Opt<Object<'js>>,
) -> Result<ReadDir> {
let (with_file_types, skip_root_pos, mut directory_walker) =
process_options_and_create_directory_walker(&mut path, options);
let mut items = Vec::with_capacity(64);
while let Some((child, metadata)) = directory_walker.walk().await? {
append_directory_and_metadata_to_vec(
with_file_types,
skip_root_pos,
&mut items,
child,
metadata,
);
}
items.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
Ok(ReadDir { items, root: path })
}
pub fn read_dir_sync<'js>(
_ctx: Ctx<'js>,
mut path: String,
options: Opt<Object<'js>>,
) -> Result<ReadDir> {
let (with_file_types, skip_root_pos, mut directory_walker) =
process_options_and_create_directory_walker(&mut path, options);
let mut items = Vec::with_capacity(64);
while let Some((child, metadata)) = directory_walker.walk_sync()? {
append_directory_and_metadata_to_vec(
with_file_types,
skip_root_pos,
&mut items,
child,
metadata,
);
}
items.sort_by(|a, b| a.name.partial_cmp(&b.name).unwrap());
Ok(ReadDir { items, root: path })
}
type OptionsAndDirectoryWalker = (bool, usize, DirectoryWalker<fn(&str) -> bool>);
fn process_options_and_create_directory_walker(
path: &mut String,
options: Opt<Object>,
) -> OptionsAndDirectoryWalker {
let mut with_file_types = false;
let mut is_recursive = false;
if let Some(options) = options.0 {
with_file_types = options
.get("withFileTypes")
.ok()
.and_then(|file_types: Value| file_types.as_bool())
.unwrap_or_default();
is_recursive = options
.get("recursive")
.ok()
.and_then(|recursive: Value| recursive.as_bool())
.unwrap_or_default();
};
let skip_root_pos = {
match path.as_str() {
"." | CURRENT_DIR_STR => CURRENT_DIR_STR.len(),
_ if path.starts_with(CURRENT_DIR_STR) => path.len() + 1,
_ => {
if !is_absolute(path.clone()) {
path.insert_str(0, CURRENT_DIR_STR);
}
path.len() + 1
}
}
};
let mut directory_walker: DirectoryWalker<fn(&str) -> bool> =
DirectoryWalker::new(PathBuf::from(&path), |_| true);
if is_recursive {
directory_walker.set_recursive(true);
}
(with_file_types, skip_root_pos, directory_walker)
}
fn append_directory_and_metadata_to_vec(
with_file_types: bool,
skip_root_pos: usize,
items: &mut Vec<ReadDirItem>,
child: PathBuf,
metadata: Metadata,
) {
let metadata = if with_file_types {
Some(metadata)
} else {
None
};
let name = child.into_os_string().to_string_lossy()[skip_root_pos..].to_string();
items.push(ReadDirItem { name, metadata })
}