1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::{
    convert::TryInto,
    fmt::Debug,
    io::{BufReader, Read},
    path::Path,
    time::SystemTime,
};

use chrono::TimeZone;
use new_mime_guess::MimeGuess;
use sha2::{Digest, Sha256};

use super::common::EmbedableFile;

pub struct DynamicFile {
    name: String,
    data: Vec<u8>,
    hash: String,
    last_modified_timestamp: Option<i64>,
    mime_type: Option<String>,
}

impl EmbedableFile for DynamicFile {
    type Data = Vec<u8>;
    type Meta = String;

    fn name(&self) -> Self::Meta {
        self.name.clone()
    }

    fn data(&self) -> Self::Data {
        self.data.clone()
    }

    fn data_gzip(&self) -> Option<Self::Data> {
        None
    }

    fn data_br(&self) -> Option<Self::Data> {
        None
    }

    fn last_modified(&self) -> Option<Self::Meta> {
        self.last_modified_timestamp()
            .map(|v| chrono::Utc.timestamp(v, 0).to_rfc2822())
    }

    fn last_modified_timestamp(&self) -> Option<i64> {
        self.last_modified_timestamp
    }

    fn hash(&self) -> Self::Meta {
        self.hash.clone()
    }

    fn etag(&self) -> Self::Meta {
        format!("\"{}\"", self.hash)
    }

    fn mime_type(&self) -> Option<Self::Meta> {
        self.mime_type.clone()
    }
}

fn modified_unix_timestamp(metadata: &std::fs::Metadata) -> Option<i64> {
    metadata.modified().ok().and_then(|modified| {
        modified
            .duration_since(SystemTime::UNIX_EPOCH)
            .ok()
            .and_then(|v| v.as_secs().try_into().ok())
            .or_else(|| {
                SystemTime::UNIX_EPOCH
                    .duration_since(modified)
                    .ok()
                    .and_then(|v| v.as_secs().try_into().ok().map(|v: i64| -v))
            })
    })
}

impl DynamicFile {
    pub fn read_from_fs<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
        let file = std::fs::OpenOptions::new().read(true).open(&path)?;

        let last_modified_timestamp = modified_unix_timestamp(&file.metadata()?);

        let mut data = Vec::new();
        BufReader::new(file).read_to_end(&mut data)?;

        let mut hasher = Sha256::new();
        hasher.update(&data);
        let hash = hasher.finalize();
        let hash = base85rs::encode(&hash[..]);

        let mime_type = MimeGuess::from_path(&path).first().map(|v| v.to_string());
        let name = Path::file_name(path.as_ref())
            .expect("Unable to parse the file name")
            .to_string_lossy()
            .to_string();

        Ok(DynamicFile {
            name,
            data,
            hash,
            last_modified_timestamp,
            mime_type,
        })
    }
}

impl Debug for DynamicFile {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DynamicFile")
            .field("name", &self.name)
            .field("hash", &self.hash)
            .field("last_modified", &self.last_modified())
            .field("mime_type", &self.mime_type)
            .finish()
    }
}