<commit_before>extern crate libc;

use std::ptr;
use std::mem;
use std::ops::Drop;

use libc::{uint32_t, uint8_t, c_void, size_t};

// B8G8R8A8 pixel size in bytes
static PIXEL_SIZE: u8 = 4;

#[link(name = "DXGCap")]
extern {
	fn init();

	fn create_dxgi_manager() -> *mut c_void;

	fn delete_dxgi_manager(dxgi_manager: *mut c_void);

	fn get_output_dimensions(dxgi_manager: *const c_void, width: *mut size_t,
		height: *mut size_t);

	fn get_frame_bytes(dxgi_manager: *mut c_void, o_size: *mut size_t,
		o_bytes: *mut *mut uint8_t) -> bool;
}

struct Capturer {
	dxgi_manager: *mut c_void,
	current_frame: Vec<u8>,
}

impl Capturer {
	fn new() -> Result<Capturer, ()> {
		let manager = unsafe { create_dxgi_manager() };
		if manager.is_null() {
			Err(())
		} else {
			Ok(Capturer{
				dxgi_manager: manager,
				current_frame: Vec::new() })
		}
	}

	fn dimensions(&self) -> (usize, usize) {
		let (mut width, mut height): (size_t, size_t) = (0, 0);
		unsafe { get_output_dimensions(self.dxgi_manager, &mut width, &mut height); }
		(width as usize, height as usize)
	}

	fn get_frame(&mut self) -> Result<&mut [u8], ()> {
		let mut buffer_size: size_t = 0;
		let mut buffer = ptr::null_mut::<u8>();
		if unsafe { get_frame_bytes(self.dxgi_manager, &mut buffer_size, &mut buffer) } {
			if buffer_size == 0 {
				Ok(self.current_frame.as_mut_slice())
			} else {
				let buffer_size = buffer_size as usize;
				if buffer.is_null() {
					Err(())
				} else {
					self.current_frame = unsafe {
						Vec::from_raw_parts(buffer, buffer_size, buffer_size) };
					Ok(self.current_frame.as_mut_slice())
				}
			}
		} else {
			Err(())
		}
	}
}

impl Drop for Capturer {
	fn drop(&mut self) {
		unsafe {
			let v = mem::replace(&mut self.current_frame, Vec::new());
			mem::forget(v);
			delete_dxgi_manager(self.dxgi_manager);
		}
	}
}

fn average_color(pixels_bytes: &[u8]) -> (u64, u64, u64) {
	let n_pixels = pixels_bytes.len() as u64 / PIXEL_SIZE as u64;
	let (mut r_sum, mut b_sum, mut g_sum) = (0u64, 0u64, 0u64);
	for n in (0..n_pixels) {
		let i = n as usize * PIXEL_SIZE as usize;
		let (b, g, r) = (pixels_bytes[i], pixels_bytes[i+1], pixels_bytes[i+2]);
		r_sum += r as u64;
		b_sum += b as u64;
		g_sum += g as u64;
	}
	(r_sum/n_pixels, b_sum/n_pixels, g_sum/n_pixels)
}

fn init_dxgi() {
	unsafe { init(); }
}

fn main() {
	init_dxgi();
	let mut capturer = Capturer::new().unwrap();

	let (width, height) = capturer.dimensions();
	println!("{} x {}", width, height);

	for i in 0..240u16 {
		let frame = capturer.get_frame().unwrap();
		// println!("null frame: {}", frame.iter().all(|b| *b == 0));
		let avs = average_color(frame);
		println!("Avs: {} {} {}", avs.0, avs.1, avs.2);
	}
}
<commit_msg>Led colors gather at 60 fps, but bugs<commit_after>#![feature(libc, core, std_misc, io, alloc)]

extern crate libc;

use std::ptr;
use std::mem;
use std::ops::Drop;
use std::old_io::timer;
use std::time::Duration;
use std::sync::Arc;
use std::sync::Future;

use libc::{uint8_t,
	c_void,
	size_t};

// B8G8R8A8 pixel size in bytes
static PIXEL_SIZE: usize = 4;

// Like the failed windows one
macro_rules! FAILED { ($hr:expr) => ($hr < 0) }

enum CaptureResult {
	CrOk,
	// Could not duplicate output, access denied. Might be in protected fullscreen.
	CrAccessDenied,
	// Access to the duplicated output was lost. Likely, mode was changed e.g. window => full
	CrAccessLost,
	// AcquireNextFrame timed out.
	CrTimeout,
	// General/Unexpected failure
	CrFail,
}

trait AsCaptureResult {
	fn as_capture_result(&self) -> CaptureResult;
}

impl AsCaptureResult for uint8_t {
	fn as_capture_result(&self) -> CaptureResult {
		match *self {
			0 => CaptureResult::CrOk,
			1 => CaptureResult::CrAccessDenied,
			2 => CaptureResult::CrAccessLost,
			3 => CaptureResult::CrTimeout,
			_ => CaptureResult::CrFail,
		}
	}
}

#[link(name = "DXGCap")]
extern {
	fn init();

	fn create_dxgi_manager() -> *mut c_void;

	fn delete_dxgi_manager(dxgi_manager: *mut c_void);

	fn get_output_dimensions(dxgi_manager: *const c_void, width: *mut size_t,
		height: *mut size_t);

	// Returns whether succeded
	fn get_frame_bytes(dxgi_manager: *mut c_void, o_size: *mut size_t,
		o_bytes: *mut *mut uint8_t) -> uint8_t;
}

#[derive(Clone, Debug)]
struct RGB8 {
	red: u8,
	green: u8,
	blue: u8
}

#[derive(Clone)]
struct Frame {
	width: usize,
	height: usize,
	data: Vec<u8>,
}

impl Frame {
	fn new() -> Frame {
		Frame{ width: 0, height: 0, data: Vec::new() }
	}

	fn average_color(&self, led: &Led) -> RGB8 {
		let (mut r_sum, mut g_sum, mut b_sum) = (0u64, 0u64, 0u64);
		// Coordinate factors
		let &Led((x1f, y1f), (x2f, y2f)) = led;
		// Skip every second row and column for better performance, with not too much
		// signal loss for most monitors
		let (widthf32, heightf32) = ((self.width/2) as f32, (self.height/2) as f32);
		for row in (y1f * heightf32) as usize .. (y2f * heightf32) as usize {
			for col in (x1f * widthf32) as usize .. (x2f * widthf32) as usize {
				let i = 2 * PIXEL_SIZE * (row * self.width + col);
				b_sum += self.data[i] as u64;
				g_sum += self.data[i+1] as u64;
				r_sum += self.data[i+2] as u64;
			}
		}

		let n64 = (self.width * self.height) as u64;
		RGB8{ red: (r_sum/n64) as u8, green: (g_sum/n64) as u8, blue: (b_sum/n64) as u8 }
	}
}

struct Capturer {
	dxgi_manager: *mut c_void,
	frame: Frame,
}

impl Capturer {
	fn new() -> Result<Capturer, ()> {
		let manager = unsafe { create_dxgi_manager() };
		if manager.is_null() {
			Err(())
		} else {
			Ok(Capturer{
				dxgi_manager: manager,
				frame: Frame { width: 0, height: 0, data: Vec::new() }})
		}
	}

	fn output_dimensions(&self) -> (usize, usize) {
		let (mut width, mut height): (size_t, size_t) = (0, 0);
		unsafe { get_output_dimensions(self.dxgi_manager, &mut width, &mut height); }
		(width as usize, height as usize)
	}

	fn capture_frame(&mut self) -> CaptureResult {
		let mut buffer_size: size_t = 0;
		let mut buffer = ptr::null_mut::<u8>();
		let cr = unsafe{
			get_frame_bytes(self.dxgi_manager, &mut buffer_size, &mut buffer)
				.as_capture_result() };
		if let CaptureResult::CrOk = cr  {
			if buffer as *const _ == self.frame.data.as_ptr() {
				CaptureResult::CrOk
			} else {
				let buffer_size = buffer_size as usize;
				if buffer.is_null() {
					CaptureResult::CrFail
				} else {
					let (width, height) = self.output_dimensions();
					self.frame = Frame{ width: width, height: height,
						data: unsafe {
							Vec::from_raw_parts(buffer, buffer_size,
								buffer_size) }};
					CaptureResult::CrOk
				}
			}
		} else {
			cr
		}
	}

	// Replaces the selfs frame, returning it. This is not a problem in and by itself, only,
	// the pointer for the vector in the frame is also used at other locations, the vector may
	// therefore be unexpectedly modified
	unsafe fn replace_frame(&mut self, replacement: Frame) -> Frame {
		mem::replace(&mut self.frame, replacement)
	}
}

impl Drop for Capturer {
	fn drop(&mut self) {
		unsafe {
			let v = mem::replace(&mut self.frame.data, Vec::new());
			mem::forget(v);
			delete_dxgi_manager(self.dxgi_manager);
		}
	}
}

fn init_dxgi() {
	unsafe { init(); }
}

// (x1, y1), (x2, y2)
struct Led((f32, f32), (f32, f32));

