<commit_before>#![allow(non_snake_case)]

extern crate merfishtools;
extern crate argparse;

use std::io::{stdout, stderr};
use std::str::FromStr;
use argparse::{ArgumentParser, Store, List, StoreTrue, StoreOption};

#[macro_use]
extern crate log;
extern crate fern;
extern crate bio;
extern crate csv;
extern crate itertools;
extern crate num;
extern crate rustc_serialize;
extern crate simple_parallel;
extern crate crossbeam;
extern crate regex;
extern crate rgsl;

pub mod model;
pub mod io;
pub mod cli;

#[allow(non_camel_case_types)]
#[derive(Debug)]
enum Command {
    exp,
    diffexp,
    //stats,
    None
}

impl FromStr for Command {
    type Err = ();

    fn from_str(src: &str) -> Result<Command, ()> {
        return match src {
            "exp"     => Ok(Command::exp),
            "diffexp" => Ok(Command::diffexp),
            //"stats"   => Ok(Command::stats),
            _         => Err(()),
        };
    }
}

fn main() {
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg: &str, level: &log::LogLevel, _: &log::LogLocation| {
            match level {
                &log::LogLevel::Debug => format!("DEBUG: {}", msg),
                _ => msg.to_owned()
            }
        }),
        output: vec![fern::OutputConfig::stderr()],
        level: log::LogLevelFilter::Debug,
    };

    let mut debug = false;
    let mut subcommand = Command::None;
    let mut args = vec![];
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("MERFISHtools is an analysis toolkit for MERFISH data.");
        ap.refer(&mut debug).add_option(&["--debug"], StoreTrue, "Print debugging information.");
        ap.refer(&mut subcommand).required()
            .add_argument("command", Store, "Command to run (exp or diffexp)");
        ap.refer(&mut args)
            .add_argument("arguments", List, "Arguments for command");
        ap.stop_on_first_argument(true);
        ap.parse_args_or_exit();
    }

    if let Err(e) = fern::init_global_logger(
        logger_config,
        if debug { log::LogLevelFilter::Debug } else { log::LogLevelFilter::Info }
    ) {
        panic!("Failed to initialize global logger: {}", e);
    }

    args.insert(0, format!("subcommand {:?}", subcommand));
    match subcommand {
        Command::exp     => exp(args),
        Command::diffexp => diffexp(args),
        //Command::stats   => stats(args),
        Command::None    => {
            error!("Unknown subcommand.");
            std::process::exit(1);
        }
    }
}

