use std::collections::HashMap;
use std::process::{Command, Stdio};
use std::sync::mpsc::{self, Receiver, Sender};
use std::sync::{
atomic::{AtomicUsize, Ordering},
LockResult, Mutex, MutexGuard, OnceLock,
};
use std::thread;
use mumu::{FunctionValue, Interpreter, Value};
use mumu::parser::core::driver::parse_tokens;
use mumu::parser::lexer::tokenize;
use crate::bridge::NetMessage;
pub static ACTIVE_TASKS: AtomicUsize = AtomicUsize::new(0);
pub struct NetManagerGlobal(OnceLock<Mutex<NetManager>>);
pub static NET_MANAGER: NetManagerGlobal = NetManagerGlobal(OnceLock::new());
impl NetManagerGlobal {
pub fn lock<'a>(&'a self) -> LockResult<MutexGuard<'a, NetManager>> {
let m = self.0.get_or_init(|| Mutex::new(NetManager::new()));
m.lock()
}
}
struct FetchTask {
callback: Box<FunctionValue>,
}
pub struct NetManager {
next_token: usize,
tx: Sender<NetMessage>,
rx: Receiver<NetMessage>,
fetches: HashMap<usize, FetchTask>,
}
impl NetManager {
pub fn new() -> Self {
let (tx, rx) = mpsc::channel::<NetMessage>();
Self {
next_token: 1,
tx,
rx,
fetches: HashMap::new(),
}
}
pub fn global() -> &'static Mutex<NetManager> {
NET_MANAGER.0.get_or_init(|| Mutex::new(NetManager::new()))
}
pub fn add_fetch_task(
&mut self,
url: String,
callback: Box<FunctionValue>,
verbose: bool,
) -> usize {
let token = self.alloc_token();
self.fetches.insert(token, FetchTask { callback });
let tx = self.tx.clone();
ACTIVE_TASKS.fetch_add(1, Ordering::SeqCst);
thread::spawn(move || {
if verbose {
eprintln!("[net:fetch] token={} url={}", token, url);
}
let mut cmd = Command::new("curl");
cmd.arg("-fsSL")
.arg(&url)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let output = cmd.output();
match output {
Ok(out) if out.status.success() => {
let body = String::from_utf8_lossy(&out.stdout).to_string();
let _ = tx.send(NetMessage::FetchOk(token, body));
}
Ok(out) => {
let err = String::from_utf8_lossy(&out.stderr).to_string();
let msg = if err.trim().is_empty() {
format!("curl exited with {}", out.status)
} else {
err
};
let _ = tx.send(NetMessage::FetchErr(token, msg));
}
Err(e) => {
let _ = tx.send(NetMessage::FetchErr(
token,
format!("failed to spawn curl: {}", e),
));
}
}
});
token
}
pub fn poll_events(&mut self, interp: &mut Interpreter) {
while let Ok(msg) = self.rx.try_recv() {
match msg {
NetMessage::FetchOk(token, body) => {
if let Some(task) = self.fetches.remove(&token) {
let _ = Self::call_callback(interp, &task.callback, Value::SingleString(body));
}
ACTIVE_TASKS.fetch_sub(1, Ordering::SeqCst);
}
NetMessage::FetchErr(token, err) => {
if let Some(task) = self.fetches.remove(&token) {
let _ = Self::call_callback(
interp,
&task.callback,
Value::SingleString(format!("ERROR: {}", err)),
);
}
ACTIVE_TASKS.fetch_sub(1, Ordering::SeqCst);
}
NetMessage::PingLine(_, _) => {
}
NetMessage::PingErr(_, _) => {
}
NetMessage::PingDone(_) => {
}
}
}
}
fn alloc_token(&mut self) -> usize {
let t = self.next_token;
self.next_token = self.next_token.wrapping_add(1);
if self.next_token == 0 {
self.next_token = 1;
}
t
}
#[inline]
fn call_callback(
interp: &mut Interpreter,
cb: &Box<FunctionValue>,
arg: Value,
) -> Result<(), String> {
interp.set_variable("__net_cb", Value::Function(cb.clone()));
interp.set_variable("__net_arg", arg);
let code = "__net_cb(__net_arg)";
let tokens = tokenize(code, interp.is_verbose())
.map_err(|e| format!("callback tokenize error: {}", e))?;
let ast = parse_tokens(&tokens, interp.is_verbose())
.map_err(|e| format!("callback parse error: {}", e))?;
for stmt in ast.iter() {
let _ = interp.exec_statement(stmt)?;
}
Ok(())
}
}