static LEDS: [Led; 32] = [
	Led((0.0000, 0.9219), (0.0600, 1.0000)),
	Led((0.0000, 0.8450), (0.0600, 0.9242)),
	Led((0.0000, 0.7681), (0.0600, 0.8473)),
	Led((0.0000, 0.6912), (0.0600, 0.7704)),
	Led((0.0000, 0.6142), (0.0600, 0.6935)),
	Led((0.0000, 0.5373), (0.0600, 0.6165)),
	Led((0.0000, 0.4604), (0.0600, 0.5396)),
	Led((0.0000, 0.3835), (0.0600, 0.4627)),
	Led((0.0000, 0.3065), (0.0600, 0.3858)),
	Led((0.0000, 0.2296), (0.0600, 0.3088)),
	Led((0.0000, 0.1527), (0.0600, 0.2319)),
	Led((0.0000, 0.0758), (0.0600, 0.1550)),
	Led((0.0000, 0.0000), (0.0600, 0.0781)),
	Led((0.0000, 0.0000), (0.0600, 0.1000)),
	Led((0.0000, 0.0000), (0.0461, 0.1000)),
	Led((0.0448, 0.0000), (0.0916, 0.1000)),
	Led((0.0902, 0.0000), (0.1370, 0.1000)),
	Led((0.1357, 0.0000), (0.1825, 0.1000)),
	Led((0.1811, 0.0000), (0.2280, 0.1000)),
	Led((0.2266, 0.0000), (0.2734, 0.1000)),
	Led((0.2720, 0.0000), (0.3189, 0.1000)),
	Led((0.3175, 0.0000), (0.3643, 0.1000)),
	Led((0.3630, 0.0000), (0.4098, 0.1000)),
	Led((0.4084, 0.0000), (0.4552, 0.1000)),
	Led((0.4539, 0.0000), (0.5007, 0.1000)),
	Led((0.4993, 0.0000), (0.5461, 0.1000)),
	Led((0.5448, 0.0000), (0.5916, 0.1000)),
	Led((0.5902, 0.0000), (0.6370, 0.1000)),
	Led((0.6357, 0.0000), (0.6825, 0.1000)),
	Led((0.6811, 0.0000), (0.7280, 0.1000)),
	Led((0.7266, 0.0000), (0.7734, 0.1000)),
	Led((0.7720, 0.0000), (0.8189, 0.1000)),
];

fn main() {
	use CaptureResult::*;
	init_dxgi();
	let mut capturer = Capturer::new().unwrap();

	let (width, height) = capturer.output_dimensions();
	println!("{} x {}", width, height);

	for _ in 0..600u16 {
		match capturer.capture_frame() {
			CrOk => (),
			// We are probably in fullscreen app with restricted access,
			// sleep until we have access again
			CrAccessDenied => timer::sleep(Duration::seconds(2)),
			// Has already been handeled in DXGCap, just try again
			CrAccessLost => continue,
			CrTimeout => continue,
			// CrFail. Might be bad, might be no big deal. Just ignore for now
			CrFail => continue,
		}

		let frame = unsafe { capturer.replace_frame(Frame::new()) };
		let mut shared_frame = Arc::new(frame);

		let out_vals: Vec<_> = LEDS.iter()
			.map(|led| {
				let child_frame = shared_frame.clone();
				Future::spawn(move || child_frame.average_color(led)) })
			.collect::<Vec<_>>()
			.into_iter()
			.map(|mut guard| guard.get())
			.collect();

		unsafe { capturer.replace_frame(mem::replace(shared_frame.make_unique(),
			Frame::new())); }

		println!("{:?}", out_vals);
	}
}
<|endoftext|>


<commit_before>//! BSPC command to control BSPWM

extern crate libc;
extern crate unix_socket;

use libc::{EXIT_FAILURE, EXIT_SUCCESS};
use std::env;
use std::io::{Read, Write};
use std::process;
use unix_socket::UnixStream;

const BUFSIZ: usize = libc::BUFSIZ as usize;
const FAILURE_MESSAGE: i32 = 7;
const SOCKET_ENV_VAR: &'static str = "BSPWM_SOCKET";

/// Prints error message and quits with error code.
///
/// # Examples
///
/// ```
/// err("Failed to do something important.", 4);
/// ```
fn err(message: &str, code: i32) {
    println!("{}", message);
    process::exit(code);
}

fn socket_file() -> String {
    format!("/tmp/bspwm{}_{}_{}-socket", "", 0, 0)
}

fn main() {
    let mut i = 0;
    let mut buffer = [0; BUFSIZ];

    // We add all the command line arguments to 'buffer', separated by a null
    // character (\0). At the end, 'i' will store the buffer's length, so we
    // can send it (and nothing more) to the socket stream.
    //
    // We skip the first argument because in most platforms it contains the
    // executable's name.
    for string in env::args().skip(1) {
        for c in string.chars() {
            buffer[i] = c as u8;
            i += 1;
        }

        buffer[i] = 0;
        i += 1;
    }

    if i == 0 {
        err("No arguments given.", 1);
    }

    // An environment variable can be set by bspwm (or the user) to tell bspc
    // where the socket file is. If the variable is set, use it. If not, use
    // the new scheme.
    let stream_file = env::var(SOCKET_ENV_VAR)
                          .unwrap_or(socket_file());

    let mut stream = {
        let maybe_stream = UnixStream::connect(stream_file);

        if let Err(_) = maybe_stream {
            err("Failed to connect to the socket.", 1);
        }

        maybe_stream.unwrap()
    };

    stream.write(&buffer[..i]).unwrap();
    let size = stream.read(&mut buffer).unwrap();
    drop(stream);

    // Force a null character at the end of the message.
    buffer[size] = 0;

    let (status, offset) = match buffer[0] as i32 {
        FAILURE_MESSAGE => (EXIT_FAILURE, 1),
        _ => (EXIT_SUCCESS, 0),
    };

    let message = String::from_utf8_lossy(&buffer[offset..size]);
    print!("{}", message);

    process::exit(status);
}
<commit_msg>Use 'unwrap_or_else' instead of 'unwrap_or'<commit_after>//! BSPC command to control BSPWM

extern crate libc;
extern crate unix_socket;

use libc::{EXIT_FAILURE, EXIT_SUCCESS};
use std::env;
use std::io::{Read, Write};
use std::process;
use unix_socket::UnixStream;

const BUFSIZ: usize = libc::BUFSIZ as usize;
const FAILURE_MESSAGE: i32 = 7;
const SOCKET_ENV_VAR: &'static str = "BSPWM_SOCKET";

/// Prints error message and quits with error code.
///
/// # Examples
///
/// ```
/// err("Failed to do something important.", 4);
/// ```
fn err(message: &str, code: i32) {
    println!("{}", message);
    process::exit(code);
}

fn socket_file() -> String {
    format!("/tmp/bspwm{}_{}_{}-socket", "", 0, 0)
}

fn main() {
    let mut i = 0;
    let mut buffer = [0; BUFSIZ];

    // We add all the command line arguments to 'buffer', separated by a null
    // character (\0). At the end, 'i' will store the buffer's length, so we
    // can send it (and nothing more) to the socket stream.
    //
    // We skip the first argument because in most platforms it contains the
    // executable's name.
    for string in env::args().skip(1) {
        for c in string.chars() {
            buffer[i] = c as u8;
            i += 1;
        }

        buffer[i] = 0;
        i += 1;
    }

    if i == 0 {
        err("No arguments given.", 1);
    }

    // An environment variable can be set by bspwm (or the user) to tell bspc
    // where the socket file is. If the variable is set, use it. If not, use
    // the new scheme.
    let stream_file = env::var(SOCKET_ENV_VAR)
                          .unwrap_or_else(|_| socket_file());

    let mut stream = {
        let maybe_stream = UnixStream::connect(stream_file);

        if let Err(_) = maybe_stream {
            err("Failed to connect to the socket.", 1);
        }

        maybe_stream.unwrap()
    };

    stream.write(&buffer[..i]).unwrap();
    let size = stream.read(&mut buffer).unwrap();
    drop(stream);

    // Force a null character at the end of the message.
    buffer[size] = 0;

    let (status, offset) = match buffer[0] as i32 {
        FAILURE_MESSAGE => (EXIT_FAILURE, 1),
        _ => (EXIT_SUCCESS, 0),
    };

    let message = String::from_utf8_lossy(&buffer[offset..size]);
    print!("{}", message);

    process::exit(status);
}
<|endoftext|>


<commit_before>extern crate num;
extern crate portaudio;
extern crate rtlsdr;

use portaudio as pa;
use rtlsdr::{RTLSDRDevice, RTLSDRError};
use num::complex::Complex;
use std::env;
use std::collections::VecDeque;
use std::f32::consts::PI;
use std::error::Error;

// Make sure that the buffer size is radix-2, otherwise the read_sync function
// will fail with an error code of -8.
const SDR_BUF_SIZE: usize = 524288;

// Separating out the buffer sizes seems to help some.
const BUF_SIZE: usize = 262144;

// Most other sample rates fail, but this one works for my particular device.
// I will investigate exactly what's happening here and generate a list of
// possible sample rates (if there are others besides this one).
const SAMPLE_RATE: u32 = 2800000;

// Here is the sample rate of the output waveform we'll try to use.
const SAMPLE_RATE_AUDIO: f64 = 48000.0;

// The bandwidth of the received signal.
const BANDWIDTH: u32 = 5000;

// The number of channels being used for audio.
const CHANNELS: i32 = 2;

fn main() {
    match run() {
        Ok(()) => (),
        Err(e) => println!("ERROR: {}", e),
    }
}

