use crate::index::DirIndex;
use crate::index::ExtractStale;
use crate::index::IndexedMeta;
use std::fmt;
use std::fs::File;
use std::fs::Metadata;
use std::io;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
use std::result;
mod error;
mod event;
mod fs;
mod options;
use error::IntoScanError;
use fs::open_at;
use fs::read_dir;
use fs::read_dir_at;
use fs::DirEntry;
use fs::FileType;
use fs::ReadDir;
use fs::SameFile;
pub use error::Error;
pub use error::ErrorOperation;
pub use error::FilesystemLoopError;
pub use event::Event;
pub use options::ScanOptions;
macro_rules! ok {
( $expr:expr ) => {
match $expr {
Ok(ok) => ok,
Err(err) => return Some(Err(err)),
}
};
}
#[cfg(feature = "unstable")]
#[allow(non_upper_case_globals)]
const FilesystemLoop: io::ErrorKind = io::ErrorKind::FilesystemLoop;
#[cfg(not(feature = "unstable"))]
#[allow(non_upper_case_globals)]
const FilesystemLoop: io::ErrorKind = io::ErrorKind::Other;
pub type Result<T> = result::Result<T, Error>;
#[must_use = "this is an iterator that must be consumed for the scan to complete"]
pub struct Scanner<'i, T, F> {
opts: ScanOptions,
index: &'i mut DirIndex<T>,
stack: Vec<ReadDir>,
stales: ExtractStale<T>,
generator: F,
}
impl<'i, T, F> Scanner<'i, T, F>
where
F: FnMut(&Path, &mut File, &Metadata) -> io::Result<T>,
{
#[inline]
fn new<P: AsRef<Path>>(
mut opts: ScanOptions,
index: &'i mut DirIndex<T>,
path: P,
generator: F,
) -> io::Result<Self> {
let mut dir = read_dir(path)?;
if let Some(prefix) = opts.add_prefix.take() {
dir.path = prefix;
}
let stack = vec![dir];
let stales = index.extract_stale();
index.mark_stale();
Ok(Self {
opts,
index,
stack,
generator,
stales,
})
}
#[inline]
pub fn index(&self) -> &DirIndex<T> {
&*self.index
}
#[inline]
pub fn index_mut(&mut self) -> &mut DirIndex<T> {
self.index
}
fn enter_dir(&mut self, dir: ReadDir) -> Result<()> {
let loop_start_path = self.stack.iter().find_map(|prev| {
if dir.is_same_as(prev) {
Some(prev.path.clone())
} else {
None
}
});
if let Some(loop_start_path) = loop_start_path {
return Err(Error::from_io_error(
dir.path,
ErrorOperation::OpenDirectory,
io::Error::new(FilesystemLoop, FilesystemLoopError::new(loop_start_path)),
));
}
self.stack.push(dir);
Ok(())
}
fn leave_dir(&mut self) {
self.stack.pop();
}
fn scan_next(&mut self) -> Option<Result<Event>> {
loop {
let cur = self.stack.last_mut()?;
let dir_entry = match cur.next() {
Some(Ok(dir_entry)) => dir_entry,
Some(Err(err)) => return Some(Err(err)),
None => {
self.leave_dir();
continue;
}
};
match dir_entry.file_type {
FileType::File => {
if let Some(res) = self.scan_file(dir_entry) {
return Some(res);
}
}
FileType::Directory => {
if let Err(err) = self.scan_dir(dir_entry) {
return Some(Err(err));
}
}
FileType::Other => (),
}
}
}
fn scan_dir(&mut self, dir_entry: DirEntry) -> Result<()> {
let cur = self
.stack
.last()
.expect("stack cannot be empty when calling `scan_dir`");
let name = dir_entry.file_name();
if name == "." || name == ".." {
return Ok(());
}
let dir = read_dir_at(cur, name)?;
if self.opts.one_file_system && dir.metadata.dev() != cur.metadata.dev() {
return Ok(());
}
self.enter_dir(dir)
}
fn scan_file(&mut self, dir_entry: DirEntry) -> Option<Result<Event>> {
let cur = self
.stack
.last()
.expect("stack cannot be empty when calling `scan_file`");
let name = dir_entry.file_name();
let path = cur.path.join(name);
let mut file = ok!(open_at(cur, name));
let md = match file.metadata() {
Ok(md) => md,
Err(err) => return Some(Err(err.into_scan_error(path, ErrorOperation::ReadMetadata))),
};
if !md.file_type().is_file() {
return None;
}
let size = md.size();
let modified = match md.modified() {
Ok(modified) => modified,
Err(err) => return Some(Err(err.into_scan_error(path, ErrorOperation::ReadMetadata))),
};
if let Some((meta, data)) = self.index.get_with_meta_mut(&path) {
if size == meta.size && modified == meta.modified {
self.index.touch(&path);
return None;
}
(*meta, *data) = ok!(Self::prepare_file(
&mut self.generator,
&path,
&mut file,
&md
));
self.index.touch(&path);
Some(Ok(Event::Modified {
path,
file,
metadata: md,
}))
} else {
let (meta, data) = ok!(Self::prepare_file(
&mut self.generator,
&path,
&mut file,
&md
));
self.index.insert(path.clone(), meta, data);
Some(Ok(Event::Added {
path,
file,
metadata: md,
}))
}
}
fn prepare_file<P: AsRef<Path>>(
generator: &mut F,
path: P,
file: &mut File,
md: &Metadata,
) -> Result<(IndexedMeta, T)> {
let path = path.as_ref();
let meta = md.try_into().map_err(|err: io::Error| {
err.into_scan_error(path.to_path_buf(), ErrorOperation::ReadMetadata)
})?;
let data = (generator)(path, file, md)
.map_err(|err| err.into_scan_error(path.to_path_buf(), ErrorOperation::GenerateData))?;
Ok((meta, data))
}
fn remove_next(&mut self) -> Option<Result<Event>> {
self.stales
.next(self.index)
.map(|(path, _)| Ok(Event::Removed { path }))
}
}
impl<'i, T, F> Iterator for Scanner<'i, T, F>
where
F: FnMut(&Path, &mut File, &Metadata) -> io::Result<T>,
{
type Item = Result<Event>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.scan_next().or_else(|| self.remove_next())
}
}
impl<'i, T, F> fmt::Debug for Scanner<'i, T, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Scanner")
.field("opts", &self.opts)
.field("index", &self.index)
.finish_non_exhaustive()
}
}