Skip to main content

flams_main/server/
files.rs

1use axum::body::Body;
2use flams_math_archives::{
3    LocallyBuilt, MathArchive,
4    backend::{GlobalBackend, LocalBackend},
5};
6use flams_system::settings::Settings;
7use ftml_uris::{
8    ArchiveId, DocumentUri, IsNarrativeUri, Language, UriWithArchive, UriWithPath,
9    components::{DocumentUriComponentTuple, DocumentUriComponents},
10};
11use http::Request;
12use std::{borrow::Cow, str::FromStr};
13use tower::ServiceExt;
14use tower_http::services::{ServeFile, fs::ServeFileSystemResponseBody};
15
16// May cache images at some point
17#[derive(Clone, Default)]
18pub struct ImageStore(/*flams_utils::triomphe::Arc<ImageStoreI>*/ ImageStoreI);
19
20#[derive(Default, Copy, Clone)]
21struct ImageStoreI {
22    //map: dashmap::DashMap<ImageSpec, ImageData>,
23    //count: AtomicU64,
24}
25
26pub(crate) struct Img(Box<[u8]>, String);
27impl axum::response::IntoResponse for Img {
28    fn into_response(self) -> axum::response::Response {
29        (
30            [(
31                axum::http::header::CONTENT_TYPE,
32                // TODO: other MIME types
33                if self.1 == "svg" {
34                    "image/svg+xml"
35                } else {
36                    "image/webp"
37                },
38            )],
39            self.0,
40        )
41            .into_response()
42    }
43}
44
45//#[axum::debug_handler]
46/// #### Errors
47pub async fn img_handler(
48    uri: http::Uri,
49    // axum::extract::State(ServerState { images, .. }): axum::extract::State<ServerState>,
50    //request: http::Request<axum::body::Body>,
51) -> Result<Img, axum::http::StatusCode> /*axum::response::Response<ServeFileSystemResponseBody>*/ {
52    let Ok(Some(img)) = tokio::task::spawn_blocking(move || {
53        let query = uri.query()?;
54        let path = flams_math_archives::images::ImagePath::from_query(query, Settings::get().lsp)?;
55        path.get()
56    })
57    .await
58    else {
59        return Err(http::StatusCode::NOT_FOUND);
60    };
61    Ok(Img(img.0, img.1))
62}
63
64pub(crate) async fn aux_handler(uri: http::Uri) -> impl axum::response::IntoResponse {
65    let deflt = || {
66        let mut resp = axum::response::Response::new(ServeFileSystemResponseBody::default());
67        *resp.status_mut() = http::StatusCode::NOT_FOUND;
68        resp
69    };
70    let Some(mut query) = uri.query() else {
71        return deflt();
72    };
73    if let Some((q, _)) = query.rsplit_once('?') {
74        query = q;
75    }
76    let Some(query) = query.strip_prefix("a=") else {
77        return deflt();
78    };
79    let Some((a, p)) = query.split_once("&f=") else {
80        return deflt();
81    };
82    let Ok(a) = ArchiveId::from_str(a) else {
83        return deflt();
84    };
85    let Some(path) = GlobalBackend.with_local_archive(&a, |a| a.map(|a| a.source_dir().join(p)))
86    else {
87        return deflt();
88    };
89    let mime = mime_guess::from_path(&path).first_or_octet_stream();
90
91    let req = Request::builder()
92        .uri(uri)
93        .body(Body::empty())
94        .expect("this is a bug");
95    ServeFile::new_with_mime(path, &mime)
96        .oneshot(req)
97        .await
98        .unwrap_or_else(|_| deflt())
99
100    //axum::response::Response<ServeFileSystemResponseBody> {
101}
102
103pub(crate) async fn doc_handler(
104    uri: http::Uri,
105) -> axum::response::Response<ServeFileSystemResponseBody> {
106    let req_uri = uri;
107    let default = || {
108        let mut resp = axum::response::Response::new(ServeFileSystemResponseBody::default());
109        *resp.status_mut() = http::StatusCode::NOT_FOUND;
110        resp
111    };
112    let err = |s: &str| {
113        let mut resp = axum::response::Response::new(ServeFileSystemResponseBody::default());
114        tracing::info!("pdf download error: {s}");
115        *resp.status_mut() = http::StatusCode::BAD_REQUEST;
116        resp
117    };
118
119    let Some(params) = Params::new(&req_uri) else {
120        return err("Invalid URI");
121    };
122
123    macro_rules! parse {
124        ($id:literal) => {
125            if let Some(s) = params.get_str($id) {
126                let Ok(r) = s.parse() else {
127                    return err("malformed uri");
128                };
129                Some(r)
130            } else {
131                None
132            }
133        };
134    }
135    let Some(format) = params.get_str("format") else {
136        return err("Missing format");
137    };
138
139    let uri: Option<DocumentUri> = parse!("uri");
140    let rp = params.get("rp");
141    let a: Option<ArchiveId> = parse!("a");
142    let p = params.get("p");
143    let l: Option<Language> = parse!("l");
144    let d = params.get("d");
145
146    let comps = DocumentUriComponentTuple {
147        uri,
148        rp,
149        a,
150        p,
151        d,
152        l,
153    };
154
155    let comps: Result<DocumentUriComponents, _> = comps.try_into();
156    let uri = if let Ok(comps) = comps {
157        let Ok(uri) =
158            comps.parse(|a| GlobalBackend.with_archive(a, |a| a.map(|a| a.uri().clone())))
159        else {
160            return err("Malformed URI components");
161        };
162        uri
163    } else {
164        return err("Malformed URI components");
165    };
166    let uri2 = uri.clone();
167    let formatstr = format.to_string();
168    let Ok(Some(path)) = tokio::task::spawn_blocking(move || {
169        GlobalBackend.with_local_archive(uri.archive_id(), |a| {
170            a.map(|a| {
171                a.out_path_of(uri.path(), uri.document_name(), None, uri.language)
172                    .join(&formatstr)
173            })
174        })
175    })
176    .await
177    else {
178        return default();
179    };
180
181    let pandq = format!("/{}.{format}", uri2.document_name());
182    let mime = mime_guess::from_ext(&format).first_or_octet_stream();
183    let req_uri = http::Uri::builder()
184        .path_and_query(pandq)
185        .build()
186        .unwrap_or(req_uri);
187    let req = Request::builder()
188        .uri(req_uri)
189        .body(Body::empty())
190        .expect("this is a bug");
191    ServeFile::new_with_mime(path, &mime)
192        .oneshot(req)
193        .await
194        .unwrap_or_else(|_| default())
195}
196
197struct Params<'a>(&'a str);
198impl<'a> Params<'a> {
199    fn new(uri: &'a http::Uri) -> Option<Self> {
200        uri.query().map(Self)
201    }
202    fn get_str(&self, name: &str) -> Option<Cow<'_, str>> {
203        self.0
204            .split('&')
205            .find(|s| s.starts_with(name) && s.as_bytes().get(name.len()) == Some(&b'='))?
206            .split('=')
207            .nth(1)
208            .and_then(|s| urlencoding::decode(s).ok())
209    }
210    fn get(&self, name: &str) -> Option<String> {
211        self.get_str(name).map(Cow::into_owned)
212    }
213}