struct RTLSDR {
    rtlsdr: RTLSDRDevice,
}

unsafe impl Send for RTLSDR {}

fn run() -> Result<(), Box<Error>> {
    let sdr_index: i32 = env::args().nth(1)
        .expect("Please specify the SDR index")
        .parse::<i32>()?;
    let fm_freq_mhz: f32 = env::args().nth(2)
        .expect("Please specify a center frequency (MHz).")
        .parse::<f32>()?;

    // Initialize the PortAudio class.
    let audio = try!(pa::PortAudio::new());
    let settings =
        try!(audio.default_output_stream_settings(CHANNELS,
                                                  SAMPLE_RATE_AUDIO,
                                                  BUF_SIZE as u32));
    let mut stream = try!(audio.open_blocking_stream(settings));
    try!(stream.start());

    let fm_freq: u32 = (fm_freq_mhz * 1e6) as u32;

    // TODO: we need to figure out the appropriate decimation here. I've seen
    // talk that we decimate based on the bandwidth but after decimation the
    // audio signal comes out much slower. Changing the decimation value
    // seems to affect the speed of the audio, so I need to establish the
    // relationship here. Just playing with the decimation value, 30 seems
    // to work well but I need to figure out _why_.
    // let dec_rate = (SAMPLE_RATE as f64 / BANDWIDTH as f64) as usize;
    let dec_rate = 25;
    println!("dec rate is {}", dec_rate);
    let mut sdr = RTLSDR { rtlsdr: init_sdr(sdr_index, fm_freq).unwrap() };

    loop {
        // Read the samples and decimate down to match the sample rate of
        // the audio that'll be going out.
        let bytes = sdr.rtlsdr.read_sync(SDR_BUF_SIZE).unwrap();
        let iq_vec =
            decimate(read_samples(bytes).unwrap().as_slice(),
                     dec_rate);

        // After decimation, demodulate the signal and send out of the
        // thread to the receiver.
        let mut demod_iq = demod_fm(iq_vec);

        // Get the write stream and write our samples to the stream.
        let out_frames = match stream.write_available() {
            Ok(available) => {
                match available {
                    pa::StreamAvailable::Frames(frames) => frames as u32,
                    _ => return Err(Box::new(pa::error::Error::NullCallback)),
                }
            }
            Err(e) => return Err(Box::new(e)),
        };

        let buffer_frames = (demod_iq.len() / CHANNELS as usize) as u32;
        let write_frames = std::cmp::min(buffer_frames, out_frames);
        if write_frames == 0 {
            println!("write frames is 0, are we not generating fast enough?");
        }
        let n_write_samples = write_frames as usize * CHANNELS as usize;

        stream.write(write_frames,
                   |output| for ix in 0..n_write_samples as usize {
                       output[ix] = demod_iq.pop_front().unwrap();
                   })?;
    }
}

fn init_sdr(sdr_index: i32, fm_freq: u32) -> Result<RTLSDRDevice, RTLSDRError> {
    let mut sdr = try!(rtlsdr::open(sdr_index));
    try!(sdr.set_center_freq(fm_freq));
    try!(sdr.set_sample_rate(SAMPLE_RATE));
    try!(sdr.set_tuner_bandwidth(BANDWIDTH));
    try!(sdr.set_agc_mode(true));
    try!(sdr.reset_buffer());
    Ok(sdr)
}

fn decimate(signal: &[Complex<f32>], dec_rate: usize) -> Vec<Complex<f32>> {
    let mut ix = 0;
    let new_size = (signal.len() / dec_rate + 1) as usize;
    let mut signal_dec = Vec::<Complex<f32>>::with_capacity(new_size);
    while ix < signal.len() {
        signal_dec.push(signal[ix]);
        ix += dec_rate;
    }
    signal_dec
}

fn read_samples(bytes: Vec<u8>) -> Option<Vec<Complex<f32>>> {
    // First, check that we've been given an even number of bytes. Not sure
    // what to do if we don't get I and Q.
    let bytes_len = bytes.len();
    if bytes_len % 2 != 0 {
        println!("Received an odd number of samples.");
        return None;

    }
    let mut iq_vec: Vec<Complex<f32>> = Vec::with_capacity(bytes_len / 2);

    // Write the values to the complex value and normalize from [0, 255] to
    // [-1, 1]. If we don't normalize, it seems we don't get anything.
    for iq in bytes.chunks(2) {
        let iq_cmplx = Complex::new((iq[0] as f32 - 127.0),
                                    (iq[1] as f32 - 127.0));
        iq_vec.push(iq_cmplx);
    }
    Some(iq_vec)
}

fn demod_fm(iq: Vec<Complex<f32>>) -> VecDeque<f32> {
    let mut demod_queue: VecDeque<f32> = VecDeque::with_capacity(iq.len());
    let gain = SAMPLE_RATE as f32 / (2.0 * PI * 75e3 / 8.0);
    let mut prev = Complex::new(0.0, 0.0);

    for samp in iq.iter() {
        let conj = prev.conj() * samp;
        let fm_val = conj.im.atan2(conj.re);
        demod_queue.push_back(fm_val * gain);
        prev = *samp;
    }
    demod_queue
}
<commit_msg>We keep track of the previous value in demodulation now<commit_after>extern crate num;
extern crate portaudio;
extern crate rtlsdr;

use portaudio as pa;
use rtlsdr::{RTLSDRDevice, RTLSDRError};
use num::complex::Complex;
use std::env;
use std::collections::VecDeque;
use std::f32::consts::PI;
use std::error::Error;

// Make sure that the buffer size is radix-2, otherwise the read_sync function
// will fail with an error code of -8.
const SDR_BUF_SIZE: usize = 524288;

// Separating out the buffer sizes seems to help some.
const BUF_SIZE: usize = 262144;

// Most other sample rates fail, but this one works for my particular device.
// I will investigate exactly what's happening here and generate a list of
// possible sample rates (if there are others besides this one).
const SAMPLE_RATE: u32 = 2800000;

// Here is the sample rate of the output waveform we'll try to use.
const SAMPLE_RATE_AUDIO: f64 = 48000.0;

// The bandwidth of the received signal.
const BANDWIDTH: u32 = 5000;

// The number of channels being used for audio.
const CHANNELS: i32 = 2;

fn main() {
    match run() {
        Ok(()) => (),
        Err(e) => println!("ERROR: {}", e),
    }
}

struct RTLSDR {
    rtlsdr: RTLSDRDevice,
}

unsafe impl Send for RTLSDR {}

fn run() -> Result<(), Box<Error>> {
    let sdr_index: i32 = env::args().nth(1)
        .expect("Please specify the SDR index")
        .parse::<i32>()?;
    let fm_freq_mhz: f32 = env::args().nth(2)
        .expect("Please specify a center frequency (MHz).")
        .parse::<f32>()?;

    // Initialize the PortAudio class.
    let audio = try!(pa::PortAudio::new());
    let settings =
        try!(audio.default_output_stream_settings(CHANNELS,
                                                  SAMPLE_RATE_AUDIO,
                                                  BUF_SIZE as u32));
    let mut stream = try!(audio.open_blocking_stream(settings));
    try!(stream.start());

    let fm_freq: u32 = (fm_freq_mhz * 1e6) as u32;

    // TODO: we need to figure out the appropriate decimation here. I've seen
    // talk that we decimate based on the bandwidth but after decimation the
    // audio signal comes out much slower. Changing the decimation value
    // seems to affect the speed of the audio, so I need to establish the
    // relationship here. Just playing with the decimation value, 30 seems
    // to work well but I need to figure out _why_.
    // let dec_rate = (SAMPLE_RATE as f64 / BANDWIDTH as f64) as usize;
    let dec_rate = 25;
    println!("dec rate is {}", dec_rate);
    let mut sdr = RTLSDR { rtlsdr: init_sdr(sdr_index, fm_freq).unwrap() };
    let mut prev = Complex::new(0.0, 0.0);

    loop {
        // Read the samples and decimate down to match the sample rate of
        // the audio that'll be going out.
        let bytes = sdr.rtlsdr.read_sync(SDR_BUF_SIZE).unwrap();
        let iq_vec =
            decimate(read_samples(bytes).unwrap().as_slice(),
                     dec_rate);

        // After decimation, demodulate the signal and send out of the
        // thread to the receiver.
        let mut res = demod_fm(iq_vec, prev);
        let mut demod_iq = res.0;
        prev = res.1;

        // Get the write stream and write our samples to the stream.
        let out_frames = match stream.write_available() {
            Ok(available) => {
                match available {
                    pa::StreamAvailable::Frames(frames) => frames as u32,
                    _ => return Err(Box::new(pa::error::Error::NullCallback)),
                }
            }
            Err(e) => return Err(Box::new(e)),
        };

        let buffer_frames = (demod_iq.len() / CHANNELS as usize) as u32;
        let write_frames = std::cmp::min(buffer_frames, out_frames);
        if write_frames == 0 {
            println!("write frames is 0, are we not generating fast enough?");
        }
        let n_write_samples = write_frames as usize * CHANNELS as usize;

        stream.write(write_frames,
                   |output| for ix in 0..n_write_samples as usize {
                       output[ix] = 0.005 * demod_iq.pop_front().unwrap();
                   })?;
    }
}

