197 lines
5.4 KiB
Rust
197 lines
5.4 KiB
Rust
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<dyn FnOnce() + Send + 'static>;
|
|
|
|
struct ThreadPoolWorker {
|
|
_thread: thread::JoinHandle<()>,
|
|
}
|
|
|
|
impl ThreadPoolWorker {
|
|
fn new(queue: Arc<Mutex<Receiver<Job>>>) -> 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<ThreadPoolWorker>,
|
|
sender: Sender<Job>,
|
|
}
|
|
|
|
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<Self> {
|
|
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<OsStr>, 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<PathBuf> {
|
|
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"),
|
|
));
|
|
}
|