fn exp(args: Vec<String>) {
    let mut N = 16;
    let mut m = 4;
    let mut p0 = 0.04;
    let mut p1 = 0.1;
    let mut dist = 4;
    let mut threads = 1;
    let mut estimate_path = None;
    let mut codebook_path: Option<String> = None;
    let mut cells = ".*".to_owned();
    let mut window_width = 100;

    {
        let mut ap = ArgumentParser::new();
        ap.set_description(
r#"For given MERFISH data, calculate expressions for each feature (e.g. gene) in each cell.
Results are provided as PMF (probability mass function) in columns:

    cell
    feature (e.g. gene, rna)
    expression
    posterior probability

Example: 'merfishtools exp < data.txt > expression.txt'"#
        );

        ap.refer(&mut codebook_path).add_option(&["--codebook", "-c"], StoreOption, r#"
Path to codebook definition consisting of tab separated columns: feature, codeword, expressed.
The last column denotes if a codeword is assigned to e.g. a gene for which expression can be expected.
Unless you have misidentification probes (see Chen et al. Science 2015), you will have only ones in this column.
"#).required();
        ap.refer(&mut estimate_path).add_option(&["--estimate"], StoreOption, r#"
Path to write expected value and standard deviation estimates of expression to.
Output is formatted into columns: cell, feature, expected value, standard deviation
"#);
        ap.refer(&mut N).add_option(&["-N"], Store, "Number of bits in readout, i.e., number of hybridization rounds (default: 16).");
        ap.refer(&mut m).add_option(&["-m"], Store, "Number of 1-bits in readout (default: 4).");
        ap.refer(&mut p0).add_option(&["--p0"], Store, "Prior probability of 0->1 error (default: 0.04).");
        ap.refer(&mut p1).add_option(&["--p1"], Store, "Prior probability of 1->0 error (default: 0.1).");
        ap.refer(&mut dist).add_option(&["--hamming-dist", "--dist"], Store, "Hamming distance between encodings (default: 4).");
        ap.refer(&mut threads)
          .add_option(&["--threads", "-t"], Store, "Number of threads to use.");
        ap.refer(&mut cells).add_option(&["--cells"], Store, "Regular expression for cells to select (default: all).");
        ap.refer(&mut window_width).add_option(&["--pmf-window-width"], Store, "Width of the window to calculate PMF for (default: 100).");
        parse_args_or_exit(&ap, args);
    }
    cli::expression(N, m, p0, p1, dist, &codebook_path.unwrap(), estimate_path, threads, &cells, window_width);
}

fn diffexp(args: Vec<String>) {
    let mut threads = 1;
    let mut group1_path = "".to_owned();
    let mut group2_path = "".to_owned();
    let mut pmf_path: Option<String> = None;
    let mut min_fc = 1.0f64.log2();

    {
        let mut ap = ArgumentParser::new();
        ap.set_description(
r#"For given MERFISH data, calculate differentially expressed features (e.g. genes) between groups of cells given as separate input data.
Results are provided as columns:

    feature (e.g. gene, rna)
    posterior error probability (PEP) for differential expression
    expected log2 fold change
    standard deviation of log2 fold change

Example: "merfishtools diffexp data1.txt data2.txt > diffexp.txt""#
        );

        ap.refer(&mut pmf_path).add_option(&["--pmf"], StoreOption,
r#"Path to write PMF (probability mass function) of Log2 fold change to.
Output is formatted into columns: feature, foldchange, posterior probability"#);
        ap.refer(&mut threads)
          .add_option(&["--threads", "-t"], Store, "Number of threads to use (default: 1).");
        ap.refer(&mut min_fc)
          .add_option(&["--min-log2fc"], Store, "Minimum absolute log2 fold change considered as differential expression (default: log2(1.5)).");
        ap.refer(&mut group1_path).required()
          .add_argument("group1", Store, "Path to expression PMFs for group of cells.");
        ap.refer(&mut group2_path).required()
          .add_argument("group2", Store, "Path to expression PMFs for group of cells.");
        parse_args_or_exit(&ap, args);

    }
    cli::differential_expression(&group1_path, &group2_path, pmf_path, min_fc, threads);
}

/*fn stats(args: Vec<String>) {
    let mut N = 16;
    let mut m = 4;
    let mut p0 = 0.04;
    let mut p1 = 0.1;
    let mut dist = 4;

    {
        let mut ap = ArgumentParser::new();
        ap.set_description(
r#"For given MERFISH data, calculate per cell statistics.
Results are provided in columns:

cell
expected total expression

Example: 'merfishtools stats < stats.txt > expression.txt'"#
        );

        ap.refer(&mut N).add_option(&["-N"], Store, "Number of bits in readout, i.e., number of hybridization rounds (default: 16).");
        ap.refer(&mut m).add_option(&["-m"], Store, "Number of 1-bits in readout (default: 4).");
        ap.refer(&mut p0).add_option(&["--p0"], Store, "Prior probability of 0->1 error (default: 0.04).");
        ap.refer(&mut p1).add_option(&["--p1"], Store, "Prior probability of 1->0 error (default: 0.1).");
        ap.refer(&mut dist).add_option(&["--hamming-dist", "--dist"], Store, "Hamming distance between encodings (default: 4).");
        parse_args_or_exit(&ap, args);
    }
    cli::stats(N, m, p0, p1, dist);
}*/

fn parse_args_or_exit(ap: &ArgumentParser, args: Vec<String>) {
    match ap.parse(args, &mut stdout(), &mut stderr()) {
        Ok(()) =>  {}
        Err(x) => {
            std::process::exit(x);
        }
    }
}
<commit_msg>Help text.<commit_after>#![allow(non_snake_case)]

extern crate merfishtools;
extern crate argparse;

use std::io::{stdout, stderr};
use std::str::FromStr;
use argparse::{ArgumentParser, Store, List, StoreTrue, StoreOption};

#[macro_use]
extern crate log;
extern crate fern;
extern crate bio;
extern crate csv;
extern crate itertools;
extern crate num;
extern crate rustc_serialize;
extern crate simple_parallel;
extern crate crossbeam;
extern crate regex;
extern crate rgsl;

pub mod model;
pub mod io;
pub mod cli;

#[allow(non_camel_case_types)]
#[derive(Debug)]
enum Command {
    exp,
    diffexp,
    //stats,
    None
}

impl FromStr for Command {
    type Err = ();

    fn from_str(src: &str) -> Result<Command, ()> {
        return match src {
            "exp"     => Ok(Command::exp),
            "diffexp" => Ok(Command::diffexp),
            //"stats"   => Ok(Command::stats),
            _         => Err(()),
        };
    }
}

fn main() {
    let logger_config = fern::DispatchConfig {
        format: Box::new(|msg: &str, level: &log::LogLevel, _: &log::LogLocation| {
            match level {
                &log::LogLevel::Debug => format!("DEBUG: {}", msg),
                _ => msg.to_owned()
            }
        }),
        output: vec![fern::OutputConfig::stderr()],
        level: log::LogLevelFilter::Debug,
    };

    let mut debug = false;
    let mut subcommand = Command::None;
    let mut args = vec![];
    {
        let mut ap = ArgumentParser::new();
        ap.set_description("MERFISHtools is an analysis toolkit for MERFISH data.");
        ap.refer(&mut debug).add_option(&["--debug"], StoreTrue, "Print debugging information.");
        ap.refer(&mut subcommand).required()
            .add_argument("command", Store, "Command to run (exp or diffexp)");
        ap.refer(&mut args)
            .add_argument("arguments", List, "Arguments for command");
        ap.stop_on_first_argument(true);
        ap.parse_args_or_exit();
    }

    if let Err(e) = fern::init_global_logger(
        logger_config,
        if debug { log::LogLevelFilter::Debug } else { log::LogLevelFilter::Info }
    ) {
        panic!("Failed to initialize global logger: {}", e);
    }

    args.insert(0, format!("subcommand {:?}", subcommand));
    match subcommand {
        Command::exp     => exp(args),
        Command::diffexp => diffexp(args),
        //Command::stats   => stats(args),
        Command::None    => {
            error!("Unknown subcommand.");
            std::process::exit(1);
        }
    }
}

fn exp(args: Vec<String>) {
    let mut N = 16;
    let mut m = 4;
    let mut p0 = 0.04;
    let mut p1 = 0.1;
    let mut dist = 4;
    let mut threads = 1;
    let mut estimate_path = None;
    let mut codebook_path: Option<String> = None;
    let mut cells = ".*".to_owned();
    let mut window_width = 100;

    {
        let mut ap = ArgumentParser::new();
        ap.set_description(
r#"For given MERFISH data, calculate expressions for each feature (e.g. gene) in each cell.
Results are provided as PMF (probability mass function) in columns:

    cell
    feature (e.g. gene, rna)
    expression
    posterior probability

Example: 'merfishtools exp < data.txt > expression.txt'"#
        );

        ap.refer(&mut codebook_path).add_option(&["--codebook", "-c"], StoreOption, r#"
Path to codebook definition consisting of tab separated columns: feature, codeword, expressed.
The last column denotes if a codeword is assigned to e.g. a gene for which expression can be expected.
Unless you have misidentification probes (see Chen et al. Science 2015), you will have only ones in this column.
"#).required();
        ap.refer(&mut estimate_path).add_option(&["--estimate"], StoreOption, r#"
Path to write expected value and standard deviation estimates of expression to.
Output is formatted into columns: cell, feature, expected value, standard deviation
"#);
        ap.refer(&mut N).add_option(&["-N"], Store, "Number of bits in readout, i.e., number of hybridization rounds (default: 16).");
        ap.refer(&mut m).add_option(&["-m"], Store, "Number of 1-bits in readout (default: 4).");
        ap.refer(&mut p0).add_option(&["--p0"], Store, "Prior probability of 0->1 error (default: 0.04).");
        ap.refer(&mut p1).add_option(&["--p1"], Store, "Prior probability of 1->0 error (default: 0.1).");
        ap.refer(&mut dist).add_option(&["--hamming-dist", "--dist"], Store, "Hamming distance between encodings (default: 4).");
        ap.refer(&mut threads)
          .add_option(&["--threads", "-t"], Store, "Number of threads to use.");
        ap.refer(&mut cells).add_option(&["--cells"], Store, "Regular expression for cells to select (default: all).");
        ap.refer(&mut window_width).add_option(&["--pmf-window-width"], Store, "Width of the window to calculate PMF for (default: 100).");
        parse_args_or_exit(&ap, args);
    }
    cli::expression(N, m, p0, p1, dist, &codebook_path.unwrap(), estimate_path, threads, &cells, window_width);
}

fn diffexp(args: Vec<String>) {
    let mut threads = 1;
    let mut group1_path = "".to_owned();
    let mut group2_path = "".to_owned();
    let mut pmf_path: Option<String> = None;
    let mut min_fc = 1.0f64.log2();

    {
        let mut ap = ArgumentParser::new();
        ap.set_description(
r#"For given MERFISH data, calculate differentially expressed features (e.g. genes) between groups of cells given as separate input data.
Results are provided as columns:

    feature (e.g. gene, rna)
    posterior error probability (PEP) for differential expression
    expected log2 fold change of first vs second group
    standard deviation of log2 fold change

Example: "merfishtools diffexp data1.txt data2.txt > diffexp.txt""#
        );

        ap.refer(&mut pmf_path).add_option(&["--pmf"], StoreOption,
r#"Path to write PMF (probability mass function) of Log2 fold change to.
Output is formatted into columns: feature, foldchange, posterior probability"#);
        ap.refer(&mut threads)
          .add_option(&["--threads", "-t"], Store, "Number of threads to use (default: 1).");
        ap.refer(&mut min_fc)
          .add_option(&["--min-log2fc"], Store, "Minimum absolute log2 fold change considered as differential expression (default: log2(1.5)).");
        ap.refer(&mut group1_path).required()
          .add_argument("group1", Store, "Path to expression PMFs for group of cells.");
        ap.refer(&mut group2_path).required()
          .add_argument("group2", Store, "Path to expression PMFs for group of cells.");
        parse_args_or_exit(&ap, args);

    }
    cli::differential_expression(&group1_path, &group2_path, pmf_path, min_fc, threads);
}

/*fn stats(args: Vec<String>) {
    let mut N = 16;
    let mut m = 4;
    let mut p0 = 0.04;
    let mut p1 = 0.1;
    let mut dist = 4;

    {
        let mut ap = ArgumentParser::new();
        ap.set_description(
r#"For given MERFISH data, calculate per cell statistics.
Results are provided in columns:

cell
expected total expression

Example: 'merfishtools stats < stats.txt > expression.txt'"#
        );

        ap.refer(&mut N).add_option(&["-N"], Store, "Number of bits in readout, i.e., number of hybridization rounds (default: 16).");
        ap.refer(&mut m).add_option(&["-m"], Store, "Number of 1-bits in readout (default: 4).");
        ap.refer(&mut p0).add_option(&["--p0"], Store, "Prior probability of 0->1 error (default: 0.04).");
        ap.refer(&mut p1).add_option(&["--p1"], Store, "Prior probability of 1->0 error (default: 0.1).");
        ap.refer(&mut dist).add_option(&["--hamming-dist", "--dist"], Store, "Hamming distance between encodings (default: 4).");
        parse_args_or_exit(&ap, args);
    }
    cli::stats(N, m, p0, p1, dist);
}*/

fn parse_args_or_exit(ap: &ArgumentParser, args: Vec<String>) {
    match ap.parse(args, &mut stdout(), &mut stderr()) {
        Ok(()) =>  {}
        Err(x) => {
            std::process::exit(x);
        }
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(test, allow(dead_code))]

mod byte_conversion;
mod byte_manipulation;
mod english_scoring;
mod general_utilities;

fn guess_single_xor_char_decode(bytes: &Vec<u8>) -> (u16, Vec<u8>) {
	let mut top_score = 0;
	let mut top_decode = vec![];
	for x in 0..255 {
		let mut spin: Vec<u8> = vec![];
		while spin.len() < bytes.len() {
			spin.push(x);
		}

		let decoded = byte_manipulation::xor_byte_streams(&bytes, &spin);
		let score = english_scoring::score_combined(&decoded);
		if score > top_score {
			top_score = score;
			top_decode = decoded;
		}
	}

	(top_score, top_decode)
}

fn problem_3() {
	// http://cryptopals.com/sets/1/challenges/3/
	use byte_conversion::*;
	let encoded = hex_to_bytes("1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736");

	let (top_score, top_decode) = guess_single_xor_char_decode(&encoded);

	println!("Best guess: `{}`, with a score of {}.", bytes_to_readable_text(&top_decode), top_score);
}

fn problem_4() {
	// http://cryptopals.com/sets/1/challenges/4/

	use byte_conversion::*;

	let s = general_utilities::read_file("C:\\Users\\Topher\\Dropbox\\Public\\Programming\\Matasano\\matasano-crypto-challenges\\res\\4.txt");

	let mut top_score = 0;
	let mut top_decode = vec![];
    for line in s.split("\n") {
    	let (score, decoded) = guess_single_xor_char_decode(&hex_to_bytes(line));

		if score > top_score {
			top_score = score;
			top_decode = decoded.clone();
		}

    }

    println!("\n\n\n{} -      `{}`", top_score, bytes_to_readable_text(&top_decode));
}

fn problem_6() {
	// http://cryptopals.com/sets/1/challenges/6/

	use byte_conversion::*;

	let file_data = general_utilities::read_file("C:\\Users\\Topher\\Dropbox\\Public\\Programming\\Matasano\\matasano-crypto-challenges\\res\\6.txt");
	let data_bytes = readable_text_to_bytes(&file_data);
	for keysize in 2..40 {

		//let  Vec<u8>
	}
}

fn main() {
	println!("Running.");

	problem_6();
}<commit_msg>Deleting main<commit_after><|endoftext|>


<commit_before>#[macro_use] extern crate log;
extern crate env_logger;
extern crate futures;
extern crate getopts;
extern crate librespot;
extern crate tokio_core;
extern crate tokio_signal;

use env_logger::LogBuilder;
use futures::{Future, Async, Poll, Stream};
use std::env;
use std::io::{self, stderr, Write};
use std::path::PathBuf;
use std::process::exit;
use std::str::FromStr;
use tokio_core::reactor::{Handle, Core};
use tokio_core::io::IoStream;
use std::mem;

use librespot::spirc::{Spirc, SpircTask};
use librespot::authentication::{get_credentials, Credentials};
use librespot::authentication::discovery::{discovery, DiscoveryStream};
use librespot::audio_backend::{self, Sink, BACKENDS};
use librespot::cache::Cache;
use librespot::player::Player;
use librespot::session::{Bitrate, Config, Session};
use librespot::mixer::{self, Mixer};

use librespot::version;

fn usage(program: &str, opts: &getopts::Options) -> String {
    let brief = format!("Usage: {} [options]", program);
    opts.usage(&brief)
}

fn setup_logging(verbose: bool) {
    let mut builder = LogBuilder::new();
    match env::var("RUST_LOG") {
        Ok(config) => {
            builder.parse(&config);
            builder.init().unwrap();

            if verbose {
                warn!("`--verbose` flag overidden by `RUST_LOG` environment variable");
            }
        }
        Err(_) => {
            if verbose {
                builder.parse("mdns=info,librespot=trace");
            } else {
                builder.parse("mdns=info,librespot=info");
            }
            builder.init().unwrap();
        }
    }
}

fn list_backends() {
    println!("Available Backends : ");
    for (&(name, _), idx) in BACKENDS.iter().zip(0..) {
        if idx == 0 {
            println!("- {} (default)", name);
        } else {
            println!("- {}", name);
        }
    }
}

#[derive(Clone)]
struct Setup {
    backend: fn(Option<String>) -> Box<Sink>,
    device: Option<String>,

    mixer: fn() -> Box<Mixer>,

    cache: Option<Cache>,
    config: Config,
    credentials: Option<Credentials>,
    enable_discovery: bool,
}

fn setup(args: &[String]) -> Setup {
    let mut opts = getopts::Options::new();
    opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
        .reqopt("n", "name", "Device name", "NAME")
        .optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
        .optopt("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM")
        .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM")
        .optflag("v", "verbose", "Enable verbose output")
        .optopt("u", "username", "Username to sign in with", "USERNAME")
        .optopt("p", "password", "Password", "PASSWORD")
        .optflag("", "disable-discovery", "Disable discovery mode")
        .optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
        .optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE")
        .optopt("", "mixer", "Mixer to use", "MIXER");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap();
            exit(1);
        }
    };

    let verbose = matches.opt_present("verbose");
    setup_logging(verbose);

    info!("librespot {} ({}). Built on {}.",
             version::short_sha(),
             version::commit_date(),
             version::short_now());

    let backend_name = matches.opt_str("backend");
    if backend_name == Some("?".into()) {
        list_backends();
        exit(0);
    }

    let backend = audio_backend::find(backend_name.as_ref())
        .expect("Invalid backend");

    let mixer_name = matches.opt_str("mixer");
    let mixer = mixer::find(mixer_name.as_ref())
        .expect("Invalid mixer");

    let bitrate = matches.opt_str("b").as_ref()
        .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
        .unwrap_or(Bitrate::Bitrate160);

    let name = matches.opt_str("name").unwrap();
    let device_id = librespot::session::device_id(&name);

    let cache = matches.opt_str("c").map(|cache_location| {
        Cache::new(PathBuf::from(cache_location))
    });

    let cached_credentials = cache.as_ref().and_then(Cache::credentials);

    let credentials = get_credentials(matches.opt_str("username"),
                                      matches.opt_str("password"),
                                      cached_credentials);

    let enable_discovery = !matches.opt_present("disable-discovery");

    let config = Config {
        user_agent: version::version_string(),
        name: name,
        device_id: device_id,
        bitrate: bitrate,
        onstart: matches.opt_str("onstart"),
        onstop: matches.opt_str("onstop"),
    };

    let device = matches.opt_str("device");

    Setup {
        backend: backend,
        cache: cache,
        config: config,
        credentials: credentials,
        device: device,
        enable_discovery: enable_discovery,
        mixer: mixer,
    }
}

struct Main {
    cache: Option<Cache>,
    config: Config,
    backend: fn(Option<String>) -> Box<Sink>,
    device: Option<String>,
    mixer: fn() -> Box<Mixer>,
    handle: Handle,

    discovery: Option<DiscoveryStream>,
    signal: IoStream<()>,

    spirc: Option<Spirc>,
    spirc_task: Option<SpircTask>,
    connect: Box<Future<Item=Session, Error=io::Error>>,

    shutdown: bool,
}

impl Main {
    fn new(handle: Handle,
           config: Config,
           backend: fn(Option<String>) -> Box<Sink>,
           device: Option<String>,
           mixer: fn() -> Box<Mixer>) -> Main
    {
        Main {
            handle: handle.clone(),
            config: config,
            backend: backend,
            device: device,
            mixer: mixer,

            cache: None,
            connect: Box::new(futures::future::empty()),
            discovery: None,
            spirc: None,
            spirc_task: None,
            shutdown: false,
            signal: tokio_signal::ctrl_c(&handle).flatten_stream().boxed(),
        }
    }

    fn discovery(&mut self) {
        let name = self.config.name.clone();
        let device_id = self.config.device_id.clone();
        self.discovery = Some(discovery(&self.handle, name, device_id).unwrap());
    }

    fn credentials(&mut self, credentials: Credentials) {
        let config = self.config.clone();
        let handle = self.handle.clone();
        let connection = Session::connect(config, credentials, self.cache.clone(), handle);

        self.connect = connection;
        self.spirc = None;
        let task = mem::replace(&mut self.spirc_task, None);
        if let Some(task) = task {
            self.handle.spawn(task);
        }
    }

    fn cache(&mut self, cache: Cache) {
        self.cache = Some(cache);
    }
}

impl Future for Main {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<(), ()> {
        loop {
            let mut progress = false;

            if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) {
                if let Some(ref spirc) = self.spirc {
                    spirc.shutdown();
                }
                self.credentials(creds);

                progress = true;
            }

            if let Async::Ready(session) = self.connect.poll().unwrap() {
                self.connect = Box::new(futures::future::empty());
                let device = self.device.clone();
                let mixer = (self.mixer)();

                let audio_filter = mixer.get_audio_filter();
                let backend = self.backend;
                let player = Player::new(session.clone(), audio_filter, move || {
                    (backend)(device)
                });

                let (spirc, spirc_task) = Spirc::new(session, player, mixer);
                self.spirc = Some(spirc);
                self.spirc_task = Some(spirc_task);

                progress = true;
            }

            if let Async::Ready(Some(())) = self.signal.poll().unwrap() {
                if !self.shutdown {
                    if let Some(ref spirc) = self.spirc {
                        spirc.shutdown();
                    }
                    self.shutdown = true;
                } else {
                    return Ok(Async::Ready(()));
                }

                progress = true;
            }

            if let Some(ref mut spirc_task) = self.spirc_task {
                if let Async::Ready(()) = spirc_task.poll().unwrap() {
                    if self.shutdown {
                        return Ok(Async::Ready(()));
                    } else {
                        panic!("Spirc shut down unexpectedly");
                    }
                }
            }

            if !progress {
                return Ok(Async::NotReady);
            }
        }
    }
}

fn main() {
    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let args: Vec<String> = std::env::args().collect();
    let Setup { backend, config, device, cache, enable_discovery, credentials, mixer } = setup(&args);

    let mut task = Main::new(handle, config.clone(), backend, device, mixer);
    if enable_discovery {
        task.discovery();
    }
    if let Some(credentials) = credentials {
        task.credentials(credentials);
    }
    if let Some(cache) = cache {
        task.cache(cache);
    }

    core.run(task).unwrap()
}
<commit_msg>main: Fix cache setup<commit_after>#[macro_use] extern crate log;
extern crate env_logger;
extern crate futures;
extern crate getopts;
extern crate librespot;
extern crate tokio_core;
extern crate tokio_signal;

use env_logger::LogBuilder;
use futures::{Future, Async, Poll, Stream};
use std::env;
use std::io::{self, stderr, Write};
use std::path::PathBuf;
use std::process::exit;
use std::str::FromStr;
use tokio_core::reactor::{Handle, Core};
use tokio_core::io::IoStream;
use std::mem;

use librespot::spirc::{Spirc, SpircTask};
use librespot::authentication::{get_credentials, Credentials};
use librespot::authentication::discovery::{discovery, DiscoveryStream};
use librespot::audio_backend::{self, Sink, BACKENDS};
use librespot::cache::Cache;
use librespot::player::Player;
use librespot::session::{Bitrate, Config, Session};
use librespot::mixer::{self, Mixer};

use librespot::version;

fn usage(program: &str, opts: &getopts::Options) -> String {
    let brief = format!("Usage: {} [options]", program);
    opts.usage(&brief)
}

fn setup_logging(verbose: bool) {
    let mut builder = LogBuilder::new();
    match env::var("RUST_LOG") {
        Ok(config) => {
            builder.parse(&config);
            builder.init().unwrap();

            if verbose {
                warn!("`--verbose` flag overidden by `RUST_LOG` environment variable");
            }
        }
        Err(_) => {
            if verbose {
                builder.parse("mdns=info,librespot=trace");
            } else {
                builder.parse("mdns=info,librespot=info");
            }
            builder.init().unwrap();
        }
    }
}

fn list_backends() {
    println!("Available Backends : ");
    for (&(name, _), idx) in BACKENDS.iter().zip(0..) {
        if idx == 0 {
            println!("- {} (default)", name);
        } else {
            println!("- {}", name);
        }
    }
}

#[derive(Clone)]
struct Setup {
    backend: fn(Option<String>) -> Box<Sink>,
    device: Option<String>,

    mixer: fn() -> Box<Mixer>,

    cache: Option<Cache>,
    config: Config,
    credentials: Option<Credentials>,
    enable_discovery: bool,
}

fn setup(args: &[String]) -> Setup {
    let mut opts = getopts::Options::new();
    opts.optopt("c", "cache", "Path to a directory where files will be cached.", "CACHE")
        .reqopt("n", "name", "Device name", "NAME")
        .optopt("b", "bitrate", "Bitrate (96, 160 or 320). Defaults to 160", "BITRATE")
        .optopt("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM")
        .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM")
        .optflag("v", "verbose", "Enable verbose output")
        .optopt("u", "username", "Username to sign in with", "USERNAME")
        .optopt("p", "password", "Password", "PASSWORD")
        .optflag("", "disable-discovery", "Disable discovery mode")
        .optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
        .optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE")
        .optopt("", "mixer", "Mixer to use", "MIXER");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap();
            exit(1);
        }
    };

    let verbose = matches.opt_present("verbose");
    setup_logging(verbose);

    info!("librespot {} ({}). Built on {}.",
             version::short_sha(),
             version::commit_date(),
             version::short_now());

    let backend_name = matches.opt_str("backend");
    if backend_name == Some("?".into()) {
        list_backends();
        exit(0);
    }

    let backend = audio_backend::find(backend_name.as_ref())
        .expect("Invalid backend");

    let mixer_name = matches.opt_str("mixer");
    let mixer = mixer::find(mixer_name.as_ref())
        .expect("Invalid mixer");

    let bitrate = matches.opt_str("b").as_ref()
        .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
        .unwrap_or(Bitrate::Bitrate160);

    let name = matches.opt_str("name").unwrap();
    let device_id = librespot::session::device_id(&name);

    let cache = matches.opt_str("c").map(|cache_location| {
        Cache::new(PathBuf::from(cache_location))
    });

    let cached_credentials = cache.as_ref().and_then(Cache::credentials);

    let credentials = get_credentials(matches.opt_str("username"),
                                      matches.opt_str("password"),
                                      cached_credentials);

    let enable_discovery = !matches.opt_present("disable-discovery");

    let config = Config {
        user_agent: version::version_string(),
        name: name,
        device_id: device_id,
        bitrate: bitrate,
        onstart: matches.opt_str("onstart"),
        onstop: matches.opt_str("onstop"),
    };

    let device = matches.opt_str("device");

    Setup {
        backend: backend,
        cache: cache,
        config: config,
        credentials: credentials,
        device: device,
        enable_discovery: enable_discovery,
        mixer: mixer,
    }
}

struct Main {
    cache: Option<Cache>,
    config: Config,
    backend: fn(Option<String>) -> Box<Sink>,
    device: Option<String>,
    mixer: fn() -> Box<Mixer>,
    handle: Handle,

    discovery: Option<DiscoveryStream>,
    signal: IoStream<()>,

    spirc: Option<Spirc>,
    spirc_task: Option<SpircTask>,
    connect: Box<Future<Item=Session, Error=io::Error>>,

    shutdown: bool,
}

impl Main {
    fn new(handle: Handle,
           config: Config,
           cache: Option<Cache>,
           backend: fn(Option<String>) -> Box<Sink>,
           device: Option<String>,
           mixer: fn() -> Box<Mixer>) -> Main
    {
        Main {
            handle: handle.clone(),
            cache: cache,
            config: config,
            backend: backend,
            device: device,
            mixer: mixer,

            connect: Box::new(futures::future::empty()),
            discovery: None,
            spirc: None,
            spirc_task: None,
            shutdown: false,
            signal: tokio_signal::ctrl_c(&handle).flatten_stream().boxed(),
        }
    }

    fn discovery(&mut self) {
        let name = self.config.name.clone();
        let device_id = self.config.device_id.clone();
        self.discovery = Some(discovery(&self.handle, name, device_id).unwrap());
    }

    fn credentials(&mut self, credentials: Credentials) {
        let config = self.config.clone();
        let handle = self.handle.clone();

        let connection = Session::connect(config, credentials, self.cache.clone(), handle);

        self.connect = connection;
        self.spirc = None;
        let task = mem::replace(&mut self.spirc_task, None);
        if let Some(task) = task {
            self.handle.spawn(task);
        }
    }
}

impl Future for Main {
    type Item = ();
    type Error = ();

    fn poll(&mut self) -> Poll<(), ()> {
        loop {
            let mut progress = false;

            if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) {
                if let Some(ref spirc) = self.spirc {
                    spirc.shutdown();
                }
                self.credentials(creds);

                progress = true;
            }

            if let Async::Ready(session) = self.connect.poll().unwrap() {
                self.connect = Box::new(futures::future::empty());
                let device = self.device.clone();
                let mixer = (self.mixer)();

                let audio_filter = mixer.get_audio_filter();
                let backend = self.backend;
                let player = Player::new(session.clone(), audio_filter, move || {
                    (backend)(device)
                });

                let (spirc, spirc_task) = Spirc::new(session, player, mixer);
                self.spirc = Some(spirc);
                self.spirc_task = Some(spirc_task);

                progress = true;
            }

            if let Async::Ready(Some(())) = self.signal.poll().unwrap() {
                if !self.shutdown {
                    if let Some(ref spirc) = self.spirc {
                        spirc.shutdown();
                    }
                    self.shutdown = true;
                } else {
                    return Ok(Async::Ready(()));
                }

                progress = true;
            }

            if let Some(ref mut spirc_task) = self.spirc_task {
                if let Async::Ready(()) = spirc_task.poll().unwrap() {
                    if self.shutdown {
                        return Ok(Async::Ready(()));
                    } else {
                        panic!("Spirc shut down unexpectedly");
                    }
                }
            }

            if !progress {
                return Ok(Async::NotReady);
            }
        }
    }
}

fn main() {
    let mut core = Core::new().unwrap();
    let handle = core.handle();

    let args: Vec<String> = std::env::args().collect();
    let Setup { backend, config, device, cache, enable_discovery, credentials, mixer } = setup(&args);

    let mut task = Main::new(handle, config.clone(), cache, backend, device, mixer);
    if enable_discovery {
        task.discovery();
    }
    if let Some(credentials) = credentials {
        task.credentials(credentials);
    }

    core.run(task).unwrap()
}

<|endoftext|>


<commit_before>use std::env;
use std::f32;
use std::thread::sleep;
use std::time::Duration;

extern crate getopts;
use getopts::Options;
extern crate rand;
use rand::Rng;
use rand::distributions::{IndependentSample,Range};
extern crate sacn;
use sacn::DmxSource;

const UNIVERSE_SIZE: usize = 510;

const GAMMA: f32 = 2.2;

#[derive(Debug)]
struct Params {
    decay:         f32,
    threshold:     f32,
    max_intensity: f32,
    sleep:         Duration
}

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

#[derive(Debug)]
#[derive(Clone)]
struct Pixel { intensity: f32, age: u8, temp: u16, rgb: RGB }

struct Zone  { head: u8, body: u8, tail: u8, name: String }

fn build_params () -> Params {
    // seed default params
    let mut params = Params { decay: 2_f32, threshold: 0.1, max_intensity: 1_f32, sleep: Duration::new(0, 200_000_000) };

    // parse command line args and adjust params accordingly
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("d", "decay", "slow decay by this factor, defaults to 2", "DECAY");
    opts.optopt("t", "threshold", "probablity that a pixel lights up, default 0.10", "THRESHOLD");
    opts.optopt("m", "maxintensity", "maximum brightness, 1..255, default 75", "MAX");
    opts.optopt("s", "sleep", "sleep interval in seconds, default 1.5", "SECONDS");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("d") {
        params.decay = matches.opt_str("d").unwrap().parse::<f32>().unwrap();
    }
    if matches.opt_present("t") {
        params.threshold = matches.opt_str("t").unwrap().parse::<f32>().unwrap();
    }
    if matches.opt_present("m") {
        let max: u8 = matches.opt_str("m").unwrap().parse::<u8>().unwrap();
        params.max_intensity = (max as f32)/255_f32
    }
    if matches.opt_present("s") {
        // take float seconds
        // convert to int seconds and nanoseconds to make Duration happy
        let seconds: f32 = matches.opt_str("s").unwrap().parse::<f32>().unwrap();
        let whole_seconds: u64 = seconds as u64;
        let nano_seconds: u32 = ((seconds - whole_seconds as f32) * 1_000_000_000_f32) as u32;
        params.sleep = Duration::new(whole_seconds, nano_seconds);
    }
    return params;
}

fn kelvin (mut temp: u16) -> RGB {
    // http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
    temp /= 100;

    let mut rgb: RGB = RGB { red: 0, green: 0, blue: 0 };
    // calculate red
    if temp <= 66 {
        rgb.red = 255;
    } else {
        let red: f32 = (temp - 60) as f32;
        rgb.red = (329.698727446 * red.powf(-0.1332047592)).round() as u8;
    }
    // calculate green
    if temp <= 66 {
        let green: f32 = temp as f32;
        rgb.green = (99.4708025861 * green.ln() - 161.1195681661).round() as u8;
    } else {
        let green: f32 = (temp - 60) as f32;
        rgb.green = (288.1221695283 * green.powf(-0.0755148492)).round() as u8;
    }
    // calculate blue
    if temp >= 66 {
        rgb.blue = 255;
    } else {
        if temp <= 19 {
            rgb.blue = 0;
        } else {
            let blue: f32 = (temp - 10) as f32;
            rgb.blue = (138.5177312231 * blue.ln() - 305.0447927307).round() as u8;
        }
    }
    return rgb;
}

fn gamma_correct(rgb: &RGB) -> RGB {
    let mut c: RGB = RGB {red: 0, green: 0, blue: 0 };
    c.red   = (255_f32 * (rgb.red   as f32 / 255_f32).powf(GAMMA)) as u8;
    c.green = (255_f32 * (rgb.green as f32 / 255_f32).powf(GAMMA)) as u8;
    c.blue  = (255_f32 * (rgb.blue  as f32 / 255_f32).powf(GAMMA)) as u8;
    return c;
}

fn scale_rgb(rgb: RGB, intensity: f32, params: &Params) -> RGB {
    let i: f32 = intensity * params.max_intensity;
    let scaled: RGB = RGB {
        red:   (rgb.red   as f32 * i).round() as u8,
        green: (rgb.green as f32 * i).round() as u8,
        blue:  (rgb.blue  as f32 * i).round() as u8
    };
    return scaled;
}

fn main() {
    let params = build_params();

    let dmx = DmxSource::new("Controller").unwrap();

    let zones: [Zone; 6] = [
        Zone { head: 0, body: 44, tail: 3, name: "10".to_string() },
        Zone { head: 2, body: 91, tail: 3, name: "11a".to_string() },
        Zone { head: 2, body: 92, tail: 2, name: "11b".to_string() },
        Zone { head: 2, body: 90, tail: 3, name: "12a".to_string() },
        Zone { head: 2, body: 91, tail: 3, name: "12b".to_string() },
        Zone { head: 2, body: 43, tail: 0, name: "13".to_string() }
    ];

    let mut lights: Vec<Pixel> = vec![];
    // TODO: probably a more idiomatic way to built the default state
    for zone in zones.iter() {
        for i in 1..zone.body {
            let pixel = Pixel {
                intensity: 0_f32,
                age: 0,
                temp: 0,
                rgb: RGB { red: 0, green: 0, blue: 0 },
            };
            lights.push(pixel);
        }
    }

    let mut rng = rand::thread_rng();
    let zero_to_one = Range::new(0_f32, 1_f32);
    let temp_range = Range::new(2700_f32, 5500_f32);

    loop {
        for light in lights.iter_mut() {
            if light.intensity == 0_f32 {
                // light is currently dark
                // test to see if we want to light it
                if zero_to_one.ind_sample(&mut rng) < params.threshold {
                    // we do, so pick a random
                    light.intensity = zero_to_one.ind_sample(&mut rng) as f32;
                    light.age += 1;
                    light.temp = temp_range.ind_sample(&mut rng).round() as u16;
                    light.rgb = scale_rgb(kelvin(light.temp), light.intensity, &params);
                }
            } else {
                // light is lit
                // test to see if we rise or fall
                // probability of falling should go up as light ages
                if zero_to_one.ind_sample(&mut rng) > 1.0/(light.age as f32) {
                    // falling
                    light.intensity -= (zero_to_one.ind_sample(&mut rng) * params.decay) as f32;
                    light.age += 1;
                    // test to see if we bottomed out
                    if light.intensity <= 0_f32 {
                        light.intensity = 0_f32;
                        light.age = 0;
                        light.temp = 0;
                        light.rgb.red = 0;
                        light.rgb.green = 0;
                        light.rgb.blue = 0;
                    }
                } else {
                    // rising
                    light.intensity += zero_to_one.ind_sample(&mut rng) * (params.max_intensity - light.intensity);

                    light.age += 1;
                    light.rgb = scale_rgb(kelvin(light.temp), light.intensity, &params);
                }
            }
        }
        render(&lights, &zones, &dmx);
        sleep(params.sleep);
    }
    dmx.terminate_stream(1);
}

// debug, rendor output to console
// fn render( lights: &[Pixel], zones: &[Zone], dmx: &DmxSource ) {
//    let mut out: Vec<i16> = vec![];
//    for light in lights {
//        out.push(light.level);
//   }
//    println!("{:?}", out)
//     let mut lit: u16 = 0;
//     let mut level: i16 = 0;
//     for light in lights {
//         if light.level > 0 {
//             lit += 1;
//             level += light.level;
//         }
//     }
//     println!("{} pixels, {} lit, {} avg level", lights.len(), lit, (level as f32)/(lit as f32));
// }

// output to lighting controller via sACN
fn render( lights: &[Pixel], zones: &[Zone], dmx: &DmxSource ) {
    let mut out: Vec<u8> = vec![];
    let mut copy: Vec<Pixel> = vec![];
    copy.extend_from_slice(lights);
    let mut idx: usize = 0;
    for zone in zones {
        // null pixels at head
        for i in 1..zone.head {
            out.push(0); // Red
            out.push(0); // Green
            out.push(0); // Blue
        }
        // set via light.level in the body
        for i in 0..(zone.body - 1) {
            let ref rgb = copy[idx].rgb;
            let gc = gamma_correct(&rgb);
            out.push(gc.red);
            out.push(gc.green);
            out.push(gc.blue);
            idx += 1;
        }
        // null pixels at tail
        for i in 1..zone.tail {
            out.push(0); // Red
            out.push(0); // Green
            out.push(0); // Blue
        }
    }
    let mut universes = Vec::new();
    while out.len() > UNIVERSE_SIZE {
        let u = out.split_off(UNIVERSE_SIZE);
        universes.push(out);
        out = u;
    }
    universes.push(out);
    let mut universe: u16 = 1;
    for u in universes {
        dmx.send(universe, &u);
        universe += 1;
    }
}

<commit_msg>playing with the defaults<commit_after>use std::env;
use std::f32;
use std::thread::sleep;
use std::time::Duration;

extern crate getopts;
use getopts::Options;
extern crate rand;
use rand::Rng;
use rand::distributions::{IndependentSample,Range};
extern crate sacn;
use sacn::DmxSource;

const UNIVERSE_SIZE: usize = 510;

const GAMMA: f32 = 2.2;

#[derive(Debug)]
struct Params {
    decay:         f32,
    threshold:     f32,
    max_intensity: f32,
    sleep:         Duration
}

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

#[derive(Debug)]
#[derive(Clone)]
struct Pixel { intensity: f32, age: u32, temp: u16, rgb: RGB }

struct Zone  { head: u8, body: u8, tail: u8, name: String }

fn build_params () -> Params {
    // seed default params
    let mut params = Params { decay: 0.002, threshold: 0.001, max_intensity: 1_f32, sleep: Duration::new(0, 20_000_000) };

    // parse command line args and adjust params accordingly
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let mut opts = Options::new();
    opts.optopt("d", "decay", "slow decay by this factor, defaults to 2", "DECAY");
    opts.optopt("t", "threshold", "probablity that a pixel lights up, default 0.10", "THRESHOLD");
    opts.optopt("m", "maxintensity", "maximum brightness, 1..255, default 75", "MAX");
    opts.optopt("s", "sleep", "sleep interval in seconds, default 1.5", "SECONDS");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("d") {
        params.decay = matches.opt_str("d").unwrap().parse::<f32>().unwrap();
    }
    if matches.opt_present("t") {
        params.threshold = matches.opt_str("t").unwrap().parse::<f32>().unwrap();
    }
    if matches.opt_present("m") {
        let max: u8 = matches.opt_str("m").unwrap().parse::<u8>().unwrap();
        params.max_intensity = (max as f32)/255_f32
    }
    if matches.opt_present("s") {
        // take float seconds
        // convert to int seconds and nanoseconds to make Duration happy
        let seconds: f32 = matches.opt_str("s").unwrap().parse::<f32>().unwrap();
        let whole_seconds: u64 = seconds as u64;
        let nano_seconds: u32 = ((seconds - whole_seconds as f32) * 1_000_000_000_f32) as u32;
        params.sleep = Duration::new(whole_seconds, nano_seconds);
    }
    return params;
}

fn kelvin (mut temp: u16) -> RGB {
    // http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
    temp /= 100;

    let mut rgb: RGB = RGB { red: 0, green: 0, blue: 0 };
    // calculate red
    if temp <= 66 {
        rgb.red = 255;
    } else {
        let red: f32 = (temp - 60) as f32;
        rgb.red = (329.698727446 * red.powf(-0.1332047592)).round() as u8;
    }
    // calculate green
    if temp <= 66 {
        let green: f32 = temp as f32;
        rgb.green = (99.4708025861 * green.ln() - 161.1195681661).round() as u8;
    } else {
        let green: f32 = (temp - 60) as f32;
        rgb.green = (288.1221695283 * green.powf(-0.0755148492)).round() as u8;
    }
    // calculate blue
    if temp >= 66 {
        rgb.blue = 255;
    } else {
        if temp <= 19 {
            rgb.blue = 0;
        } else {
            let blue: f32 = (temp - 10) as f32;
            rgb.blue = (138.5177312231 * blue.ln() - 305.0447927307).round() as u8;
        }
    }
    return rgb;
}

fn gamma_correct(rgb: &RGB) -> RGB {
    let mut c: RGB = RGB {red: 0, green: 0, blue: 0 };
    c.red   = (255_f32 * (rgb.red   as f32 / 255_f32).powf(GAMMA)) as u8;
    c.green = (255_f32 * (rgb.green as f32 / 255_f32).powf(GAMMA)) as u8;
    c.blue  = (255_f32 * (rgb.blue  as f32 / 255_f32).powf(GAMMA)) as u8;
    return c;
}

fn scale_rgb(rgb: RGB, intensity: f32, params: &Params) -> RGB {
    let i: f32 = intensity * params.max_intensity;
    let scaled: RGB = RGB {
        red:   (rgb.red   as f32 * i).round() as u8,
        green: (rgb.green as f32 * i).round() as u8,
        blue:  (rgb.blue  as f32 * i).round() as u8
    };
    return scaled;
}

fn main() {
    let params = build_params();

    let dmx = DmxSource::new("Controller").unwrap();

    let zones: [Zone; 6] = [
        Zone { head: 0, body: 44, tail: 3, name: "10".to_string() },
        Zone { head: 2, body: 91, tail: 3, name: "11a".to_string() },
        Zone { head: 2, body: 92, tail: 2, name: "11b".to_string() },
        Zone { head: 2, body: 90, tail: 3, name: "12a".to_string() },
        Zone { head: 2, body: 91, tail: 3, name: "12b".to_string() },
        Zone { head: 2, body: 43, tail: 0, name: "13".to_string() }
    ];

    let mut lights: Vec<Pixel> = vec![];
    // TODO: probably a more idiomatic way to built the default state
    for zone in zones.iter() {
        for i in 1..zone.body {
            let pixel = Pixel {
                intensity: 0_f32,
                age: 0,
                temp: 0,
                rgb: RGB { red: 0, green: 0, blue: 0 },
            };
            lights.push(pixel);
        }
    }

    let mut rng = rand::thread_rng();
    let zero_to_one = Range::new(0_f32, 1_f32);
    let temp_range = Range::new(2700_f32, 5500_f32);

    loop {
        for light in lights.iter_mut() {
            if light.intensity == 0_f32 {
                // light is currently dark
                // test to see if we want to light it
                if zero_to_one.ind_sample(&mut rng) < params.threshold {
                    // we do, so pick a random
                    light.intensity = zero_to_one.ind_sample(&mut rng) as f32;
                    light.age += 1;
                    light.temp = temp_range.ind_sample(&mut rng).round() as u16;
                    light.rgb = scale_rgb(kelvin(light.temp), light.intensity, &params);
                }
            } else {
                // light is lit
                // test to see if we rise or fall
                // probability of falling should go up as light ages
                if zero_to_one.ind_sample(&mut rng) > 1.0/(light.age as f32) {
                    // falling
                    light.intensity -= (zero_to_one.ind_sample(&mut rng) * params.decay) as f32;
                    light.age += 1;
                    // test to see if we bottomed out
                    if light.intensity <= 0_f32 {
                        light.intensity = 0_f32;
                        light.age = 0;
                        light.temp = 0;
                        light.rgb.red = 0;
                        light.rgb.green = 0;
                        light.rgb.blue = 0;
                    }
                } else {
                    // rising
                    light.intensity += zero_to_one.ind_sample(&mut rng) * params.decay * (params.max_intensity - light.intensity);

                    light.age += 1;
                    light.rgb = scale_rgb(kelvin(light.temp), light.intensity, &params);
                }
            }
        }
        render(&lights, &zones, &dmx);
        sleep(params.sleep);
    }
    dmx.terminate_stream(1);
}

// debug, rendor output to console
// fn render( lights: &[Pixel], zones: &[Zone], dmx: &DmxSource ) {
//    let mut out: Vec<i16> = vec![];
//    for light in lights {
//        out.push(light.level);
//   }
//    println!("{:?}", out)
//     let mut lit: u16 = 0;
//     let mut level: i16 = 0;
//     for light in lights {
//         if light.level > 0 {
//             lit += 1;
//             level += light.level;
//         }
//     }
//     println!("{} pixels, {} lit, {} avg level", lights.len(), lit, (level as f32)/(lit as f32));
// }

// output to lighting controller via sACN
fn render( lights: &[Pixel], zones: &[Zone], dmx: &DmxSource ) {
    let mut out: Vec<u8> = vec![];
    let mut copy: Vec<Pixel> = vec![];
    copy.extend_from_slice(lights);
    let mut idx: usize = 0;
    for zone in zones {
        // null pixels at head
        for i in 1..zone.head {
            out.push(0); // Red
            out.push(0); // Green
            out.push(0); // Blue
        }
        // set via light.level in the body
        for i in 0..(zone.body - 1) {
            let ref rgb = copy[idx].rgb;
            let gc = gamma_correct(&rgb);
            out.push(gc.red);
            out.push(gc.green);
            out.push(gc.blue);
            idx += 1;
        }
        // null pixels at tail
        for i in 1..zone.tail {
            out.push(0); // Red
            out.push(0); // Green
            out.push(0); // Blue
        }
    }
    let mut universes = Vec::new();
    while out.len() > UNIVERSE_SIZE {
        let u = out.split_off(UNIVERSE_SIZE);
        universes.push(out);
        out = u;
    }
    universes.push(out);
    let mut universe: u16 = 1;
    for u in universes {
        dmx.send(universe, &u);
        universe += 1;
    }
}

<|endoftext|>


<commit_before>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#[macro_use]
extern crate glium;
extern crate cgmath;

use std::path::PathBuf;

use glium::{ Surface};
use glium::glutin::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode};

