use oden_js::{module::native::NativeModuleBuilder, ContextRef, ValueResult}; use std::ffi::{OsStr, OsString}; use std::path::PathBuf; use std::sync::mpsc::{channel, Receiver, Sender}; use std::sync::{Arc, Mutex}; use std::thread; type Job = Box; struct ThreadPoolWorker { _thread: thread::JoinHandle<()>, } impl ThreadPoolWorker { fn new(queue: Arc>>) -> Self { let thread = thread::spawn(move || loop { let r = { let locked = queue.lock(); locked.expect("Should not be orphaning the lock").recv() }; if let Ok(item) = r { item(); } else { break; } }); ThreadPoolWorker { _thread: thread } } } struct ThreadPool { _workers: Vec, sender: Sender, } impl ThreadPool { fn new(size: usize) -> Self { let (sender, receiver) = channel(); let receiver = Arc::new(Mutex::new(receiver)); let mut workers = vec![]; for _ in 0..size { workers.push(ThreadPoolWorker::new(receiver.clone())); } ThreadPool { _workers: workers, sender, } } fn execute(&self, job: Job) { let _ = self.sender.send(job); } } struct IoImpl { thread_pool: ThreadPool, } impl IoImpl { fn new() -> Self { IoImpl { thread_pool: ThreadPool::new(4), } } fn load(&self, context: &ContextRef, path: &str) -> ValueResult { let (value, promise) = context.new_promise()?; let path = path.to_string(); self.thread_pool.execute(Box::new(move || { let path = path; let result = resolve_path(&path, &[]).and_then(|p| std::fs::read(p)); promise.resolve(move |ctx: &ContextRef| match result { Ok(v) => ctx.new_array_buffer(v), Err(err) => Err(err.into()), }); })); Ok(value) } fn load_string(&self, context: &ContextRef, path: &str) -> ValueResult { let (value, promise) = context.new_promise()?; let path = path.to_string(); self.thread_pool.execute(Box::new(move || { let path = path; let result = resolve_path(&path, &[]).and_then(|p| std::fs::read(p)); promise.resolve(move |ctx: &ContextRef| { let result = result?; let string = std::str::from_utf8(&result)?; ctx.new_string(string) }); })); Ok(value) } } pub struct IoAPI {} impl IoAPI { pub fn define(ctx: &ContextRef) -> oden_js::Result { let io = Arc::new(IoImpl::new()); let mut builder = NativeModuleBuilder::new(ctx); { let io = io.clone(); builder.export( "load", ctx.new_fn(move |ctx: &ContextRef, p: String| io.load(ctx, &p))?, )?; } { let io = io.clone(); builder.export( "load_string", ctx.new_fn(move |ctx: &ContextRef, p: String| io.load_string(ctx, &p))?, )?; } builder.build("io-core")?; Ok(IoAPI {}) } } fn append_ext(ext: impl AsRef, path: PathBuf) -> PathBuf { let mut os_string: OsString = path.into(); os_string.push("."); os_string.push(ext.as_ref()); os_string.into() } /// Resolve a path to an existing file. pub fn resolve_path(input: &str, extensions: &[&str]) -> std::io::Result { let mut path = PathBuf::with_capacity(input.len()); for segment in input.split('/') { if segment == "." { continue; } else if segment == ".." { if path.parent().is_none() { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("{input} contains too many '..'; directory underflow detected"), )); } path.pop(); } else if segment.contains(":") || segment.contains('\\') { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("{input} contains invalid characters"), )); } else { path.push(segment); } } if path.has_root() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("{input} is absolute"), )); } if path.file_name().is_none() { return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("{input} refers to a directory"), )); } for prefix in ["game", "src"] { let mut actual = PathBuf::from(prefix); actual.push(path.clone()); //eprintln!("Trying '{}'...", actual.to_string_lossy()); match actual.try_exists() { Ok(true) => return Ok(actual), _ => (), } for extension in extensions.iter() { let actual = append_ext(extension, actual.clone()); //eprintln!("Trying '{}'...", actual.to_string_lossy()); match actual.try_exists() { Ok(true) => return Ok(actual), _ => (), } } } return Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!("{input} not found or not accessible"), )); }