use crates::chrono::Utc;
use crates::lzma::LzmaWriter;
use crates::rand::{self, Rng};
use crates::serde::Serialize;
use crates::serde_json::{self, Value};
use crates::tar::Builder;
use crates::tempdir::TempDir;
use std::fs::{self, File};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::thread;
use std::time::Duration;
error_chain! { }
fn write_job(queue: &Path, data: &Value) -> Result<()> {
let rndpart = rand::thread_rng()
.gen_ascii_chars()
.take(12)
.collect::<String>();
let filename = format!("{}-{}.json", Utc::now().to_rfc3339(), rndpart);
let job_file = queue.join(&filename);
{
let mut file = File::create(&job_file).chain_err(|| "failed to create the job file")?;
serde_json::to_writer(&mut file, data)
.chain_err(|| format!("failed to write the job to {}", filename))?;
}
while job_file.exists() {
thread::sleep(Duration::from_millis(100));
}
loop {
if !job_file.exists() {
break;
}
thread::sleep(Duration::from_millis(100));
}
Ok(())
}
pub fn drop_job<Q, K, V>(queue: Q, kind: K, data: V) -> Result<()>
where Q: AsRef<Path>,
K: AsRef<str>,
V: Serialize,
{
let job = json!({
"kind": kind.as_ref(),
"data": data,
});
write_job(queue.as_ref(), &job)
}
pub fn restart<Q>(queue: Q) -> Result<()>
where Q: AsRef<Path>,
{
drop_job(queue, "watchdog:restart", json!({}))
}
pub fn exit<Q>(queue: Q) -> Result<()>
where Q: AsRef<Path>,
{
drop_job(queue, "watchdog:exit", json!({}))
}
const LZMA_COMPRESSION: u32 = 6;
pub fn archive_queue<Q, O>(queue: Q, output: O) -> Result<()>
where Q: AsRef<Path>,
O: AsRef<Path>,
{
for result in &["accept", "fail", "reject"] {
let (filename, file) = archive_file(output.as_ref(), result)?;
let opt_writer = archive_directory(queue.as_ref(), output.as_ref(), result, file)?;
if let Some(mut writer) = opt_writer {
writer.finish()
.chain_err(|| format!("failed to finish archive stream for {}", result))?;
} else {
fs::remove_file(&filename)
.chain_err(|| format!("failed to delete file {}", filename.display()))?;
}
}
Ok(())
}
fn archive_file(path: &Path, result: &str) -> Result<(PathBuf, LzmaWriter<File>)> {
let now = Utc::now();
let filepath = path.join(format!("{}-{}.tar.xz", now.to_rfc3339(), result));
let file = File::create(&filepath).chain_err(|| {
format!("failed to create output file {}",
filepath.display())
})?;
let writer = LzmaWriter::new_compressor(file, LZMA_COMPRESSION)
.chain_err(|| "failed to construct LZMA writer")?;
Ok((filepath, writer))
}
fn archive_directory<O>(path: &Path, workdir: &Path, subdir: &str, output: O) -> Result<Option<O>>
where O: Write,
{
let tempdir = TempDir::new_in(workdir, "archive-jobs")
.chain_err(|| "failed to create temporary directory")?;
let targetdir = tempdir.path().join(subdir);
fs::create_dir_all(&targetdir).chain_err(|| "failed to create target directory")?;
let mut archive = Builder::new(output);
archive.append_dir(subdir, &targetdir)
.chain_err(|| format!("failed to append directory to {}", subdir))?;
let entries = fs::read_dir(path.join(subdir)).chain_err(|| "failed to read input directory")?;
let mut is_empty = true;
for entry in entries {
is_empty = false;
let entry = entry.chain_err(|| "failed to read entry")?;
let path = entry.path();
let file_name = path.file_name().expect("expected the path to have a filename");
let target_path = targetdir.join(&file_name);
fs::rename(&path, &target_path)
.chain_err(|| format!("failed to move input file {}", path.display()))?;
let mut added_file = File::open(&target_path)
.chain_err(|| format!("failed to open job file {}", target_path.display()))?;
archive.append_file(format!("{}/{}", subdir, file_name.to_string_lossy()),
&mut added_file)
.chain_err(|| {
format!("failed to append {} to the archive",
target_path.display())
})?;
}
if is_empty {
return Ok(None);
}
Ok(Some(archive.into_inner()
.chain_err(|| format!("failed to finish TAR stream for {}", subdir))?))
}