use cgmath::{Vector3, Point3};

mod camera;
mod input;
mod renderer;
mod scene;

use camera::Camera;
use input::InputState;

/// Get the root directory of the project
fn get_project_root() -> PathBuf {
    let exe_dir = std::env::current_exe().unwrap();
    let mut parent_dir = exe_dir.parent().unwrap();
    // This fails if the executable is not in the project tree
    while !parent_dir.ends_with("rusty") {
        parent_dir = parent_dir.parent().unwrap();
    }
    parent_dir.to_path_buf()
}

fn main() {
    let mut events_loop = glium::glutin::EventsLoop::new();
    let window = glium::glutin::WindowBuilder::new();
    let context = glium::glutin::ContextBuilder::new().with_depth_buffer(24);
    let display = glium::Display::new(window, context, &events_loop).expect("Failed to create display");

    let root_path = get_project_root();
    // TODO: Enable use of arbitrary scene
    let scenes = vec!("scenes/plane.obj",
                      "scenes/cornell/cornell.obj",
                      "scenes/cornell/cornell_chesterfield.obj",
                      "scenes/cornell-box/CornellBox-Original.obj",
                      "scenes/cornell-box/CornellBox-Glossy.obj",
                      "scenes/cornell-box/CornellBox-Water.obj",
                      "scenes/nanosuit/nanosuit.obj",
                      "scenes/sibenik/sibenik.obj");
    let mut scene = scene::Scene::new(&root_path.join(scenes[0]), &display);
    let gl_renderer = renderer::GLRenderer::new(&display);
    let pt_renderer = renderer::PTRenderer::new(&display);

    let mut input = InputState::new();
    let mut camera = Camera::new(Point3::from(scene.get_center())
                                 + scene.get_size() * Vector3::new(0.0, 0.0, 1.0f32),
                                 Vector3::new(0.0, 0.0, -1.0f32));
    camera.set_scale(scene.get_size());
    let mut trace = false;

    loop {
        let mut target = display.draw();

        let (width, height) = target.get_dimensions();
        // Don't draw if the window is minimized
        if width != 0 && height != 0 {
            let camera_to_clip = camera.get_camera_to_clip(width, height);
            let world_to_camera = camera.get_world_to_camera();

            target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
            if trace {
                pt_renderer.render(&scene, &mut target, &display, width as usize, height as usize, &camera);
            } else {
                gl_renderer.render(&scene, &mut target, camera_to_clip * world_to_camera);
            }
        }
        target.finish().unwrap();

        { // TODO clean this up
        let event_handler = |event| {
            input.update(&event);
            match event {
                Event::WindowEvent{event: WindowEvent::KeyboardInput{input, ..}, ..} => {
                    match input {
                        KeyboardInput{state: ElementState::Pressed,
                                      virtual_keycode: Some(VirtualKeyCode::Space), ..}
                        => trace = !trace,
                        // Get number pressed based on the keycode
                        KeyboardInput{state: ElementState::Pressed, scancode, ..} => {
                            let i = scancode as usize - 2;
                            if 0 < i && i < scenes.len() {
                                scene = scene::Scene::new(&root_path.join(scenes[i]), &display);
                                camera.set_position(Point3::from(scene.get_center())
                                                    + scene.get_size() * Vector3::new(0.0, 0.0, 1.0f32),
                                                    Vector3::new(0.0, 0.0, -1.0f32));
                                camera.set_scale(scene.get_size());
                            }
                        },
                        _ => ()
                    }
                }
                Event::WindowEvent{event: WindowEvent::Closed, ..} => panic!(),
                _ => ()
            }
        };
        events_loop.poll_events(event_handler);
        }
        camera.process_input(&input);
        input.reset_deltas();
    }
}
<commit_msg>Make program exit gracefully rather than panic on window close.<commit_after>#![cfg_attr(feature="clippy", feature(plugin))]
#![cfg_attr(feature="clippy", plugin(clippy))]
#[macro_use]
extern crate glium;
extern crate cgmath;

