rocket_include_static_resources/debug/
file_resources.rs

1use std::{
2    collections::HashMap,
3    fs,
4    io::{self, ErrorKind},
5    path::PathBuf,
6    sync::Arc,
7    time::SystemTime,
8};
9
10use mime::Mime;
11
12use crate::{functions::compute_data_etag, mime, EntityTag};
13
14#[derive(Debug)]
15struct Resource {
16    path:  PathBuf,
17    // mime could be an atom `Mime`, so just clone it
18    mime:  Mime,
19    data:  Arc<Vec<u8>>,
20    etag:  EntityTag<'static>,
21    mtime: Option<SystemTime>,
22}
23
24#[derive(Debug)]
25/// Reloadable file resources.
26pub struct FileResources {
27    resources: HashMap<&'static str, Resource>,
28}
29
30impl FileResources {
31    /// Create an instance of `FileResources`.
32    #[inline]
33    pub fn new() -> FileResources {
34        FileResources {
35            resources: HashMap::new()
36        }
37    }
38
39    /// Register a resource from a path and it can be reloaded automatically.
40    #[inline]
41    pub fn register_resource_file<P: Into<PathBuf>>(
42        &mut self,
43        name: &'static str,
44        file_path: P,
45    ) -> Result<(), io::Error> {
46        let path = file_path.into();
47
48        let metadata = path.metadata()?;
49
50        let mtime = metadata.modified().ok();
51
52        let data = fs::read(&path)?;
53
54        let etag = compute_data_etag(&data);
55
56        let mime = match path.extension() {
57            Some(extension) => match extension.to_str() {
58                Some(extension) => mime_guess::from_ext(extension).first_or_octet_stream(),
59                None => mime::APPLICATION_OCTET_STREAM,
60            },
61            None => mime::APPLICATION_OCTET_STREAM,
62        };
63
64        let resource = Resource {
65            path,
66            mime,
67            data: Arc::new(data),
68            etag,
69            mtime,
70        };
71
72        self.resources.insert(name, resource);
73
74        Ok(())
75    }
76
77    /// Unregister a resource from a file by a name.
78    #[inline]
79    pub fn unregister_resource_file<S: AsRef<str>>(&mut self, name: S) -> Option<PathBuf> {
80        let name = name.as_ref();
81
82        self.resources.remove(name).map(|resource| resource.path)
83    }
84
85    /// Reload resources if needed.
86    #[inline]
87    pub fn reload_if_needed(&mut self) -> Result<(), io::Error> {
88        for resource in self.resources.values_mut() {
89            let metadata = resource.path.metadata()?;
90
91            let (reload, new_mtime) = match resource.mtime {
92                Some(mtime) => match metadata.modified() {
93                    Ok(new_mtime) => (new_mtime > mtime, Some(new_mtime)),
94                    Err(_) => (true, None),
95                },
96                None => match metadata.modified() {
97                    Ok(new_mtime) => (true, Some(new_mtime)),
98                    Err(_) => (true, None),
99                },
100            };
101
102            if reload {
103                let new_data = fs::read(&resource.path)?;
104
105                let new_etag = compute_data_etag(&new_data);
106
107                resource.data = Arc::new(new_data);
108
109                resource.etag = new_etag;
110
111                resource.mtime = new_mtime;
112            }
113        }
114
115        Ok(())
116    }
117
118    #[allow(clippy::type_complexity)]
119    /// Get the specific resource.
120    #[inline]
121    pub fn get_resource<S: AsRef<str>>(
122        &mut self,
123        name: S,
124    ) -> Result<(Mime, Arc<Vec<u8>>, &EntityTag<'static>), io::Error> {
125        let name = name.as_ref();
126
127        let resource = self.resources.get_mut(name).ok_or_else(|| {
128            io::Error::new(ErrorKind::NotFound, format!("The name `{}` is not found.", name))
129        })?;
130
131        let metadata = resource.path.metadata()?;
132
133        let (reload, new_mtime) = match resource.mtime {
134            Some(mtime) => match metadata.modified() {
135                Ok(new_mtime) => (new_mtime > mtime, Some(new_mtime)),
136                Err(_) => (true, None),
137            },
138            None => match metadata.modified() {
139                Ok(new_mtime) => (true, Some(new_mtime)),
140                Err(_) => (true, None),
141            },
142        };
143
144        if reload {
145            let new_data = fs::read(&resource.path)?;
146
147            let new_etag = compute_data_etag(&new_data);
148
149            resource.data = Arc::new(new_data);
150
151            resource.etag = new_etag;
152
153            resource.mtime = new_mtime;
154        }
155
156        Ok((resource.mime.clone(), resource.data.clone(), &resource.etag))
157    }
158}
159
160impl Default for FileResources {
161    #[inline]
162    fn default() -> Self {
163        FileResources::new()
164    }
165}