use log::{debug, info};
#[cfg(test)]
use mockall::{automock, predicate::*};
use crate::util::{osstr_to_vec, vec_to_osstr, Result};
use std::{
fs::File,
io::{BufRead, BufReader},
path::{Path, PathBuf},
};
#[derive(Debug, Clone)]
pub struct BackupInformation {
pub num: u32,
pub backup_type: String,
pub start_time: u64,
pub end_time: u64,
pub n_files: u32,
pub size: u64,
pub n_files_exist: u32,
pub size_exist: u64,
pub n_files_new: u32,
pub size_new: u64,
pub xfer_errs: u32,
pub xfer_bad_file: u32,
pub xfer_bad_share: u32,
pub tar_errs: u32,
pub compress: u32,
pub size_exist_comp: u64,
pub size_new_comp: u64,
pub no_fill: u32,
pub fill_from_num: i32,
pub mangle: u64,
pub xfer_method: String,
pub level: u32,
pub charset: String,
pub version: String,
pub inode_last: u64,
}
#[cfg_attr(test, automock)]
pub trait HostsTrait: Send + Sync {
fn list_hosts(&self) -> Result<Vec<Vec<u8>>>;
fn list_backups(&self, hostname: &[u8]) -> Result<Vec<BackupInformation>>;
fn list_backups_to_fill(&self, hostname: &[u8], backup_number: u32) -> Vec<BackupInformation>;
}
pub struct Hosts {
topdir: PathBuf,
}
impl Hosts {
#[must_use]
pub fn new<P: AsRef<Path>>(topdir: P) -> Self {
Hosts {
topdir: topdir.as_ref().to_path_buf(),
}
}
}
impl HostsTrait for Hosts {
fn list_hosts(&self) -> Result<Vec<Vec<u8>>> {
info!("Listing hosts in {}", self.topdir.display());
let pc_dir = self.topdir.join("pc");
let mut hosts = Vec::new();
for entry in std::fs::read_dir(pc_dir)? {
match entry {
Ok(entry) => {
let path = entry.path();
if path.is_dir() {
let host = osstr_to_vec(path.file_name().unwrap_or_default());
hosts.push(host);
}
}
Err(err) => {
eprintln!("Error reading pc directory: {err}");
}
}
}
debug!("Found {} hosts", hosts.len());
Ok(hosts)
}
fn list_backups(&self, hostname: &[u8]) -> Result<Vec<BackupInformation>> {
let hostname_str = vec_to_osstr(hostname);
info!("Listing backups for {}", hostname_str.to_string_lossy());
let mut backups = Vec::new();
let path = self.topdir.join("pc").join(hostname_str).join("backups");
let file = File::open(path)?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line.unwrap();
let fields: Vec<&str> = line.split('\t').collect();
let backup = BackupInformation {
num: fields[0].parse().unwrap_or_default(),
backup_type: fields[1].to_string(),
start_time: fields[2].parse().unwrap_or_default(),
end_time: fields[3].parse().unwrap_or_default(),
n_files: fields[4].parse().unwrap_or_default(),
size: fields[5].parse().unwrap_or_default(),
n_files_exist: fields[6].parse().unwrap_or_default(),
size_exist: fields[7].parse().unwrap_or_default(),
n_files_new: fields[8].parse().unwrap_or_default(),
size_new: fields[9].parse().unwrap_or_default(),
xfer_errs: fields[10].parse().unwrap_or_default(),
xfer_bad_file: fields[11].parse().unwrap_or_default(),
xfer_bad_share: fields[12].parse().unwrap_or_default(),
tar_errs: fields[13].parse().unwrap_or_default(),
compress: fields[14].parse().unwrap_or_default(),
size_exist_comp: fields[15].parse().unwrap_or_default(),
size_new_comp: fields[16].parse().unwrap_or_default(),
no_fill: fields[17].parse().unwrap_or_default(),
fill_from_num: fields[18].parse().unwrap_or(-1),
mangle: fields[19].parse().unwrap_or_default(),
xfer_method: fields[20].to_string(),
level: fields[21].parse().unwrap_or_default(),
charset: fields[22].to_string(),
version: fields[23].to_string(),
inode_last: fields[24].parse().unwrap_or_default(),
};
backups.push(backup);
}
debug!("Found {} backups", backups.len());
Ok(backups)
}
fn list_backups_to_fill(&self, hostname: &[u8], backup_number: u32) -> Vec<BackupInformation> {
let backups = self.list_backups(hostname).unwrap_or_else(|_| Vec::new());
let backups = backups.iter().filter(|backup| backup.num >= backup_number);
let mut backups_to_search: Vec<crate::hosts::BackupInformation> = Vec::new();
for backup in backups {
backups_to_search.push(backup.clone());
if backup.no_fill > 0 {
continue;
}
break;
}
backups_to_search.reverse();
backups_to_search
}
}