use std::path::PathBuf;

use glium::{ Surface};
use glium::glutin::{Event, WindowEvent, KeyboardInput, ElementState, VirtualKeyCode};

use cgmath::{Vector3, Point3};

mod camera;
mod input;
mod renderer;
mod scene;

use camera::Camera;
use input::InputState;

/// Get the root directory of the project
fn get_project_root() -> PathBuf {
    let exe_dir = std::env::current_exe().unwrap();
    let mut parent_dir = exe_dir.parent().unwrap();
    // This fails if the executable is not in the project tree
    while !parent_dir.ends_with("rusty") {
        parent_dir = parent_dir.parent().unwrap();
    }
    parent_dir.to_path_buf()
}

fn main() {
    let mut events_loop = glium::glutin::EventsLoop::new();
    let window = glium::glutin::WindowBuilder::new();
    let context = glium::glutin::ContextBuilder::new().with_depth_buffer(24);
    let display = glium::Display::new(window, context, &events_loop).expect("Failed to create display");

    let root_path = get_project_root();
    // TODO: Enable use of arbitrary scene
    let scenes = vec!("scenes/plane.obj",
                      "scenes/cornell/cornell.obj",
                      "scenes/cornell/cornell_chesterfield.obj",
                      "scenes/cornell-box/CornellBox-Original.obj",
                      "scenes/cornell-box/CornellBox-Glossy.obj",
                      "scenes/cornell-box/CornellBox-Water.obj",
                      "scenes/nanosuit/nanosuit.obj",
                      "scenes/sibenik/sibenik.obj");
    let mut scene = scene::Scene::new(&root_path.join(scenes[0]), &display);
    let gl_renderer = renderer::GLRenderer::new(&display);
    let pt_renderer = renderer::PTRenderer::new(&display);

    let mut input = InputState::new();
    let mut camera = Camera::new(Point3::from(scene.get_center())
                                 + scene.get_size() * Vector3::new(0.0, 0.0, 1.0f32),
                                 Vector3::new(0.0, 0.0, -1.0f32));
    camera.set_scale(scene.get_size());
    let mut trace = false;

    loop {
        let mut target = display.draw();

        let (width, height) = target.get_dimensions();
        // Don't draw if the window is minimized
        if width != 0 && height != 0 {
            let camera_to_clip = camera.get_camera_to_clip(width, height);
            let world_to_camera = camera.get_world_to_camera();

            target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
            if trace {
                pt_renderer.render(&scene, &mut target, &display, width as usize, height as usize, &camera);
            } else {
                gl_renderer.render(&scene, &mut target, camera_to_clip * world_to_camera);
            }
        }
        target.finish().unwrap();

        { // TODO clean this up
        let event_handler = |event| {
            input.update(&event);
            match event {
                Event::WindowEvent{event: WindowEvent::KeyboardInput{input, ..}, ..} => {
                    match input {
                        KeyboardInput{state: ElementState::Pressed,
                                      virtual_keycode: Some(VirtualKeyCode::Space), ..}
                        => trace = !trace,
                        // Get number pressed based on the keycode
                        KeyboardInput{state: ElementState::Pressed, scancode, ..} => {
                            let i = scancode as usize - 2;
                            if 0 < i && i < scenes.len() {
                                scene = scene::Scene::new(&root_path.join(scenes[i]), &display);
                                camera.set_position(Point3::from(scene.get_center())
                                                    + scene.get_size() * Vector3::new(0.0, 0.0, 1.0f32),
                                                    Vector3::new(0.0, 0.0, -1.0f32));
                                camera.set_scale(scene.get_size());
                            }
                        },
                        _ => ()
                    }
                }
                Event::WindowEvent{event: WindowEvent::Closed, ..} => std::process::exit(0),
                _ => ()
            }
        };
        events_loop.poll_events(event_handler);
        }
        camera.process_input(&input);
        input.reset_deltas();
    }
}
<|endoftext|>


