flams/server/
files.rs

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    //request: http::Request<axum::body::Body>,
70) -> 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    //tracing::info!("HERE: {spec:?}");
101    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}