fn init_sdr(sdr_index: i32, fm_freq: u32) -> Result<RTLSDRDevice, RTLSDRError> {
    let mut sdr = try!(rtlsdr::open(sdr_index));
    try!(sdr.set_center_freq(fm_freq));
    try!(sdr.set_sample_rate(SAMPLE_RATE));
    try!(sdr.set_tuner_bandwidth(BANDWIDTH));
    try!(sdr.set_agc_mode(false));
    try!(sdr.reset_buffer());
    Ok(sdr)
}

fn decimate(signal: &[Complex<f32>], dec_rate: usize) -> Vec<Complex<f32>> {
    let mut ix = 0;
    let new_size = (signal.len() / dec_rate + 1) as usize;
    let mut signal_dec = Vec::<Complex<f32>>::with_capacity(new_size);
    while ix < signal.len() {
        signal_dec.push(signal[ix]);
        ix += dec_rate;
    }
    signal_dec
}

fn read_samples(bytes: Vec<u8>) -> Option<Vec<Complex<f32>>> {
    // First, check that we've been given an even number of bytes. Not sure
    // what to do if we don't get I and Q.
    let bytes_len = bytes.len();
    if bytes_len % 2 != 0 {
        println!("Received an odd number of samples.");
        return None;

    }
    let mut iq_vec: Vec<Complex<f32>> = Vec::with_capacity(bytes_len / 2);

    // Write the values to the complex value and normalize from [0, 255] to
    // [-1, 1]. If we don't normalize, it seems we don't get anything.
    for iq in bytes.chunks(2) {
        let iq_cmplx = Complex::new((iq[0] as f32 - 127.0),
                                    (iq[1] as f32 - 127.0));
        iq_vec.push(iq_cmplx);
    }
    Some(iq_vec)
}

