quick_doc_viewer/
app.rs

1use std::io;
2use std::path::Path;
3// for `Mime::from_str`
4use std::str::FromStr;
5
6use http_types::Mime;
7use tide::{Body, Request, Response, Result, StatusCode};
8use urlencoding::decode as url_decode;
9
10use crate::{
11    Files,
12    MimeType,
13    PageQuery,
14    Templates,
15    guess_mime_type,
16    is_markdown,
17    read_text_file_to_string,
18    markdown,
19};
20
21#[derive(Clone)]
22pub struct State {
23    storage_root: String,
24    file_paths: Vec<String>,
25    templates: Templates,
26}
27
28async fn files(req: Request<State>) -> Result<impl Into<Response>> {
29    // read all files and generate index
30    let state = req.state();
31    let file_paths = &state.file_paths;
32    let templates = &state.templates;
33
34    let html_content = templates.files(file_paths);
35    Ok(html_content)
36}
37
38async fn page(req: Request<State>) -> Result<impl Into<Response>> {
39    // render preview or raw
40    let p: PageQuery = req.query()?;
41    if p.raw {
42        raw(req).await
43    } else {
44        preview(req).await
45    }
46}
47
48async fn preview(req: Request<State>) -> Result<Response> {
49    let state = req.state();
50    let storage_root = &state.storage_root;
51    let file_paths = &state.file_paths;
52    let templates = &state.templates;
53
54    let path = req.url().path();
55
56    let path_string = path.to_string();
57
58    // file path may be encoded
59    let path_string = url_decode(&path_string).unwrap().into_owned();
60
61    if !file_paths.contains(&path_string) {
62        return Ok(Response::new(StatusCode::NotFound));
63    }
64
65    let file_path = Path::new(&storage_root.to_owned()).join(&path_string.strip_prefix('/').unwrap());
66    let content = read_text_file_to_string(&file_path).await?;
67
68    // prewiew as selected format, currently support Markdown
69
70    let p: PageQuery = req.query()?;
71    match p.format.as_str() {
72        "markdown" => {
73            let content = markdown(&content);
74            let html_content = templates.page(&path_string, &content).into_string();
75            return Ok(html_response(html_content));
76        },
77        "plain_text" => {
78            let html_content = templates.text(&path_string, &content).into_string();
79            return Ok(html_response(html_content));
80        },
81        _ => {},
82    }
83
84    // guess mime type of the file to determine preview format
85    // if mime type of the file is other, send raw of file
86
87    let mime_type = guess_mime_type(&path_string);
88    match mime_type {
89        MimeType::Text => {
90            let html_content = if is_markdown(&path_string) {
91                let content = markdown(&content);
92                templates.page(&path_string, &content).into_string()
93            } else {
94                templates.text(&path_string, &content).into_string()
95            };
96            Ok(html_response(html_content))
97        },
98        _ => {
99            raw(req).await
100        },
101    }
102}
103
104fn html_response(html_content: String) -> Response {
105    let mime = Mime::from_str("text/html;charset=utf-8").unwrap();
106    Response::builder(200)
107        .body(html_content)
108        .content_type(mime)
109        .build()
110}
111
112async fn raw(req: Request<State>) -> Result<Response> {
113    let state = req.state();
114    let storage_root = &state.storage_root;
115    let file_paths = &state.file_paths;
116
117    let path = req.url().path();
118
119    let path_string = path.to_string();
120
121    // file path may be encoded
122    let path_string = url_decode(&path_string).unwrap().into_owned();
123
124    if !file_paths.contains(&path_string) {
125        return Ok(Response::new(StatusCode::NotFound));
126    }
127
128    let file_path = Path::new(&storage_root.to_owned()).join(&path_string.strip_prefix('/').unwrap());
129
130    // copied from tide 0.16.0 src/fs/serve_file.rs
131
132    match Body::from_file(&file_path).await {
133        Ok(body) => Ok(Response::builder(StatusCode::Ok).body(body).build()),
134        Err(e) if e.kind() == io::ErrorKind::NotFound => {
135            Ok(Response::new(StatusCode::NotFound))
136        }
137        Err(e) => Err(e.into()),
138    }
139}
140
141pub fn make_app() -> tide::Server<State> {
142    let files_ = Files::load(".");
143    let file_paths = files_.paths;
144
145    let templates = Templates::build(&files_.special_file_paths);
146
147    let state = State {
148        storage_root: ".".to_string(),
149        file_paths: file_paths,
150        templates: templates,
151    };
152
153    let mut app = tide::with_state(state);
154    app.at("/").get(files);
155    app.at("/*").get(page);
156    app
157}