use chrono::Local;
use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
use std::path::{Path, PathBuf};
use std::{fs, io};
use crate::util;
pub const RECORD: &str = ".record";
#[derive(Debug)]
pub struct RecordItem<'a> {
_time: &'a str,
pub orig: &'a Path,
pub dest: &'a Path,
}
impl RecordItem<'_> {
pub fn new(line: &str) -> RecordItem {
let mut tokens = line.split('\t');
let time: &str = tokens.next().expect("Bad format: column A");
let orig: &str = tokens.next().expect("Bad format: column B");
let dest: &str = tokens.next().expect("Bad format: column C");
RecordItem {
_time: time,
orig: Path::new(orig),
dest: Path::new(dest),
}
}
}
#[derive(Debug)]
pub struct Record {
path: PathBuf,
}
impl Record {
pub fn new(graveyard: &Path) -> Record {
let path = graveyard.join(RECORD);
if !path.exists() {
let mut record_file = fs::OpenOptions::new()
.truncate(true)
.create(true)
.write(true)
.open(&path)
.expect("Failed to open record file");
record_file
.write_all(b"Time\tOriginal\tDestination\n")
.expect("Failed to write header to record file");
}
Record { path }
}
pub fn open(&self) -> Result<fs::File, Error> {
fs::File::open(&self.path)
.map_err(|_| Error::new(ErrorKind::NotFound, "Failed to read record!"))
}
pub fn get_last_bury(&self) -> Result<PathBuf, Error> {
let record_file = self.open()?;
let contents = {
let path_f = PathBuf::from(&self.path);
fs::read_to_string(path_f)?
};
let mut graves_to_exhume = Vec::new();
let mut lines = contents.lines();
lines.next();
for entry in lines.rev().map(RecordItem::new) {
if util::symlink_exists(entry.dest) {
if !graves_to_exhume.is_empty() {
self.delete_lines(record_file, &graves_to_exhume)?;
}
return Ok(PathBuf::from(entry.dest));
} else {
graves_to_exhume.push(PathBuf::from(entry.dest));
}
}
if !graves_to_exhume.is_empty() {
self.delete_lines(record_file, &graves_to_exhume)?;
}
Err(Error::new(ErrorKind::NotFound, "No files in graveyard"))
}
fn delete_lines(&self, record_file: fs::File, graves: &[PathBuf]) -> Result<(), Error> {
let record_path = &self.path;
let mut reader = BufReader::new(record_file).lines();
reader.next();
let lines_to_write: Vec<String> = reader
.map_while(Result::ok)
.filter(|line| !graves.iter().any(|y| y == RecordItem::new(line).dest))
.collect();
let mut mutable_record_file = fs::File::create(record_path)?;
for line in lines_to_write {
writeln!(mutable_record_file, "{}", line)?;
}
Ok(())
}
pub fn log_exhumed_graves(&self, graves_to_exhume: &[PathBuf]) -> Result<(), Error> {
let record_file = self.open()?;
self.delete_lines(record_file, graves_to_exhume)
.map_err(|e| {
Error::new(
e.kind(),
format!("Failed to remove unburied files from record: {}", e),
)
})
}
pub fn lines_of_graves<'a>(
&'a self,
graves: &'a [PathBuf],
) -> impl Iterator<Item = String> + 'a {
let record_file = self.open().unwrap();
let mut reader = BufReader::new(record_file).lines();
reader.next();
reader
.map_while(Result::ok)
.filter(move |line| graves.iter().any(|y| y == RecordItem::new(line).dest))
}
pub fn seance<'a>(
&'a self,
gravepath: &'a PathBuf,
) -> io::Result<impl Iterator<Item = PathBuf> + 'a> {
let record_file = self.open()?;
let mut reader = BufReader::new(record_file).lines();
reader.next();
Ok(reader
.map_while(Result::ok)
.map(|line| PathBuf::from(RecordItem::new(&line).dest))
.filter(move |d| d.starts_with(gravepath)))
}
pub fn write_log(&self, source: impl AsRef<Path>, dest: impl AsRef<Path>) -> io::Result<()> {
let (source, dest) = (source.as_ref(), dest.as_ref());
let mut record_file = fs::OpenOptions::new()
.create(true)
.append(true)
.open(&self.path)?;
writeln!(
record_file,
"{}\t{}\t{}",
Local::now().to_rfc3339(),
source.display(),
dest.display()
)
.map_err(|e| {
Error::new(
e.kind(),
format!("Failed to write record at {}", &self.path.display()),
)
})?;
Ok(())
}
}