use sealed::sealed;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
use crate::config::{Config, Configurable};
use crate::error::StamError;
use crate::types::*;
const KNOWN_EXTENSIONS: &[&str; 14] = &[
".store.stam.json",
".annotationset.stam.json",
".stam.json",
".store.stam.cbor",
".stam.cbor",
".store.stam.csv",
".annotationset.stam.csv",
".annotations.stam.csv",
".stam.csv",
".json",
".cbor",
".csv",
".txt",
".md",
];
pub(crate) fn get_filepath(filename: &str, workdir: Option<&Path>) -> Result<PathBuf, StamError> {
if filename == "-" {
return Ok(filename.into());
}
if filename.starts_with("https://") || filename.starts_with("http://") {
return Err(StamError::OtherError("Loading URLs is not supported yet"));
}
let path = if filename.starts_with("file://") {
PathBuf::from(&filename[7..])
} else {
PathBuf::from(filename)
};
if path.is_absolute() {
Ok(path)
} else {
if let Some(workdir) = workdir {
let path = workdir.join(&path);
Ok(path)
} else {
Ok(path)
}
}
}
pub(crate) fn open_file(filename: &str, config: &Config) -> Result<File, StamError> {
let found_filename = get_filepath(filename, config.workdir())?;
debug(config, || {
format!("open_file: {:?} at {:?}", filename, found_filename)
});
File::open(found_filename.as_path()).map_err(|e| {
StamError::IOError(
e,
found_filename
.as_path()
.to_str()
.expect("path must be valid unicode")
.to_owned(),
"Opening file for reading failed",
)
})
}
pub(crate) fn create_file(filename: &str, config: &Config) -> Result<File, StamError> {
let found_filename = get_filepath(filename, config.workdir())?;
debug(config, || {
format!(
"create_file: {:?}, workdir: {:?}",
found_filename,
config.workdir()
)
});
File::create(found_filename.as_path()).map_err(|e| {
StamError::IOError(
e,
found_filename
.as_path()
.to_str()
.expect("path must be valid unicode")
.to_owned(),
"Opening file for reading failed",
)
})
}
pub(crate) fn open_file_reader(
filename: &str,
config: &Config,
) -> Result<Box<dyn BufRead>, StamError> {
if filename == "-" {
Ok(Box::new(std::io::stdin().lock()))
} else {
Ok(Box::new(BufReader::new(open_file(filename, config)?)))
}
}
pub(crate) fn open_file_writer(
filename: &str,
config: &Config,
) -> Result<Box<dyn Write>, StamError> {
if filename == "-" {
Ok(Box::new(std::io::stdout()))
} else {
Ok(Box::new(BufWriter::new(create_file(filename, config)?)))
}
}
pub(crate) fn strip_known_extension(s: &str) -> &str {
for extension in KNOWN_EXTENSIONS.iter() {
if s.ends_with(extension) {
return &s[0..s.len() - extension.len()];
}
}
s
}
pub(crate) fn sanitize_id_to_filename(id: &str) -> String {
let mut id = id.replace("://", ".").replace(&['/', '\\', ':', '?'], ".");
for extension in KNOWN_EXTENSIONS.iter() {
if id.ends_with(extension) {
id.truncate(id.len() - extension.len());
}
}
id
}
pub(crate) fn filename_without_workdir<'a>(filename: &'a str, config: &Config) -> &'a str {
if let Some(workdir) = config.workdir().map(|x| x.to_str().expect("valid utf-8")) {
if filename.starts_with(workdir) {
let filename = &filename[workdir.len()..];
if filename.starts_with(&['/', '\\']) {
return &filename[1..];
} else {
return filename;
}
}
}
filename
}
#[sealed(pub(crate))] #[allow(private_bounds)]
pub trait AssociatedFile: Configurable + ChangeMarker {
fn filename(&self) -> Option<&str>;
fn set_filename(&mut self, filename: &str) -> &mut Self;
fn with_filename(mut self, filename: &str) -> Self
where
Self: Sized,
{
self.set_filename(filename);
self
}
fn dirname(&self) -> Option<PathBuf> {
if let Some(mut storedir) = self.filename().map(|s| {
let pb: PathBuf = s.into();
pb
}) {
storedir.pop();
if let Some(workdir) = self.config().workdir.as_ref() {
let mut workdir = workdir.clone();
workdir.extend(&storedir);
debug(self.config(), || {
format!("dirname(): workdir + storedir = {:?}", workdir)
});
return Some(workdir);
} else {
debug(self.config(), || {
format!("dirname(): storedir = {:?}", storedir)
});
return Some(storedir);
}
} else if let Some(workdir) = self.config().workdir.as_ref() {
debug(self.config(), || {
format!("dirname(): workdir = {:?}", workdir)
});
return Some(workdir.clone());
}
debug(self.config(), || format!("dirname(): none"));
None
}
fn filename_without_extension(&self) -> Option<&str> {
if let Some(filename) = self.filename() {
Some(strip_known_extension(filename))
} else {
None
}
}
fn filename_without_workdir(&self) -> Option<&str> {
if let Some(filename) = self.filename() {
Some(filename_without_workdir(filename, self.config()))
} else {
None
}
}
}
#[sealed(pub(crate))] pub(crate) trait ChangeMarker {
fn change_marker(&self) -> &Arc<RwLock<bool>>;
fn changed(&self) -> bool {
let mut result = true;
if let Ok(changed) = self.change_marker().read() {
result = *changed;
}
result
}
fn mark_changed(&self) {
if let Ok(mut changed) = self.change_marker().write() {
*changed = true;
}
}
fn mark_unchanged(&self) {
if let Ok(mut changed) = self.change_marker().write() {
*changed = false;
}
}
}