use futures::Future;
use futures_cpupool::CpuPool;
use mock_command::{CommandChild, RunCommand};
use ring::digest::{SHA512, Context};
use std::ffi::{OsStr, OsString};
use std::fs::File;
use std::hash::Hasher;
use std::io::BufReader;
use std::io::prelude::*;
use std::path::PathBuf;
use std::process::{self,Stdio};
use std::time::Duration;
use errors::*;
pub struct Digest {
inner: Context,
}
impl Digest {
pub fn new() -> Digest {
Digest { inner: Context::new(&SHA512) }
}
pub fn file<T>(path: T, pool: &CpuPool) -> SFuture<String>
where T: Into<PathBuf>
{
let path = path.into();
Box::new(pool.spawn_fn(move || -> Result<_> {
let f = File::open(&path).chain_err(|| format!("Failed to open file for hashing: {:?}", path))?;
let mut m = Digest::new();
let mut reader = BufReader::new(f);
loop {
let mut buffer = [0; 1024];
let count = reader.read(&mut buffer[..])?;
if count == 0 {
break;
}
m.update(&buffer[..count]);
}
Ok(m.finish())
}))
}
pub fn update(&mut self, bytes: &[u8]) {
self.inner.update(bytes);
}
pub fn finish(self) -> String {
hex(self.inner.finish().as_ref())
}
}
fn hex(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
s.push(hex(byte & 0xf));
s.push(hex((byte >> 4)& 0xf));
}
return s;
fn hex(byte: u8) -> char {
match byte {
0...9 => (b'0' + byte) as char,
_ => (b'a' + byte - 10) as char,
}
}
}
pub fn fmt_duration_as_secs(duration: &Duration) -> String
{
format!("{}.{:03} s", duration.as_secs(), duration.subsec_nanos() / 1000_000)
}
fn wait_with_input_output<T>(mut child: T, input: Option<Vec<u8>>)
-> SFuture<process::Output>
where T: CommandChild + 'static,
{
use tokio_io::io::{write_all, read_to_end};
let stdin = input.and_then(|i| {
child.take_stdin().map(|stdin| {
write_all(stdin, i).chain_err(|| "failed to write stdin")
})
});
let stdout = child.take_stdout().map(|io| {
read_to_end(io, Vec::new()).chain_err(|| "failed to read stdout")
});
let stderr = child.take_stderr().map(|io| {
read_to_end(io, Vec::new()).chain_err(|| "failed to read stderr")
});
let status = Future::and_then(stdin, |io| {
drop(io);
child.wait().chain_err(|| "failed to wait for child")
});
Box::new(status.join3(stdout, stderr).map(|(status, out, err)| {
let stdout = out.map(|p| p.1);
let stderr = err.map(|p| p.1);
process::Output {
status: status,
stdout: stdout.unwrap_or_default(),
stderr: stderr.unwrap_or_default(),
}
}))
}
pub fn run_input_output<C>(mut command: C, input: Option<Vec<u8>>)
-> SFuture<process::Output>
where C: RunCommand
{
let child = command
.no_console()
.stdin(if input.is_some() { Stdio::piped() } else { Stdio::inherit() })
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn();
Box::new(child
.and_then(|child| {
wait_with_input_output(child, input).and_then(|output| {
if output.status.success() {
f_ok(output)
} else {
f_err(ErrorKind::ProcessError(output))
}
})
}))
}
pub trait OsStrExt {
fn starts_with(&self, s: &str) -> bool;
fn split_prefix(&self, s: &str) -> Option<OsString>;
}
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt as _OsStrExt;
#[cfg(unix)]
impl OsStrExt for OsStr {
fn starts_with(&self, s: &str) -> bool {
self.as_bytes().starts_with(s.as_bytes())
}
fn split_prefix(&self, s: &str) -> Option<OsString> {
let bytes = self.as_bytes();
if bytes.starts_with(s.as_bytes()) {
Some(OsStr::from_bytes(&bytes[s.len()..]).to_owned())
} else {
None
}
}
}
#[cfg(windows)]
use std::os::windows::ffi::{OsStrExt as _OsStrExt, OsStringExt};
#[cfg(windows)]
impl OsStrExt for OsStr {
fn starts_with(&self, s: &str) -> bool {
let mut u16s = self.encode_wide();
let mut utf8 = s.chars();
while let Some(codepoint) = u16s.next() {
let to_match = match utf8.next() {
Some(ch) => ch,
None => return true,
};
let to_match = to_match as u32;
let codepoint = codepoint as u32;
if to_match < 0xd7ff {
if to_match != codepoint {
return false
}
} else {
return false
}
}
utf8.next().is_none()
}
fn split_prefix(&self, s: &str) -> Option<OsString> {
let mut u16s = self.encode_wide().peekable();
let mut utf8 = s.chars();
while let Some(&codepoint) = u16s.peek() {
let to_match = match utf8.next() {
Some(ch) => ch,
None => {
let codepoints = u16s.collect::<Vec<_>>();
return Some(OsString::from_wide(&codepoints))
}
};
let to_match = to_match as u32;
let codepoint = codepoint as u32;
if to_match < 0xd7ff {
if to_match != codepoint {
return None
}
} else {
return None
}
u16s.next();
}
if utf8.next().is_none() {
Some(OsString::new())
} else {
None
}
}
}
pub struct HashToDigest<'a> {
pub digest: &'a mut Digest,
}
impl<'a> Hasher for HashToDigest<'a> {
fn write(&mut self, bytes: &[u8]) {
self.digest.update(bytes)
}
fn finish(&self) -> u64 {
panic!("not supposed to be called");
}
}
#[cfg(test)]
mod tests {
use std::ffi::{OsStr, OsString};
use super::OsStrExt;
#[test]
fn simple_starts_with() {
let a: &OsStr = "foo".as_ref();
assert!(a.starts_with(""));
assert!(a.starts_with("f"));
assert!(a.starts_with("fo"));
assert!(a.starts_with("foo"));
assert!(!a.starts_with("foo2"));
assert!(!a.starts_with("b"));
assert!(!a.starts_with("b"));
let a: &OsStr = "".as_ref();
assert!(!a.starts_with("a"))
}
#[test]
fn simple_strip_prefix() {
let a: &OsStr = "foo".as_ref();
assert_eq!(a.split_prefix(""), Some(OsString::from("foo")));
assert_eq!(a.split_prefix("f"), Some(OsString::from("oo")));
assert_eq!(a.split_prefix("fo"), Some(OsString::from("o")));
assert_eq!(a.split_prefix("foo"), Some(OsString::from("")));
assert_eq!(a.split_prefix("foo2"), None);
assert_eq!(a.split_prefix("b"), None);
}
}