servlin 0.8.0

Modular HTTP server library, threaded handlers and async performance
Documentation
#![allow(clippy::unnecessary_debug_formatting)]
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::fs::remove_file;
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime};

pub struct PrefixFile {
    pub path: PathBuf,
    pub mtime: SystemTime,
    pub len: u64,
}
impl PartialEq for PrefixFile {
    fn eq(&self, other: &Self) -> bool {
        other.mtime.eq(&self.mtime)
    }
}
impl Eq for PrefixFile {}
impl PartialOrd for PrefixFile {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for PrefixFile {
    fn cmp(&self, other: &Self) -> Ordering {
        other.mtime.cmp(&self.mtime)
    }
}

pub struct PrefixFileSet {
    files: BinaryHeap<PrefixFile>,
    len: u64,
}
impl PrefixFileSet {
    /// # Errors
    /// Returns `Err` when it fails to list the directory or get the metadata of files in it.
    ///
    /// # Panics
    /// Panics on platforms that do not support [`std::fs::Metadata::modified`].
    pub fn new(path_prefix: &Path) -> Result<Self, String> {
        let dir = path_prefix
            .parent()
            .ok_or_else(|| format!("path has no parent: {path_prefix:?}"))?;
        let mut files = BinaryHeap::new();
        for dir_entry in dir
            .read_dir()
            .map_err(|e| format!("error reading dir {dir:?}: {e:?}"))?
        {
            let dir_entry = dir_entry.map_err(|e| format!("error reading dir {dir:?}: {e:?}"))?;
            let path = dir_entry.path();
            if path.starts_with(path_prefix) {
                let metadata = dir_entry.metadata().map_err(|e| {
                    format!("error reading metadata of {:?}: {e:?}", dir_entry.path())
                })?;
                if metadata.is_file() {
                    let mtime = metadata.modified().unwrap();
                    let len = metadata.len();
                    files.push(PrefixFile { path, mtime, len });
                }
            }
        }
        let len = files.iter().map(|f| f.len).sum();
        Ok(Self { files, len })
    }

    /// # Errors
    /// Returns `Err` when it fails to delete the file.
    #[allow(clippy::missing_panics_doc)]
    pub fn delete_oldest(&mut self) -> Result<(), String> {
        let file = self.files.peek().unwrap();
        remove_file(&file.path)
            .map_err(|e| format!("error deleting file {:?}: {e:?}", file.path))?;
        self.len -= file.len;
        self.files.pop();
        Ok(())
    }

    /// # Errors
    /// Returns `Err` when it fails to delete a file.
    pub fn delete_older_than(&mut self, now: SystemTime, duration: Duration) -> Result<(), String> {
        let min_mtime = now - duration;
        while let Some(file) = self.files.peek() {
            if file.mtime < min_mtime {
                self.delete_oldest()?;
            } else {
                break;
            }
        }
        Ok(())
    }

    /// # Errors
    /// Returns `Err` when it fails to delete a file.
    pub fn delete_oldest_while_over_max_len(&mut self, max_len: u64) -> Result<(), String> {
        while self.len > max_len {
            self.delete_oldest()?;
        }
        Ok(())
    }

    pub fn push(&mut self, file: PrefixFile) {
        self.files.push(file);
    }
}