1use std::{default, ops::Deref, path::PathBuf, sync::atomic::AtomicU64};
2
3use axum::body::Body;
4use flams_ontology::{
5 languages::Language,
6 uris::{ArchiveId, DocumentURI},
7};
8use flams_router_base::uris::DocURIComponents;
9use flams_system::{
10 backend::{Backend, GlobalBackend},
11 settings::Settings,
12};
13use flams_utils::time::Timestamp;
14use http::Request;
15use leptos_router::location::LocationProvider;
16use tower::ServiceExt;
17use tower_http::services::{fs::ServeFileSystemResponseBody, ServeFile};
18
19use super::ServerState;
20
21#[derive(Clone, Default)]
22pub struct ImageStore(flams_utils::triomphe::Arc<ImageStoreI>);
23
24#[derive(Default)]
25struct ImageStoreI {
26 map: dashmap::DashMap<ImageSpec, ImageData>,
27 count: AtomicU64,
28}
29
30#[derive(Clone, Debug, Hash, PartialEq, Eq)]
31pub enum ImageSpec {
32 Kpse(Box<str>),
33 ARp(ArchiveId, Box<str>),
34 File(Box<str>),
35}
36impl ImageSpec {
37 pub fn path(&self) -> Option<PathBuf> {
38 match self {
39 Self::Kpse(p) => tex_engine::engine::filesystem::kpathsea::KPATHSEA.which(p),
40 Self::ARp(a, p) => {
41 GlobalBackend::get().with_local_archive(a, |a| a.map(|a| a.path().join(&**p)))
42 }
43 Self::File(p) => Some(std::path::PathBuf::from(p.to_string())),
44 }
45 }
46}
47
48pub struct ImageData {
49 img: Box<[u8]>,
50 timestamp: AtomicU64,
51}
52impl ImageData {
53 pub fn update(&self) {
54 let now = Timestamp::now();
55 self.timestamp
56 .store(now.0.get() as _, std::sync::atomic::Ordering::SeqCst);
57 }
58 pub fn new(data: &[u8]) -> Self {
59 Self {
60 img: data.into(),
61 timestamp: AtomicU64::new(Timestamp::now().0.get()),
62 }
63 }
64}
65
66pub(crate) async fn img_handler(
67 uri: http::Uri,
68 axum::extract::State(ServerState { images: _, .. }): axum::extract::State<ServerState>,
69 ) -> axum::response::Response<ServeFileSystemResponseBody> {
71 let default = || {
72 let mut resp = axum::response::Response::new(ServeFileSystemResponseBody::default());
73 *resp.status_mut() = http::StatusCode::NOT_FOUND;
74 resp
75 };
76
77 let Some(s) = uri.query() else {
78 return default();
79 };
80
81 let spec = if let Some(s) = s.strip_prefix("kpse=") {
82 ImageSpec::Kpse(s.into())
83 } else if let Some(f) = s.strip_prefix("file=") {
84 if Settings::get().lsp {
85 ImageSpec::File(f.into())
86 } else {
87 return default();
88 }
89 } else if let Some(s) = s.strip_prefix("a=") {
90 let Some((a, rp)) = s.split_once("&rp=") else {
91 return default();
92 };
93 let a = a.parse().unwrap_or_else(|_| unreachable!());
94 let rp = rp.into();
95 ImageSpec::ARp(a, rp)
96 } else {
97 return default();
98 };
99
100 if let Some(p) = spec.path() {
102 let req = Request::builder()
103 .uri(uri.clone())
104 .body(Body::empty())
105 .unwrap();
106 ServeFile::new(p)
107 .oneshot(req)
108 .await
109 .unwrap_or_else(|_| default())
110 } else {
111 default()
112 }
113}
114
115pub(crate) async fn doc_handler(
116 uri: http::Uri,
117) -> axum::response::Response<ServeFileSystemResponseBody> {
118 let req_uri = uri;
119 let default = || {
120 let mut resp = axum::response::Response::new(ServeFileSystemResponseBody::default());
121 *resp.status_mut() = http::StatusCode::NOT_FOUND;
122 resp
123 };
124 let err = |s: &str| {
125 let mut resp = axum::response::Response::new(ServeFileSystemResponseBody::default());
126 *resp.status_mut() = http::StatusCode::BAD_REQUEST;
127 resp
128 };
129
130 let Some(params) = Params::new(&req_uri) else {
131 return err("Invalid URI");
132 };
133
134 macro_rules! parse {
135 ($id:literal) => {
136 if let Some(s) = params.get_str($id) {
137 let Ok(r) = s.parse() else {
138 return err("malformed uri");
139 };
140 Some(r)
141 } else {
142 None
143 }
144 };
145 }
146 let Some(format) = params.get_str("format") else {
147 return err("Missing format");
148 };
149
150 let uri: Option<DocumentURI> = parse!("uri");
151 let rp = params.get("rp");
152 let a: Option<ArchiveId> = parse!("a");
153 let p = params.get("p");
154 let l: Option<Language> = parse!("l");
155 let d = params.get("d");
156
157 let comps: Result<DocURIComponents, _> = (uri, rp, a, p, l, d).try_into();
158 let uri = if let Ok(comps) = comps {
159 let Some(uri) = comps.parse() else {
160 return err("Malformed URI components");
161 };
162 uri
163 } else {
164 return err("Malformed URI components");
165 };
166 let Some(path) = GlobalBackend::get().artifact_path(&uri, format) else {
167 return default();
168 };
169
170 let pandq = format!("/{}.{format}", uri.name().first_name());
171 let mime = mime_guess::from_ext(&format).first_or_octet_stream();
172 let req_uri = http::Uri::builder()
173 .path_and_query(pandq)
174 .build()
175 .unwrap_or(req_uri);
176 let req = Request::builder().uri(req_uri).body(Body::empty()).unwrap();
177 ServeFile::new_with_mime(path, &mime)
178 .oneshot(req)
179 .await
180 .unwrap_or_else(|_| default())
181}
182
183struct Params<'a>(&'a str);
184impl<'a> Params<'a> {
185 fn new(uri: &'a http::Uri) -> Option<Self> {
186 uri.query().map(Self)
187 }
188 fn get_str(&self, name: &str) -> Option<&str> {
189 self.0
190 .split('&')
191 .find(|s| s.starts_with(name) && s.as_bytes().get(name.len()) == Some(&b'='))?
192 .split('=')
193 .nth(1)
194 }
195 fn get(&self, name: &str) -> Option<String> {
196 self.get_str(name).map(|s| s.to_string())
197 }
198}