char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somrearrange libs into lib and extensions into extsf4a0f5b

main
5.6 KiB176 linesraw
1use std::collections::HashMap;
2use std::convert::Infallible;
3use std::io;
4use std::sync::atomic::{AtomicU64, Ordering};
5use std::sync::{Arc, Mutex};
6
7use avril_caps::Capabilities;
8use avril_core::Op;
9use avril_core::serde_v8::ToJsBuffer;
10use avril_http::Engine;
11use avril_http::file::{CheckFile, FileBody, HeaderPairs, header_pairs, headers, serve_file};
12use serde::Deserialize;
13use tokio::runtime::Handle;
14
15use avril_ext_file::{Files as Handles, response_file};
16use avril_host::io::response;
17
18#[derive(Deserialize)]
19#[serde(untagged)]
20enum Source {
21    Path(String),
22    Handle(u64),
23}
24
25type HandleFile = (String, std::fs::File, CheckFile);
26type Prepare = (String, HeaderPairs, Source, HeaderPairs);
27type Prepared = (u16, HeaderPairs, Option<u64>);
28
29const MAX_ID: u64 = 9_007_199_254_740_991;
30
31struct Files {
32    runtime: Handle,
33    caps: Arc<Capabilities>,
34    bodies: Mutex<HashMap<u64, Arc<FileBody>>>,
35    sources: Mutex<HashMap<u64, HandleFile>>,
36    next: AtomicU64,
37}
38
39impl Files {
40    fn next_id(&self) -> io::Result<u64> {
41        self.next
42            .fetch_update(Ordering::AcqRel, Ordering::Acquire, |id| {
43                (id <= MAX_ID).then_some(id + 1)
44            })
45            .map_err(|_| io::Error::other("File response identifiers exhausted"))
46    }
47
48    fn take(&self, id: u64) -> io::Result<Arc<FileBody>> {
49        self.bodies
50            .lock()
51            .unwrap()
52            .remove(&id)
53            .ok_or_else(|| io::Error::new(io::ErrorKind::NotConnected, "File response is closed"))
54    }
55
56    fn prepare(
57        self: &Arc<Self>,
58        (method, request, source, extra): Prepare,
59    ) -> impl Future<Output = io::Result<Prepared>> + use<> {
60        let files = self.clone();
61        let source = match source {
62            Source::Path(path) => Ok((path, None)),
63            Source::Handle(id) => self
64                .sources
65                .lock()
66                .unwrap()
67                .remove(&id)
68                .map(|(name, file, check)| (name, Some((file, check))))
69                .ok_or_else(|| {
70                    io::Error::new(io::ErrorKind::NotConnected, "File source is closed")
71                }),
72        };
73        let pending = self.runtime.spawn_blocking(move || {
74            let (path, opened) = source?;
75            let response = serve_file(
76                files.runtime.clone(),
77                &method,
78                headers(request)?,
79                &path,
80                headers(extra)?,
81                || match opened {
82                    Some(opened) => Ok(opened),
83                    None => response_file(files.caps.clone(), path.clone()),
84                },
85            )?;
86            let status = response.status().as_u16();
87            let headers = header_pairs(response.headers());
88            let id = if let Some(body) = response.into_body() {
89                let id = files.next_id()?;
90                files.bodies.lock().unwrap().insert(id, body);
91                Some(id)
92            } else {
93                None
94            };
95            Ok((status, headers, id))
96        });
97        async move { pending.await.map_err(io::Error::other)? }
98    }
99}
100
101impl Drop for Files {
102    fn drop(&mut self) {
103        for (_, body) in self.bodies.get_mut().unwrap().drain() {
104            body.close();
105        }
106        let sources = std::mem::take(self.sources.get_mut().unwrap());
107        if !sources.is_empty() {
108            self.runtime.spawn_blocking(move || drop(sources));
109        }
110    }
111}
112
113pub fn ops(
114    runtime: Handle,
115    caps: Arc<Capabilities>,
116    handles: Arc<Handles>,
117    engine: Arc<Engine>,
118) -> Vec<Op> {
119    let files = Arc::new(Files {
120        runtime,
121        caps,
122        bodies: Mutex::new(HashMap::new()),
123        sources: Mutex::new(HashMap::new()),
124        next: AtomicU64::new(1),
125    });
126    let acquire = files.clone();
127    let prepare = files.clone();
128    let read = files.clone();
129    let send = files.clone();
130    vec![
131        Op::new("op_http_file_handle", move |(id, consume): (u64, bool)| {
132            let acquired = (|| {
133                let token = acquire.next_id()?;
134                let source = handles.response_handle(id, consume)?;
135                acquire.sources.lock().unwrap().insert(token, source);
136                Ok(token)
137            })();
138            Ok::<_, Infallible>(response(acquired))
139        }),
140        Op::asynchronous("op_http_serve_file", move |args: Prepare| {
141            let pending = prepare.prepare(args);
142            async move { Ok::<_, Infallible>(response(pending.await)) }
143        }),
144        Op::asynchronous("op_http_file_read", move |id: u64| {
145            let body = read.bodies.lock().unwrap().get(&id).cloned();
146            async move {
147                let result = match body {
148                    Some(body) => body
149                        .read()
150                        .await
151                        .map(|(bytes, done)| (ToJsBuffer::from(bytes), done)),
152                    None => Err(io::Error::new(
153                        io::ErrorKind::NotConnected,
154                        "File response is closed",
155                    )),
156                };
157                Ok::<_, Infallible>(response(result))
158            }
159        }),
160        Op::new("op_http_file_close", move |id: u64| {
161            if let Ok(body) = files.take(id) {
162                body.close();
163            }
164            Ok::<_, Infallible>(())
165        }),
166        Op::new(
167            "op_http_respond_file",
168            move |(id, status, headers, body): (u64, u16, HeaderPairs, u64)| {
169                let file = send.take(body)?;
170                engine
171                    .respond_file(id, status, headers, file)
172                    .map_err(io::Error::other)
173            },
174        ),
175    ]
176}