use std::fs::File;
use std::io::{ErrorKind, Write};
use std::path::PathBuf;
use serde::Serialize;
use crate::field::BinrootsField;
use crate::fileserializer::{FileOperationHint, FileSerializer, SerializerError};
#[derive(Debug, Clone)]
pub enum RootType {
InMemory,
Persistent,
}
#[derive(Debug)]
pub enum SaveError {
CreateDirectoryError {
path: PathBuf,
kind: std::io::ErrorKind,
},
CreateFileError {
path: PathBuf,
kind: std::io::ErrorKind,
},
DeleteFileError {
path: PathBuf,
kind: std::io::ErrorKind,
},
WriteFileError {
path: PathBuf,
contents: Vec<u8>,
kind: std::io::ErrorKind,
},
SerializeError(SerializerError),
RootLocationError(RootLocationError),
}
impl std::fmt::Display for SaveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::CreateDirectoryError { path, kind } =>
format!("Failed to create directory at {path:?} during save; {kind}"),
Self::CreateFileError { path, kind } =>
format!("Failed to create (open) file at {path:?} during save; {kind}"),
Self::DeleteFileError { path, kind } =>
format!("Failed to delete a file at {path:?} during save; {kind}"),
Self::WriteFileError { path, kind, .. } =>
format!("Faile to write to {path:?} during save; {kind}"),
Self::SerializeError(e) => format!("Failed to serialize during save: {e}"),
Self::RootLocationError(e) => format!("{e}"),
}
)
}
}
impl std::error::Error for SaveError {}
pub trait Save {
fn save<P: Into<PathBuf>>(&self, root: P, root_type: RootType) -> Result<(), SaveError>;
}
impl<T: Serialize> Save for T {
fn save<P: Into<PathBuf>>(&self, root: P, root_type: RootType) -> Result<(), SaveError> {
let mut serializer = FileSerializer::default();
self.serialize(&mut serializer)
.map_err(|e| SaveError::SerializeError(e))?;
save_root(serializer, root.into(), root_type)
}
}
impl<const N: &'static str, T: Serialize> BinrootsField<N, T> {
pub fn save<P: Into<PathBuf>>(&self, root: P, root_type: RootType) -> Result<(), SaveError> {
let mut serializer = FileSerializer::default();
serializer.root = format!("/{N}");
self.value
.serialize(&mut serializer)
.map_err(|e| SaveError::SerializeError(e))?;
save_root(serializer, root.into(), root_type)
}
}
pub(crate) fn save_root(
serializer: FileSerializer,
root: PathBuf,
root_type: RootType,
) -> Result<(), SaveError> {
let path = root_location(root_type.clone())
.map_err(|e| SaveError::RootLocationError(e))?
.join(root);
for file in serializer.output {
let path = &PathBuf::from(format!(
"{}{}",
path.to_string_lossy().trim_end_matches('/'),
if let Some(folder_variant) = file.folder_variant.clone() {
format!(".{folder_variant}")
} else {
format!("")
}
));
let file_path = if let Some(name) = &file.name {
path.join(PathBuf::from(
format!("{}/{}", &file.path.trim_matches('/'), name).trim_start_matches("/"),
))
} else {
path.join(PathBuf::from(&file.path.trim_matches('/')))
};
if file.hint == FileOperationHint::DeleteValue {
rmdir(file_path.with_extension("value"))?;
rm(file_path.with_extension("value"))?;
}
if !file.is_path {
std::fs::create_dir_all(if &file_path != path {
&path
} else {
path.parent().unwrap()
})
.map_err(|e| SaveError::CreateDirectoryError {
path: path.clone(),
kind: e.kind(),
})?;
if file.hint == FileOperationHint::Delete {
rm(file_path)?;
} else {
let filename = format!(
"{}{}",
file_path.to_string_lossy().trim_end_matches('/'),
if let Some(ext) = &file.variant {
format!(".{ext}")
} else {
format!("")
}
);
save_to(filename.into(), file.output)?;
}
} else {
std::fs::create_dir_all(format!(
"{}{}",
file_path.to_string_lossy().trim_end_matches('/'),
if let Some(ext) = &file.variant {
format!(".{ext}")
} else {
format!("")
}
))
.map_err(|e| SaveError::CreateDirectoryError {
path: file_path,
kind: e.kind(),
})?;
}
}
Ok(())
}
fn rmdir(path: PathBuf) -> Result<(), SaveError> {
std::fs::remove_dir_all(path.clone()).map_or_else(
|e| {
let kind = e.kind();
match kind {
ErrorKind::NotFound => Ok(()),
ErrorKind::NotADirectory => Ok(()),
_ => Err(SaveError::DeleteFileError { path, kind }),
}
},
|_| Ok(()),
)
}
fn rm(path: PathBuf) -> Result<(), SaveError> {
std::fs::remove_file(path.to_string_lossy().trim_end_matches('/')).map_or_else(
|e| {
let kind = e.kind();
match kind {
ErrorKind::NotFound => Ok(()),
_ => Err(SaveError::DeleteFileError { path, kind }),
}
},
|_| Ok(()),
)
}
fn save_to(path: PathBuf, contents: Vec<u8>) -> Result<(), SaveError> {
let mut file_tgt = File::create(&path).map_err(|e| SaveError::CreateFileError {
path: path.clone(),
kind: e.kind(),
})?;
file_tgt
.write(&contents)
.map_err(|e| SaveError::WriteFileError {
path,
contents,
kind: e.kind(),
})?;
Ok(())
}
#[derive(Debug)]
pub enum RootLocationError {
PathBufError(std::convert::Infallible),
GetVarError(std::env::VarError),
CreateDirectoryError {
path: PathBuf,
kind: std::io::ErrorKind,
},
}
impl std::fmt::Display for RootLocationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
Self::PathBufError(inf) =>
format!("Failed while retrieving the program's root directory: {inf}"),
Self::GetVarError(ve) =>
format!("Failed to get an environment variable while retrieving the program's root directory: {ve}"),
Self::CreateDirectoryError { path, kind } =>
format!("Failed to create (open) file at {path:?} while attempting to initialize the program's root directory; {kind}"),
}
)
}
}
pub fn root_location(location: RootType) -> Result<PathBuf, RootLocationError> {
use std::str::FromStr;
#[cfg(target_family = "unix")]
let path = match location {
RootType::InMemory => PathBuf::from_str(&format!("/tmp/{}", env!("CARGO_PKG_NAME")))
.map_err(|e| RootLocationError::PathBufError(e)),
RootType::Persistent => PathBuf::from_str(&format!(
"{}/.cache/{}",
std::env::var("HOME").map_err(|e| RootLocationError::GetVarError(e))?,
env!("CARGO_PKG_NAME")
))
.map_err(|e| RootLocationError::PathBufError(e)),
}?;
#[cfg(target_family = "windows")]
let path = PathBuf::from_str(&format!(
"{}\\{}\\.cache",
std::env::var("LOCALAPPDATA").map_err(|e| RootLocationError::GetVarError(e))?,
env!("CARGO_PKG_NAME")
))
.map_err(|e| RootLocationError::PathBufError(e))?;
std::fs::create_dir_all(path.clone()).map_err(|e| RootLocationError::CreateDirectoryError {
path: path.clone(),
kind: e.kind(),
})?;
Ok(path)
}