<commit_before>// ZSnapMgr :: ZFS snapshot and backup manager
//
// Copyright (c) 2016 by William R. Fraser
//

use std::env;
use std::iter::Iterator;
use std::io;
use std::io::{Read, Write};
use std::ops::Deref;
use std::process;

extern crate termios;
use termios::*;

extern crate zsnapmgr;
use zsnapmgr::ZSnapMgr;

mod table;
use table::Table;

mod backups;
use backups::{Backup, Backups};

static USE_SUDO: bool = true;

trait OptionDeref<T: Deref> {
    fn as_deref(&self) -> Option<&T::Target>;
}

impl<T: Deref> OptionDeref<T> for Option<T> {
    fn as_deref(&self) -> Option<&T::Target> {
        self.as_ref().map(Deref::deref)
    }
}

trait VecDeref<T: Deref> {
    fn as_deref(&self) -> Vec<&T::Target>;
}

impl<T: Deref> VecDeref<T> for Vec<T> {
    fn as_deref(&self) -> Vec<&T::Target> {
        self.iter().map(Deref::deref).collect()
    }
}

fn enumerate_files(path: &str) -> Box<Iterator<Item = String>> {
    let out = std::fs::read_dir(std::path::Path::new(path))
                  .expect("error reading directory")
                  .map(|entry_result| entry_result.expect("error enumerating files"))
                  .filter(|entry| {
                      match entry.file_type() {
                          Ok(ft) => ft.is_file(),
                          Err(e) => {
                              println!("error getting file type of \"{}\": {}",
                                       entry.path().to_string_lossy(),
                                       e);
                              false
                          }
                      }
                  })
                  .map(|entry| entry.file_name().to_string_lossy().into_owned());
    Box::new(out)
}

