Skip to content

Commit 15f5558

Browse files
committed
Tokio/Futures - First working version!
1 parent 3bf1672 commit 15f5558

File tree

16 files changed

+905
-421
lines changed

16 files changed

+905
-421
lines changed

core/src/command/mod.rs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,29 @@ pub mod providers;
1010

1111
use erased_serde::Serialize;
1212
use errors::*;
13-
use ExecutableProvider;
13+
use Executable;
14+
use futures::{future, Future};
1415
use host::Host;
15-
use self::providers::{Nix, NixRemoteProvider};
16+
use self::providers::{Nix, NixRunnable};
17+
use std::sync::Arc;
18+
19+
pub trait CommandProvider<H: Host> {
20+
fn available(&Arc<H>) -> Box<Future<Item = bool, Error = Error>> where Self: Sized;
21+
fn try_new(&Arc<H>, &str, Option<&[&str]>) -> Box<Future<Item = Option<Self>, Error = Error>> where Self: Sized;
22+
fn exec(&mut self) -> Box<Future<Item = CommandResult, Error = Error>>;
23+
}
1624

1725
#[doc(hidden)]
1826
#[derive(Serialize, Deserialize)]
27+
pub enum CommandRunnable {
28+
Nix(NixRunnable)
29+
}
30+
31+
#[doc(hidden)]
32+
#[derive(Clone, Serialize, Deserialize)]
1933
pub struct Command {
2034
shell: Vec<String>,
21-
cmd: Vec<String>,
35+
cmd: String,
2236
}
2337

2438
#[derive(Debug, Serialize, Deserialize)]
@@ -29,30 +43,18 @@ pub struct CommandResult {
2943
pub stderr: Vec<u8>,
3044
}
3145

