dirx 0.1.0

Creates an in-memory index of all the files in a directory tree, and allows efficient scanning of only those files that have been modified since the index got created
Documentation
use indexmap::map::IndexMap;
use std::convert::TryFrom;
use std::fmt;
use std::fs::Metadata;
use std::io::Error;
use std::io::Result;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::path::PathBuf;
use std::time::SystemTime;

mod extract;
mod iter;

pub(crate) use extract::ExtractStale;

pub use iter::IntoIter;
pub use iter::IntoIterWithMeta;
pub use iter::Iter;
pub use iter::IterMut;
pub use iter::IterWithMeta;
pub use iter::IterWithMetaMut;
pub use iter::Paths;

#[derive(Clone)]
pub struct DirIndex<T = ()> {
    pub(crate) entries: IndexMap<PathBuf, IndexedFile<T>>,
}

#[derive(Clone, Debug)]
pub(crate) struct IndexedFile<T> {
    pub(crate) meta: IndexedMeta,
    pub(crate) data: T,
    pub(crate) stale: bool,
}

#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct IndexedMeta {
    pub size: u64,
    pub modified: SystemTime,
}

impl<T> DirIndex<T> {
    #[inline]
    pub fn new() -> Self {
        Self {
            entries: IndexMap::new(),
        }
    }

    #[inline]
    pub fn get<P: AsRef<Path>>(&self, path: P) -> Option<&T> {
        self.entries.get(path.as_ref()).map(|entry| &entry.data)
    }

    #[inline]
    pub fn get_mut<P: AsRef<Path>>(&mut self, path: P) -> Option<&mut T> {
        self.entries
            .get_mut(path.as_ref())
            .map(|entry| &mut entry.data)
    }

    #[inline]
    pub fn get_with_meta<P: AsRef<Path>>(&self, path: P) -> Option<(&IndexedMeta, &T)> {
        self.entries
            .get(path.as_ref())
            .map(|entry| (&entry.meta, &entry.data))
    }

    #[inline]
    pub fn get_with_meta_mut<P: AsRef<Path>>(
        &mut self,
        path: P,
    ) -> Option<(&mut IndexedMeta, &mut T)> {
        self.entries
            .get_mut(path.as_ref())
            .map(|entry| (&mut entry.meta, &mut entry.data))
    }

    #[inline]
    pub fn insert(
        &mut self,
        path: PathBuf,
        meta: IndexedMeta,
        data: T,
    ) -> Option<(IndexedMeta, T)> {
        self.entries
            .insert(
                path,
                IndexedFile {
                    meta,
                    data,
                    stale: false,
                },
            )
            .map(|entry| (entry.meta, entry.data))
    }

    #[inline]
    pub fn remove<P: AsRef<Path>>(&mut self, path: P) -> Option<T> {
        self.entries.remove(path.as_ref()).map(|entry| entry.data)
    }

    #[inline]
    pub fn remove_with_meta<P: AsRef<Path>>(&mut self, path: P) -> Option<(IndexedMeta, T)> {
        self.entries
            .remove(path.as_ref())
            .map(|entry| (entry.meta, entry.data))
    }

    #[inline]
    pub fn len(&self) -> usize {
        self.entries.len()
    }

    #[inline]
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }

    #[inline]
    pub fn iter(&self) -> Iter<'_, T> {
        Iter::new(self)
    }

    #[inline]
    pub fn iter_mut(&mut self) -> IterMut<'_, T> {
        IterMut::new(self)
    }

    #[inline]
    pub fn iter_with_meta(&self) -> IterWithMeta<'_, T> {
        IterWithMeta::new(self)
    }

    #[inline]
    pub fn iter_with_meta_mut(&mut self) -> IterWithMetaMut<'_, T> {
        IterWithMetaMut::new(self)
    }

    #[inline]
    pub fn into_iter_with_meta(self) -> IntoIterWithMeta<T> {
        IntoIterWithMeta::new(self)
    }

    #[inline]
    pub fn paths(&self) -> Paths<'_, T> {
        Paths::new(self)
    }

    #[inline]
    pub fn sort(&mut self) {
        self.entries.sort_keys();
    }

    pub(crate) fn mark_stale(&mut self) {
        for value in self.entries.values_mut() {
            value.stale = true;
        }
    }

    #[inline]
    pub(crate) fn extract_stale(&self) -> ExtractStale<T> {
        ExtractStale::new()
    }

    #[inline]
    pub(crate) fn touch<P: AsRef<Path>>(&mut self, path: P) {
        if let Some(entry) = self.entries.get_mut(path.as_ref()) {
            entry.stale = false;
        }
    }
}

impl<T> Default for DirIndex<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T> IntoIterator for DirIndex<T> {
    type Item = (PathBuf, T);
    type IntoIter = IntoIter<T>;

    fn into_iter(self) -> Self::IntoIter {
        Self::IntoIter::new(self)
    }
}

impl<'i, T> IntoIterator for &'i DirIndex<T> {
    type Item = (&'i Path, &'i T);
    type IntoIter = Iter<'i, T>;

    fn into_iter(self) -> Self::IntoIter {
        Self::IntoIter::new(self)
    }
}

impl<'i, T> IntoIterator for &'i mut DirIndex<T> {
    type Item = (&'i Path, &'i mut T);
    type IntoIter = IterMut<'i, T>;

    fn into_iter(self) -> Self::IntoIter {
        Self::IntoIter::new(self)
    }
}

impl<T> fmt::Debug for DirIndex<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        struct Entries(usize);

        impl fmt::Debug for Entries {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                if self.0 == 1 {
                    write!(f, "{} entry", self.0)
                } else {
                    write!(f, "{} entries", self.0)
                }
            }
        }

        f.debug_struct("DirIndex")
            .field("entries", &Entries(self.len()))
            .finish()
    }
}

impl TryFrom<Metadata> for IndexedMeta {
    type Error = Error;

    fn try_from(md: Metadata) -> Result<Self> {
        Ok(IndexedMeta {
            size: md.size(),
            modified: md.modified()?,
        })
    }
}

impl TryFrom<&Metadata> for IndexedMeta {
    type Error = Error;

    fn try_from(md: &Metadata) -> Result<Self> {
        Ok(IndexedMeta {
            size: md.size(),
            modified: md.modified()?,
        })
    }
}