fn demod_fm(iq: Vec<Complex<f32>>, prev: Complex<f32>) -> (VecDeque<f32>, Complex<f32>) {
    let mut p = prev.clone();
    let mut demod_queue: VecDeque<f32> = VecDeque::with_capacity(iq.len());
    let gain = SAMPLE_RATE as f32 / (2.0 * PI * 75e3 / 8.0);

    for samp in iq.iter() {
        let conj = p.conj() * samp;
        let fm_val = conj.im.atan2(conj.re);
        demod_queue.push_back(fm_val * gain);
        p = *samp;
    }
    (demod_queue, p)
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate snarkov;

use clap::{App, Arg};
use std::path::Path;
use std::str::FromStr;
use snarkov::Corpus;

fn format_seed(seed: [u32; 4]) -> String {
    format!("{:x}{:x}{:x}{:x}", seed[0], seed[1], seed[2], seed[3])
}

fn parse_seed(seed: &str) -> [u32; 4] {
    assert!(seed.len() == 32);
    let s1 = u32::from_str_radix(&seed[..8], 16).unwrap();
    let s2 = u32::from_str_radix(&seed[8..16], 16).unwrap();
    let s3 = u32::from_str_radix(&seed[16..24], 16).unwrap();
    let s4 = u32::from_str_radix(&seed[24..], 16).unwrap();
    [s1, s2, s3, s4]
}

fn main() {
    let matches = App::new("snarkov")
        .about("Generate markov chains from text")
        .arg(Arg::with_name("context")
             .help("Amount of context for prediction")
             .short("c")
             .long("context")
             .value_name("NUM")
             .default_value("2")
             .takes_value(true))
        .arg(Arg::with_name("length")
             .help("Minimum length of chain to produce")
             .short("l")
             .long("length")
             .value_name("LEN")
             .default_value("25")
             .takes_value(true))
        .arg(Arg::with_name("seed")
             .help("Random seed")
             .short("s")
             .long("seed")
             .value_name("SEED")
             .takes_value(true))
        .arg(Arg::with_name("INPUT")
             .help("Input text file")
             .required(true))
        .arg(Arg::with_name("start")
             .help("Starting words")
             .required(true)
             .min_values(1))
        .get_matches();

    let input = Path::new(matches.value_of("INPUT").unwrap());
    let context = match usize::from_str(matches.value_of("context").unwrap()) {
        Ok(v) => v,
        Err(_) => {
            println!("error: context was not an integer");
            ::std::process::exit(1);
        }
    };
    let length = match usize::from_str(matches.value_of("length").unwrap()) {
        Ok(v) => v,
        Err(_) => {
            println!("error: length was not an integer");
            ::std::process::exit(1);
        }
    };
    let start = matches.values_of("start").unwrap().collect::<Vec<_>>();

    let mut corpus = Corpus::new(input, context).unwrap();
    if let Some(seed) = matches.value_of("seed") {
        corpus.seed(parse_seed(seed));
    }
    println!("random seed = {}", format_seed(corpus.get_seed()));
    print!("{} ", start.join(" "));
    for (i, word) in corpus.words(&start).enumerate() {
        print!("{}", word);
        if i < length - 1 || !word.ends_with(".") {
            print!(" ");
        } else {
            break
        }
    }
}
<commit_msg>Handle misisng input file more gracefully<commit_after>extern crate clap;
extern crate snarkov;

use clap::{App, Arg};
use std::path::Path;
use std::str::FromStr;
use snarkov::Corpus;

fn format_seed(seed: [u32; 4]) -> String {
    format!("{:x}{:x}{:x}{:x}", seed[0], seed[1], seed[2], seed[3])
}

fn parse_seed(seed: &str) -> [u32; 4] {
    assert!(seed.len() == 32);
    let s1 = u32::from_str_radix(&seed[..8], 16).unwrap();
    let s2 = u32::from_str_radix(&seed[8..16], 16).unwrap();
    let s3 = u32::from_str_radix(&seed[16..24], 16).unwrap();
    let s4 = u32::from_str_radix(&seed[24..], 16).unwrap();
    [s1, s2, s3, s4]
}

fn main() {
    let matches = App::new("snarkov")
        .about("Generate markov chains from text")
        .arg(Arg::with_name("context")
             .help("Amount of context for prediction")
             .short("c")
             .long("context")
             .value_name("NUM")
             .default_value("2")
             .takes_value(true))
        .arg(Arg::with_name("length")
             .help("Minimum length of chain to produce")
             .short("l")
             .long("length")
             .value_name("LEN")
             .default_value("25")
             .takes_value(true))
        .arg(Arg::with_name("seed")
             .help("Random seed")
             .short("s")
             .long("seed")
             .value_name("SEED")
             .takes_value(true))
        .arg(Arg::with_name("INPUT")
             .help("Input text file")
             .required(true))
        .arg(Arg::with_name("start")
             .help("Starting words")
             .required(true)
             .min_values(1))
        .get_matches();

    let input = Path::new(matches.value_of("INPUT").unwrap());
    let context = match usize::from_str(matches.value_of("context").unwrap()) {
        Ok(v) => v,
        Err(_) => {
            println!("error: context was not an integer");
            ::std::process::exit(1);
        }
    };
    let length = match usize::from_str(matches.value_of("length").unwrap()) {
        Ok(v) => v,
        Err(_) => {
            println!("error: length was not an integer");
            ::std::process::exit(1);
        }
    };
    let start = matches.values_of("start").unwrap().collect::<Vec<_>>();

    let mut corpus = match Corpus::new(input, context) {
        Ok(corpus) => corpus,
        Err(e) => {
            println!("error: {}", e);
            ::std::process::exit(1);
        }
    };
    if let Some(seed) = matches.value_of("seed") {
        corpus.seed(parse_seed(seed));
    }
    println!("random seed = {}", format_seed(corpus.get_seed()));
    print!("{} ", start.join(" "));
    for (i, word) in corpus.words(&start).enumerate() {
        print!("{}", word);
        if i < length - 1 || !word.ends_with(".") {
            print!(" ");
        } else {
            break
        }
    }
}
<|endoftext|>


<commit_before>extern crate bs58;
extern crate byteorder;
extern crate clap;
extern crate crypto;
extern crate curve25519_dalek;
extern crate ocl_core as core;
extern crate ring;

mod address;
mod device;
mod pattern;
mod sha256;
mod util;

use clap::{App, Arg};
use core::{PlatformInfo, DeviceInfo, DeviceId, PlatformId};
use pattern::Pattern;
use ring::rand::SystemRandom;
use std::{str, thread, time};
use std::fs::File;
use std::io::{self, Read, Write};
use std::sync::Arc;
use std::sync::atomic;
use std::sync::mpsc::{self, Sender, Receiver};

fn main() {
    let matches = App::new("Zcash Vanity Address Generator")
        .version("0.0.1")
        .about("Generates Zcash shielded addresses (\"z-addrs\") that match given prefixes.")
        .arg(Arg::with_name("device")
             .short("d")
             .takes_value(true)
             .number_of_values(1)
             .multiple(true)
             .help("OpenCL device string: <platform>:<device>.\n\
                    Specify multiple times for multiple devices, e.g. -d 0:0 -d 0:1.\n\
                    If not specified, uses all available platforms and devices."))
        .arg(Arg::with_name("file")
             .short("f")
             .takes_value(true)
             .help("Load prefixes from file, one per line.\nCan be combined with --insensitive."))
        .arg(Arg::with_name("insensitive")
             .short("i")
             .help("Case-insensitive prefix search."))
        .arg(Arg::with_name("continue")
             .short("c")
             .help("Continue searching after a match is found."))
        .arg(Arg::with_name("pattern")
             .index(1)
             .help("Zcash address prefix to be matched."))
        .after_help("Matches will be printed to stdout; each z-addr on its own line followed by its spending key on the next line.\n\
                     You can import the spending key to your Zcash client using `zcash-cli z_importkey <key> <rescan>`.")
        .get_matches();

    let mut device_specifiers = if matches.is_present("device") {
        matches.values_of("device").unwrap().map(|s| {
            let mut values = s.split(':');
            (values.next().unwrap().parse::<usize>().unwrap(), values.next().unwrap().parse::<usize>().unwrap())
        }).collect()
    } else {
        vec![]
    };
    device_specifiers.sort();
    let mut device_specifiers_iter = device_specifiers.into_iter();

    let mut stderr = io::stderr();
    let mut devices = vec![];
    let mut device_specifier = device_specifiers_iter.next();
    let all_devices = device_specifier.is_none();
    for (i, &platform) in core::get_platform_ids().unwrap().iter().enumerate() {
        writeln!(&mut stderr, "Available devices on platform {}:", core::get_platform_info(&platform, PlatformInfo::Name)).unwrap();
        for (j, &device) in core::get_device_ids(&platform, None, None).unwrap().iter().enumerate() {
            writeln!(&mut stderr, "  {}:{} {}", i, j, core::get_device_info(device, DeviceInfo::Name)).unwrap();
            if all_devices || Some((i, j)) == device_specifier {
                devices.push((platform, device));
                device_specifier = device_specifiers_iter.next();
            }
        }
    }
    stderr.flush().unwrap();

    let mut patterns = if let Some(filename) = matches.value_of("file") {
        let mut file = File::open(&filename).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        contents.lines().map(|s| Pattern::new(s.to_string()).unwrap()).collect()
    } else {
        vec![Pattern::new(matches.value_of("pattern").unwrap_or("").to_string()).unwrap()]
    };

    if matches.is_present("insensitive") {
        patterns = patterns.iter().flat_map(|p| p.case_insensitive()).collect()
    }

    vanity(&devices, &patterns, !matches.is_present("continue"));
}

fn vanity(devices: &[(PlatformId, DeviceId)], patterns: &[Pattern], single_match: bool) {
    let mut pattern_prefixes: Vec<String> = patterns.into_iter().map(|p| p.prefix.to_owned()).collect();
    pattern_prefixes.sort();

    let mut pattern_ranges: Vec<(u64, u64)> = patterns.iter().map(|p| p.range).collect();
    pattern_ranges.sort_by_key(|r| r.0);

    // TODO: the pairs in pattern_words are guaranteed to be sorted, but only by virtue of
    // lexicographic ordering of the default base58 alphabet being equivalent to ordering of the
    // big-endian data.
    let mut pattern_words = vec![];
    let mut search_size = 0;
    for (from, to) in pattern_ranges {
        pattern_words.push(from);
        pattern_words.push(to);
        search_size += to - from;
    }

    let (tx, rx): (Sender<u64>, Receiver<u64>) = mpsc::channel();
    let pattern_prefixes = Arc::new(pattern_prefixes);
    let pattern_words = Arc::new(pattern_words);
    let rng = Arc::new(SystemRandom::new());
    let finished = Arc::new(atomic::AtomicBool::new(false));

    for &device_specifier in devices {
        let tx = tx.clone();
        let pattern_prefixes = pattern_prefixes.clone();
        let pattern_words = pattern_words.clone();
        let rng = rng.clone();
        let finished = finished.clone();
        thread::spawn(move || {
            device::vanity_device(&*finished, &*rng, &tx, device_specifier.0, device_specifier.1, &*pattern_prefixes, &*pattern_words, single_match);
            finished.store(true, atomic::Ordering::Relaxed);
        });
    }
    drop(tx);

    let difficulty = (!0u64 as f64) / (search_size as f64);
    let start = time::Instant::now();
    let mut cumulative_work = 0u64;
    let mut now = start;
    let mut stderr = io::stderr();

    while let Ok(work) = rx.recv() {
        cumulative_work += work;
        let now_elapsed = now.elapsed();

        if now_elapsed.as_secs() > 0 {
            let start_elapsed = start.elapsed();
            let start_elapsed_secs = (start_elapsed.as_secs() as f64) + (start_elapsed.subsec_nanos() as f64 / 1_000_000_000.0);

            let rate = cumulative_work as f64 / (now_elapsed.as_secs() as f64) + (now_elapsed.subsec_nanos() as f64 / 1_000_000_000.0);
            let lambda = rate / difficulty;
            let probability = 1. - (-start_elapsed_secs * lambda).exp();

            write!(&mut stderr, "\rElapsed: {:.0}/{:.0}s Rate: {:.0}/s Prob: {:.2}%", start_elapsed_secs, 1. / lambda, rate, 100. * probability).unwrap();
            stderr.flush().unwrap();

            cumulative_work = 0;
            now += now_elapsed;
        }
    }
}
<commit_msg>Fix off-by-one in rate calculation.<commit_after>extern crate bs58;
extern crate byteorder;
extern crate clap;
extern crate crypto;
extern crate curve25519_dalek;
extern crate ocl_core as core;
extern crate ring;

mod address;
mod device;
mod pattern;
mod sha256;
mod util;

use clap::{App, Arg};
use core::{PlatformInfo, DeviceInfo, DeviceId, PlatformId};
use pattern::Pattern;
use ring::rand::SystemRandom;
use std::{str, thread, time};
use std::fs::File;
use std::io::{self, Read, Write};
use std::sync::Arc;
use std::sync::atomic;
use std::sync::mpsc::{self, Sender, Receiver};

fn main() {
    let matches = App::new("Zcash Vanity Address Generator")
        .version("0.0.1")
        .about("Generates Zcash shielded addresses (\"z-addrs\") that match given prefixes.")
        .arg(Arg::with_name("device")
             .short("d")
             .takes_value(true)
             .number_of_values(1)
             .multiple(true)
             .help("OpenCL device string: <platform>:<device>.\n\
                    Specify multiple times for multiple devices, e.g. -d 0:0 -d 0:1.\n\
                    If not specified, uses all available platforms and devices."))
        .arg(Arg::with_name("file")
             .short("f")
             .takes_value(true)
             .help("Load prefixes from file, one per line.\nCan be combined with --insensitive."))
        .arg(Arg::with_name("insensitive")
             .short("i")
             .help("Case-insensitive prefix search."))
        .arg(Arg::with_name("continue")
             .short("c")
             .help("Continue searching after a match is found."))
        .arg(Arg::with_name("pattern")
             .index(1)
             .help("Zcash address prefix to be matched."))
        .after_help("Matches will be printed to stdout; each z-addr on its own line followed by its spending key on the next line.\n\
                     You can import the spending key to your Zcash client using `zcash-cli z_importkey <key> <rescan>`.")
        .get_matches();

    let mut device_specifiers = if matches.is_present("device") {
        matches.values_of("device").unwrap().map(|s| {
            let mut values = s.split(':');
            (values.next().unwrap().parse::<usize>().unwrap(), values.next().unwrap().parse::<usize>().unwrap())
        }).collect()
    } else {
        vec![]
    };
    device_specifiers.sort();
    let mut device_specifiers_iter = device_specifiers.into_iter();

    let mut stderr = io::stderr();
    let mut devices = vec![];
    let mut device_specifier = device_specifiers_iter.next();
    let all_devices = device_specifier.is_none();
    for (i, &platform) in core::get_platform_ids().unwrap().iter().enumerate() {
        writeln!(&mut stderr, "Available devices on platform {}:", core::get_platform_info(&platform, PlatformInfo::Name)).unwrap();
        for (j, &device) in core::get_device_ids(&platform, None, None).unwrap().iter().enumerate() {
            writeln!(&mut stderr, "  {}:{} {}", i, j, core::get_device_info(device, DeviceInfo::Name)).unwrap();
            if all_devices || Some((i, j)) == device_specifier {
                devices.push((platform, device));
                device_specifier = device_specifiers_iter.next();
            }
        }
    }
    stderr.flush().unwrap();

    let mut patterns = if let Some(filename) = matches.value_of("file") {
        let mut file = File::open(&filename).unwrap();
        let mut contents = String::new();
        file.read_to_string(&mut contents).unwrap();
        contents.lines().map(|s| Pattern::new(s.to_string()).unwrap()).collect()
    } else {
        vec![Pattern::new(matches.value_of("pattern").unwrap_or("").to_string()).unwrap()]
    };

    if matches.is_present("insensitive") {
        patterns = patterns.iter().flat_map(|p| p.case_insensitive()).collect()
    }

    vanity(&devices, &patterns, !matches.is_present("continue"));
}

fn vanity(devices: &[(PlatformId, DeviceId)], patterns: &[Pattern], single_match: bool) {
    let mut pattern_prefixes: Vec<String> = patterns.into_iter().map(|p| p.prefix.to_owned()).collect();
    pattern_prefixes.sort();

    let mut pattern_ranges: Vec<(u64, u64)> = patterns.iter().map(|p| p.range).collect();
    pattern_ranges.sort_by_key(|r| r.0);

    // TODO: the pairs in pattern_words are guaranteed to be sorted, but only by virtue of
    // lexicographic ordering of the default base58 alphabet being equivalent to ordering of the
    // big-endian data.
    let mut pattern_words = vec![];
    let mut search_size = 0;
    for (from, to) in pattern_ranges {
        pattern_words.push(from);
        pattern_words.push(to);
        search_size += 1 + to - from;
    }

    let (tx, rx): (Sender<u64>, Receiver<u64>) = mpsc::channel();
    let pattern_prefixes = Arc::new(pattern_prefixes);
    let pattern_words = Arc::new(pattern_words);
    let rng = Arc::new(SystemRandom::new());
    let finished = Arc::new(atomic::AtomicBool::new(false));

    for &device_specifier in devices {
        let tx = tx.clone();
        let pattern_prefixes = pattern_prefixes.clone();
        let pattern_words = pattern_words.clone();
        let rng = rng.clone();
        let finished = finished.clone();
        thread::spawn(move || {
            device::vanity_device(&*finished, &*rng, &tx, device_specifier.0, device_specifier.1, &*pattern_prefixes, &*pattern_words, single_match);
            finished.store(true, atomic::Ordering::Relaxed);
        });
    }
    drop(tx);

    let difficulty = 2.0_f64.powi(64) / (search_size as f64);
    let start = time::Instant::now();
    let mut cumulative_work = 0u64;
    let mut now = start;
    let mut stderr = io::stderr();

    while let Ok(work) = rx.recv() {
        cumulative_work += work;
        let now_elapsed = now.elapsed();

        if now_elapsed.as_secs() > 0 {
            let start_elapsed = start.elapsed();
            let start_elapsed_secs = (start_elapsed.as_secs() as f64) + (start_elapsed.subsec_nanos() as f64 / 1_000_000_000.0);

            let rate = cumulative_work as f64 / (now_elapsed.as_secs() as f64) + (now_elapsed.subsec_nanos() as f64 / 1_000_000_000.0);
            let lambda = rate / difficulty;
            let probability = 1. - (-start_elapsed_secs * lambda).exp();

            write!(&mut stderr, "\rElapsed: {:.0}/{:.0}s Rate: {:.0}/s Prob: {:.2}%", start_elapsed_secs, 1. / lambda, rate, 100. * probability).unwrap();
            stderr.flush().unwrap();

            cumulative_work = 0;
            now += now_elapsed;
        }
    }
}
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(regex_macros)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(write_to_temp_file(new2));

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
        for attribute in attributes {
            // TODO: do I seriously have to construct a StrTendril here?
            // There has to be a better way.
            if attribute.name.local.as_slice() == "id" &&
                attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                    return true;
                }
        }
        return false;
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

