use std::sync::Arc;
use rquickjs::JsLifetime;
use rquickjs::class::Trace;
use crate::error::ScriptError;
use crate::fs::PathSandbox;
#[derive(JsLifetime, Trace)]
#[rquickjs::class(rename = "Artifacts")]
pub struct ArtifactsJs {
#[qjs(skip_trace)]
sandbox: Arc<PathSandbox>,
}
impl ArtifactsJs {
#[must_use]
pub fn new(sandbox: Arc<PathSandbox>) -> Self {
Self { sandbox }
}
fn io_err(op: &'static str, msg: String) -> rquickjs::Error {
rquickjs::Error::new_from_js_message("artifacts", op, msg)
}
fn sandbox_err(err: &ScriptError) -> rquickjs::Error {
rquickjs::Error::new_from_js_message("artifacts", "sandbox", err.message.clone())
}
}
#[rquickjs::methods]
impl ArtifactsJs {
#[qjs(get, rename = "root")]
pub fn root(&self) -> String {
self.sandbox.root().to_string_lossy().into_owned()
}
#[qjs(rename = "write")]
pub async fn write(&self, name: String, contents: String) -> rquickjs::Result<()> {
let sb = self.sandbox.clone();
let resolved = sb.resolve_write(&name).map_err(|e| Self::sandbox_err(&e))?;
tokio::fs::write(&resolved, contents)
.await
.map_err(|e| Self::io_err("write", e.to_string()))
}
#[qjs(rename = "writeBytes")]
pub async fn write_bytes(&self, name: String, bytes: Vec<u8>) -> rquickjs::Result<()> {
let sb = self.sandbox.clone();
let resolved = sb.resolve_write(&name).map_err(|e| Self::sandbox_err(&e))?;
tokio::fs::write(&resolved, bytes)
.await
.map_err(|e| Self::io_err("writeBytes", e.to_string()))
}
#[qjs(rename = "read")]
pub async fn read(&self, name: String) -> rquickjs::Result<String> {
let sb = self.sandbox.clone();
let resolved = sb.resolve_read(&name).map_err(|e| Self::sandbox_err(&e))?;
tokio::fs::read_to_string(&resolved)
.await
.map_err(|e| Self::io_err("read", e.to_string()))
}
#[qjs(rename = "readBytes")]
pub async fn read_bytes(&self, name: String) -> rquickjs::Result<Vec<u8>> {
let sb = self.sandbox.clone();
let resolved = sb.resolve_read(&name).map_err(|e| Self::sandbox_err(&e))?;
tokio::fs::read(&resolved)
.await
.map_err(|e| Self::io_err("readBytes", e.to_string()))
}
#[qjs(rename = "list")]
pub async fn list(&self) -> rquickjs::Result<Vec<String>> {
let root = self.sandbox.root().to_path_buf();
let mut entries = tokio::fs::read_dir(&root)
.await
.map_err(|e| Self::io_err("list", e.to_string()))?;
let mut names = Vec::new();
while let Some(entry) = entries
.next_entry()
.await
.map_err(|e| Self::io_err("list", e.to_string()))?
{
names.push(entry.file_name().to_string_lossy().into_owned());
}
Ok(names)
}
#[qjs(rename = "readdir")]
pub async fn readdir(&self, subpath: String) -> rquickjs::Result<Vec<String>> {
let sb = self.sandbox.clone();
let resolved = sb.resolve_read(&subpath).map_err(|e| Self::sandbox_err(&e))?;
let mut entries = tokio::fs::read_dir(&resolved)
.await
.map_err(|e| Self::io_err("readdir", e.to_string()))?;
let mut names = Vec::new();
while let Some(entry) = entries
.next_entry()
.await
.map_err(|e| Self::io_err("readdir", e.to_string()))?
{
names.push(entry.file_name().to_string_lossy().into_owned());
}
Ok(names)
}
#[qjs(rename = "exists")]
pub async fn exists(&self, name: String) -> rquickjs::Result<bool> {
match self.sandbox.resolve_read(&name) {
Ok(resolved) => Ok(tokio::fs::try_exists(&resolved).await.unwrap_or(false)),
Err(_) => Ok(false),
}
}
#[qjs(rename = "remove")]
pub async fn remove(&self, name: String) -> rquickjs::Result<bool> {
let sb = self.sandbox.clone();
let Ok(resolved) = sb.resolve_read(&name) else {
return Ok(false);
};
match tokio::fs::remove_file(&resolved).await {
Ok(()) => Ok(true),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false),
Err(e) => Err(Self::io_err("remove", e.to_string())),
}
}
}