#[macro_use]
extern crate clap;
extern crate chrono;
extern crate crc32fast;
#[cfg(feature = "mmap")]
extern crate memmap;
use std::fs::File;
use std::fmt::Debug;
use std::io::Read;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::io::Error as IoError;
use std::iter::IntoIterator;
use std::path::Path;
use chrono::DateTime;
use chrono::Datelike;
use chrono::Local;
use chrono::Timelike;
use getset::Getters;
use getset::MutGetters;
use getset::Setters;
use crc32fast::Hasher;
const DEFAULT_BUFFER_SIZE: usize = 65536;
pub type Crc32 = u32;
fn compute_crc32(file: &Path) -> Result<Crc32, IoError> {
if file.is_dir() {
return Err(std::io::Error::from_raw_os_error(21));
}
File::open(&file).and_then(compute_crc32_inner)
}
#[cfg(feature = "mmap")]
fn compute_crc32_inner(mut file: File) -> Result<Crc32, IoError> {
let mut hasher = Hasher::new();
let mmap = unsafe { memmap::MmapOptions::new().map(&file)? };
hasher.update(&mmap[..]);
Ok(hasher.finalize())
}
#[cfg(not(feature = "mmap"))]
fn compute_crc32_inner(mut file: File) -> Result<Crc32, IoError> {
let mut hasher = Hasher::new();
let mut buffer = [0; DEFAULT_BUFFER_SIZE];
loop {
let n = file.read(&mut buffer)?;
if n == 0 {
break;
}
hasher.update(&mut buffer[..n]);
}
Ok(hasher.finalize())
}
pub trait WriteDebug: Debug + Write {}
impl<F: Debug + Write> WriteDebug for F {}
#[derive(Debug)]
pub enum Output {
Devnull,
Stdout(std::io::Stdout),
Stderr(std::io::Stderr),
}
impl Output {
pub fn devnull() -> Self {
Self::Devnull
}
pub fn stdout() -> Self {
Output::Stdout(std::io::stdout())
}
pub fn stderr() -> Self {
Output::Stderr(std::io::stderr())
}
}
impl Clone for Output {
fn clone(&self) -> Self {
use self::Output::*;
match self {
Devnull => Self::devnull(),
Stdout(_) => Self::stdout(),
Stderr(_) => Self::stderr(),
}
}
}
impl Default for Output {
fn default() -> Self {
Self::stderr()
}
}
impl Write for Output {
fn write(&mut self, buf: &[u8]) -> Result<usize, IoError> {
use self::Output::*;
match self {
Devnull => Ok(buf.len()),
Stdout(out) => out.write(buf),
Stderr(err) => err.write(buf),
}
}
fn flush(&mut self) -> Result<(), IoError> {
use self::Output::*;
match self {
Devnull => Ok(()),
Stdout(out) => out.flush(),
Stderr(err) => err.flush(),
}
}
}
#[derive(Clone, Debug, Getters, MutGetters, Setters)]
pub struct Config {
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
stdout: Output,
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
stderr: Output,
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
quiet: bool,
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
print_basename: bool,
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
ignore_case: bool,
#[get = "pub"]
#[get_mut = "pub"]
#[set = "pub"]
force_slashes: bool,
}
impl Default for Config {
fn default() -> Self {
Self::new()
}
}
impl Config {
pub fn new() -> Self {
Config {
stdout: Output::stdout(),
stderr: Output::stderr(),
quiet: false,
print_basename: false,
ignore_case: false,
force_slashes: false,
}
}
pub fn with_stdout(mut self, stdout: Output) -> Self {
self.stdout = stdout;
self
}
pub fn with_stderr(mut self, stderr: Output) -> Self {
self.stderr = stderr;
self
}
pub fn with_print_basenamet(mut self, print_basename: bool) -> Self {
self.print_basename = print_basename;
self
}
pub fn extract_stdout(self) -> Output {
self.stdout
}
}
pub fn newsfv<'a, F, C>(files: F, config: C) -> Result<bool, IoError>
where
F: IntoIterator<Item = &'a Path>,
C: Into<Option<Config>>
{
let mut cfg: Config = config.into().unwrap_or_default();
let files: Vec<&Path> = files.into_iter().collect();
let now: DateTime<Local> = Local::now();
write!(
cfg.stdout,
"; Generated by cksfv.rs v{} on {:04}-{:02}-{:02} at {:02}:{:02}.{:02}\n",
crate_version!(),
now.year(),
now.month(),
now.day(),
now.hour(),
now.minute(),
now.second(),
)?;
write!(cfg.stdout, "; Project web site: {}\n", env!("CARGO_PKG_REPOSITORY"))?;
write!(cfg.stdout, ";\n")?;
for file in files.iter().filter(|p| p.is_file()) {
if let Ok(metadata) = std::fs::metadata(file) {
let mtime: DateTime<Local> = From::from(metadata.modified().unwrap());
write!(
cfg.stdout,
"; {:>12} {:02}:{:02}.{:02} {:04}-{:02}-{:02} {}\n",
metadata.len(),
mtime.hour(),
mtime.minute(),
mtime.second(),
mtime.year(),
mtime.month(),
mtime.day(),
file.display()
)?;
}
}
let mut success = true;
for file in &files {
match compute_crc32(file) {
Ok(crc32) if cfg.print_basename => {
let name = file.file_name().unwrap();
write!(cfg.stdout, "{} {:08X}\n", AsRef::<Path>::as_ref(&name).display(), crc32)?
}
Ok(crc32) => write!(cfg.stdout, "{} {:08X}\n", file.display(), crc32)?,
Err(err) => {
success = false;
write!(cfg.stderr, "cksfv: {}: {}\n", file.display(), err)?
}
}
}
Ok(success)
}
pub fn cksfv<'a, F, C>(sfv: &Path, workdir: Option<&Path>, config: C, files: Option<F>) -> Result<bool, IoError>
where
F: IntoIterator<Item = &'a Path>,
C: Into<Option<Config>>
{
let mut cfg: Config = config.into().unwrap_or_default();
let workdir = workdir.unwrap_or_else(|| Path::new("."));
write!(
cfg.stderr,
"--( Verifying: {} ){}\n",
sfv.display(),
"-".repeat(63 - sfv.display().to_string().len())
)?;
let listing = match File::open(sfv) {
Ok(file) => BufReader::new(file),
Err(err) => {
write!(cfg.stderr, "cksfv: {}: {}\n", sfv.display(), err)?;
return Ok(false);
}
};
let mut success = true;
let mut lines = listing.lines();
if let Some(_files) = files {
unimplemented!("TODO: checking with file arguments");
} else {
while let Some(Ok(line)) = lines.next() {
if !line.starts_with(';') {
let i = line.trim_end().rfind(' ').unwrap();
let filename = Path::new(&line[..i]);
let crc32_old = u32::from_str_radix(&line[i+1..], 16).unwrap();
match compute_crc32(&workdir.join(filename)) {
Ok(crc32_new) if crc32_new != crc32_old => {
success = false;
if cfg.quiet {
write!(cfg.stdout, "{:<50}different CRC\n", filename.display())?;
} else {
write!(cfg.stdout, "cksfv: {}: Has a different CRC\n", filename.display())?;
}
}
Err(err) if cfg.quiet => {
write!(cfg.stdout, "cksfv: {}: {}\n", filename.display(), err)?;
}
Err(err) => {
write!(cfg.stdout, "{:<50}{:<30}\n", filename.display(), err)?;
success = false
}
Ok(_) if !cfg.quiet => {
write!(cfg.stdout, "{:<50}OK\n", filename.display())?;
}
Ok(_) => (),
}
}
}
}
write!(cfg.stderr, "{}\n", "-".repeat(80))?;
if !cfg.quiet {
if success {
write!(cfg.stdout, "Everything OK\n")?;
} else {
write!(cfg.stdout, "Errors Occured\n")?;
}
}
Ok(success)
}