#[macro_use]
extern crate serde_derive;
extern crate futures;
extern crate serde;
extern crate serde_json;
extern crate textnonce;
use std::error::Error as StdError;
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
use serde::{Serialize, Deserialize};
pub struct Queue<T> {
path: String,
seq: u32,
_placeholder: std::marker::PhantomData<T>,
}
impl<T: Serialize + Deserialize> Queue<T> {
pub fn new(path: &str) -> Result<Queue<T>, std::io::Error> {
std::fs::DirBuilder::new().recursive(true).mode(0o700).create(path)?;
Ok(Queue::<T> {
path: path.to_string(),
seq: 0,
_placeholder: std::marker::PhantomData,
})
}
pub fn push(&mut self, item: T) -> Result<(), std::io::Error> {
let mut item_path = std::path::PathBuf::from(&self.path);
let item_name = format!("{:016x}-{}", self.seq, rand_string());
item_path.push(item_name);
let complete_path = item_path.to_str().unwrap().to_string();
let incomplete_path = item_path.with_extension("inc").to_str().unwrap().to_string();
{
let mut item_file = std::fs::OpenOptions::new().write(true)
.mode(0o600)
.create_new(true)
.open(&incomplete_path)?;
serde_json::to_writer(&mut item_file, &item).map_err(to_ioerror)?;
}
std::fs::rename(incomplete_path, complete_path)?;
self.seq += 1;
Ok(())
}
pub fn pop(&self) -> Result<Option<T>, std::io::Error> {
let dirh = std::fs::read_dir(&self.path)?;
for maybe_dirent in dirh {
let item_path = match maybe_dirent {
Ok(dirent) => {
let p = dirent.path();
if let Some(_) = p.extension() {
continue;
}
p
}
Err(e) => return Err(e),
};
let stage_path = item_path.with_extension("pop");
{
let item_file = std::fs::OpenOptions::new().read(true)
.open(&item_path)?;
let item = serde_json::from_reader(item_file).map_err(to_ioerror)?;
std::fs::rename(item_path, stage_path)?;
return Ok(Some(item));
}
}
Ok(None)
}
pub fn flush(&self) -> Result<(), std::io::Error> {
let dirh = std::fs::read_dir(&self.path)?;
for maybe_dirent in dirh {
match maybe_dirent {
Ok(dirent) => {
let p = dirent.path();
if let Some(e) = p.extension() {
if e != "pop" {
continue;
}
}
std::fs::remove_file(p)?;
}
Err(e) => return Err(e),
}
}
Ok(())
}
pub fn recover(&self) -> Result<(), std::io::Error> {
let dirh = std::fs::read_dir(&self.path)?;
for maybe_dirent in dirh {
match maybe_dirent {
Ok(dirent) => {
let p = dirent.path();
if let Some(e) = p.extension() {
if e != "pop" {
continue;
}
}
let unmarked =
p.parent().unwrap().join(std::path::Path::new(p.file_stem().unwrap()));
std::fs::rename(p, unmarked)?;
}
Err(e) => return Err(e),
}
}
Ok(())
}
}
pub struct QueueStream<T> {
queue: Queue<T>,
}
impl<T: Serialize + Deserialize> QueueStream<T> {
pub fn queue(&self) -> &Queue<T> {
&self.queue
}
pub fn mut_queue(&mut self) -> &mut Queue<T> {
&mut self.queue
}
}
impl<T: Serialize + Deserialize> QueueStream<T> {
pub fn new(q: Queue<T>) -> QueueStream<T> {
QueueStream::<T> { queue: q }
}
}
impl<T: Serialize + Deserialize> futures::stream::Stream for QueueStream<T> {
type Item = T;
type Error = std::io::Error;
fn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> {
match self.queue.pop() {
Ok(Some(t)) => Ok(futures::Async::Ready(Some(t))),
Ok(None) => Ok(futures::Async::NotReady),
Err(e) => Err(e),
}
}
}
fn to_ioerror<E: StdError>(e: E) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::Other, e.description())
}
mod cleanup {
use std;
use std::ops::Drop;
#[allow(dead_code)]
pub enum Cleanup {
Dir(String), }
impl Drop for Cleanup {
fn drop(&mut self) {
match self {
&mut Cleanup::Dir(ref path) => {
match std::fs::remove_dir_all(path) {
Ok(_) => {}
Err(e) => {
println!("warning: failed to remove file {}: {}", path, e);
}
}
}
}
}
}
}
fn rand_string() -> String {
textnonce::TextNonce::sized_urlsafe(32).unwrap().into_string()
}
#[cfg(test)]
mod tests {
use std;
use std::collections::HashSet;
use futures::{Future, Stream};
use super::*;
#[derive(Serialize, Deserialize)]
#[derive(PartialEq, Debug)]
struct Foo {
i: i32,
b: bool,
s: String,
}
fn new_queue() -> (Queue<Foo>, cleanup::Cleanup) {
let mut spool_path_buf = std::env::temp_dir();
spool_path_buf.push(rand_string());
let spool_dir = spool_path_buf.to_str().unwrap();
let _cleanup = cleanup::Cleanup::Dir(spool_dir.to_string());
let q = Queue::<Foo>::new(spool_dir).unwrap();
(q, _cleanup)
}
#[test]
fn test_push_pop() {
let (mut q, _cleanup) = new_queue();
assert!(q.push(Foo {
i: 999,
b: true,
s: "foo".to_string(),
})
.is_ok());
let result = q.pop().unwrap().unwrap();
assert_eq!(result,
Foo {
i: 999,
b: true,
s: "foo".to_string(),
});
assert!(match q.pop() {
Ok(None) => true,
_ => false,
})
}
#[test]
fn test_push_pop_many() {
let (mut q, _cleanup) = new_queue();
let mut indexes = HashSet::<i32>::new();
for i in 0..100 {
assert!(q.push(Foo {
i: i,
b: i % 3 == 0,
s: format!("#{}", i),
})
.is_ok());
indexes.insert(i);
}
for _ in 0..100 {
let item = q.pop().unwrap().unwrap();
assert_eq!(item.b, item.i % 3 == 0);
assert_eq!(item.s, format!("#{}", item.i));
assert!(item.i > -1);
assert!(item.i < 100);
indexes.remove(&item.i);
}
assert!(match q.pop() {
Ok(None) => true,
_ => false,
});
assert!(indexes.is_empty());
}
#[test]
fn test_recover_flush() {
let (mut q, _cleanup) = new_queue();
let mut indexes = HashSet::<i32>::new();
for i in 0..100 {
assert!(q.push(Foo {
i: i,
b: i % 3 == 0,
s: format!("#{}", i),
})
.is_ok());
indexes.insert(i);
}
for _ in 0..100 {
let item = q.pop().unwrap().unwrap();
assert_eq!(item.b, item.i % 3 == 0);
assert_eq!(item.s, format!("#{}", item.i));
assert!(item.i > -1);
assert!(item.i < 100);
indexes.remove(&item.i);
}
assert!(match q.pop() {
Ok(None) => true,
_ => false,
});
assert!(indexes.is_empty());
q.recover().unwrap();
for _ in 0..100 {
let item = q.pop().unwrap().unwrap();
assert_eq!(item.b, item.i % 3 == 0);
assert_eq!(item.s, format!("#{}", item.i));
assert!(item.i > -1);
assert!(item.i < 100);
}
assert!(match q.pop() {
Ok(None) => true,
_ => false,
});
q.flush().unwrap();
q.recover().unwrap();
assert!(match q.pop() {
Ok(None) => true,
_ => false,
});
}
#[test]
fn test_push_in_stream_out() {
let (q, _cleanup) = new_queue();
let mut qs = QueueStream::new(q);
for i in 0..100 {
assert!(qs.mut_queue()
.push(Foo {
i: i,
b: i % 3 == 0,
s: format!("#{}", i),
})
.is_ok());
}
let f = qs.take(100).fold(0,
|agg, item| -> Result<i32, std::io::Error> { Ok(agg + item.i) });
let result = f.wait().unwrap();
assert_eq!(result, 4950); }
}