use {CANCELLED_TWICE, POLLED_TWICE, Spawn};
use env::{AsyncIoEnvironment, FileDescEnvironment, RedirectEnvRestorer, RedirectRestorer};
use error::RedirectionError;
use eval::RedirectEval;
use io::FileDesc;
use future::{Async, EnvFuture, Poll};
use std::fmt;
#[must_use = "futures do nothing unless polled"]
pub struct LocalRedirections<I, S, E: ?Sized, RR = RedirectRestorer<E>>
where I: Iterator,
I::Item: RedirectEval<E>,
S: Spawn<E>,
{
restorer: Option<RR>,
state: State<I, <I::Item as RedirectEval<E>>::EvalFuture, S, S::EnvFuture>,
}
impl<R, I, S, E: ?Sized, RR> fmt::Debug for LocalRedirections<I, S, E, RR>
where I: Iterator<Item = R> + fmt::Debug,
R: RedirectEval<E>,
R::EvalFuture: fmt::Debug,
S: Spawn<E> + fmt::Debug,
S::EnvFuture: fmt::Debug,
RR: fmt::Debug,
{
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("LocalRedirections")
.field("restorer", &self.restorer)
.field("state", &self.state)
.finish()
}
}
#[derive(Debug)]
enum State<I, RF, S, SEF> {
Redirections {
cur_redirect: Option<RF>,
remaining_redirects: I,
cmd: Option<S>,
},
Spawned(SEF),
Gone,
}
pub fn spawn_with_local_redirections<I, S, E: ?Sized>(redirects: I, cmd: S)
-> LocalRedirections<I::IntoIter, S, E, RedirectRestorer<E>>
where I: IntoIterator,
I::Item: RedirectEval<E>,
S: Spawn<E>,
E: FileDescEnvironment,
E::FileHandle: Clone,
{
spawn_with_local_redirections_and_restorer(RedirectRestorer::new(), redirects, cmd)
}
pub fn spawn_with_local_redirections_and_restorer<RR, I, S, E: ?Sized>(
mut restorer: RR,
redirects: I,
cmd: S,
) -> LocalRedirections<I::IntoIter, S, E, RR>
where I: IntoIterator,
I::Item: RedirectEval<E>,
S: Spawn<E>,
RR: RedirectEnvRestorer<E>,
{
let iter = redirects.into_iter();
let (lo, hi) = iter.size_hint();
let capacity = hi.unwrap_or(lo);
restorer.reserve(capacity);
LocalRedirections {
restorer: Some(restorer),
state: State::Redirections {
cur_redirect: None,
remaining_redirects: iter,
cmd: Some(cmd),
},
}
}
impl<R, I, S, E: ?Sized, RR> EnvFuture<E> for LocalRedirections<I, S, E, RR>
where R: RedirectEval<E, Handle = E::FileHandle>,
I: Iterator<Item = R>,
S: Spawn<E>,
S::Error: From<RedirectionError> + From<R::Error>,
E: AsyncIoEnvironment + FileDescEnvironment,
E::FileHandle: Clone + From<FileDesc>,
RR: RedirectEnvRestorer<E>,
{
type Item = S::Future;
type Error = S::Error;
fn poll(&mut self, env: &mut E) -> Poll<Self::Item, Self::Error> {
macro_rules! try_restore {
($result:expr) => {{
match $result {
Ok(ret) => ret,
Err(e) => {
self.restorer.take().expect(POLLED_TWICE).restore(env);
return Err(e.into());
},
}
}}
}
macro_rules! try_ready_restore {
($result:expr) => {
match try_restore!($result) {
Async::Ready(ret) => ret,
Async::NotReady => return Ok(Async::NotReady),
}
}
}
loop {
let next_state = match self.state {
State::Redirections {
ref mut cur_redirect,
ref mut remaining_redirects,
ref mut cmd,
} => {
if cur_redirect.is_none() {
*cur_redirect = remaining_redirects.next()
.map(|r| r.eval(env));
}
let should_continue = match *cur_redirect {
None => false,
Some(ref mut rf) => {
let action = try_ready_restore!(rf.poll(env));
let action_result = self.restorer.as_mut()
.expect(POLLED_TWICE)
.apply_action(action, env)
.map_err(|err| RedirectionError::Io(err, None));
try_restore!(action_result);
true
},
};
if should_continue {
*cur_redirect = None;
continue;
}
State::Spawned(cmd.take().expect(POLLED_TWICE).spawn(env))
},
State::Spawned(ref mut f) => {
let ret = try_ready_restore!(f.poll(env));
self.restorer.take().expect(POLLED_TWICE).restore(env);
return Ok(Async::Ready(ret))
},
State::Gone => panic!(POLLED_TWICE),
};
self.state = next_state;
}
}
fn cancel(&mut self, env: &mut E) {
match self.state {
State::Redirections { ref mut cur_redirect, .. } => {
cur_redirect.as_mut().map(|r| r.cancel(env));
},
State::Spawned(ref mut f) => f.cancel(env),
State::Gone => panic!(CANCELLED_TWICE),
}
self.state = State::Gone;
self.restorer.take().map(|mut restorer| restorer.restore(env));
}
}