fn gather_volumes(path: &str) -> Vec<Backup> {
    let z = ZSnapMgr::new(USE_SUDO);
    let snapshots: Vec<String> = match z.get_snapshots(None) {
        Ok(s) => s,
        Err(e) => {
            println!("Error getting snapshots from ZFS: {}", e);
            return vec![];
        }
    };

    let volumes: Vec<String> = match z.get_volumes() {
        Ok(v) => v,
        Err(e) => {
            println!("Error getting volumes from ZFS: {}", e);
            return vec![];
        }
    };

    let mut backups = Backups::new();

    for filename in enumerate_files(path) {
        if let Some(zfs_pos) = filename.find(".zfs") {
            if !(&filename).ends_with("_partial") {
                let parts = filename[0..zfs_pos].splitn(2, '@').collect::<Vec<&str>>();
                let volume_name = parts[0].replace("_", "/");
                let backup_snap = parts[1];

                if volumes.binary_search(&volume_name).is_ok() {
                    backups.insert(parts[0].to_string(),
                                   volume_name.to_string(),
                                   Some(backup_snap.to_string()));
                } else {
                    let volume_name_mod = "/".to_string() + &volume_name;
                    let matches: Vec<&str> = volumes.iter()
                                                    .filter(|ref vol| {
                                                        vol.ends_with(&volume_name_mod)
                                                    })
                                                    .map(Deref::deref)
                                                    .collect();

                    if matches.len() == 1 {
                        backups.insert(matches[0].to_string(),
                                       volume_name.to_string(),
                                       Some(backup_snap.to_string()));
                    } else {
                        print!("Backup filename \"{}\" ", filename);
                        if matches.len() > 1 {
                            println!("matches more than one volume.\nIt could be any of: {:?}",
                                     matches);
                        } else {
                            println!("doesn't match any volumes.");
                        }
                        println!("Skipping it.\n");
                    }
                }
            }
        }
    }

    // Now fill in the latest snapshot available for each volume in the proposed backups.
    for backup in backups.iter_mut() {
        let volume_at = backup.volume.clone() + "@";
        let volume_snaps: Vec<&str> = snapshots.iter()
                                               .filter(|snap| snap.starts_with(&volume_at))
                                               .map(Deref::deref)
                                               .collect();

        if backup.start_snapshot.is_some() {
            // Check that the start snapshot still exists.
            let start_snapshot = volume_at.clone() + backup.start_snapshot.as_ref().unwrap();

            if !volume_snaps.binary_search(&start_snapshot.deref()).is_ok() {
                println!("Snapshot \"{}\" doesn't exist any more; doing full backup instead.\n",
                         start_snapshot);
                backup.start_snapshot = None;
            }
        }

        let last_snapshot: &str = volume_snaps.last().unwrap().splitn(2, '@').last().unwrap();
        println!("last snapshot: {}", last_snapshot);

        if backup.start_snapshot
                 .as_ref()
                 .and_then(|start| Some(start != last_snapshot))
                 .unwrap_or(false) {
            backup.end_snapshot = Some(last_snapshot.to_string());
        } else {
            println!("Backup of \"{}\" is up to date (@{}). Skipping.\n",
                     backup.volume,
                     backup.start_snapshot.as_ref().unwrap());
            backup.end_snapshot = None;
        }
    }

    backups.values()
}

