1use crate::header::{Mime, CONTENT_LENGTH};
2use crate::into::IntoResponse;
3use crate::{Body, Response};
4
5use std::path::Path;
6
7use tokio::{fs, io};
8
9use bytes::Bytes;
10
11pub struct File {
12 file: fs::File,
13 mime_type: Mime,
14 size: usize,
15}
16
17impl File {
18 pub fn new<M>(file: fs::File, mime_type: M, size: usize) -> Self
19 where
20 M: Into<Mime>,
21 {
22 Self {
23 file,
24 mime_type: mime_type.into(),
25 size,
26 }
27 }
28
29 pub async fn open<P>(path: P) -> io::Result<Self>
32 where
33 P: AsRef<Path>,
34 {
35 let extension = path.as_ref().extension().and_then(|f| f.to_str());
36
37 let mime_type = extension
38 .and_then(Mime::from_extension)
39 .unwrap_or(Mime::BINARY);
40
41 let file = fs::File::open(path).await?;
42 let metadata = file.metadata().await?;
43
44 if !metadata.is_file() {
46 return Err(io::Error::new(
47 io::ErrorKind::NotFound,
48 "expected file found folder",
49 ));
50 }
51
52 let size = metadata.len() as usize;
53
54 Ok(Self {
55 file,
56 mime_type,
57 size,
58 })
59 }
60}
61
62impl IntoResponse for File {
63 fn into_response(self) -> Response {
64 Response::builder()
65 .content_type(self.mime_type)
66 .header(CONTENT_LENGTH, self.size)
67 .body(Body::from_async_reader(self.file))
68 .build()
69 }
70}
71
72pub fn serve_memory_file(
73 path: &'static str,
74 bytes: &'static [u8],
75) -> io::Result<Response> {
76 let mime_type = path
77 .rsplit('.')
78 .next()
79 .and_then(Mime::from_extension)
80 .unwrap_or(Mime::BINARY);
81
82 let response = Response::builder()
83 .content_type(mime_type)
84 .header(CONTENT_LENGTH, bytes.len())
85 .body(Bytes::from_static(bytes))
86 .build();
87
88 Ok(response)
89}