use std::{
fs::{create_dir_all, hard_link, File},
io::{stdin, BufRead, BufReader, Read},
path::PathBuf,
};
use anyhow::Result;
use ghee_lang::{Key, Value, Xattr};
use thiserror::Error;
use crate::{paths::PathBufExt, table_info, Record};
#[derive(Error, Debug)]
pub enum InsErr {
#[error("Table info not found at {0}")]
TableInfoNotFound(PathBuf),
#[error("Path {0} not found")]
PathNotFound(PathBuf),
#[error("An IO error occurred at {path}: {err}")]
IoError { err: std::io::Error, path: PathBuf },
#[error("A JSON error occurred: {0}")]
JsonError(serde_json::Error),
#[error("Raw (unnamed) json value not supported: {0}")]
RawJsonValue(serde_json::Value),
#[error("Record wouldn't be included in any index")]
IncludedInNoIndex,
}
fn json_to_record(json: &serde_json::Value) -> Result<Record> {
if let serde_json::Value::Object(o) = json {
let mut record = Record::new();
for (k, v) in o {
let xattr = Xattr::from(k.as_str());
let value = Value::try_from(v.clone())?;
record.insert(xattr, value);
}
Ok(record)
} else {
Err(InsErr::RawJsonValue(json.clone()).into())
}
}
fn write_record_as_xattrs(path: &PathBuf, record: &Record) -> Result<()> {
for (k, v) in record {
let bytes = v.as_bytes();
xattr::set(path, k.to_osstring(), bytes.as_slice()).map_err(|err| InsErr::IoError {
err,
path: path.clone(),
})?;
}
Ok(())
}
pub fn ins(table_path: &PathBuf, records_path: &Option<PathBuf>, verbose: bool) -> Result<()> {
let r: Box<dyn Read> = if let Some(path) = records_path {
Box::new(File::open(path).map_err(|_| InsErr::PathNotFound(path.clone()))?)
} else {
Box::new(stdin())
};
let r = BufReader::new(r);
let records_iter = r.lines().map(|line| {
let line = line.unwrap_or_else(|e| panic!("Error reading records line: {}", e));
let json_record: serde_json::Value = serde_json::from_str(&line)
.map_err(InsErr::JsonError)
.unwrap_or_else(|e| panic!("Error parsing line as JSON: {}", e));
let record = json_to_record(&json_record)
.unwrap_or_else(|e| panic!("Error interpreting JSON as xattrs: {}", e));
record
});
ins_records(table_path, records_iter, verbose)
}
pub fn ins_records(
table_path: &PathBuf,
records: impl Iterator<Item = Record>,
verbose: bool,
) -> Result<()> {
let info =
table_info(table_path)?.ok_or_else(|| InsErr::TableInfoNotFound(table_path.clone()))?;
for record in records {
let paths: Vec<(&PathBuf, PathBuf)> = {
let ordered = {
let mut ordered: Vec<(&Key, &PathBuf)> = info.indices_abs().iter().collect();
ordered.sort_by_key(|(_k, p)| p.to_string_lossy().len());
ordered
};
ordered
.into_iter()
.map(|(key, table_path)| {
let record_path = key.path_for_record(table_path.clone(), &record);
(table_path, record_path)
})
.filter(|(_table_path, record_path)| record_path.is_ok())
.map(|(table_path, record_path)| (table_path, record_path.unwrap()))
.collect()
};
if paths.is_empty() {
return Err(InsErr::IncludedInNoIndex.into());
}
let (_index_path, record_path) = &paths[0];
create_dir_all(record_path.parent().unwrap()).map_err(|err| InsErr::IoError {
err,
path: record_path.parent().unwrap().into(),
})?;
File::create(&record_path).map_err(|err| InsErr::IoError {
err,
path: record_path.clone(),
})?;
write_record_as_xattrs(&record_path, &record)?;
let record_path_display = record_path.relative_to_curdir_if_possible();
if verbose {
eprintln!("Initialized {}", record_path_display.display());
}
for (_dest_index_path, dest_record_path) in paths.iter().skip(1) {
create_dir_all(dest_record_path.parent().unwrap()).map_err(|err| InsErr::IoError {
err,
path: dest_record_path.parent().unwrap().into(),
})?;
hard_link(&record_path, &dest_record_path).map_err(|err| InsErr::IoError {
err,
path: record_path.clone(),
})?;
if verbose {
let dest_record_path_display = dest_record_path.relative_to_curdir_if_possible();
eprintln!(
"Linked {} -> {}",
dest_record_path_display.display(),
record_path_display.display()
);
}
}
}
Ok(())
}
#[cfg(test)]
mod test {
use ghee_lang::Value;
use crate::{test_support::Scenario, xattr_values};
#[test]
fn test_ins() {
let s = Scenario::new("ins");
let mut record_path = s.dir1.clone();
record_path.push("0");
let mut indexed_record_path = s.dir2.clone();
indexed_record_path.push("1");
indexed_record_path.push("2");
assert!(record_path.exists());
assert!(indexed_record_path.exists());
let xattrs = xattr_values(&record_path).unwrap();
assert_eq!(xattrs.len(), 4);
assert_eq!(xattrs[&s.xattr1].clone(), Value::Number(0f64));
assert_eq!(xattrs[&s.xattr2].clone(), Value::Number(1f64));
assert_eq!(xattrs[&s.xattr3].clone(), Value::Number(2f64));
assert_eq!(xattrs[&s.xattr4].clone(), Value::Number(3f64));
let indexed_xattrs = xattr_values(&indexed_record_path).unwrap();
assert_eq!(xattrs.len(), 4);
assert_eq!(indexed_xattrs[&s.xattr1].clone(), Value::Number(0f64));
assert_eq!(indexed_xattrs[&s.xattr2].clone(), Value::Number(1f64));
assert_eq!(indexed_xattrs[&s.xattr3].clone(), Value::Number(2f64));
assert_eq!(indexed_xattrs[&s.xattr4].clone(), Value::Number(3f64));
}
}