fn getpass(prompt: &str) -> io::Result<String> {
    let mut termios = Termios::from_fd(0).expect("failed to get termios settings");

    let old = termios.c_lflag;
    termios.c_lflag &= !ECHO;   // disable echo
    termios.c_lflag &= !ICANON; // disable line-buffering
    tcsetattr(0, TCSAFLUSH, &mut termios).expect("failed to set termios settings");

    let mut stdout = io::stdout();
    write!(stdout, "{}", prompt).unwrap();
    stdout.flush().unwrap();

    let stdin = io::stdin();
    let mut bytes = stdin.lock().bytes();
    let mut line = String::new();
    let mut utf8 = Vec::<u8>::new();
    loop {
        match bytes.next().or(Some(Err(io::Error::new(io::ErrorKind::Other, "EOF in getpass!")))) {
            Some(Ok(byte)) => {
                // 0x4 is EOT; aka ctrl-D
                if byte == 0x4 && utf8.is_empty() {
                    return Err(io::Error::new(io::ErrorKind::Other, "EOF in getpass!"));
                }

                utf8.push(byte);

                let mut valid_utf8 = false;
                if let Ok(c) = std::str::from_utf8(&utf8) {
                    if c == "\n" {
                        write!(stdout, "\n").unwrap();
                        stdout.flush().unwrap();
                        break;
                    } else {
                        valid_utf8 = true;
                        line.push_str(c);
                        write!(stdout, "*").unwrap();
                        stdout.flush().unwrap();
                    }
                }

                if valid_utf8 {
                    utf8.clear();
                }
            }
            Some(Err(e)) => {
                return Err(e);
            }
            _ => unreachable!(),
        }
    }

    termios.c_lflag = old;
    tcsetattr(0, TCSAFLUSH, &mut termios).expect("failed to reset termios settings");

    Ok(line)
}

fn do_backups(backups: &Vec<Backup>, path: &str) {
    if backups.is_empty() {
        println!("Nothing to do.");
        return;
    }

    let passphrase: String;
    loop {
        let pass1 = getpass("GPG passphrase: ").unwrap();
        let pass2 = getpass("again: ").unwrap();
        if pass1 != pass2 {
            println!("Passphrases do not match.");
        } else {
            passphrase = pass1;
            break;
        }
    }

    for backup in backups {
        let z = ZSnapMgr::new(USE_SUDO);

        let snapshot = format!("{}@{}",
                               backup.volume,
                               backup.end_snapshot.as_deref().unwrap());

        println!("\nBacking up: {}", snapshot);

        z.backup(path,
                 &snapshot,
                 &passphrase,
                 backup.start_snapshot.as_deref())
         .err()
         .and_then(|e| {
             println!("failed backup of {}: {}", backup.volume, e);
             Some(())
         });
    }
}

