use serde::{Deserialize, Serialize};
use serde_json::{error::Category, json, Value};
use std::{
fmt::{self, Display},
fs::{File, OpenOptions},
num::ParseIntError,
path::{Path, PathBuf},
str::Utf8Error,
};
use thiserror::Error;
use xattr::FileExt;
pub mod cmds;
pub mod dt;
#[derive(Error, Debug)]
pub enum Errors {
#[error(transparent)]
Utf8(#[from] Utf8Error),
#[error(transparent)]
ParseInt(#[from] ParseIntError),
#[error(transparent)]
StdIo(#[from] std::io::Error),
#[error(transparent)]
StdFmt(#[from] std::fmt::Error),
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
#[error(transparent)]
ChronoParse(#[from] chrono::ParseError),
#[error("Value is not valid json or a raw string")]
BadValue,
#[error("<{0}> key doesn exist on <{1}> file")]
KeyDoesntExist(String, PathBuf),
#[error("Could not parse <{0}> as value")]
FromStr(String),
#[error("{0}")]
Generic(String),
}
pub type Res<T> = Result<T, Errors>;
pub fn del_xattr<S: AsRef<str>>(file: &File, key: S) -> Res<()> {
file.remove_xattr(key.as_ref())?;
Ok(())
}
pub fn set_xattr<S: AsRef<str>, V: ToString>(file: &File, key: S, value: V) -> Res<()> {
let strng = value.to_string();
file.set_xattr(key.as_ref(), strng.as_bytes())?;
Ok(())
}
pub fn get_xattr<S: AsRef<str>>(file: &File, key: S) -> Res<Option<String>> {
let value = if let Some(bytes) = file.get_xattr(key.as_ref())? {
let strng = std::str::from_utf8(&bytes)?;
Some(strng.to_string())
} else {
None
};
Ok(value)
}
pub fn get_val<K: AsRef<str>>(file: &File, key: K) -> Result<String, Errors> {
let b = file.get_xattr(key.as_ref())?.unwrap();
let s = std::str::from_utf8(&b)?;
Ok(s.to_string())
}
pub fn list_xattrs(file: &File) -> Res<Option<Vec<String>>> {
let xattrs = file.list_xattr()?;
let out: Vec<_> = xattrs.map(|x| x.to_string_lossy().to_string()).collect();
if !out.is_empty() {
Ok(Some(out))
} else {
Ok(None)
}
}
pub fn dump_xattr(file: &File) -> Res<Option<Vec<(String, String)>>> {
if let Some(keys) = list_xattrs(file)? {
let mut out = vec![];
for key in keys {
let v = get_val(file, &key)?;
out.push((key, v));
}
Ok(Some(out))
} else {
Ok(None)
}
}
pub fn strng2value<S: AsRef<str>>(s: S) -> Result<Value, Errors> {
let val: Value = match serde_json::from_str(s.as_ref()) {
Ok(v) => v,
Err(e) => match e.classify() {
Category::Syntax | Category::Data | Category::Eof => {
Value::String(s.as_ref().to_string())
}
_ => return Err(Errors::BadValue),
},
};
Ok(val)
}
#[derive(Serialize, Deserialize, Clone)]
pub struct Jattr {
key: String,
val: Value,
}
impl Display for Jattr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}={}",
self.key,
serde_json::to_string(&self.val).expect("error in display")
)
}
}
impl Jattr {
pub fn new<S: AsRef<str>>(key: S, val: Value) -> Self {
Jattr {
key: key.as_ref().to_string(),
val,
}
}
pub fn new_none<S: AsRef<str>>(key: S) -> Self {
Jattr {
key: key.as_ref().to_string(),
val: Value::Null,
}
}
pub fn set<P: AsRef<Path>>(&self, p: P) -> Result<(), Errors> {
let file = File::open(p.as_ref())?;
let v_str = serde_json::to_string(&self.val)?;
file.set_xattr(Self::format_key(&self.key), v_str.as_bytes())?;
Ok(())
}
pub fn get<P: AsRef<Path>, K: AsRef<str>>(p: P, key: K) -> Result<Value, Errors> {
let file = File::open(p.as_ref())?;
let v = if let Some(bytes) = file.get_xattr(Self::format_key(&key))? {
let s = std::str::from_utf8(&bytes)?;
strng2value(s)?
} else {
Value::Null
};
Ok(v)
}
pub fn load<P: AsRef<Path>, K: AsRef<str>>(p: &P, key: K) -> Result<Option<Self>, Errors> {
let k = Self::format_key(&key);
if p.as_ref().is_file() {
let file = OpenOptions::new().write(true).read(true).open(p.as_ref())?;
let x = if let Some(bytes) = file.get_xattr(k)? {
let strng = std::str::from_utf8(&bytes)?;
let val = strng2value(strng)?;
let x = Jattr::new(&key, val);
Some(x)
} else {
None
};
Ok(x)
} else if let Some(j) = Jattrs::load(p)? {
let filtered: Vec<_> = j.0.iter().filter(|x| x.key == k).collect();
if !filtered.is_empty() {
Ok(Some(filtered[0].clone()))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
pub fn del<P: AsRef<Path>, K: AsRef<str>>(&self, p: P, key: K) -> Result<(), Errors> {
let file = File::open(p.as_ref())?;
file.remove_xattr(key.as_ref())?;
Ok(())
}
pub fn format_key<S: AsRef<str>>(s: S) -> String {
let strng = s.as_ref();
if strng.starts_with("user.j.") || strng.starts_with("user.") {
strng.to_string()
} else {
format!("user.j.{}", strng)
}
}
pub(crate) fn to_value(&self) -> Value {
json!([self.key, self.val])
}
}
pub struct Jattrs(Vec<Jattr>);
impl Display for Jattrs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let strngs: Vec<_> = self.0.iter().map(|x| x.to_string()).collect();
write!(f, "{}", strngs.join("\n"))
}
}
impl Jattrs {
pub fn new(jattrs: Vec<Jattr>) -> Self {
Self(jattrs)
}
pub fn load<P: AsRef<Path>>(p: P) -> Result<Option<Self>, Errors> {
let mut xattrs = xattr::list(p.as_ref())?.peekable();
if xattrs.peek().is_none() {
Ok(None)
} else {
let mut out: Vec<Jattr> = vec![];
for key in xattrs {
let key = key.to_string_lossy();
if key.starts_with("user.j.") {
let v = Jattr::get(&p, &key)?;
let j = Jattr::new(key, v);
out.push(j);
} else {
println!("key: {}", key);
}
}
if !out.is_empty() {
let jattrs = Jattrs::new(out);
Ok(Some(jattrs))
} else {
Ok(None)
}
}
}
pub fn to_value(&self) -> Value {
let vals: Vec<Value> = self.0.iter().map(|x| x.to_value()).collect();
json!({"xattrs": vals})
}
pub fn set<P: AsRef<Path>>(&self, p: P) -> Result<(), Errors> {
for j in self.0.iter() {
j.set(p.as_ref())?;
}
Ok(())
}
}