rocket-include-static-resources 0.10.5

This is a crate which provides macros `static_resources_initializer!` and `static_response_handler!` to statically include files from your Rust project and make them be the HTTP response sources quickly.
Documentation
use std::{
    collections::HashMap,
    fs,
    io::{self, ErrorKind},
    path::PathBuf,
    sync::Arc,
    time::SystemTime,
};

use mime::Mime;

use crate::{functions::compute_data_etag, mime, EntityTag};

#[derive(Debug)]
struct Resource {
    path:  PathBuf,
    // mime could be an atom `Mime`, so just clone it
    mime:  Mime,
    data:  Arc<Vec<u8>>,
    etag:  EntityTag<'static>,
    mtime: Option<SystemTime>,
}

#[derive(Debug)]
/// Reloadable file resources.
pub struct FileResources {
    resources: HashMap<&'static str, Resource>,
}

impl FileResources {
    /// Create an instance of `FileResources`.
    #[inline]
    pub fn new() -> FileResources {
        FileResources {
            resources: HashMap::new()
        }
    }

    /// Register a resource from a path and it can be reloaded automatically.
    #[inline]
    pub fn register_resource_file<P: Into<PathBuf>>(
        &mut self,
        name: &'static str,
        file_path: P,
    ) -> Result<(), io::Error> {
        let path = file_path.into();

        let metadata = path.metadata()?;

        let mtime = metadata.modified().ok();

        let data = fs::read(&path)?;

        let etag = compute_data_etag(&data);

        let mime = match path.extension() {
            Some(extension) => match extension.to_str() {
                Some(extension) => mime_guess::from_ext(extension).first_or_octet_stream(),
                None => mime::APPLICATION_OCTET_STREAM,
            },
            None => mime::APPLICATION_OCTET_STREAM,
        };

        let resource = Resource {
            path,
            mime,
            data: Arc::new(data),
            etag,
            mtime,
        };

        self.resources.insert(name, resource);

        Ok(())
    }

    /// Unregister a resource from a file by a name.
    #[inline]
    pub fn unregister_resource_file<S: AsRef<str>>(&mut self, name: S) -> Option<PathBuf> {
        let name = name.as_ref();

        self.resources.remove(name).map(|resource| resource.path)
    }

    /// Reload resources if needed.
    #[inline]
    pub fn reload_if_needed(&mut self) -> Result<(), io::Error> {
        for resource in self.resources.values_mut() {
            let metadata = resource.path.metadata()?;

            let (reload, new_mtime) = match resource.mtime {
                Some(mtime) => match metadata.modified() {
                    Ok(new_mtime) => (new_mtime > mtime, Some(new_mtime)),
                    Err(_) => (true, None),
                },
                None => match metadata.modified() {
                    Ok(new_mtime) => (true, Some(new_mtime)),
                    Err(_) => (true, None),
                },
            };

            if reload {
                let new_data = fs::read(&resource.path)?;

                let new_etag = compute_data_etag(&new_data);

                resource.data = Arc::new(new_data);

                resource.etag = new_etag;

                resource.mtime = new_mtime;
            }
        }

        Ok(())
    }

    #[allow(clippy::type_complexity)]
    /// Get the specific resource.
    #[inline]
    pub fn get_resource<S: AsRef<str>>(
        &mut self,
        name: S,
    ) -> Result<(Mime, Arc<Vec<u8>>, &EntityTag<'static>), io::Error> {
        let name = name.as_ref();

        let resource = self.resources.get_mut(name).ok_or_else(|| {
            io::Error::new(ErrorKind::NotFound, format!("The name `{}` is not found.", name))
        })?;

        let metadata = resource.path.metadata()?;

        let (reload, new_mtime) = match resource.mtime {
            Some(mtime) => match metadata.modified() {
                Ok(new_mtime) => (new_mtime > mtime, Some(new_mtime)),
                Err(_) => (true, None),
            },
            None => match metadata.modified() {
                Ok(new_mtime) => (true, Some(new_mtime)),
                Err(_) => (true, None),
            },
        };

        if reload {
            let new_data = fs::read(&resource.path)?;

            let new_etag = compute_data_etag(&new_data);

            resource.data = Arc::new(new_data);

            resource.etag = new_etag;

            resource.mtime = new_mtime;
        }

        Ok((resource.mime.clone(), resource.data.clone(), &resource.etag))
    }
}

impl Default for FileResources {
    #[inline]
    fn default() -> Self {
        FileResources::new()
    }
}