fn interactive_backup(backups_dir: &str) {
    let z = ZSnapMgr::new(USE_SUDO);
    let mut volumes: Vec<Backup> = gather_volumes(backups_dir);
    loop {
        let mut table = Table::new(&vec!["_", "volume", "incremental", "snapshot date"]);
        for i in 0..volumes.len() {
            let start = if volumes[i].start_snapshot.is_none() {
                "full backup".to_string()
            } else {
                volumes[i].start_snapshot.as_ref().unwrap().clone()
            };

            table.push(vec![(i + 1).to_string(),
                            volumes[i].volume.clone(),
                            start,
                            volumes[i].end_snapshot.as_ref().unwrap().clone()]);
        }

        println!("Volumes to backup:\n{}", table);

        print!(concat!("Enter a number to make changes,\n",
                         "\t'+' to add a volume,\n",
                         "\t'-' to remove one,\n",
                         "\t'd' to change all dates,\n",
                         "\tor <return> to start backup: "));
        io::stdout().flush().unwrap();

        let mut input = String::new();
        match io::stdin().read_line(&mut input) {
            Ok(bytes_read) => {
                if bytes_read == 0 {
                    panic!("EOF");
                }
                input.pop();
            }
            Err(e) => panic!(e),
        }

        if input == "+" {
            print!("Volume: ");
            io::stdout().flush().unwrap();

            let mut vol = String::new();
            io::stdin().read_line(&mut vol).unwrap();
            vol.pop();

            let latest_snap: String;
            match z.get_snapshots(Some(&vol))
                   .and_then(|ref mut snaps| {
                       Ok(snaps.pop())
                    }) {
                Ok(Some(snap)) => {
                    latest_snap = snap;
                }
                Ok(None) => {
                    println!("No snapshots available for that volume.\n");
                    continue;
                }
                Err(e) => {
                    println!("Error listing snapshots: {}\n", e);
                    continue;
                }
            }

            volumes.push(Backup {
                filename_base: vol.replace("/", "_").to_string(),
                volume: vol.clone(),
                start_snapshot: None,
                end_snapshot: Some(latest_snap),
            });

        } else if input.starts_with("-") {

            let index: usize;

            if input.len() > 1 {
                match input[1..].parse::<usize>() {
                    Ok(n) => {
                        index = n;
                    },
                    Err(e) => {
                        println!("Invalid number: {}\n", e);
                        continue;
                    }
                }
            } else {
                print!("Remove which one?: ");
                io::stdout().flush().unwrap();

                input.clear();
                io::stdin().read_line(&mut input).unwrap();
                input.pop();

                match input.parse::<usize>() {
                    Ok(n) => {
                        index = n;
                    },
                    Err(e) => {
                        println!("Invalid number: {}\n", e);
                        continue;
                    }
                }
            }

            if volumes.len() < index || index == 0 {
                println!("Number out of range.\n");
                continue;
            }

            volumes.remove(index - 1);

        } else if input.starts_with("d") || input.starts_with("D") {

            print!("Snapshot date (yyyy-MM-dd): ");
            io::stdout().flush().unwrap();

            let mut date = String::new();
            io::stdin().read_line(&mut date).unwrap();
            date.pop();

            for volume in volumes.iter_mut() {
                volume.end_snapshot = Some(date.clone());
                //TODO: check that the snapshot exists for that volume
            }

        } else if input.is_empty() {
            println!("Starting backups.\n");
            do_backups(&volumes, backups_dir);
            break;
        } else {
            let index: usize;

            match input.parse::<usize>() {
                Ok(n) => {
                    index = n;
                }
                Err(e) => {
                    println!("Invalid number: {}\n", e);
                    continue;
                }
            }

            if volumes.len() < index || index == 0 {
                println!("Number out of range.\n");
                continue;
            }

            let vol = volumes.get_mut(index - 1).unwrap();

            print!("Change (I)ncremental starting snapshot, (S)napshot date: ");
            io::stdout().flush().unwrap();

            input.clear();
            io::stdin().read_line(&mut input).unwrap();
            input.pop();

            let start: bool;
            if input == "I" || input == "i" {
                start = true;
            } else if input == "S" || input == "s" {
                start = false;
            } else {
                println!("Invalid selection.\n");
                continue;
            }

            print!("Date (yyyy-MM-dd): ");
            io::stdout().flush().unwrap();

            input.clear();
            io::stdin().read_line(&mut input).unwrap();
            input.pop();

            if start {
                vol.start_snapshot = Some(input.clone());
            } else {
                vol.end_snapshot = Some(input.clone());
            }
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program_name = &args[0].rsplitn(2, '/').next().unwrap();

    let command = if args.len() < 2 {
        "help"
    } else {
        args[1].as_ref()
    };

    match command {
        "backup" => {
            if args.len() == 3 {
                interactive_backup(&args[2]);
            } else {
                println!("usage: {} backup <backups_location>", program_name);
                process::exit(-1);
            }
        }
        "automanage" => {
            println!("snapshot automanage is not yet implemented.");
            process::exit(-1);
        }
        _ => {
            if command != "help" {
                println!("unknown command \"{}\"", command);
            }
            println!("usage: {} <backup | automanage> [options]", program_name);
            process::exit(-1);
        }
    }
}
<commit_msg>make a macro printf! that does print and flush<commit_after>// ZSnapMgr :: ZFS snapshot and backup manager
//
// Copyright (c) 2016 by William R. Fraser
//

use std::env;
use std::iter::Iterator;
use std::io;
use std::io::{Read, Write};
use std::ops::Deref;
use std::process;

extern crate termios;
use termios::*;

extern crate zsnapmgr;
use zsnapmgr::ZSnapMgr;

mod table;
use table::Table;

mod backups;
use backups::{Backup, Backups};

static USE_SUDO: bool = true;

// Print and flush.
macro_rules! printf {
    ( $fmt:expr ) => {
        print!($fmt);
        io::stdout().flush().unwrap();
    };
    ( $fmt:expr, $($arg:tt)* ) => {
        print!($fmt, $($arg)*);
        io::stdout().flush().unwrap();
    }
}

trait OptionDeref<T: Deref> {
    fn as_deref(&self) -> Option<&T::Target>;
}

impl<T: Deref> OptionDeref<T> for Option<T> {
    fn as_deref(&self) -> Option<&T::Target> {
        self.as_ref().map(Deref::deref)
    }
}

trait VecDeref<T: Deref> {
    fn as_deref(&self) -> Vec<&T::Target>;
}

impl<T: Deref> VecDeref<T> for Vec<T> {
    fn as_deref(&self) -> Vec<&T::Target> {
        self.iter().map(Deref::deref).collect()
    }
}

fn enumerate_files(path: &str) -> Box<Iterator<Item = String>> {
    let out = std::fs::read_dir(std::path::Path::new(path))
                  .expect("error reading directory")
                  .map(|entry_result| entry_result.expect("error enumerating files"))
                  .filter(|entry| {
                      match entry.file_type() {
                          Ok(ft) => ft.is_file(),
                          Err(e) => {
                              println!("error getting file type of \"{}\": {}",
                                       entry.path().to_string_lossy(),
                                       e);
                              false
                          }
                      }
                  })
                  .map(|entry| entry.file_name().to_string_lossy().into_owned());
    Box::new(out)
}

fn gather_volumes(path: &str) -> Vec<Backup> {
    let z = ZSnapMgr::new(USE_SUDO);
    let snapshots: Vec<String> = match z.get_snapshots(None) {
        Ok(s) => s,
        Err(e) => {
            println!("Error getting snapshots from ZFS: {}", e);
            return vec![];
        }
    };

    let volumes: Vec<String> = match z.get_volumes() {
        Ok(v) => v,
        Err(e) => {
            println!("Error getting volumes from ZFS: {}", e);
            return vec![];
        }
    };

    let mut backups = Backups::new();

    for filename in enumerate_files(path) {
        if let Some(zfs_pos) = filename.find(".zfs") {
            if !(&filename).ends_with("_partial") {
                let parts = filename[0..zfs_pos].splitn(2, '@').collect::<Vec<&str>>();
                let volume_name = parts[0].replace("_", "/");
                let backup_snap = parts[1];

                if volumes.binary_search(&volume_name).is_ok() {
                    backups.insert(parts[0].to_string(),
                                   volume_name.to_string(),
                                   Some(backup_snap.to_string()));
                } else {
                    let volume_name_mod = "/".to_string() + &volume_name;
                    let matches: Vec<&str> = volumes.iter()
                                                    .filter(|ref vol| {
                                                        vol.ends_with(&volume_name_mod)
                                                    })
                                                    .map(Deref::deref)
                                                    .collect();

                    if matches.len() == 1 {
                        backups.insert(matches[0].to_string(),
                                       volume_name.to_string(),
                                       Some(backup_snap.to_string()));
                    } else {
                        print!("Backup filename \"{}\" ", filename);
                        if matches.len() > 1 {
                            println!("matches more than one volume.\nIt could be any of: {:?}",
                                     matches);
                        } else {
                            println!("doesn't match any volumes.");
                        }
                        println!("Skipping it.\n");
                    }
                }
            }
        }
    }

    // Now fill in the latest snapshot available for each volume in the proposed backups.
    for backup in backups.iter_mut() {
        let volume_at = backup.volume.clone() + "@";
        let volume_snaps: Vec<&str> = snapshots.iter()
                                               .filter(|snap| snap.starts_with(&volume_at))
                                               .map(Deref::deref)
                                               .collect();

        if backup.start_snapshot.is_some() {
            // Check that the start snapshot still exists.
            let start_snapshot = volume_at.clone() + backup.start_snapshot.as_ref().unwrap();

            if !volume_snaps.binary_search(&start_snapshot.deref()).is_ok() {
                println!("Snapshot \"{}\" doesn't exist any more; doing full backup instead.\n",
                         start_snapshot);
                backup.start_snapshot = None;
            }
        }

        let last_snapshot: &str = volume_snaps.last().unwrap().splitn(2, '@').last().unwrap();
        println!("last snapshot: {}", last_snapshot);

        if backup.start_snapshot
                 .as_ref()
                 .and_then(|start| Some(start != last_snapshot))
                 .unwrap_or(false) {
            backup.end_snapshot = Some(last_snapshot.to_string());
        } else {
            println!("Backup of \"{}\" is up to date (@{}). Skipping.\n",
                     backup.volume,
                     backup.start_snapshot.as_ref().unwrap());
            backup.end_snapshot = None;
        }
    }

    backups.values()
}

fn getpass(prompt: &str) -> io::Result<String> {
    let mut termios = Termios::from_fd(0).expect("failed to get termios settings");

    let old = termios.c_lflag;
    termios.c_lflag &= !ECHO;   // disable echo
    termios.c_lflag &= !ICANON; // disable line-buffering
    tcsetattr(0, TCSAFLUSH, &mut termios).expect("failed to set termios settings");

    printf!("{}", prompt);

    let stdin = io::stdin();
    let mut bytes = stdin.lock().bytes();
    let mut line = String::new();
    let mut utf8 = Vec::<u8>::new();
    loop {
        match bytes.next().or(Some(Err(io::Error::new(io::ErrorKind::Other, "EOF in getpass!")))) {
            Some(Ok(byte)) => {
                // 0x4 is EOT; aka ctrl-D
                if byte == 0x4 && utf8.is_empty() {
                    return Err(io::Error::new(io::ErrorKind::Other, "EOF in getpass!"));
                }

                utf8.push(byte);

                let mut valid_utf8 = false;
                if let Ok(c) = std::str::from_utf8(&utf8) {
                    if c == "\n" {
                        printf!("\n");
                        break;
                    } else {
                        valid_utf8 = true;
                        line.push_str(c);
                        printf!("*");
                    }
                }

                if valid_utf8 {
                    utf8.clear();
                }
            }
            Some(Err(e)) => {
                return Err(e);
            }
            _ => unreachable!(),
        }
    }

    termios.c_lflag = old;
    tcsetattr(0, TCSAFLUSH, &mut termios).expect("failed to reset termios settings");

    Ok(line)
}

fn do_backups(backups: &Vec<Backup>, path: &str) {
    if backups.is_empty() {
        println!("Nothing to do.");
        return;
    }

    let passphrase: String;
    loop {
        let pass1 = getpass("GPG passphrase: ").unwrap();
        let pass2 = getpass("again: ").unwrap();
        if pass1 != pass2 {
            println!("Passphrases do not match.");
        } else {
            passphrase = pass1;
            break;
        }
    }

    for backup in backups {
        let z = ZSnapMgr::new(USE_SUDO);

        let snapshot = format!("{}@{}",
                               backup.volume,
                               backup.end_snapshot.as_deref().unwrap());

        println!("\nBacking up: {}", snapshot);

        z.backup(path,
                 &snapshot,
                 &passphrase,
                 backup.start_snapshot.as_deref())
         .err()
         .and_then(|e| {
             println!("failed backup of {}: {}", backup.volume, e);
             Some(())
         });
    }
}

fn interactive_backup(backups_dir: &str) {
    let z = ZSnapMgr::new(USE_SUDO);
    let mut volumes: Vec<Backup> = gather_volumes(backups_dir);
    loop {
        let mut table = Table::new(&vec!["_", "volume", "incremental", "snapshot date"]);
        for i in 0..volumes.len() {
            let start = if volumes[i].start_snapshot.is_none() {
                "full backup".to_string()
            } else {
                volumes[i].start_snapshot.as_ref().unwrap().clone()
            };

            table.push(vec![(i + 1).to_string(),
                            volumes[i].volume.clone(),
                            start,
                            volumes[i].end_snapshot.as_ref().unwrap().clone()]);
        }

        println!("Volumes to backup:\n{}", table);

        printf!(concat!("Enter a number to make changes,\n",
                         "\t'+' to add a volume,\n",
                         "\t'-' to remove one,\n",
                         "\t'd' to change all dates,\n",
                         "\tor <return> to start backup: "));

        let mut input = String::new();
        match io::stdin().read_line(&mut input) {
            Ok(bytes_read) => {
                if bytes_read == 0 {
                    panic!("EOF");
                }
                input.pop();
            }
            Err(e) => panic!(e),
        }

        if input == "+" {
            printf!("Volume: ");

            let mut vol = String::new();
            io::stdin().read_line(&mut vol).unwrap();
            vol.pop();

            let latest_snap: String;
            match z.get_snapshots(Some(&vol))
                   .and_then(|ref mut snaps| {
                       Ok(snaps.pop())
                    }) {
                Ok(Some(snap)) => {
                    latest_snap = snap;
                }
                Ok(None) => {
                    println!("No snapshots available for that volume.\n");
                    continue;
                }
                Err(e) => {
                    println!("Error listing snapshots: {}\n", e);
                    continue;
                }
            }

            volumes.push(Backup {
                filename_base: vol.replace("/", "_").to_string(),
                volume: vol.clone(),
                start_snapshot: None,
                end_snapshot: Some(latest_snap),
            });

        } else if input.starts_with("-") {

            let index: usize;

            if input.len() > 1 {
                match input[1..].parse::<usize>() {
                    Ok(n) => {
                        index = n;
                    },
                    Err(e) => {
                        println!("Invalid number: {}\n", e);
                        continue;
                    }
                }
            } else {
                printf!("Remove which one?: ");

                input.clear();
                io::stdin().read_line(&mut input).unwrap();
                input.pop();

                match input.parse::<usize>() {
                    Ok(n) => {
                        index = n;
                    },
                    Err(e) => {
                        println!("Invalid number: {}\n", e);
                        continue;
                    }
                }
            }

            if volumes.len() < index || index == 0 {
                println!("Number out of range.\n");
                continue;
            }

            volumes.remove(index - 1);

        } else if input.starts_with("d") || input.starts_with("D") {

            printf!("Snapshot date (yyyy-MM-dd): ");

            let mut date = String::new();
            io::stdin().read_line(&mut date).unwrap();
            date.pop();

            for volume in volumes.iter_mut() {
                volume.end_snapshot = Some(date.clone());
                //TODO: check that the snapshot exists for that volume
            }

        } else if input.is_empty() {
            println!("Starting backups.\n");
            do_backups(&volumes, backups_dir);
            break;
        } else {
            let index: usize;

            match input.parse::<usize>() {
                Ok(n) => {
                    index = n;
                }
                Err(e) => {
                    println!("Invalid number: {}\n", e);
                    continue;
                }
            }

            if volumes.len() < index || index == 0 {
                println!("Number out of range.\n");
                continue;
            }

            let vol = volumes.get_mut(index - 1).unwrap();

            printf!("Change (I)ncremental starting snapshot, (S)napshot date: ");

            input.clear();
            io::stdin().read_line(&mut input).unwrap();
            input.pop();

            let start: bool;
            if input == "I" || input == "i" {
                start = true;
            } else if input == "S" || input == "s" {
                start = false;
            } else {
                println!("Invalid selection.\n");
                continue;
            }

            printf!("Date (yyyy-MM-dd): ");

            input.clear();
            io::stdin().read_line(&mut input).unwrap();
            input.pop();

            if start {
                vol.start_snapshot = Some(input.clone());
            } else {
                vol.end_snapshot = Some(input.clone());
            }
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let program_name = &args[0].rsplitn(2, '/').next().unwrap();

    let command = if args.len() < 2 {
        "help"
    } else {
        args[1].as_ref()
    };

    match command {
        "backup" => {
            if args.len() == 3 {
                interactive_backup(&args[2]);
            } else {
                println!("usage: {} backup <backups_location>", program_name);
                process::exit(-1);
            }
        }
        "automanage" => {
            println!("snapshot automanage is not yet implemented.");
            process::exit(-1);
        }
        _ => {
            if command != "help" {
                println!("unknown command \"{}\"", command);
            }
            println!("usage: {} <backup | automanage> [options]", program_name);
            process::exit(-1);
        }
    }
}
<|endoftext|>