148 lines
4.2 KiB
Rust
148 lines
4.2 KiB
Rust
use chrono::{Local, Utc};
|
|
use std::collections::HashSet;
|
|
use std::ffi::OsString;
|
|
use std::fs::DirEntry;
|
|
use std::path::{Path, PathBuf};
|
|
use std::{fs, io};
|
|
|
|
pub struct ReadDirRecursive {
|
|
ignored: HashSet<OsString>,
|
|
read_dir: fs::ReadDir,
|
|
dir_stack: Vec<PathBuf>,
|
|
files_only: bool,
|
|
}
|
|
|
|
impl ReadDirRecursive {
|
|
/// Start the iterator for a new directory
|
|
pub fn start<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
|
let path = path.as_ref();
|
|
let read_dir = path.read_dir()?;
|
|
|
|
Ok(ReadDirRecursive {
|
|
ignored: HashSet::new(),
|
|
read_dir,
|
|
dir_stack: Vec::new(),
|
|
files_only: false,
|
|
})
|
|
}
|
|
|
|
pub fn ignored<S: Into<OsString>>(mut self, s: S) -> Self {
|
|
self.ignored.insert(s.into());
|
|
|
|
self
|
|
}
|
|
|
|
pub fn files(mut self) -> Self {
|
|
self.files_only = true;
|
|
|
|
self
|
|
}
|
|
|
|
/// Tries to populate the `read_dir` field with a new `ReadDir` instance to consume.
|
|
fn next_read_dir(&mut self) -> io::Result<bool> {
|
|
if let Some(path) = self.dir_stack.pop() {
|
|
self.read_dir = path.read_dir()?;
|
|
|
|
Ok(true)
|
|
} else {
|
|
Ok(false)
|
|
}
|
|
}
|
|
|
|
/// Convenience method to add a new directory to the stack.
|
|
fn push_entry(&mut self, entry: &io::Result<DirEntry>) {
|
|
if let Ok(entry) = entry {
|
|
if entry.path().is_dir() {
|
|
self.dir_stack.push(entry.path());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Determine whether an entry should be returned by the iterator.
|
|
fn should_return(&self, entry: &io::Result<DirEntry>) -> bool {
|
|
if let Ok(entry) = entry {
|
|
let mut res = !self.ignored.contains(&entry.file_name());
|
|
|
|
// Please just let me combine these already
|
|
if self.files_only {
|
|
if let Ok(file_type) = entry.file_type() {
|
|
res = res && file_type.is_file();
|
|
}
|
|
// We couldn't determine if it's a file, so we don't return it
|
|
else {
|
|
res = false;
|
|
}
|
|
}
|
|
|
|
res
|
|
} else {
|
|
true
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Iterator for ReadDirRecursive {
|
|
type Item = io::Result<DirEntry>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
loop {
|
|
// First, we try to consume the current directory's items
|
|
while let Some(entry) = self.read_dir.next() {
|
|
self.push_entry(&entry);
|
|
|
|
if self.should_return(&entry) {
|
|
return Some(entry);
|
|
}
|
|
}
|
|
|
|
// If we get an error while setting up a new directory, we return this, otherwise we
|
|
// keep trying to consume the directories
|
|
match self.next_read_dir() {
|
|
Ok(true) => (),
|
|
// There's no more directories to traverse, so the iterator is done
|
|
Ok(false) => return None,
|
|
Err(e) => return Some(Err(e)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub trait PathExt {
|
|
/// Confirm whether the file has not been modified since the given timestamp.
|
|
///
|
|
/// This function will only return true if it can determine with certainty that the file hasn't
|
|
/// been modified.
|
|
///
|
|
/// # Args
|
|
///
|
|
/// * `timestamp` - Timestamp to compare modified time with
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// True if the file has not been modified for sure, false otherwise.
|
|
fn not_modified_since(&self, timestamp: chrono::DateTime<Utc>) -> bool;
|
|
|
|
/// An extension of the `read_dir` command that runs through the entire underlying directory
|
|
/// structure using breadth-first search
|
|
fn read_dir_recursive(&self) -> io::Result<ReadDirRecursive>;
|
|
}
|
|
|
|
impl PathExt for Path {
|
|
fn not_modified_since(&self, timestamp: chrono::DateTime<Utc>) -> bool {
|
|
if let Ok(metadata) = self.metadata() {
|
|
if let Ok(last_modified) = metadata.modified() {
|
|
let t: chrono::DateTime<Utc> = last_modified.into();
|
|
let t = t.with_timezone(&Local);
|
|
|
|
return t < timestamp;
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn read_dir_recursive(&self) -> io::Result<ReadDirRecursive> {
|
|
ReadDirRecursive::start(self)
|
|
}
|
|
}
|