rust_embed_for_web_utils/file/
dynamic.rs1use std::{
2 convert::TryInto,
3 fmt::Debug,
4 io::{BufReader, Read},
5 path::Path,
6 time::SystemTime,
7};
8
9use chrono::TimeZone;
10use new_mime_guess::MimeGuess;
11use sha2::{Digest, Sha256};
12
13use super::common::EmbedableFile;
14
15#[derive(Clone)]
23pub struct DynamicFile {
24 name: String,
25 data: Vec<u8>,
26 hash: String,
27 last_modified_timestamp: Option<i64>,
28 mime_type: Option<String>,
29}
30
31impl EmbedableFile for DynamicFile {
32 type Data = Vec<u8>;
33 type Meta = String;
34
35 fn name(&self) -> Self::Meta {
36 self.name.clone()
37 }
38
39 fn data(&self) -> Self::Data {
40 self.data.clone()
41 }
42
43 fn data_gzip(&self) -> Option<Self::Data> {
44 None
45 }
46
47 fn data_br(&self) -> Option<Self::Data> {
48 None
49 }
50
51 fn last_modified(&self) -> Option<Self::Meta> {
52 self.last_modified_timestamp()
53 .map(|v| chrono::Utc.timestamp_opt(v, 0).unwrap().to_rfc2822())
54 }
55
56 fn last_modified_timestamp(&self) -> Option<i64> {
57 self.last_modified_timestamp
58 }
59
60 fn hash(&self) -> Self::Meta {
61 self.hash.clone()
62 }
63
64 fn etag(&self) -> Self::Meta {
65 format!("\"{}\"", self.hash)
66 }
67
68 fn mime_type(&self) -> Option<Self::Meta> {
69 self.mime_type.clone()
70 }
71}
72
73fn modified_unix_timestamp(metadata: &std::fs::Metadata) -> Option<i64> {
74 metadata.modified().ok().and_then(|modified| {
75 modified
76 .duration_since(SystemTime::UNIX_EPOCH)
77 .ok()
78 .and_then(|v| v.as_secs().try_into().ok())
79 .or_else(|| {
80 SystemTime::UNIX_EPOCH
81 .duration_since(modified)
82 .ok()
83 .and_then(|v| v.as_secs().try_into().ok().map(|v: i64| -v))
84 })
85 })
86}
87
88impl DynamicFile {
89 pub fn read_from_fs<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
90 let file = std::fs::OpenOptions::new().read(true).open(&path)?;
91
92 let last_modified_timestamp = modified_unix_timestamp(&file.metadata()?);
93
94 let mut data = Vec::new();
95 BufReader::new(file).read_to_end(&mut data)?;
96
97 let mut hasher = Sha256::new();
98 hasher.update(&data);
99 let hash = hasher.finalize();
100 let hash = base85rs::encode(&hash[..]);
101
102 let mime_type = MimeGuess::from_path(&path).first().map(|v| v.to_string());
103 let name = Path::file_name(path.as_ref())
104 .expect("Unable to parse the file name")
105 .to_string_lossy()
106 .to_string();
107
108 Ok(DynamicFile {
109 name,
110 data,
111 hash,
112 last_modified_timestamp,
113 mime_type,
114 })
115 }
116}
117
118impl Debug for DynamicFile {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 f.debug_struct("DynamicFile")
121 .field("name", &self.name)
122 .field("hash", &self.hash)
123 .field("last_modified", &self.last_modified())
124 .field("mime_type", &self.mime_type)
125 .finish()
126 }
127}
128
129impl PartialEq for DynamicFile {
130 fn eq(&self, other: &Self) -> bool {
131 self.hash.eq(&other.hash)
132 }
133}