use crate::errors::*;
use lazy_static::lazy_static;
use std::{
ffi::{OsStr, OsString},
fs::{self, File},
io::prelude::*,
path::{Path, PathBuf},
};
lazy_static! {
static ref XDG_DIRS: xdg::BaseDirectories =
xdg::BaseDirectories::with_prefix("i3nator").expect("couldn't get XDG base directory");
}
pub trait ConfigFile: Sized {
fn copy<S: AsRef<OsStr> + ?Sized>(&self, new_name: &S) -> Result<Self>;
fn create<S: AsRef<OsStr> + ?Sized>(name: &S) -> Result<Self>;
fn create_from_template<S: AsRef<OsStr> + ?Sized>(name: &S, template: &[u8]) -> Result<Self>;
fn delete(&self) -> Result<()>;
fn from_path<P: AsRef<Path> + ?Sized>(path: &P) -> Result<Self>;
fn list() -> Vec<OsString>;
fn name(&self) -> String;
fn open<S: AsRef<OsStr> + ?Sized>(name: &S) -> Result<Self>;
fn path(&self) -> PathBuf;
fn prefix() -> &'static OsStr;
fn rename<S: AsRef<OsStr> + ?Sized>(&self, new_name: &S) -> Result<Self>;
fn verify(&self) -> Result<()>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConfigFileImpl {
prefix: OsString,
pub name: String,
pub path: PathBuf,
}
impl ConfigFileImpl {
pub fn create<S: AsRef<OsStr> + ?Sized>(prefix: &S, name: &S) -> Result<Self> {
let path = config_path(prefix, name);
if XDG_DIRS.find_config_file(&path).is_some() {
Err(ErrorKind::ConfigExists(
prefix.as_ref().to_string_lossy().into_owned(),
name.as_ref().to_string_lossy().into_owned(),
)
.into())
} else {
XDG_DIRS
.place_config_file(path)
.map(|path| ConfigFileImpl {
prefix: prefix.as_ref().to_owned(),
name: name.as_ref().to_string_lossy().into_owned(),
path,
})
.map_err(|e| e.into())
}
}
pub fn create_from_template<S: AsRef<OsStr> + ?Sized>(
prefix: &S,
name: &S,
template: &[u8],
) -> Result<Self> {
let configfile = ConfigFileImpl::create(prefix, name)?;
let mut file = File::create(&configfile.path)?;
file.write_all(template)?;
file.flush()?;
drop(file);
Ok(configfile)
}
pub fn open<S: AsRef<OsStr> + ?Sized>(prefix: &S, name: &S) -> Result<Self> {
let path = config_path(prefix, name);
let name = name.as_ref().to_string_lossy().into_owned();
XDG_DIRS
.find_config_file(&path)
.map(|path| ConfigFileImpl {
prefix: prefix.as_ref().to_owned(),
name: name.to_owned(),
path,
})
.ok_or_else(|| {
ErrorKind::UnknownConfig(prefix.as_ref().to_string_lossy().into_owned(), name)
.into()
})
}
}
impl ConfigFile for ConfigFileImpl {
fn copy<S: AsRef<OsStr> + ?Sized>(&self, new_name: &S) -> Result<Self> {
let new_configfile = ConfigFileImpl::create(self.prefix.as_os_str(), new_name.as_ref())?;
fs::copy(&self.path, &new_configfile.path)?;
Ok(new_configfile)
}
fn create<S: AsRef<OsStr> + ?Sized>(name: &S) -> Result<Self> {
Err(ErrorKind::UnknownConfig(
"NO PREFIX".to_owned(),
name.as_ref().to_string_lossy().into_owned(),
)
.into())
}
fn create_from_template<S: AsRef<OsStr> + ?Sized>(name: &S, _template: &[u8]) -> Result<Self> {
Err(ErrorKind::UnknownConfig(
"NO PREFIX".to_owned(),
name.as_ref().to_string_lossy().into_owned(),
)
.into())
}
fn delete(&self) -> Result<()> {
fs::remove_file(&self.path)?;
Ok(())
}
fn from_path<P: AsRef<Path> + ?Sized>(path: &P) -> Result<Self> {
let path = path.as_ref();
if !path.exists() || !path.is_file() {
Err(ErrorKind::PathDoesntExist(path.to_string_lossy().into_owned()).into())
} else {
Ok(ConfigFileImpl {
prefix: "local".to_owned().into(),
name: "local".to_owned(),
path: path.to_path_buf(),
})
}
}
fn list() -> Vec<OsString> {
vec![]
}
fn name(&self) -> String {
self.name.to_owned()
}
fn open<S: AsRef<OsStr> + ?Sized>(name: &S) -> Result<Self> {
Err(ErrorKind::UnknownConfig(
"NO PREFIX".to_owned(),
name.as_ref().to_string_lossy().into_owned(),
)
.into())
}
fn path(&self) -> PathBuf {
self.path.to_owned()
}
fn prefix() -> &'static OsStr {
OsStr::new("")
}
fn rename<S: AsRef<OsStr> + ?Sized>(&self, new_name: &S) -> Result<Self> {
let new_configfile = ConfigFileImpl::create(self.prefix.as_os_str(), new_name.as_ref())?;
fs::rename(&self.path, &new_configfile.path)?;
Ok(new_configfile)
}
fn verify(&self) -> Result<()> {
Ok(())
}
}
fn config_path<S: AsRef<OsStr> + ?Sized>(prefix: &S, name: &S) -> PathBuf {
let mut path = OsString::new();
path.push(prefix);
path.push("/");
path.push(name);
path.push(".toml");
path.into()
}
pub fn list<S: AsRef<OsStr> + ?Sized>(prefix: &S) -> Vec<OsString> {
let mut files = XDG_DIRS.list_config_files_once(prefix.as_ref().to_string_lossy().into_owned());
files.sort();
files
.iter()
.map(|file| file.file_stem())
.filter(Option::is_some)
.map(Option::unwrap)
.map(OsStr::to_os_string)
.collect::<Vec<_>>()
}