use crate::header::FSMetaV1;
use crate::jobfile;
use crate::seqfile;
use anyhow::bail;
use command_group::{CommandGroup, GroupChild};
use std::collections::BTreeMap;
use std::ffi::OsString;
use std::fs::{create_dir, DirEntry, File};
use std::io::{Read, Write};
use std::os::unix::ffi::OsStrExt;
use std::path::PathBuf;
use std::process::{ChildStdout, Command, Stdio};
use tracing::*;
use uuid::Uuid;
pub fn queuewrite<R: Read>(input: &mut R, queuedir: &OsString) -> Result<(), anyhow::Error> {
let mut buf = Uuid::encode_buffer();
let uuid = Uuid::new_v4().hyphenated().encode_lower(&mut buf);
let fnpart: String = format!("fspl-{}.fspl", uuid);
let tmpfnpart: String = format!("tmp-{}.tmp", fnpart);
let tmppath = PathBuf::from(queuedir).join("jobs").join(tmpfnpart);
debug!("Copying source to temporary file {:?}", tmppath);
let mut tmpfile = File::create(&tmppath)?;
let bytes = std::io::copy(input, &mut tmpfile)?;
tmpfile.flush()?;
tmpfile.sync_all()?;
std::mem::drop(tmpfile);
let finalpath = PathBuf::from(queuedir).join("jobs").join(fnpart);
debug!(
"Wrote {} bytes. Now renaming {:?} to {:?}",
bytes, tmppath, finalpath
);
std::fs::rename(tmppath, finalpath)?;
Ok(())
}
pub fn get_seqfile(queuedir: &OsString) -> OsString {
let mut seqfile = queuedir.clone();
seqfile.push("/nextseq");
seqfile
}
pub fn queueinit(queuedir: &OsString, append_only: bool) -> Result<(), anyhow::Error> {
let path = PathBuf::from(queuedir);
create_dir(&path)?;
create_dir(path.join("jobs"))?;
if !append_only {
let seqfn = get_seqfile(queuedir);
let mut lock = seqfile::prepare_seqfile_lock(&seqfn, true)?;
seqfile::SeqFile::open(&seqfn, &mut lock)?;
}
Ok(())
}
pub fn scanqueue(
queuedir: &OsString,
decoder: &Option<OsString>,
) -> Result<impl Iterator<Item = Result<(OsString, FSMetaV1), anyhow::Error>>, anyhow::Error> {
fn procdir(
queuedir: &OsString,
decoder: &Option<OsString>,
entry: std::io::Result<DirEntry>,
) -> Result<(OsString, FSMetaV1), anyhow::Error> {
let e = entry?;
let filename = e.file_name();
if !(filename.as_bytes().starts_with(b"fspl-") && filename.as_bytes().ends_with(b".fspl")) {
debug!("Ignoring file {:?}", filename);
bail!("File {:?} doesn't match our specs", filename);
}
debug!(
"Queue scan reading header from {:?}",
queue_genfilename(queuedir, &filename)
);
let mut input = queue_openjob(queuedir, &filename, decoder)?;
let meta = jobfile::read_jobfile_header(&mut input.reader.take().unwrap().as_read())?;
Ok((filename, meta))
}
let dirpath = PathBuf::from(queuedir).join("jobs");
let dir = std::fs::read_dir(&dirpath)?;
let queuedir = queuedir.clone();
let decoder = decoder.clone();
Ok(dir.map(move |e| procdir(&queuedir, &decoder, e)))
}
#[instrument(level = "debug")]
pub fn scanqueue_map(
queuedir: &OsString,
decoder: &Option<OsString>,
) -> Result<BTreeMap<u64, (OsString, FSMetaV1)>, anyhow::Error> {
let mut retval = BTreeMap::new();
for (filename, meta) in scanqueue(queuedir, decoder)?.flatten() {
if let Some(prev) = retval.insert(meta.seq, (filename.clone(), meta.clone())) {
bail!(
"Attempted to process {:?} with seq {}, which was already seen in {:?}",
filename,
meta.seq,
prev.0
);
}
}
Ok(retval)
}
pub fn queue_genfilename(queuedir: &OsString, filename: &OsString) -> PathBuf {
PathBuf::from(queuedir).join("jobs").join(filename)
}
pub enum DecoderHandle {
DHFile(Box<File>),
DHChildStdout(Box<ChildStdout>),
}
impl From<File> for DecoderHandle {
fn from(f: File) -> Self {
DecoderHandle::DHFile(Box::new(f))
}
}
impl From<ChildStdout> for DecoderHandle {
fn from(f: ChildStdout) -> Self {
DecoderHandle::DHChildStdout(Box::new(f))
}
}
impl From<DecoderHandle> for Stdio {
fn from(dh: DecoderHandle) -> Stdio {
match dh {
DecoderHandle::DHFile(f) => (*f).into(),
DecoderHandle::DHChildStdout(f) => (*f).into(),
}
}
}
impl DecoderHandle {
pub fn as_read(self) -> Box<dyn Read> {
match self {
DecoderHandle::DHFile(f) => f,
DecoderHandle::DHChildStdout(f) => f,
}
}
pub fn with_read<T>(&mut self, func: fn(&mut dyn Read) -> T) -> T {
match self {
DecoderHandle::DHFile(o) => func(&mut *o),
DecoderHandle::DHChildStdout(o) => func(&mut *o),
}
}
}
pub struct PossibleDecoder {
pub child: Option<GroupChild>,
pub reader: Option<DecoderHandle>,
}
impl Drop for PossibleDecoder {
fn drop(&mut self) {
if let Some(mut child) = self.child.take() {
trace!("Killing decoder");
let _ = child.kill();
trace!("Waiting for decoder to terminate");
let res = child.wait();
trace!("Decoder termination status {:?}", res);
}
}
}
pub fn queue_openjob(
queuedir: &OsString,
filename: &OsString,
decoder: &Option<OsString>,
) -> Result<PossibleDecoder, anyhow::Error> {
let genfilename = queue_genfilename(queuedir, filename);
trace!("Opening queue file at {:?}", genfilename);
let file = File::open(genfilename)?;
match decoder {
None => Ok(PossibleDecoder {
child: None,
reader: Some(file.into()),
}),
Some(decodecmd) => {
let (child, childstdout) = with_decoder(decodecmd, file)?;
Ok(PossibleDecoder {
child: Some(child),
reader: Some(childstdout.into()),
})
}
}
}
#[instrument(level = "debug", skip(input))]
pub fn with_decoder<T: Into<Stdio>>(
decoder: &OsString,
input: T,
) -> Result<(GroupChild, ChildStdout), anyhow::Error> {
let args = [OsString::from("-c"), decoder.clone()];
let shell = getshell();
debug!("Preparing to invoke decoder: {:?} {:?}", shell, args);
let mut child = Command::new(shell)
.args(args)
.stdin(input)
.stdout(Stdio::piped())
.group_spawn()?;
debug!("Decoder PID {} started successfully", child.id());
let stdout = child.inner().stdout.take().expect("Missing stdout in child");
Ok((child, stdout))
}
pub fn getshell() -> OsString {
match std::env::var_os("SHELL") {
Some(x) => x,
None => OsString::from("/bin/sh"),
}
}