use std::collections::{HashMap, VecDeque};
use std::path::PathBuf;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use oino_extension_api::manifest::Permissions;
use oino_extension_api::{CallContext, ExtensionId, HostError, HostServices, Manifest};
use wasmtime::component::{Access, Accessor, GuestTaskId, HasData, ResourceTable};
use wasmtime::{AsContextMut, StoreLimits};
use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
use crate::http::HttpService;
use crate::kv::KvStore;
use crate::paths::{ExtPaths, Preopen};
use crate::process::ProcessService;
use crate::timers::Timers;
pub(crate) fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(PoisonError::into_inner)
}
#[derive(Clone, Debug)]
pub struct HostConfig {
pub agent_dir: PathBuf,
pub cwd: PathBuf,
pub oino_version: String,
pub http_proxy: Option<String>,
pub http_idle_timeout: std::time::Duration,
}
impl HostConfig {
#[must_use]
pub fn new(agent_dir: impl Into<PathBuf>, cwd: impl Into<PathBuf>) -> Self {
Self {
agent_dir: agent_dir.into(),
cwd: cwd.into(),
oino_version: env!("CARGO_PKG_VERSION").to_owned(),
http_proxy: None,
http_idle_timeout: std::time::Duration::from_secs(300),
}
}
}
pub(crate) struct ExtShared {
pub(crate) id: ExtensionId,
pub(crate) manifest: Manifest,
pub(crate) package_root: Option<PathBuf>,
pub(crate) bundled: bool,
pub(crate) host: Arc<dyn HostServices>,
pub(crate) config: HostConfig,
pub(crate) paths: ExtPaths,
pub(crate) kv: KvStore,
pub(crate) timers: Timers,
pub(crate) http: HttpService,
pub(crate) processes: ProcessService,
pub(crate) preopen_list: Vec<Preopen>,
pub(crate) http_grants: HttpGrants,
pub(crate) last_cx: Mutex<CallContext>,
pub(crate) dialogs_open: std::sync::atomic::AtomicUsize,
}
impl ExtShared {
pub(crate) fn perms(&self) -> &Permissions {
&self.manifest.permissions
}
pub(crate) fn preopens(&self, _cx: &CallContext) -> Vec<Preopen> {
self.preopen_list.clone()
}
pub(crate) fn record_daemon(&self, pid: u32) {
let path = self.paths.global.join("daemons.json");
let mut list: Vec<serde_json::Value> = std::fs::read(&path)
.ok()
.and_then(|b| serde_json::from_slice(&b).ok())
.unwrap_or_default();
list.push(serde_json::json!({"pid": pid, "startedAt": crate::timers::now_ms()}));
if let Ok(bytes) = serde_json::to_vec_pretty(&list) {
let _ = crate::kv::atomic_write(&path, &bytes, None);
}
}
pub(crate) fn require(granted: bool, name: &str) -> Result<(), HostError> {
if granted {
Ok(())
} else {
Err(HostError::Denied(name.to_owned()))
}
}
}
#[derive(Default)]
pub(crate) struct CallTable {
map: HashMap<GuestTaskId, CallContext>,
order: VecDeque<GuestTaskId>,
}
const CALL_TABLE_CAP: usize = 512;
impl CallTable {
pub(crate) fn insert(&mut self, task: GuestTaskId, cx: CallContext) {
if self.map.insert(task, cx).is_none() {
self.order.push_back(task);
}
while self.order.len() > CALL_TABLE_CAP {
if let Some(old) = self.order.pop_front() {
self.map.remove(&old);
}
}
}
pub(crate) fn get(&self, task: GuestTaskId) -> Option<&CallContext> {
self.map.get(&task)
}
}
pub(crate) struct HostState {
pub(crate) wasi: WasiCtx,
pub(crate) table: ResourceTable,
pub(crate) limits: StoreLimits,
pub(crate) ext: Arc<ExtShared>,
pub(crate) calls: CallTable,
}
impl WasiView for HostState {
fn ctx(&mut self) -> WasiCtxView<'_> {
WasiCtxView {
ctx: &mut self.wasi,
table: &mut self.table,
}
}
}
impl HasData for HostState {
type Data<'a> = &'a mut HostState;
}
pub(crate) fn current<T>(access: &mut Access<'_, T, HostState>) -> (Arc<ExtShared>, CallContext) {
let root = access
.as_context_mut()
.async_call_stack()
.ok()
.and_then(Iterator::last);
let state = access.get();
let ext = Arc::clone(&state.ext);
let cx = root
.and_then(|task| state.calls.get(task).cloned())
.unwrap_or_else(|| lock(&ext.last_cx).clone());
(ext, cx)
}
pub(crate) fn current_async<T>(accessor: &Accessor<T, HostState>) -> (Arc<ExtShared>, CallContext) {
accessor.with(|mut access| current(&mut access))
}
#[derive(Default)]
pub(crate) struct HttpGrants(Mutex<std::collections::HashSet<String>>);
impl HttpGrants {
pub(crate) fn add_url(&self, url: &str) {
if let Some(host) = reqwest::Url::parse(url)
.ok()
.as_ref()
.and_then(crate::http::host_of)
{
lock(&self.0).insert(host);
}
}
pub(crate) fn allows(&self, host: &str) -> bool {
lock(&self.0).contains(host)
}
}