// TODO: rename?
struct WikipediaWithoutWikipedians {
    wiki: Wiki,
    client: Client
}

impl WikipediaWithoutWikipedians {
    fn new(wiki: Wiki, client: Client) -> WikipediaWithoutWikipedians {
        WikipediaWithoutWikipedians { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut revisions = vec![];
        for revision in try!(self.get_vandalism_reversions(&canonical_title)) {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    info!(concat!("For page \"{}\", restored vandalism ",
                                  "https://{}/w/index.php?title={}&diff=prev&oldid={}"),
                          &title, self.wiki.hostname, &canonical_title, revision.revid);
                    accumulated_contents = merged_contents;
                    revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        // TODO: randomize the placeholder string per-request
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT"));
        Ok(page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &body))
    }
}

impl Handler for WikipediaWithoutWikipedians {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => Response::with(
                        (iron::status::InternalServerError, "ERROR")),
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: not good enough. Needs to include query string. Maybe should be moved to wiki module.
            match self.client.get(
                &format!("https://{}/{}", self.wiki.hostname, request.url.path.join("/")))
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        wikipedia_response.read_to_end(&mut wikipedia_body);

                        let mut response = Response::with(wikipedia_body);
                        response.status = Some(wikipedia_response.status);
                        response.headers = wikipedia_response.headers.clone();
                        info!("Forwarded request for {} to {}", request.url.path.join("/"),
                              self.wiki.hostname);
                        Ok(response)
                    },
                    Err(error) => {
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             format!("ERROR: {}", error)));
                        response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let wikipedia_without_wikipedians =
        WikipediaWithoutWikipedians::new(Wiki::new(wiki.to_string(), Client::new()), Client::new());
    Iron::new(wikipedia_without_wikipedians).http(("localhost", port)).unwrap();
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<commit_msg>Rewrite has_matching_id to use Iterator::any().<commit_after>#![feature(plugin)]
#![plugin(regex_macros)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(write_to_temp_file(new2));

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        return attributes.into_iter().any(
            // TODO: do I seriously have to construct a StrTendril here?
            // There has to be a better way.
            |attribute| attribute.name.local.as_slice() == "id" &&
                attribute.value == tendril::StrTendril::from_str(id).unwrap());
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

// TODO: rename?
struct WikipediaWithoutWikipedians {
    wiki: Wiki,
    client: Client
}

impl WikipediaWithoutWikipedians {
    fn new(wiki: Wiki, client: Client) -> WikipediaWithoutWikipedians {
        WikipediaWithoutWikipedians { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut revisions = vec![];
        for revision in try!(self.get_vandalism_reversions(&canonical_title)) {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    info!(concat!("For page \"{}\", restored vandalism ",
                                  "https://{}/w/index.php?title={}&diff=prev&oldid={}"),
                          &title, self.wiki.hostname, &canonical_title, revision.revid);
                    accumulated_contents = merged_contents;
                    revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        // TODO: randomize the placeholder string per-request
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT"));
        Ok(page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &body))
    }
}

impl Handler for WikipediaWithoutWikipedians {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => Response::with(
                        (iron::status::InternalServerError, "ERROR")),
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: not good enough. Needs to include query string. Maybe should be moved to wiki module.
            match self.client.get(
                &format!("https://{}/{}", self.wiki.hostname, request.url.path.join("/")))
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        wikipedia_response.read_to_end(&mut wikipedia_body);

                        let mut response = Response::with(wikipedia_body);
                        response.status = Some(wikipedia_response.status);
                        response.headers = wikipedia_response.headers.clone();
                        info!("Forwarded request for {} to {}", request.url.path.join("/"),
                              self.wiki.hostname);
                        Ok(response)
                    },
                    Err(error) => {
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             format!("ERROR: {}", error)));
                        response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let wikipedia_without_wikipedians =
        WikipediaWithoutWikipedians::new(Wiki::new(wiki.to_string(), Client::new()), Client::new());
    Iron::new(wikipedia_without_wikipedians).http(("localhost", port)).unwrap();
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

#[macro_use]
extern crate clap;
extern crate requests;
extern crate serde;
extern crate serde_json;

use clap::{App, SubCommand, AppSettings, ArgMatches};
mod crates;

const CARGO: &'static str = "cargo";

#[derive(Debug, PartialEq)]
enum Flags {
    Repository,
    Documentation,
    Downloads,
    Homepage,
    Default,
}

#[derive(Debug)]
struct Report {
    flags: Vec<Flags>,
    verbose: bool,
}

impl Report {
    pub fn new(info: &ArgMatches) -> Self {

        let mut flags: Vec<Flags> = vec![];
        if info.is_present("repository") {
            flags.push(Flags::Repository);
        }
        if info.is_present("documentation") {
            flags.push(Flags::Documentation);
        }
        if info.is_present("downloads") {
            flags.push(Flags::Downloads);
        }
        if info.is_present("homepage") {
            flags.push(Flags::Homepage);
        }

        if flags.is_empty() {
            flags.push(Flags::Default);
        }

        Report {
            flags: flags,
            verbose: info.is_present("verbose"),
        }
    }

    pub fn report(&self, name: &str) {
        if let Some(krate) = query(name).map(|r| r.krate) {
            for flag in &self.flags {
                match *flag {
                    Flags::Repository => krate.print_repository(self.verbose),
                    Flags::Documentation => krate.print_documentation(self.verbose),
                    Flags::Downloads => krate.print_downloads(self.verbose),
                    Flags::Homepage => krate.print_homepage(self.verbose),
                        Flags::Default => reportv(&krate, self.verbose),
                }
            }
        }
    }
}

fn reportv(krate: &crates::Crate, verbose: bool) {
    if verbose {
        println!("{:#}", krate);
    } else {
        println!("{}", krate);
    }
}

fn query(krate: &str) -> Option<crates::Reply> {
    let response = requests::get(&format!("http://crates.io/api/v1/crates/{}", krate)).unwrap();
    // println!("{:?}", response.json().unwrap());
    response.from_json::<crates::Reply>()
}

// use std::fmt;
// fn debug<T>(item: &T)
//     where T: fmt::Debug
// {
//     println!("{:#?}", item);
// }