32-
pub trait CommandProvider<'a> {
33-
fn available(&Host) -> bool where Self: Sized;
34-
fn try_new(&'a Host, &[&str], Option<&[&str]>) -> Option<Self> where Self: Sized;
35-
fn exec(&self) -> Result<CommandResult>;
36-
}
37-
38-
#[doc(hidden)]
39-
#[derive(Serialize, Deserialize)]
40-
pub enum RemoteProvider {
41-
Nix(NixRemoteProvider)
42-
}
43-
44-
impl <'de>ExecutableProvider<'de> for RemoteProvider {
45-
fn exec(self, host: &Host) -> Result<Box<Serialize>> {
46+
impl Executable for CommandRunnable {
47+
fn exec(self) -> Box<Future<Item = Box<Serialize>, Error = Error>> {
4648
match self {
47-
RemoteProvider::Nix(p) => p.exec(host)
49+
CommandRunnable::Nix(p) => p.exec()
4850
}
4951
}
5052
}
5153

52-
pub fn factory<'a>(host: &'a Host, cmd: &[&str], shell: Option<&[&str]>) -> Result<Box<CommandProvider<'a> + 'a>> {
53-
if let Some(p) = Nix::try_new(host, cmd, shell) {
54-
Ok(Box::new(p))
55-
} else {
56-
Err(ErrorKind::ProviderUnavailable("Command").into())
57-
}
54+
pub fn factory<H: Host + 'static>(host: &Arc<H>, cmd: &str, shell: Option<&[&str]>) -> Box<Future<Item = Box<CommandProvider<H>>, Error = Error>> {
55+
Box::new(Nix::try_new(host, cmd, shell)
56+
.and_then(|opt| match opt {
57+
Some(provider) => future::ok(Box::new(provider) as Box<CommandProvider<H>>),
58+
None => future::err(ErrorKind::ProviderUnavailable("Command").into())
59+
}))
5860
}

core/src/command/providers/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66

77
mod nix;
88

9-
pub use self::nix::{Nix, RemoteProvider as NixRemoteProvider};
9+
pub use self::nix::{Nix, NixRunnable};

core/src/command/providers/nix.rs

Lines changed: 63 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -4,84 +4,90 @@
44
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
55
// modified, or distributed except according to those terms.
66

7-
use command::{Command, CommandProvider, CommandResult};
7+
use command::{Command, CommandProvider, CommandResult, CommandRunnable};
88
use erased_serde::Serialize;
99
use errors::*;
10-
use ExecutableProvider;
11-
use host::*;
10+
use Executable;
11+
use futures::{future, Future};
12+
use host::Host;
13+
use Runnable;
1214
use std::process;
15+
use std::sync::Arc;
1316

1417
const DEFAULT_SHELL: [&'static str; 2] = ["/bin/sh", "-c"];
1518

16-
pub struct Nix<'a> {
17-
host: &'a Host,
19+
pub struct Nix<H: Host> {
20+
host: Arc<H>,
1821
inner: Command
1922
}
2023

2124
#[doc(hidden)]
2225
#[derive(Serialize, Deserialize)]
23-
pub enum RemoteProvider {
26+
pub enum NixRunnable {
2427
Available,
2528
Exec(Command),
2629
}
2730

28-
impl <'de>ExecutableProvider<'de> for RemoteProvider {
29-
fn exec(self, host: &Host) -> Result<Box<Serialize>> {
30-
match self {
31-
RemoteProvider::Available => Ok(Box::new(Nix::available(host))),
32-
RemoteProvider::Exec(inner) => {
33-
let p = Nix { host, inner };
34-
Ok(Box::new(p.exec()?))
35-
}
36-
}
31+
impl <H: Host + 'static>CommandProvider<H> for Nix<H> {
32+
fn available(host: &Arc<H>) -> Box<Future<Item = bool, Error = Error>> {
33+
host.run(Runnable::Command(CommandRunnable::Nix(NixRunnable::Available)))
34+
.chain_err(|| ErrorKind::Runnable { endpoint: "Command::Nix", func: "available" })
3735
}
38-
}
3936

40-
impl <'a>CommandProvider<'a> for Nix<'a> {
41-
fn available(host: &Host) -> bool {
42-
if host.is_local() {
43-
cfg!(not(windows))
44-
} else {
45-
unimplemented!();
46-
// let r = RemoteProvider::Available;
47-
// self.host.send(r).chain_err(|| ErrorKind::RemoteProvider("Command", "available"))?;
48-
// Ok(self.host.recv()?)
49-
}
37+
fn try_new(host: &Arc<H>, cmd: &str, shell: Option<&[&str]>) -> Box<Future<Item = Option<Nix<H>>, Error = Error>> {
38+
let cmd_owned = cmd.to_owned();
39+
let shell_owned: Vec<String> = shell.unwrap_or(&DEFAULT_SHELL).to_owned().iter().map(|s| s.to_string()).collect();
40+
let host = host.clone();
41+
42+
Box::new(Self::available(&host).and_then(move |available| {
43+
if available {
44+
let inner = Command {
45+
shell: shell_owned,
46+
cmd: cmd_owned,
47+
};
48+
future::ok(Some(Nix { host, inner }))
49+
} else {
50+
future::ok(None)
51+
}
52+
}))
5053
}
5154

52-
fn try_new(host: &'a Host, cmd: &[&str], shell: Option<&[&str]>) -> Option<Nix<'a>> {
53-
if Self::available(host) {
54-
let inner = Command {
55-
shell: shell.unwrap_or(&DEFAULT_SHELL).to_owned().iter().map(|s| s.to_string()).collect(),
56-
cmd: cmd.to_owned().iter().map(|s| s.to_string()).collect(),
57-
};
58-
Some(Nix { host, inner })
59-
} else {
60-
None
61-
}
55+
fn exec(&mut self) -> Box<Future<Item = CommandResult, Error = Error>> {
56+
self.host.run(Runnable::Command(CommandRunnable::Nix(NixRunnable::Exec(self.inner.clone()))))
57+
.chain_err(|| ErrorKind::Runnable { endpoint: "Command::Nix", func: "exec" })
6258
}
59+
}
6360

64-
fn exec(&self) -> Result<CommandResult> {
65-
if self.host.is_local() {
66-
let (shell, shell_args) = self.inner.shell.split_first()
67-
.ok_or("Invalid shell provided")?;
68-
let out = process::Command::new(shell)
69-
.args(shell_args)
70-
.args(&self.inner.cmd)
71-
.output()
72-
.chain_err(|| "Command execution failed")?;
73-
Ok(CommandResult {
74-
success: out.status.success(),
75-
exit_code: out.status.code(),
76-
stdout: out.stdout,
77-
stderr: out.stderr
78-
})
79-
} else {
80-
unimplemented!();
81-
// let r = RemoteProvider::Load;
82-
// self.host.send(r).chain_err(|| ErrorKind::RemoteProvider("Command", "exec"))?;
83-
// let result: CommandResult = self.host.recv()?;
84-
// Ok(result)
61+
impl Executable for NixRunnable {
62+
fn exec(self) -> Box<Future<Item = Box<Serialize>, Error = Error>> {
63+
match self {
64+
NixRunnable::Available =>
65+
Box::new(future::ok(Box::new(
66+
cfg!(unix)
67+
) as Box<Serialize>)),
68+
NixRunnable::Exec(inner) => {
69+
Box::new(future::lazy(move || -> future::FutureResult<Box<Serialize>, Error> {
70+
let (shell, shell_args) = match inner.shell.split_first() {
71+
Some((s, a)) => (s, a),
72+
None => return future::err("Invalid shell provided".into()),
73+
};
74+
75+
let out = process::Command::new(shell)
76+
.args(shell_args)
77+
.arg(&inner.cmd)
78+
.output()
79+
.chain_err(|| "Command execution failed");
80+
match out {
81+
Ok(output) => future::ok(Box::new(CommandResult {
82+
success: output.status.success(),
83+
exit_code: output.status.code(),
84+
stdout: output.stdout,
85+
stderr: output.stderr,
86+
}) as Box<Serialize>),
87+
Err(e) => future::err(e),
88+
}
89+
}))
90+
}
8591
}
8692
}
8793
}

core/src/errors.rs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@
44
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
55
// modified, or distributed except according to those terms.
66

7+
use futures::Future;
8+
use std::{error, io};
9+
710
error_chain! {
11+
foreign_links {
12+
Io(io::Error);
13+
}
14+
815
errors {
916
InvalidTelemetryKey {
1017
cmd: &'static str,
@@ -19,7 +26,7 @@ error_chain! {
1926
display("No providers available for {}", p),
2027
}
2128

22-
RemoteProvider {
29+
Runnable {
2330
endpoint: &'static str,
2431
func: &'static str,
2532
} {
@@ -48,3 +55,26 @@ error_chain! {
4855
}
4956
}
5057
}
58+
59+
// @todo This should disappear once Futures are officially supported
60+
// by error_chain.
61+
// See: https://github.com/rust-lang-nursery/error-chain/issues/90
62+
pub type SFuture<T> = Box<Future<Item = T, Error = Error>>;
63+
64+
pub trait FutureChainErr<T> {
65+
fn chain_err<F, E>(self, callback: F) -> SFuture<T>
66+
where F: FnOnce() -> E + 'static,
67+
E: Into<ErrorKind>;
68+
}
69+
70+
impl<F> FutureChainErr<F::Item> for F
71+
where F: Future + 'static,
72+
F::Error: error::Error + Send + 'static,
73+
{
74+
fn chain_err<C, E>(self, callback: C) -> SFuture<F::Item>
75+
where C: FnOnce() -> E + 'static,
76+
E: Into<ErrorKind>,
77+
{
78+
Box::new(self.then(|r| r.chain_err(callback)))
79+
}
80+
}

0 commit comments

Comments
 (0)