use std::{
fs::{create_dir_all, File},
io::{Read, Write},
path::PathBuf,
time::{SystemTime, UNIX_EPOCH},
};
use serde::{Deserialize, Serialize};
use tracing::info;
use uuid::Uuid;
use crate::{
config::Config,
error::{Error, Result},
packages::{Manager, PackageList},
pathinfo::PathInfo,
};
pub type Id = String;
#[derive(Debug, Serialize, Deserialize)]
pub struct Backup {
pub id: String,
pub timestamp: u64,
pub packages: PackageList,
pub files: Vec<PathInfo>,
pub device: String,
}
impl Backup {
pub fn create(config: &Config, manager: Option<Manager>) -> Result<Self> {
let mut files: Vec<PathInfo> = Vec::new();
for dir in &config.directories {
files.push(PathInfo::from_path(config, dir)?);
}
Ok(Self {
// TODO: UUID not really needed, maybe a shorter hash
id: Uuid::new_v4().to_string(),
timestamp: Self::get_timestamp(),
packages: Manager::get_manager(manager)?.get_installed()?,
files,
device: config.device.clone(),
})
}
pub fn save(&self, config: &Config) -> Result<()> {
info!("Save Backup {:?}", self.get_location(config));
self.get_location(config).append_to_root(config)?;
let backup_root = self.get_location(config).get_absolute_dir(config);
create_dir_all(&backup_root).unwrap();
let path = format!("{backup_root}/index.json");
let mut f = File::create(path).unwrap();
f.write_all(&serde_json::to_vec(self).unwrap()).unwrap();
for path in &self.files {
path.save(&backup_root)?;
}
Ok(())
}
pub fn get_last(config: &Config) -> Result<Option<Self>> {
let backup_index_root = format!("{}/index.json", config.root);
info!(?backup_index_root, "backup index location:");
let list: Vec<IndexEntry> = match Self::get_json_content(&backup_index_root) {
Ok(list) => list,
Err(err) => {
if err.to_string() == "io: No such file or directory (os error 2)" {
return Ok(None);
};
return Err(err);
}
};
info!(?list, "backup index:");
Ok(Some(Self::from_index(
config,
&list.last().ok_or(Error::BackupNotFound)?.id,
)?))
}
pub fn from_index(config: &Config, id: &Id) -> Result<Self> {
let backup_index_root = format!("{}/index.json", config.root);
let list: Vec<IndexEntry> = Self::get_json_content(&backup_index_root)?;
let index_loc = list
.iter()
.find(|bl| &bl.id == id)
.ok_or(Error::BackupNotFound)?
.rel_location
.clone();
let path = format!("{}/{index_loc}/index.json", config.root);
let index_file: Self = Self::get_json_content(&path)?;
Ok(index_file)
}
pub fn get_location(&self, config: &Config) -> IndexEntry {
let rel_location = format!("{}_{}", config.device, self.timestamp);
IndexEntry {
id: self.id.to_string(),
rel_location,
}
}
pub fn get_absolute_file_location(&self, config: &Config, rel_location: &str) -> String {
let loc = self.get_location(config).get_absolute_dir(config);
format!("{loc}/{rel_location}")
}
pub fn restore(&self) {
todo!()
}
fn get_json_content<T: for<'a> Deserialize<'a>>(path: &str) -> Result<T> {
let mut file = File::open(path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(serde_json::from_str(&content)?)
}
fn get_timestamp() -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexEntry {
id: Id,
rel_location: String,
}
impl IndexEntry {
pub fn get_absolute_dir(&self, config: &Config) -> String {
format!("{}/{}", config.root, self.rel_location)
}
pub fn append_to_root(&self, config: &Config) -> Result<()> {
let backup_index_root = format!("{}/index.json", config.root);
let path = PathBuf::from(&backup_index_root);
if path.exists() {
let mut f = File::open(&path)?;
let mut content = String::new();
f.read_to_string(&mut content)?;
let mut loc: Vec<IndexEntry> = serde_json::from_str(&content)?;
let mut f = File::create(path)?;
loc.push(self.clone());
f.write_all(&serde_json::to_vec(&loc)?)?;
} else {
create_dir_all(&config.root).unwrap();
let mut f = File::create(backup_index_root)?;
f.write_all(&serde_json::to_vec(&vec![self])?)?;
};
Ok(())
}
}