fn main() {

    let matches = App::new(CARGO)
        .bin_name(CARGO)
        .author(crate_authors!())
        .version(crate_version!())
        .about("Queries registry for crates details")
        .setting(AppSettings::GlobalVersion)
        .setting(AppSettings::VersionlessSubcommands)
        .setting(AppSettings::SubcommandRequired)
        .setting(AppSettings::ArgRequiredElseHelp)
        .subcommand(SubCommand::with_name("info")
            .setting(AppSettings::ArgRequiredElseHelp)
            .setting(AppSettings::TrailingVarArg)
            .arg_from_usage("-d, --documentation 'Report documentation URL'")
            .arg_from_usage("-D, --downloads 'Report number of crate downloads'")
            .arg_from_usage("-H, --homepage 'Report home page URL'")
            .arg_from_usage("-r, --repository 'Report crate repository URL'")
            .arg_from_usage("-v, --verbose 'Provides more info'")
            .arg_from_usage("<crate>... 'crate to query'"))
        .get_matches();

    if let Some(info) = matches.subcommand_matches("info") {
        if let Some(crates) = info.values_of("crate") {
            let rep = Report::new(info);
            for krate in crates {
                // debug(&krate);
                rep.report(krate);
            }
        }
    }
}
<commit_msg>Fine tune clap settings<commit_after>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#![feature(custom_derive, plugin)]
#![plugin(serde_macros)]

#[macro_use]
extern crate clap;
extern crate requests;
extern crate serde;
extern crate serde_json;

use clap::{App, SubCommand, AppSettings, ArgMatches};
mod crates;

const CARGO: &'static str = "cargo";

#[derive(Debug, PartialEq)]
enum Flags {
    Repository,
    Documentation,
    Downloads,
    Homepage,
    Default,
}

#[derive(Debug)]
struct Report {
    flags: Vec<Flags>,
    verbose: bool,
}

impl Report {
    pub fn new(info: &ArgMatches) -> Self {

        let mut flags: Vec<Flags> = vec![];
        if info.is_present("repository") {
            flags.push(Flags::Repository);
        }
        if info.is_present("documentation") {
            flags.push(Flags::Documentation);
        }
        if info.is_present("downloads") {
            flags.push(Flags::Downloads);
        }
        if info.is_present("homepage") {
            flags.push(Flags::Homepage);
        }

        if flags.is_empty() {
            flags.push(Flags::Default);
        }

        Report {
            flags: flags,
            verbose: info.is_present("verbose"),
        }
    }

    pub fn report(&self, name: &str) {
        if let Some(krate) = query(name).map(|r| r.krate) {
            for flag in &self.flags {
                match *flag {
                    Flags::Repository => krate.print_repository(self.verbose),
                    Flags::Documentation => krate.print_documentation(self.verbose),
                    Flags::Downloads => krate.print_downloads(self.verbose),
                    Flags::Homepage => krate.print_homepage(self.verbose),
                        Flags::Default => reportv(&krate, self.verbose),
                }
            }
        }
    }
}

fn reportv(krate: &crates::Crate, verbose: bool) {
    if verbose {
        println!("{:#}", krate);
    } else {
        println!("{}", krate);
    }
}

fn query(krate: &str) -> Option<crates::Reply> {
    let response = requests::get(&format!("http://crates.io/api/v1/crates/{}", krate)).unwrap();
    // println!("{:?}", response.json().unwrap());
    response.from_json::<crates::Reply>()
}

// use std::fmt;
// fn debug<T>(item: &T)
//     where T: fmt::Debug
// {
//     println!("{:#?}", item);
// }

fn main() {

    let matches = App::new(CARGO)
        .bin_name(CARGO)
        .author(crate_authors!())
        .version(crate_version!())
        .about("Queries registry for crates details")
        .setting(AppSettings::GlobalVersion)
        .setting(AppSettings::VersionlessSubcommands)
        .setting(AppSettings::SubcommandRequiredElseHelp)
        .subcommand(SubCommand::with_name("info")
            .setting(AppSettings::ArgRequiredElseHelp)
            .setting(AppSettings::TrailingVarArg)
            .arg_from_usage("-d, --documentation 'Report documentation URL'")
            .arg_from_usage("-D, --downloads 'Report number of crate downloads'")
            .arg_from_usage("-H, --homepage 'Report home page URL'")
            .arg_from_usage("-r, --repository 'Report crate repository URL'")
            .arg_from_usage("-v, --verbose 'Provides more info'")
            .arg_from_usage("<crate>... 'crate to query'"))
        .get_matches();

    if let Some(info) = matches.subcommand_matches("info") {
        if let Some(crates) = info.values_of("crate") {
            let rep = Report::new(info);
            for krate in crates {
                // debug(&krate);
                rep.report(krate);
            }
        }
    }
}
<|endoftext|>


<commit_before>use docopt::Docopt;
use eyre::bail;
use serde::Deserialize;
use std::fmt::Write;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Read};
use std::path::Path;
use std::process;

const BUFFER_SIZE: usize = 8 * 1024;
const USAGE: &str = "
Print or check BLAKE2 (512-bit) checksums.
With no FILE, or when FILE is -, read standard input.

Usage:
  b2sum [options] [<filename>]...
  b2sum (-h | --help)
  b2sum --version

Options:
  -c, --check           read BLAKE2 sums from the FILEs and check them
  -l, --length=BITS     digest length in bits; must not exceed the maximum for the
                        blake2 algorithm and must be a multiple of 8 [default: 512]
      --tag             create a BSD-style checksum

The following five options are useful only when verifying checksums:
      --ignore-missing  don't fail or report status for missing files
      --quiet           don't print OK for each successfully verified file
      --status          don't output anything, status code shows success
      --strict          exit non-zero for improperly formatted checksum lines
  -w, --warn            warn about improperly formatted checksum lines

  -h, --help            display this help and exit
      --version         output version information and exit

The sums are computed as described in RFC 7693.  When checking, the input
should be a former output of this program.  The default mode is to print
a line with checksum and name for each FILE.
";

#[derive(Debug, Deserialize)]
struct Args {
    arg_filename: Vec<String>,
    flag_check: bool,
    flag_ignore_missing: bool,
    flag_length: usize,
    flag_quiet: bool,
    flag_status: bool,
    flag_strict: bool,
    flag_tag: bool,
    flag_version: bool,
    flag_warn: bool,
}

fn print_version() -> ! {
    let version = env!("CARGO_PKG_VERSION");
    println!("b2sum-rs {}", version);
    process::exit(0)
}

fn hash_reader<R: Read>(length: usize, mut reader: R) -> eyre::Result<String> {
    let mut digest = blake2b_simd::Params::new().hash_length(length).to_state();
    let mut buffer = [0; BUFFER_SIZE];

    loop {
        match reader.read(&mut buffer) {
            Ok(0) => break,
            Ok(c) => {
                digest.update(&buffer[..c]);
            }
            Err(e) => bail!(e),
        }
    }

    let output = digest.finalize();
    let mut result = String::with_capacity(length * 2);
    for &b in output.as_bytes() {
        write!(&mut result, "{:02x}", b)?;
    }

    Ok(result)
}

fn hash_file<P: AsRef<Path>>(length: usize, path: P) -> eyre::Result<String> {
    let file = File::open(path)?;
    hash_reader(length, file)
}

fn split_check_line(line: &str) -> eyre::Result<(&str, &str)> {
    let hash_length = line.chars().position(|c| !c.is_digit(16)).unwrap_or(0);
    if hash_length < 2 || hash_length % 2 != 0 || hash_length > 128 {
        bail!("Invalid hash length: {}", hash_length);
    }

    let hash = &line[0..hash_length];
    let line = &line[hash_length..];
    if line.len() < 3 {
        bail!("Malformed line");
    }

    let filename = &line[2..];

    Ok((hash, filename))
}

fn check_input<R: BufRead>(args: &Args, check_filename: &str, reader: R) -> eyre::Result<bool> {
    let print_result = !(args.flag_quiet || args.flag_status);
    let mut errors = false;

    for (i, line) in reader.lines().enumerate() {
        let line = line?;
        let line = line.trim();
        if line.starts_with('#') {
            continue;
        }

        let (hash, filename) = match split_check_line(line) {
            Ok((hash, filename)) => (hash, filename),
            Err(e) => {
                if args.flag_strict {
                    errors = true;
                }

                if args.flag_warn {
                    println!("{}:{}: {}", check_filename, i + 1, e)
                }

                continue;
            }
        };

        let length = hash.len() / 2;
        let calculated_hash = match hash_file(length, filename) {
            Ok(h) => h,
            Err(e) => {
                if let Some(io_err) = e.downcast_ref::<io::Error>() {
                    if io_err.kind() == io::ErrorKind::NotFound && args.flag_ignore_missing {
                        continue;
                    }
                }

                errors = true;
                if !args.flag_status {
                    println!("{}: FAILED {}", filename, e);
                }

                continue;
            }
        };

        let matched = hash == calculated_hash;
        if !matched {
            errors = true;
        }

        if print_result {
            print!("{}: ", filename);
            if matched {
                println!("OK");
            } else {
                println!("FAILED");
            }
        }
    }

    Ok(errors)
}

fn check_args(args: Args) -> eyre::Result<i32> {
    let filename = args.arg_filename[0].as_str();
    let errors = if filename == "-" {
        let stdin = io::stdin();
        let reader = stdin.lock();
        check_input(&args, filename, reader)?
    } else {
        let file = File::open(filename)?;
        let reader = BufReader::new(file);
        check_input(&args, filename, reader)?
    };

    let code = if errors { 1 } else { 0 };
    Ok(code)
}

fn hash_args(args: Args) -> eyre::Result<i32> {
    let length = args.flag_length / 8;
    for filename in args.arg_filename {
        let hash = if filename == "-" {
            let stdin = io::stdin();
            hash_reader(length, stdin)?
        } else {
            hash_file(length, &filename)?
        };

        if args.flag_tag {
            print!("BLAKE2b");
            if args.flag_length < 512 {
                print!("-{}", args.flag_length);
            }

            println!(" ({}) = {}", filename, hash);
        } else {
            println!("{}  {}", hash, filename);
        }
    }

    Ok(0)
}

