use std::fs;
use std::io::Write;
use std::ops::AddAssign;
use std::path::Path;
use crate::errors::ResultExt;
use crate::lock::DirLockOptions;
use crate::lock::ScopedDirLock;
use crate::lock::READER_LOCK_OPTS;
pub trait Repair<T> {
fn repair(path: impl AsRef<Path>) -> crate::Result<String>;
}
pub trait OpenWithRepair {
type Output;
fn open_with_repair(&self, path: impl AsRef<Path>) -> crate::Result<Self::Output>
where
Self: Sized;
}
pub trait DefaultOpenOptions<T> {
fn default_open_options() -> T;
}
pub trait OpenOptionsRepair {
fn open_options_repair(&self, path: impl AsRef<Path>) -> crate::Result<String>;
}
pub trait OpenOptionsOutput {
type Output;
fn open_path(&self, path: &Path) -> crate::Result<Self::Output>;
}
pub(crate) struct RepairMessage {
output: String,
additional_outputs: Vec<Box<dyn Write>>,
}
impl RepairMessage {
pub(crate) fn new(dir: &Path) -> Self {
let mut additional_outputs = Vec::new();
let path = dir.join("repair.log");
let mut need_truncate = false;
if let Ok(meta) = fs::metadata(&path) {
const REPAIR_LOG_SIZE_LIMIT: u64 = 1 << 20;
if meta.len() > REPAIR_LOG_SIZE_LIMIT {
need_truncate = true;
}
}
let mut opts = fs::OpenOptions::new();
opts.write(true).create(true);
if !need_truncate {
opts.append(true);
}
if let Ok(mut file) = opts.open(path) {
if need_truncate {
let _ = file.write_all(b"# This file was truncated\n\n");
}
if let Ok(duration) = std::time::UNIX_EPOCH.elapsed() {
let msg = format!("date -d @{}\n", duration.as_secs());
let _ = file.write_all(msg.as_bytes());
}
additional_outputs.push(Box::new(file) as Box<dyn Write>);
}
Self {
output: String::new(),
additional_outputs,
}
}
pub(crate) fn as_str(&self) -> &str {
self.output.as_str()
}
pub(crate) fn into_string(mut self) -> String {
for out in self.additional_outputs.iter_mut() {
let _ = out.write_all(b"\n");
let _ = out.flush();
}
self.output
}
}
impl AddAssign<&str> for RepairMessage {
fn add_assign(&mut self, rhs: &str) {
self.output += rhs;
for out in self.additional_outputs.iter_mut() {
let _ = out.write_all(rhs.as_bytes());
}
}
}
impl<T: DefaultOpenOptions<O>, O: OpenOptionsRepair> Repair<O> for T {
fn repair(path: impl AsRef<Path>) -> crate::Result<String> {
T::default_open_options().open_options_repair(path.as_ref())
}
}
pub(crate) fn open_with_repair<T>(opts: &T, path: &Path) -> crate::Result<T::Output>
where
T: OpenOptionsOutput + OpenOptionsRepair,
{
match opts.open_path(path) {
Ok(v) => Ok(v),
Err(e) if e.is_corruption() => {
static CHECK_READER_LOCK_OPTS: DirLockOptions = DirLockOptions {
exclusive: true,
non_blocking: true,
..READER_LOCK_OPTS
};
let mut msg = RepairMessage::new(path);
msg += &format!("Corruption detected: {:?}.\n", &e);
let lock = match ScopedDirLock::new_with_options(path, &CHECK_READER_LOCK_OPTS) {
Ok(lock) => lock,
Err(lock_err) => {
msg += &"Auto-repair is skipped due to active readers.\n";
let _ = msg.into_string();
return Err(e.source(lock_err))
.context(|| format!("in open_with_repair({:?})", path))
.context("repair is skipped due to active readers");
}
};
drop(lock);
msg += &"Starting auto repair.\n";
let _ = msg.into_string();
let repair_message = opts
.open_options_repair(path)
.context(|| format!("in open_with_repair({:?}), attempt to repair", path))?;
tracing::info!("Auto-repair {:?} Result:\n{}", path, &repair_message);
return opts.open_path(path).context(|| {
format!(
"in open_with_repair({:?}), after repair ({})",
path, repair_message
)
});
}
Err(e) => Err(e),
}
}
impl<T> OpenWithRepair for T
where
T: OpenOptionsOutput + OpenOptionsRepair,
{
type Output = T::Output;
fn open_with_repair(&self, path: impl AsRef<Path>) -> crate::Result<Self::Output>
where
Self: Sized,
{
let path = path.as_ref();
open_with_repair(self, path)
}
}