fn main() -> eyre::Result<()> {
    color_eyre::install()?;

    let mut args: Args = Docopt::new(USAGE).and_then(|d| d.deserialize()).unwrap_or_else(|e| e.exit());

    if args.flag_version {
        print_version();
    }

    if args.arg_filename.is_empty() {
        args.arg_filename.push("-".to_string());
    }

    let result = if args.flag_check { check_args(args)? } else { hash_args(args)? };

    process::exit(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn split_check_line_with_valid_line() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534  test";
        let (hash, filename) = split_check_line(line).unwrap();
        assert_eq!(
            "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534",
            hash
        );
        assert_eq!("test", filename);
    }

    #[test]
    fn split_check_line_with_truncated_line() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534 ";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Malformed line", result.to_string());
    }

    #[test]
    fn split_check_line_with_missing_filename() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534  ";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Malformed line", result.to_string());
    }

    #[test]
    fn split_check_line_with_too_small_hash() {
        let line = "c  test";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Invalid hash length: 1", result.to_string());
    }

    #[test]
    fn split_check_line_with_too_long_hash() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea2353400  test";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Invalid hash length: 130", result.to_string());
    }

    #[test]
    fn split_check_line_with_non_even_hash() {
        let line = "c0ae0  test";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Invalid hash length: 5", result.to_string());
    }

    #[test]
    fn test_hash_formatting() {
        let expected = "7ea59e7a000ec003846b6607dfd5f9217b681dc1a81b0789b464c3995105d93083f7f0a86fca01a1bed27e9f9303ae58d01746e3b20443480bea56198e65bfc5";
        assert_eq!(expected, hash_reader(64, "hi\n".as_bytes()).unwrap());
    }
}
<commit_msg>Switch to BufReader for buffering<commit_after>use docopt::Docopt;
use eyre::bail;
use serde::Deserialize;
use std::fs::File;
use std::io::{self, BufRead, BufReader};
use std::path::Path;
use std::process;

const USAGE: &str = "
Print or check BLAKE2 (512-bit) checksums.
With no FILE, or when FILE is -, read standard input.

Usage:
  b2sum [options] [<filename>]...
  b2sum (-h | --help)
  b2sum --version

Options:
  -c, --check           read BLAKE2 sums from the FILEs and check them
  -l, --length=BITS     digest length in bits; must not exceed the maximum for the
                        blake2 algorithm and must be a multiple of 8 [default: 512]
      --tag             create a BSD-style checksum

The following five options are useful only when verifying checksums:
      --ignore-missing  don't fail or report status for missing files
      --quiet           don't print OK for each successfully verified file
      --status          don't output anything, status code shows success
      --strict          exit non-zero for improperly formatted checksum lines
  -w, --warn            warn about improperly formatted checksum lines

  -h, --help            display this help and exit
      --version         output version information and exit

The sums are computed as described in RFC 7693.  When checking, the input
should be a former output of this program.  The default mode is to print
a line with checksum and name for each FILE.
";

#[derive(Debug, Deserialize)]
struct Args {
    arg_filename: Vec<String>,
    flag_check: bool,
    flag_ignore_missing: bool,
    flag_length: usize,
    flag_quiet: bool,
    flag_status: bool,
    flag_strict: bool,
    flag_tag: bool,
    flag_version: bool,
    flag_warn: bool,
}

fn print_version() -> ! {
    let version = env!("CARGO_PKG_VERSION");
    println!("b2sum-rs {}", version);
    process::exit(0)
}

fn hash_reader<R>(length: usize, mut reader: R) -> eyre::Result<String>
where
    R: BufRead,
{
    let mut digest = blake2b_simd::Params::new().hash_length(length).to_state();

    loop {
        let count = {
            let data = reader.fill_buf()?;
            if data.is_empty() {
                break;
            }

            digest.update(data);
            data.len()
        };

        reader.consume(count);
    }

    let output = digest.finalize();
    let result = output.to_hex().to_ascii_lowercase();

    Ok(result)
}

fn hash_file<P>(length: usize, path: P) -> eyre::Result<String>
where
    P: AsRef<Path>,
{
    let file = File::open(path)?;
    let reader = BufReader::new(file);
    hash_reader(length, reader)
}

fn split_check_line(line: &str) -> eyre::Result<(&str, &str)> {
    let hash_length = line.chars().position(|c| !c.is_digit(16)).unwrap_or(0);
    if hash_length < 2 || hash_length % 2 != 0 || hash_length > 128 {
        bail!("Invalid hash length: {}", hash_length);
    }

    let hash = &line[0..hash_length];
    let line = &line[hash_length..];
    if line.len() < 3 {
        bail!("Malformed line");
    }

    let filename = &line[2..];

    Ok((hash, filename))
}

fn check_input<R>(args: &Args, check_filename: &str, reader: R) -> eyre::Result<bool>
where
    R: BufRead,
{
    let print_result = !(args.flag_quiet || args.flag_status);
    let mut errors = false;

    for (i, line) in reader.lines().enumerate() {
        let line = line?;
        let line = line.trim();
        if line.starts_with('#') {
            continue;
        }

        let (hash, filename) = match split_check_line(line) {
            Ok((hash, filename)) => (hash, filename),
            Err(e) => {
                if args.flag_strict {
                    errors = true;
                }

                if args.flag_warn {
                    println!("{}:{}: {}", check_filename, i + 1, e)
                }

                continue;
            }
        };

        let length = hash.len() / 2;
        let calculated_hash = match hash_file(length, filename) {
            Ok(h) => h,
            Err(e) => {
                if let Some(io_err) = e.downcast_ref::<io::Error>() {
                    if io_err.kind() == io::ErrorKind::NotFound && args.flag_ignore_missing {
                        continue;
                    }
                }

                errors = true;
                if !args.flag_status {
                    println!("{}: FAILED {}", filename, e);
                }

                continue;
            }
        };

        let matched = hash == calculated_hash;
        if !matched {
            errors = true;
        }

        if print_result {
            print!("{}: ", filename);
            if matched {
                println!("OK");
            } else {
                println!("FAILED");
            }
        }
    }

    Ok(errors)
}

fn check_args(args: Args) -> eyre::Result<i32> {
    let filename = args.arg_filename[0].as_str();
    let errors = if filename == "-" {
        let stdin = io::stdin();
        check_input(&args, filename, stdin.lock())?
    } else {
        let file = File::open(filename)?;
        let reader = BufReader::new(file);
        check_input(&args, filename, reader)?
    };

    let code = if errors { 1 } else { 0 };
    Ok(code)
}

fn hash_args(args: Args) -> eyre::Result<i32> {
    let length = args.flag_length / 8;
    for filename in args.arg_filename {
        let hash = if filename == "-" {
            let stdin = io::stdin();
            hash_reader(length, stdin.lock())?
        } else {
            hash_file(length, &filename)?
        };

        if args.flag_tag {
            print!("BLAKE2b");
            if args.flag_length < 512 {
                print!("-{}", args.flag_length);
            }

            println!(" ({}) = {}", filename, hash);
        } else {
            println!("{}  {}", hash, filename);
        }
    }

    Ok(0)
}

fn main() -> eyre::Result<()> {
    color_eyre::install()?;

    let mut args: Args = Docopt::new(USAGE).and_then(|d| d.deserialize()).unwrap_or_else(|e| e.exit());
    if args.flag_version {
        print_version();
    }

    if args.arg_filename.is_empty() {
        args.arg_filename.push("-".to_string());
    }

    let result = if args.flag_check { check_args(args)? } else { hash_args(args)? };
    process::exit(result)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn split_check_line_with_valid_line() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534  test";
        let (hash, filename) = split_check_line(line).unwrap();
        assert_eq!(
            "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534",
            hash
        );
        assert_eq!("test", filename);
    }

    #[test]
    fn split_check_line_with_truncated_line() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534 ";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Malformed line", result.to_string());
    }

    #[test]
    fn split_check_line_with_missing_filename() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea23534  ";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Malformed line", result.to_string());
    }

    #[test]
    fn split_check_line_with_too_small_hash() {
        let line = "c  test";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Invalid hash length: 1", result.to_string());
    }

    #[test]
    fn split_check_line_with_too_long_hash() {
        let line = "c0ae24f806df19d850565b234bc37afd5035e7536388290db9413c98578394313f38b093143ecfbc208425d54b9bfef0d9917a9e93910f7914a97e73fea2353400  test";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Invalid hash length: 130", result.to_string());
    }

    #[test]
    fn split_check_line_with_non_even_hash() {
        let line = "c0ae0  test";
        let result = split_check_line(line).unwrap_err();
        assert_eq!("Invalid hash length: 5", result.to_string());
    }

    #[test]
    fn test_hash_formatting() {
        let expected = "7ea59e7a000ec003846b6607dfd5f9217b681dc1a81b0789b464c3995105d93083f7f0a86fca01a1bed27e9f9303ae58d01746e3b20443480bea56198e65bfc5";
        assert_eq!(expected, hash_reader(64, "hi\n".as_bytes()).unwrap());
    }
}
<|endoftext|>