<commit_before>#[macro_use]
extern crate log;
#[macro_use]
extern crate nom;

extern crate bip_bencode;
extern crate bip_metainfo;
extern crate bip_utracker;
extern crate bip_util;
extern crate chrono;
extern crate hyper;
extern crate pnet;
extern crate pnet_macros_support;
extern crate pretty_env_logger;
extern crate rustyline;
extern crate url;

mod btclient;
mod packet;

use bip_bencode::Bencode;
use bip_metainfo::MetainfoFile;
use bip_utracker::contact::CompactPeersV4;
use chrono::{TimeZone, UTC};
use hyper::Client;
use hyper::header::Connection;
use packet::peer_pkt::{MutablePeerHandshakePacket, MutablePeerMessagePacket};
use pnet::packet::Packet;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use url::Url;

use std::fs::File;
use std::io::prelude::*;
use std::net::TcpStream;
use std::str;
use std::string::String;
use std::time::{SystemTime, UNIX_EPOCH};
use std::thread;

const HISTORY_FILE: &'static str = ".rustyline.history";
const PEER_HANDSHAKE_STRUCT_SZ: usize = 68;
const PEER_REQ_PKT_SZ: usize = 14;

/// Print general information about the torrent.
fn print_metainfo_overview(bytes: &[u8]) {
    let metainfo = MetainfoFile::from_bytes(bytes).unwrap();
    let info = metainfo.info();
    let info_hash_hex = metainfo.info_hash()
        .as_ref()
        .iter()
        .map(|b| format!("{:02x}", b))
        .fold(String::new(), |mut acc, nex| {
            acc.push_str(&nex);
            acc
        });
    let utc_creation_date = metainfo.creation_date().map(|c| UTC.timestamp(c, 0));

    println!("------Metainfo File Overview-----");

    println!("InfoHash: {}", info_hash_hex);
    println!("Main Tracker: {:?}", metainfo.main_tracker());
    println!("Comment: {:?}", metainfo.comment());
    println!("Creator: {:?}", metainfo.created_by());
    println!("Creation Date: {:?}", utc_creation_date);

    println!("Directory: {:?}", info.directory());
    println!("Piece Length: {:?}", info.piece_length());
    println!("Number Of Pieces: {}", info.pieces().count());
    println!("Number Of Files: {}", info.files().count());
    println!("Total File Size: {}",
             info.files().fold(0, |acc, nex| acc + nex.length()));
}

fn connect_to_tracker(metainfo: MetainfoFile,
                      peer_id: &str,
                      port: u16)
                      -> Result<(Vec<String>, String), String> {
    debug!("connecting to tracker: {:?}", metainfo.main_tracker());

    let info_hash = metainfo.info_hash();
    // TODO figure can this conversion to url-encoded form be done safely?
    let info_hash_str = unsafe { str::from_utf8_unchecked(info_hash.as_ref()) };

    // TODO this needs to be calculated based on what we have
    let total_len = metainfo.info().files().fold(0, |acc, nex| acc + nex.length());

    let mut url = Url::parse(metainfo.main_tracker().unwrap()).unwrap();
    url.query_pairs_mut()
        .append_pair("info_hash", info_hash_str)
        .append_pair("peer_id", peer_id)
        .append_pair("port", &(port.to_string()))
        // TODO parametrize this
        .append_pair("uploaded", "0")
        // TODO parametrize this
        .append_pair("downloaded", "0")
        // TODO see note on total_len above
        .append_pair("left", &(total_len.to_string()))
        .append_pair("compact", "1")
        .append_pair("event", "started")
        .append_pair("supportcrypto", "0");
    trace!("URL {:?}", url);

    let client = Client::new();
    let mut res = client.get(url).header(Connection::close()).send().unwrap();
    let mut buffer = Vec::new();
    res.read_to_end(&mut buffer).unwrap();
    debug!("{:?}", res);
    let bencode = Bencode::decode(&buffer).unwrap();
    trace!("{:?}", bencode);
    let (_, peers) = CompactPeersV4::from_bytes(bencode.dict()
            .unwrap()
            .lookup("peers")
            .unwrap()
            .bytes()
            .unwrap())
        .unwrap();
    trace!("{:?}", peers);
    let mut ip_ports: Vec<String> = Vec::new();
    debug!("Peer list received:");
    for peer in peers.iter() {
        debug!("{:?}", peer);
        ip_ports.push(peer.to_string());
    }
    Ok((ip_ports, info_hash_str.to_string()))
}

fn peer_connections(peer_ip_ports: Vec<String>,
                    info_hash: &str,
                    peer_id: &str)
                    -> Result<(), String> {
    for peer_ip_port in peer_ip_ports {
        let peer_ip_port_cl = peer_ip_port.clone();
        let info_hash_cl = info_hash.to_string().clone();
        let peer_id_cl = peer_id.to_string().clone();
        thread::spawn(move || {
            match handshake_peer(&peer_ip_port_cl, &info_hash_cl, &peer_id_cl) {
                Ok(mut stream) => {
                    debug!("Communicating with peer {:?}", peer_ip_port_cl);
                    let mut buff = [0; PEER_HANDSHAKE_STRUCT_SZ];
                    match stream.read(&mut buff) {
                        Ok(_) => {
                            let mut buf = vec![0u8; PEER_REQ_PKT_SZ];
                            let mut peer_msg_pkt = MutablePeerMessagePacket::new(&mut buf).unwrap();
                            peer_msg_pkt.set_len(13);
                            peer_msg_pkt.set_id(6);
                            // TODO fill length properly
                            let index_begin_length = vec![0, 0, 10];
                            peer_msg_pkt.set_payload(&index_begin_length);
                            match stream.write(peer_msg_pkt.packet()) {
                                Ok(_) => {
                                    let mut buf_read = vec![0; 2048];
                                    match stream.read(&mut buf_read) {
                                        Ok(bytes_read) => {
                                            debug!("Bytes read: {:?}", bytes_read);
                                        }
                                        Err(e) => error!("Read failed! {:?}", e),
                                    }
                                }
                                Err(e) => error!("Write to stream failed! {:?}", e),
                            }
                        }
                        Err(e) => error!("Reading from the stream failed! {:?}", e),
                    }
                }
                Err(_) => {
                    error!("Closing thread with peer {:?}", peer_ip_port_cl);
                }
            }
        });
    }
    Ok(())
}

fn handshake_peer(peer_ip_port: &str, info_hash: &str, peer_id: &str) -> Result<TcpStream, String> {
    let mut buf = vec![0u8; PEER_HANDSHAKE_STRUCT_SZ];
    let mut ph = MutablePeerHandshakePacket::new(&mut buf).unwrap();
    ph.set_pstrlen("BitTorrent protocol".len() as u8);
    ph.set_pstr(&String::from("BitTorrent protocol").into_bytes());
    ph.set_reserved(&[0; 8]);
    ph.set_info_hash(info_hash.as_bytes());
    ph.set_peer_id(peer_id.as_bytes());
    match TcpStream::connect(peer_ip_port) {
        Ok(mut stream) => {
            debug!("Connection to peer {:?} successful!", peer_ip_port);
            match stream.write(ph.packet()) {
                Ok(_) => {
                    debug!("Sending message successful!");
                    trace!("{:?}", ph.packet());
                    Ok((stream))
                }
                Err(_) => {
                    error!("Sending message failed!");
                    Err("Sending message failed!".to_owned())
                }
            }
        }
        Err(e) => {
            error!("Connection to peer {:?} failed! {:?}", peer_ip_port, e);
            Err("Connection to peer failed!".to_owned())
        }
    }
}

fn main() {
    pretty_env_logger::init();

    let mut rl = Editor::<()>::new();
    if rl.load_history(HISTORY_FILE).is_err() {
        info!("No previous history!");
    }

    let now = SystemTime::now();
    let duration = now.duration_since(UNIX_EPOCH).unwrap();
    let client_id = "-bittorrent-rs-".to_owned() + &format!("{}", duration.as_secs() % 100_000);

    loop {
        let readline = rl.readline("> ");
        match readline {
            Ok(cmd) => {
                rl.add_history_entry(&cmd);

                let cmd = cmd.trim().split(' ').collect::<Vec<&str>>();

                match cmd[0] {
                    "parse" => {
                        if cmd.len() != 2 {
                            error!("usage: parse <torrent file>");
                        } else {
                            let path = cmd[1];
                            debug!("MetainfoFile: {:?}", path);

                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();
                                    print_metainfo_overview(&bytes);
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "connect" => {
                        if cmd.len() != 2 {
                            error!("usage: connect <torrent file>");
                        } else {
                            let path = cmd[1];
                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();

                                    // TODO: generate peer ID
                                    let result =
                                        connect_to_tracker(MetainfoFile::from_bytes(&bytes)
                                                               .unwrap(),
                                                           &client_id,
                                                           6882)
                                            .unwrap();
                                    peer_connections(result.0, &result.1, &client_id).unwrap();
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    _ => {}
                }
            }
            Err(ReadlineError::Interrupted) |
            Err(ReadlineError::Eof) => {
                break;
            }
            Err(err) => {
                error!("Error: {:?}", err);
                break;
            }
        }
    }
    rl.save_history(HISTORY_FILE).unwrap();
}
<commit_msg>Revert code deletion from "send request message"<commit_after>#[macro_use]
extern crate log;
#[macro_use]
extern crate nom;

extern crate bip_bencode;
extern crate bip_metainfo;
extern crate bip_utracker;
extern crate bip_util;
extern crate chrono;
extern crate hyper;
extern crate pnet;
extern crate pnet_macros_support;
extern crate pretty_env_logger;
extern crate rustyline;
extern crate url;

mod btclient;
mod packet;

use bip_bencode::Bencode;
use bip_metainfo::MetainfoFile;
use bip_utracker::contact::CompactPeersV4;
use chrono::{TimeZone, UTC};
use hyper::Client;
use hyper::header::Connection;
use packet::peer_pkt::{MutablePeerHandshakePacket, MutablePeerMessagePacket};
use pnet::packet::Packet;
use rustyline::Editor;
use rustyline::error::ReadlineError;
use url::Url;

use std::fs::File;
use std::io::prelude::*;
use std::net::TcpStream;
use std::str;
use std::string::String;
use std::time::{SystemTime, UNIX_EPOCH};
use std::thread;

const HISTORY_FILE: &'static str = ".rustyline.history";
const PEER_HANDSHAKE_STRUCT_SZ: usize = 68;
const PEER_REQ_PKT_SZ: usize = 14;

/// Print file list from the torrent.
fn print_files(bytes: &[u8]) {
    let metainfo = MetainfoFile::from_bytes(bytes).unwrap();
    let info = metainfo.info();

    println!("File List:");
    println!("Size (bytes)\tPath");
    println!("------------\t----------------------------------------------");
    for file in info.files() {
        println!("{:12}\t{}",
                 file.length(),
                 file.paths().next().unwrap_or("<unknown>"));
    }
}

/// Print general information about the torrent.
fn print_metainfo_overview(bytes: &[u8]) {
    let metainfo = MetainfoFile::from_bytes(bytes).unwrap();
    let info = metainfo.info();
    let info_hash_hex = metainfo.info_hash()
        .as_ref()
        .iter()
        .map(|b| format!("{:02x}", b))
        .fold(String::new(), |mut acc, nex| {
            acc.push_str(&nex);
            acc
        });
    let utc_creation_date = metainfo.creation_date().map(|c| UTC.timestamp(c, 0));

    println!("------Metainfo File Overview-----");

    println!("InfoHash: {}", info_hash_hex);
    println!("Main Tracker: {:?}", metainfo.main_tracker());
    println!("Comment: {:?}", metainfo.comment());
    println!("Creator: {:?}", metainfo.created_by());
    println!("Creation Date: {:?}", utc_creation_date);

    println!("Directory: {:?}", info.directory());
    println!("Piece Length: {:?}", info.piece_length());
    println!("Number Of Pieces: {}", info.pieces().count());
    println!("Number Of Files: {}", info.files().count());
    println!("Total File Size: {}\n",
             info.files().fold(0, |acc, nex| acc + nex.length()));

    print_files(bytes);
}

fn connect_to_tracker(metainfo: MetainfoFile,
                      peer_id: &str,
                      port: u16)
                      -> Result<(Vec<String>, String), String> {
    debug!("connecting to tracker: {:?}", metainfo.main_tracker());

    let info_hash = metainfo.info_hash();
    // TODO figure can this conversion to url-encoded form be done safely?
    let info_hash_str = unsafe { str::from_utf8_unchecked(info_hash.as_ref()) };

    // TODO this needs to be calculated based on what we have
    let total_len = metainfo.info().files().fold(0, |acc, nex| acc + nex.length());

    let mut url = Url::parse(metainfo.main_tracker().unwrap()).unwrap();
    url.query_pairs_mut()
        .append_pair("info_hash", info_hash_str)
        .append_pair("peer_id", peer_id)
        .append_pair("port", &(port.to_string()))
        // TODO parametrize this
        .append_pair("uploaded", "0")
        // TODO parametrize this
        .append_pair("downloaded", "0")
        // TODO see note on total_len above
        .append_pair("left", &(total_len.to_string()))
        .append_pair("compact", "1")
        .append_pair("event", "started")
        .append_pair("supportcrypto", "0");
    trace!("URL {:?}", url);

    let client = Client::new();
    let mut res = client.get(url).header(Connection::close()).send().unwrap();
    let mut buffer = Vec::new();
    res.read_to_end(&mut buffer).unwrap();
    debug!("{:?}", res);
    let bencode = Bencode::decode(&buffer).unwrap();
    trace!("{:?}", bencode);
    let (_, peers) = CompactPeersV4::from_bytes(bencode.dict()
            .unwrap()
            .lookup("peers")
            .unwrap()
            .bytes()
            .unwrap())
        .unwrap();
    trace!("{:?}", peers);
    let mut ip_ports: Vec<String> = Vec::new();
    debug!("Peer list received:");
    for peer in peers.iter() {
        debug!("{:?}", peer);
        ip_ports.push(peer.to_string());
    }
    Ok((ip_ports, info_hash_str.to_string()))
}

fn peer_connections(peer_ip_ports: Vec<String>,
                    info_hash: &str,
                    peer_id: &str)
                    -> Result<(), String> {
    for peer_ip_port in peer_ip_ports {
        let peer_ip_port_cl = peer_ip_port.clone();
        let info_hash_cl = info_hash.to_string().clone();
        let peer_id_cl = peer_id.to_string().clone();
        thread::spawn(move || {
            match handshake_peer(&peer_ip_port_cl, &info_hash_cl, &peer_id_cl) {
                Ok(mut stream) => {
                    debug!("Communicating with peer {:?}", peer_ip_port_cl);
                    let mut buff = [0; PEER_HANDSHAKE_STRUCT_SZ];
                    match stream.read(&mut buff) {
                        Ok(_) => {
                            let mut buf = vec![0u8; PEER_REQ_PKT_SZ];
                            let mut peer_msg_pkt = MutablePeerMessagePacket::new(&mut buf).unwrap();
                            peer_msg_pkt.set_len(13);
                            peer_msg_pkt.set_id(6);
                            // TODO fill length properly
                            let index_begin_length = vec![0, 0, 10];
                            peer_msg_pkt.set_payload(&index_begin_length);
                            match stream.write(peer_msg_pkt.packet()) {
                                Ok(_) => {
                                    let mut buf_read = vec![0; 2048];
                                    match stream.read(&mut buf_read) {
                                        Ok(bytes_read) => {
                                            debug!("Bytes read: {:?}", bytes_read);
                                        }
                                        Err(e) => error!("Read failed! {:?}", e),
                                    }
                                }
                                Err(e) => error!("Write to stream failed! {:?}", e),
                            }
                        }
                        Err(e) => error!("Reading from the stream failed! {:?}", e),
                    }
                }
                Err(_) => {
                    error!("Closing thread with peer {:?}", peer_ip_port_cl);
                }
            }
        });
    }
    Ok(())
}

fn handshake_peer(peer_ip_port: &str, info_hash: &str, peer_id: &str) -> Result<TcpStream, String> {
    let mut buf = vec![0u8; PEER_HANDSHAKE_STRUCT_SZ];
    let mut ph = MutablePeerHandshakePacket::new(&mut buf).unwrap();
    ph.set_pstrlen("BitTorrent protocol".len() as u8);
    ph.set_pstr(&String::from("BitTorrent protocol").into_bytes());
    ph.set_reserved(&[0; 8]);
    ph.set_info_hash(info_hash.as_bytes());
    ph.set_peer_id(peer_id.as_bytes());
    match TcpStream::connect(peer_ip_port) {
        Ok(mut stream) => {
            debug!("Connection to peer {:?} successful!", peer_ip_port);
            match stream.write(ph.packet()) {
                Ok(_) => {
                    debug!("Sending message successful!");
                    trace!("{:?}", ph.packet());
                    Ok((stream))
                }
                Err(_) => {
                    error!("Sending message failed!");
                    Err("Sending message failed!".to_owned())
                }
            }
        }
        Err(e) => {
            error!("Connection to peer {:?} failed! {:?}", peer_ip_port, e);
            Err("Connection to peer failed!".to_owned())
        }
    }
}

fn main() {
    pretty_env_logger::init();

    let mut rl = Editor::<()>::new();
    if rl.load_history(HISTORY_FILE).is_err() {
        info!("No previous history!");
    }

    let now = SystemTime::now();
    let duration = now.duration_since(UNIX_EPOCH).unwrap();
    let client_id = "-bittorrent-rs-".to_owned() + &format!("{}", duration.as_secs() % 100_000);

    loop {
        let readline = rl.readline("> ");
        match readline {
            Ok(cmd) => {
                rl.add_history_entry(&cmd);

                let cmd = cmd.trim().split(' ').collect::<Vec<&str>>();

                match cmd[0] {
                    "help" | "h" => {
                        println!("Commands:
parse/p <torrent file path>      - show Metainfo File Overview
connect/c <torrent file path>    - initiate connection to tracker, and handshake with peers
showfiles/sf <torrent file path> - show files in the torrent
help/h                           - show this help");
                    }
                    "parse" | "p" => {
                        if cmd.len() != 2 {
                            error!("usage: parse <torrent file>");
                        } else {
                            let path = cmd[1];
                            debug!("MetainfoFile: {:?}", path);

                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();
                                    print_metainfo_overview(&bytes);
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "connect" | "c" => {
                        if cmd.len() != 2 {
                            error!("usage: connect <torrent file>");
                        } else {
                            let path = cmd[1];
                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();

                                    // TODO: generate peer ID
                                    let result =
                                        connect_to_tracker(MetainfoFile::from_bytes(&bytes)
                                                               .unwrap(),
                                                           &client_id,
                                                           6882)
                                            .unwrap();
                                    peer_connections(result.0, &result.1, &client_id).unwrap();
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "showfiles" | "sf" => {
                        if cmd.len() != 2 {
                            error!("usage: connect <torrent file>");
                        } else {
                            let path = cmd[1];
                            match File::open(path) {
                                Ok(mut f) => {
                                    let mut bytes: Vec<u8> = Vec::new();
                                    f.read_to_end(&mut bytes).unwrap();

                                    print_files(&bytes);
                                }
                                Err(e) => error!("{:?}", e),
                            }
                        }
                    }
                    "" => {}
                    _ => {
                        println!("invalid command, see \"help\"");
                    }
                }
            }
            Err(ReadlineError::Interrupted) |
            Err(ReadlineError::Eof) => {
                break;
            }
            Err(err) => {
                error!("Error: {:?}", err);
                break;
            }
        }
    }
    rl.save_history(HISTORY_FILE).unwrap();
}
<|endoftext|>


<commit_before>extern crate getopts;
extern crate libudev;

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

fn get_hidraw_node() -> Option<std::path::PathBuf> {
    let udev = libudev::Context::new().unwrap();
    let mut enumerator = libudev::Enumerator::new(&udev).unwrap();

    enumerator.match_subsystem("hidraw").unwrap();

    for device in enumerator.scan_devices().unwrap() {
        if let Some(usb_parent) = device.parent_with_subsystem_devtype("usb", "usb_device") {
            if usb_parent.attribute_value("idVendor").unwrap() == "1532" &&
                usb_parent.attribute_value("idProduct").unwrap() == "0037" {
                return Some(device.devnode().unwrap().to_owned());
            }
        }
    }

    None
}

fn boolarg(a: String, argname: &str) -> bool {
    match a.to_lowercase().as_str() {
        "on" | "true" | "1" | "enabled" => true,
        "off" | "false" | "0" | "disabled" => false,
        _ => {
            println!("Invalid boolean for {}", argname);
            std::process::exit(1);
        }
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let progname = args[0].clone();

    let mut opts = getopts::Options::new();
    opts.optopt("d", "device", "hidraw node of the mouse", "DEV");
    opts.optopt("r", "dpi", "Sensor resolution (100-6400 DPI in increments of 100)", "RES/X,Y");
    opts.optopt("f", "freq", "Polling rate (125, 500 or 1000 Hz)", "HZ");
    opts.optopt("l", "logo", "Logo LED", "on/off");
    opts.optopt("w", "wheel", "Scroll wheel LED", "on/off");
    opts.optflag("h", "help", "Print help and exit");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            println!("{}", f.to_string());
            usage(&progname, opts);
            return;
        }
    };

    // Show help when loose arguments are passed
    if matches.opt_present("h") || !matches.free.is_empty() {
        usage(&progname, opts);
        return;
    }

    // TODO I'm sure this can be improved somehow
    let dev = match (matches.opt_str("d"), get_hidraw_node()) {
        (Some(d), _) => std::path::PathBuf::from(d),
        (None, Some(d)) => d,
        (None, None) => {
            println!("Can't detect the device with udev, make sure it's plugged in, \
                     or specify -d");
            usage(&progname, opts);
            return;
        }
    };

    let dpi = matches.opt_str("r").map(|s| {
        let r = s.split(',').map(|s| match s.parse::<i32>() {
            Ok(r) => {
                if r < 100 || r > 6400 || (r % 100 != 0) {
                    println!("Invalid resolution value");
                    std::process::exit(1);
                }
                r
            }
            Err(_) => {
                println!("Resolution is not a valid integer");
                std::process::exit(1);
            }
        }).collect::<Vec<_>>();
        if r.len() > 2 {
            println!("Too many values for resolution");
            std::process::exit(1);
        }
        r
    });

    let freq = matches.opt_str("f").map(|s| {
        match s.as_str() {
            "125" => 125,
            "500" => 500,
            "1000" => 1000,
            _ => {
                println!("Polling rate must be one of 125, 500 or 1000 Hz");
                std::process::exit(1);
            }
        }
    });

    let led_logo = matches.opt_str("l").map(|s| boolarg(s, "logo LED"));
    let led_wheel = matches.opt_str("w").map(|s| boolarg(s, "wheel LED"));

    println!("Using device {}", dev.to_str().unwrap());
}
<commit_msg>Use exit() instead of return<commit_after>extern crate getopts;
extern crate libudev;

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

fn get_hidraw_node() -> Option<std::path::PathBuf> {
    let udev = libudev::Context::new().unwrap();
    let mut enumerator = libudev::Enumerator::new(&udev).unwrap();

    enumerator.match_subsystem("hidraw").unwrap();

    for device in enumerator.scan_devices().unwrap() {
        if let Some(usb_parent) = device.parent_with_subsystem_devtype("usb", "usb_device") {
            if usb_parent.attribute_value("idVendor").unwrap() == "1532" &&
                usb_parent.attribute_value("idProduct").unwrap() == "0037" {
                return Some(device.devnode().unwrap().to_owned());
            }
        }
    }

    None
}

fn boolarg(a: String, argname: &str) -> bool {
    match a.to_lowercase().as_str() {
        "on" | "true" | "1" | "enabled" => true,
        "off" | "false" | "0" | "disabled" => false,
        _ => {
            println!("Invalid boolean for {}", argname);
            std::process::exit(1);
        }
    }
}

fn main() {
    let args: Vec<String> = std::env::args().collect();
    let progname = args[0].clone();

    let mut opts = getopts::Options::new();
    opts.optopt("d", "device", "hidraw node of the mouse", "DEV");
    opts.optopt("r", "dpi", "Sensor resolution (100-6400 DPI in increments of 100)", "RES/X,Y");
    opts.optopt("f", "freq", "Polling rate (125, 500 or 1000 Hz)", "HZ");
    opts.optopt("l", "logo", "Logo LED", "on/off");
    opts.optopt("w", "wheel", "Scroll wheel LED", "on/off");
    opts.optflag("h", "help", "Print help and exit");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => {
            println!("{}", f.to_string());
            usage(&progname, opts);
            std::process::exit(1);
        }
    };

    // Show help when loose arguments are passed
    if matches.opt_present("h") || !matches.free.is_empty() {
        usage(&progname, opts);
        return;
    }

    // TODO I'm sure this can be improved somehow
    let dev = match (matches.opt_str("d"), get_hidraw_node()) {
        (Some(d), _) => std::path::PathBuf::from(d),
        (None, Some(d)) => d,
        (None, None) => {
            println!("Can't detect the device with udev, make sure it's plugged in, \
                     or specify -d");
            usage(&progname, opts);
            std::process::exit(1);
        }
    };

    let dpi = matches.opt_str("r").map(|s| {
        let r = s.split(',').map(|s| match s.parse::<i32>() {
            Ok(r) => {
                if r < 100 || r > 6400 || (r % 100 != 0) {
                    println!("Invalid resolution value");
                    std::process::exit(1);
                }
                r
            }
            Err(_) => {
                println!("Resolution is not a valid integer");
                std::process::exit(1);
            }
        }).collect::<Vec<_>>();
        if r.len() > 2 {
            println!("Too many values for resolution");
            std::process::exit(1);
        }
        r
    });

    let freq = matches.opt_str("f").map(|s| {
        match s.as_str() {
            "125" => 125,
            "500" => 500,
            "1000" => 1000,
            _ => {
                println!("Polling rate must be one of 125, 500 or 1000 Hz");
                std::process::exit(1);
            }
        }
    });

    let led_logo = matches.opt_str("l").map(|s| boolarg(s, "logo LED"));
    let led_wheel = matches.opt_str("w").map(|s| boolarg(s, "wheel LED"));

    println!("Using device {}", dev.to_str().unwrap());
}
<|endoftext|>


<commit_before>extern crate ar;
extern crate clap;
extern crate libflate;
extern crate tar;
extern crate tempfile;
extern crate xz2;
extern crate zip;

use std::fs;
use std::io;
use std::rc::Rc;
use std::vec::Vec;

use clap::{Arg, App};

use libflate::gzip;
use tempfile::tempfile;

// magic:
use std::io::Seek;

#[derive(Debug)]
struct Node<T> {
    next: Option<Rc<Node<T>>>,
    value: T,
}

impl<T> Node<T> {
    fn head(obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: None,
            value: obj,
        })
    }

    fn plus(what: &Rc<Node<T>>, obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: Some(what.clone()),
            value: obj
        })
    }
}

struct OutputTo {
    path: Rc<Node<String>>,
}

impl OutputTo {
    fn warn(&self, msg: String) {
        println!("TODO: {}", msg);
    }

    fn raw(&self, file: fs::File) -> io::Result<()> {
        unimplemented!();
    }
}

impl OutputTo {
    fn with_path(&self, path: String) -> OutputTo {
        OutputTo {
            path: Node::plus(&self.path, path)
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FileType {
    GZip,
    Zip,
    Tar,
    Ar,
    BZip2,
    Xz,
    Other,
}

fn read_octal(bytes: &[u8]) -> Option<u32> {
    let mut start = 0;
    while start < bytes.len() && b' ' == bytes[start] {
        start += 1;
    }

    let mut end = bytes.len() - 1;
    while end > start && (b' ' == bytes[end] || 0 == bytes[end]) {
        end -= 1;
    }

    if let Ok(string) = std::str::from_utf8(&bytes[start..(end+1)]) {
        if let Ok(val) = u32::from_str_radix(string, 8) {
            return Some(val);
        }
    }
    None
}

fn is_probably_tar(header: &[u8]) -> bool {
    if header.len() < 512 {
        return false;
    }

    if let Some(checksum) = read_octal(&header[148..156]) {
        let mut sum: u32 = (b' ' as u32) * 8;
        for i in 0..148 {
            sum += header[i] as u32;
        }
        for i in 156..512 {
            sum += header[i] as u32;
        }

        if checksum == sum {
            return true;
        }
    }

    return false;
}

fn identify<'a>(fd: &mut Box<io::BufRead + 'a>) -> io::Result<FileType> {
    let header = fd.fill_buf()?;
    if header.len() >= 20
        && 0x1f == header[0] && 0x8b == header[1] {
        Ok(FileType::GZip)
   } else if header.len() >= 152
        && b'P' == header[0] && b'K' == header[1]
        && 0x03 == header[2] && 0x04 == header[3] {
        Ok(FileType::Zip)
    } else if header.len() > 257 + 10
        && b'u' == header[257] && b's' == header[258]
        && b't' == header[259] && b'a' == header[260]
        && b'r' == header[261]
        && (
            (0 == header[262] && b'0' == header[263] && b'0' == header[264]) ||
            (b' ' == header[262] && b' ' == header[263] && 0 == header[264])
        ) {
        Ok(FileType::Tar)
    } else if header.len() > 8
        && b'!' == header[0] && b'<' == header[1]
        && b'a' == header[2] && b'r' == header[3]
        && b'c' == header[4] && b'h' == header[5]
        && b'>' == header[6] && b'\n' == header[7] {
        Ok(FileType::Ar)
    } else if header.len() > 3
        && b'B' == header[0] && b'Z' == header[1] {
        Ok(FileType::BZip2)
    } else if header.len() > 6
        && 0xfd == header[0] && b'7' == header[1]
        && b'z' == header[2] && b'X' == header[3]
        && b'Z' == header[4] && 0 == header[5] {
        Ok(FileType::Xz)
    } else if is_probably_tar(header) {
        Ok(FileType::Tar)
    } else {
        Ok(FileType::Other)
    }
}

fn plus<T: Clone>(vec: &Vec<T>, thing: T) -> Vec<T> {
    let mut ret = vec.clone();
    ret.push(thing);
    ret
}

struct BoxReader<'a> {
    fd: Box<io::BufRead + 'a>
}

impl<'a> io::Read for BoxReader<'a> {
    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
        self.fd.read(&mut buf)
    }
}

fn count_bytes<'a, R: io::Read>(mut fd: &mut R) -> io::Result<u64> {
    let mut block = [0u8; 4096];
    let mut count = 0u64;
    loop {
        let found = fd.read(&mut block[..])?;
        if 0 == found {
            break;
        }
        count += found as u64;
    }
    Ok(count)
}

fn unpack_or_raw<'a, F>(
    fd: Box<io::BufRead + 'a>,
    output: &OutputTo,
    fun: F) -> io::Result<()>
where F: FnOnce(Box<io::BufRead + 'a>) -> io::Result<Box<io::BufRead + 'a>>
{
    let mut temp = tempfile()?;

    match fun(fd).map(|stream| io::copy(&mut BoxReader { fd: stream }, &mut temp)) {
        Ok(_) => {
            temp.seek(io::SeekFrom::Start(0))?;
            unpack(Box::new(io::BufReader::new(temp)), output)
        },
        Err(e) => {
            output.warn(format!(
                    "thought we could unpack '{:?}' but we couldn't: {}",
                    output.path, e));
            output.raw(temp)
        }
    }
}

fn unpack<'a>(mut fd: Box<io::BufRead + 'a>, output: &OutputTo) -> io::Result<()> {
    match identify(&mut fd)? {
        FileType::GZip => {
            unpack_or_raw(fd, output,
                |fd| gzip::Decoder::new(fd).map(
                    |dec| Box::new(io::BufReader::new(dec)) as Box<io::BufRead>))
        },
        FileType::Ar if output.path.value.ends_with(".deb") => {
            let mut decoder = ar::Archive::new(fd);
            while let Some(entry) = decoder.next_entry() {
                let entry = entry?;
                let new_output = output.with_path(entry.header().identifier().to_string());
                unpack(Box::new(io::BufReader::new(entry)), &new_output)?;
            }
            Ok(())
        },
        FileType::Tar => {
            let mut decoder = tar::Archive::new(fd);
            for entry in decoder.entries()? {
                let entry = entry?;
                let new_output = output.with_path(entry.path()?.to_str().expect("valid utf-8").to_string());
                unpack(Box::new(io::BufReader::new(entry)), &new_output)?;
            }
            Ok(())
        },
        FileType::Xz => {
            unpack(Box::new(io::BufReader::new(xz2::bufread::XzDecoder::new(fd))), output)
        },
        FileType::Zip => {
            let mut angry = BoxReader { fd };
            while let Some(mut entry) = zip::read::read_single(&mut angry)? {
                let new_output = output.with_path((&*entry.name).to_string());
                let reader = entry.get_reader();
                let unpacked = unpack(Box::new(io::BufReader::new(reader)), &new_output);
                if let Err(e) = unpacked {
                    if e.kind() == io::ErrorKind::InvalidInput {
                        break;
                    } else {
                        return Err(e)
                    };
                }
            }
            let bytes_at_end = count_bytes(&mut angry)?;
            if 0 != bytes_at_end {
                println!("{:?}: discarding trailing bytes: {}", output.path, bytes_at_end);
            }
            Ok(())
        },
        other => {
            println!("{:?}: {:?} {}", output.path, other, count_bytes(&mut BoxReader { fd })?);
            Ok(())
        },
    }
}

fn main() {
    let matches = App::new("contentin")
                    .arg(Arg::with_name("to-tar")
                         .long("to-tar")
                         .help("emit a tar file")
                         .required(true))
                    .arg(Arg::with_name("INPUT")
                         .required(true)
                         .multiple(true))
                    .get_matches();
    for path in matches.values_of("INPUT").unwrap() {
        let file = fs::File::open(path).unwrap();
        let read = io::BufReader::new(file);
        unpack(Box::new(read), &OutputTo {
            path: Node::head(path.to_string())
        }).unwrap();
    }
}

<commit_msg>reorder<commit_after>extern crate ar;
extern crate clap;
extern crate libflate;
extern crate tar;
extern crate tempfile;
extern crate xz2;
extern crate zip;

use std::fs;
use std::io;
use std::rc::Rc;
use std::vec::Vec;

use clap::{Arg, App};

use libflate::gzip;
use tempfile::tempfile;

// magic:
use std::io::Seek;

#[derive(Debug)]
struct Node<T> {
    next: Option<Rc<Node<T>>>,
    value: T,
}

impl<T> Node<T> {
    fn head(obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: None,
            value: obj,
        })
    }

    fn plus(what: &Rc<Node<T>>, obj: T) -> Rc<Node<T>> {
        Rc::new(Node {
            next: Some(what.clone()),
            value: obj
        })
    }
}

struct OutputTo {
    path: Rc<Node<String>>,
}

impl OutputTo {
    fn warn(&self, msg: String) {
        println!("TODO: {}", msg);
    }

    fn raw(&self, file: fs::File) -> io::Result<()> {
        unimplemented!();
    }
}

impl OutputTo {
    fn with_path(&self, path: String) -> OutputTo {
        OutputTo {
            path: Node::plus(&self.path, path)
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum FileType {
    GZip,
    Zip,
    Tar,
    Ar,
    BZip2,
    Xz,
    Other,
}

fn read_octal(bytes: &[u8]) -> Option<u32> {
    let mut start = 0;
    while start < bytes.len() && b' ' == bytes[start] {
        start += 1;
    }

    let mut end = bytes.len() - 1;
    while end > start && (b' ' == bytes[end] || 0 == bytes[end]) {
        end -= 1;
    }

    if let Ok(string) = std::str::from_utf8(&bytes[start..(end+1)]) {
        if let Ok(val) = u32::from_str_radix(string, 8) {
            return Some(val);
        }
    }
    None
}

fn is_probably_tar(header: &[u8]) -> bool {
    if header.len() < 512 {
        return false;
    }

    if let Some(checksum) = read_octal(&header[148..156]) {
        let mut sum: u32 = (b' ' as u32) * 8;
        for i in 0..148 {
            sum += header[i] as u32;
        }
        for i in 156..512 {
            sum += header[i] as u32;
        }

        if checksum == sum {
            return true;
        }
    }

    return false;
}

fn identify<'a>(fd: &mut Box<io::BufRead + 'a>) -> io::Result<FileType> {
    let header = fd.fill_buf()?;
    if header.len() >= 20
        && 0x1f == header[0] && 0x8b == header[1] {
        Ok(FileType::GZip)
   } else if header.len() >= 152
        && b'P' == header[0] && b'K' == header[1]
        && 0x03 == header[2] && 0x04 == header[3] {
        Ok(FileType::Zip)
    } else if header.len() > 257 + 10
        && b'u' == header[257] && b's' == header[258]
        && b't' == header[259] && b'a' == header[260]
        && b'r' == header[261]
        && (
            (0 == header[262] && b'0' == header[263] && b'0' == header[264]) ||
            (b' ' == header[262] && b' ' == header[263] && 0 == header[264])
        ) {
        Ok(FileType::Tar)
    } else if header.len() > 8
        && b'!' == header[0] && b'<' == header[1]
        && b'a' == header[2] && b'r' == header[3]
        && b'c' == header[4] && b'h' == header[5]
        && b'>' == header[6] && b'\n' == header[7] {
        Ok(FileType::Ar)
    } else if header.len() > 3
        && b'B' == header[0] && b'Z' == header[1] {
        Ok(FileType::BZip2)
    } else if header.len() > 6
        && 0xfd == header[0] && b'7' == header[1]
        && b'z' == header[2] && b'X' == header[3]
        && b'Z' == header[4] && 0 == header[5] {
        Ok(FileType::Xz)
    } else if is_probably_tar(header) {
        Ok(FileType::Tar)
    } else {
        Ok(FileType::Other)
    }
}

fn plus<T: Clone>(vec: &Vec<T>, thing: T) -> Vec<T> {
    let mut ret = vec.clone();
    ret.push(thing);
    ret
}

struct BoxReader<'a> {
    fd: Box<io::BufRead + 'a>
}

impl<'a> io::Read for BoxReader<'a> {
    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
        self.fd.read(&mut buf)
    }
}

fn count_bytes<'a, R: io::Read>(mut fd: &mut R) -> io::Result<u64> {
    let mut block = [0u8; 4096];
    let mut count = 0u64;
    loop {
        let found = fd.read(&mut block[..])?;
        if 0 == found {
            break;
        }
        count += found as u64;
    }
    Ok(count)
}

fn unpack_or_raw<'a, F>(
    fd: Box<io::BufRead + 'a>,
    output: &OutputTo,
    fun: F) -> io::Result<()>
where F: FnOnce(Box<io::BufRead + 'a>) -> io::Result<Box<io::BufRead + 'a>>
{
    let mut temp = tempfile()?;

    match fun(fd).map(|stream| io::copy(&mut BoxReader { fd: stream }, &mut temp)) {
        Ok(_) => {
            temp.seek(io::SeekFrom::Start(0))?;
            unpack(Box::new(io::BufReader::new(temp)), output)
        },
        Err(e) => {
            output.warn(format!(
                    "thought we could unpack '{:?}' but we couldn't: {}",
                    output.path, e));
            output.raw(temp)
        }
    }
}

fn unpack<'a>(mut fd: Box<io::BufRead + 'a>, output: &OutputTo) -> io::Result<()> {
    match identify(&mut fd)? {
        FileType::GZip => {
            unpack_or_raw(fd, output,
                |fd| gzip::Decoder::new(fd).map(
                    |dec| Box::new(io::BufReader::new(dec)) as Box<io::BufRead>))
        },
        FileType::Xz => {
            unpack(Box::new(io::BufReader::new(xz2::bufread::XzDecoder::new(fd))), output)
        },
        FileType::Ar if output.path.value.ends_with(".deb") => {
            let mut decoder = ar::Archive::new(fd);
            while let Some(entry) = decoder.next_entry() {
                let entry = entry?;
                let new_output = output.with_path(entry.header().identifier().to_string());
                unpack(Box::new(io::BufReader::new(entry)), &new_output)?;
            }
            Ok(())
        },
        FileType::Tar => {
            let mut decoder = tar::Archive::new(fd);
            for entry in decoder.entries()? {
                let entry = entry?;
                let new_output = output.with_path(entry.path()?.to_str().expect("valid utf-8").to_string());
                unpack(Box::new(io::BufReader::new(entry)), &new_output)?;
            }
            Ok(())
        },
        FileType::Zip => {
            let mut angry = BoxReader { fd };
            while let Some(mut entry) = zip::read::read_single(&mut angry)? {
                let new_output = output.with_path((&*entry.name).to_string());
                let reader = entry.get_reader();
                let unpacked = unpack(Box::new(io::BufReader::new(reader)), &new_output);
                if let Err(e) = unpacked {
                    if e.kind() == io::ErrorKind::InvalidInput {
                        break;
                    } else {
                        return Err(e)
                    };
                }
            }
            let bytes_at_end = count_bytes(&mut angry)?;
            if 0 != bytes_at_end {
                println!("{:?}: discarding trailing bytes: {}", output.path, bytes_at_end);
            }
            Ok(())
        },
        other => {
            println!("{:?}: {:?} {}", output.path, other, count_bytes(&mut BoxReader { fd })?);
            Ok(())
        },
    }
}

fn main() {
    let matches = App::new("contentin")
                    .arg(Arg::with_name("to-tar")
                         .long("to-tar")
                         .help("emit a tar file")
                         .required(true))
                    .arg(Arg::with_name("INPUT")
                         .required(true)
                         .multiple(true))
                    .get_matches();
    for path in matches.values_of("INPUT").unwrap() {
        let file = fs::File::open(path).unwrap();
        let read = io::BufReader::new(file);
        unpack(Box::new(read), &OutputTo {
            path: Node::head(path.to_string())
        }).unwrap();
    }
}

<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;

extern crate toml;
extern crate semver;
extern crate term;
extern crate tempfile;
extern crate rustc_serialize;

use std::io::Write;
use std::path::Path;
use std::process::Command;
use semver::Version;
use rr_result::{RrResult, RrError};
use config::Config;
use cargo_proj::CargoProj;
use utils::{check_output, modify_file};

mod git;
mod rr_result;
mod version_kind;
mod config;
mod cargo_proj;
mod cargo;

#[macro_use]
mod utils;

fn main() {
    let mut exit_code = 0;
    execute().unwrap_or_else(|err| {
        match err {
            RrError::Message(_) => {
                stderrln!("{}", err);
                exit_code = 1;
            }

            RrError::ClapDisplaysInfo(_) => {
                exit_code = 2;
            }
        }
    });

    std::process::exit(exit_code);
}

fn execute() -> RrResult<()> {
    let config = try!(Config::from_file_and_command_args());
    let mut cargo_proj = try!(CargoProj::find(&config.start_dir));
    try!(std::env::set_current_dir(try!(cargo_proj.root_dir())));

    stdoutln!("Checking git state ...");
    try!(git::check_state());

    stdoutln!("Testing ...");
    try!(cargo::test());

    let curr_version = cargo_proj.version().clone();
    let tag_name_curr_version = config.tag_name(&cargo_proj);

    let new_version = config.version_kind.increment(&curr_version);
    try!(cargo_proj.write_version(&new_version));

    stdoutln!("Building release ...");
    try!(cargo::build_release());

    if let Some(changelog) = cargo_proj.changelog() {
        stdoutln!("Updating changelog ...");
        try!(update_changelog(config.editor(), changelog, &tag_name_curr_version, &new_version));
    }

    stdoutln!("Creating git commit ...");
    try!(git::add_update());
    try!(git::commit(&config.commit_message(&cargo_proj)));
    try!(git::tag(&config.tag_name(&cargo_proj)));

    if config.git_push {
        stdoutln!("Pushing git changes ...");
        try!(git::push());
    }

    if config.cargo_publish {
        stdoutln!("Publishing to crates.io ...");
        try!(cargo::publish());
    }

    Ok(())
}

/// Adds `new_version` at the top of the `changelog` and opens
/// `changelog` and a temporary file containing the commits from
/// HEAD till the last release.
fn update_changelog(mut editor_cmd: Command,
                    changelog: &Path,
                    tag_name_curr_version: &str,
                    new_version: &Version)
                    -> RrResult<()> {
    try!(modify_file(changelog, |contents| { format!("{}\n\n{}", new_version, contents) }));

    let log_to = if try!(git::has_tag(tag_name_curr_version)) {
        Some(tag_name_curr_version)
    } else {
        None
    };

    let log_file = try!(git::log_file("HEAD", log_to));

    let output = try!(editor_cmd.arg(changelog)
        .arg(log_file.path())
        .output());

    try!(check_output(&output));
    Ok(())
}
<commit_msg>More correct user feedback<commit_after>#[macro_use]
extern crate clap;

extern crate toml;
extern crate semver;
extern crate term;
extern crate tempfile;
extern crate rustc_serialize;

use std::io::Write;
use std::path::Path;
use std::process::Command;
use semver::Version;
use rr_result::{RrResult, RrError};
use config::Config;
use cargo_proj::CargoProj;
use utils::{check_output, modify_file};

mod git;
mod rr_result;
mod version_kind;
mod config;
mod cargo_proj;
mod cargo;

#[macro_use]
mod utils;

fn main() {
    let mut exit_code = 0;
    execute().unwrap_or_else(|err| {
        match err {
            RrError::Message(_) => {
                stderrln!("{}", err);
                exit_code = 1;
            }

            RrError::ClapDisplaysInfo(_) => {
                exit_code = 2;
            }
        }
    });

    std::process::exit(exit_code);
}

fn execute() -> RrResult<()> {
    let config = try!(Config::from_file_and_command_args());
    let mut cargo_proj = try!(CargoProj::find(&config.start_dir));
    try!(std::env::set_current_dir(try!(cargo_proj.root_dir())));

    stdoutln!("Checking git state ...");
    try!(git::check_state());

    stdoutln!("Testing ...");
    try!(cargo::test());

    let curr_version = cargo_proj.version().clone();
    let tag_name_curr_version = config.tag_name(&cargo_proj);

    let new_version = config.version_kind.increment(&curr_version);
    try!(cargo_proj.write_version(&new_version));

    stdoutln!("Building release ...");
    try!(cargo::build_release());

    if let Some(changelog) = cargo_proj.changelog() {
        stdoutln!("Updating changelog ...");
        try!(update_changelog(config.editor(), changelog, &tag_name_curr_version, &new_version));
    }

    stdoutln!("Creating git commit/tag ...");
    try!(git::add_update());
    try!(git::commit(&config.commit_message(&cargo_proj)));
    try!(git::tag(&config.tag_name(&cargo_proj)));

    if config.git_push {
        stdoutln!("Pushing git changes ...");
        try!(git::push());
    }

    if config.cargo_publish {
        stdoutln!("Publishing to crates.io ...");
        try!(cargo::publish());
    }

    Ok(())
}

/// Adds `new_version` at the top of the `changelog` and opens
/// `changelog` and a temporary file containing the commits from
/// HEAD till the last release.
fn update_changelog(mut editor_cmd: Command,
                    changelog: &Path,
                    tag_name_curr_version: &str,
                    new_version: &Version)
                    -> RrResult<()> {
    try!(modify_file(changelog, |contents| { format!("{}\n\n{}", new_version, contents) }));

    let log_to = if try!(git::has_tag(tag_name_curr_version)) {
        Some(tag_name_curr_version)
    } else {
        None
    };

    let log_file = try!(git::log_file("HEAD", log_to));

    let output = try!(editor_cmd.arg(changelog)
        .arg(log_file.path())
        .output());

    try!(check_output(&output));
    Ok(())
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

use clap::{Arg, App, SubCommand};
use std::env;
use std::io;
use std::io::prelude::*;

const GANDI_URL_PROD: &'static str = "https://rpc.gandi.net/";

trait GandiAPI {
    fn isRecordAlreadyDeclared(&self, record: &str) -> Option<&str>;
    fn updateRecord(&self, record: &str, ipAddr: &str);
    fn createRecord(&self, record: &str, ipAddr: &str);
}

struct GandiAPIImpl;

impl GandiAPI for GandiAPIImpl {
    fn isRecordAlreadyDeclared(&self, record: &str) -> Option<&str> {
        unimplemented!();
    }

    fn updateRecord(&self, record: &str, ipAddr: &str) {
        unimplemented!();
    }

    fn createRecord(&self, record: &str, ipAddr: &str) {
        unimplemented!();
    }
}

fn main() {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Gandi DNS updater, useful to reflect your dynamic IP address to your Gandi DNS zone file.\nIP address is read from stdin.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("record_name")
            .help("Name of the A record to update or create (without domain)")
            .index(1)
            .required(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    if matches.is_present("verbose") {
        env::set_var("RUST_LOG", "DEBUG");
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(n) => {
            println!("Input: {}", input);
        }
        Err(error) => println!("error: {}", error),
    }

    let detectedIpAddr = input.trim();

    let gandi_api = GandiAPIImpl;

    let maybeChecked = gandi_api.isRecordAlreadyDeclared(record_name);

    match maybeChecked {
        Some(ipAddr) => {
            if ipAddr != detectedIpAddr {
                gandi_api.updateRecord(record_name, ipAddr);
            }
        }
        None => gandi_api.createRecord(record_name, detectedIpAddr)
    }

}
<commit_msg>Configure with gandi's rpc parameters<commit_after>#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

use clap::{Arg, App, SubCommand};
use std::env;
use std::io;
use std::io::prelude::*;

const GANDI_URL_PROD: &'static str = "https://rpc.gandi.net/";

trait GandiAPI {
    fn isRecordAlreadyDeclared(&self, record: &str) -> Option<&str>;
    fn updateRecord(&self, record: &str, ipAddr: &str);
    fn createRecord(&self, record: &str, ipAddr: &str);
}

struct GandiAPIImpl<'a> {
  xmlrpc_server: &'a str,
  apikey: &'a str
}

impl<'a> GandiAPI for GandiAPIImpl<'a> {
    fn isRecordAlreadyDeclared(&self, record: &str) -> Option<&str> {
        unimplemented!();
    }

    fn updateRecord(&self, record: &str, ipAddr: &str) {
        unimplemented!();
    }

    fn createRecord(&self, record: &str, ipAddr: &str) {
        unimplemented!();
    }
}

fn main() {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Gandi DNS updater, useful to reflect your dynamic IP address to your Gandi DNS zone file.\nIP address is read from stdin.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("record_name")
            .help("Name of the A record to update or create (without domain)")
            .index(1)
            .required(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    if matches.is_present("verbose") {
        env::set_var("RUST_LOG", "DEBUG");
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let mut input = String::new();
    match io::stdin().read_line(&mut input) {
        Ok(n) => {
            println!("Input: {}", input);
        }
        Err(error) => println!("error: {}", error),
    }

    let detectedIpAddr = input.trim();

    let gandi_api = GandiAPIImpl {
      xmlrpc_server: GANDI_URL_PROD,
      apikey: apikey
    };

    let maybeChecked = gandi_api.isRecordAlreadyDeclared(record_name);

    match maybeChecked {
        Some(ipAddr) => {
            if ipAddr != detectedIpAddr {
                gandi_api.updateRecord(record_name, ipAddr);
            }
        }
        None => gandi_api.createRecord(record_name, detectedIpAddr)
    }

}
<|endoftext|>


<commit_before>extern crate cargo_update;
extern crate tabwriter;
extern crate lazysort;
extern crate regex;
extern crate git2;

use std::process::{Command, exit};
use std::io::{Write, stdout};
use tabwriter::TabWriter;
use lazysort::SortedBy;
use std::fmt::Display;
use git2::Repository;
#[cfg(target_os="windows")]
use std::fs::File;
use regex::Regex;
use std::env;
use std::fs;

fn main() {
    let result = actual_main().err().unwrap_or(0);
    exit(result);
}

fn actual_main() -> Result<(), i32> {
    let opts = cargo_update::Options::parse();

    if cfg!(target_os = "windows") {
        let old_version_r = Regex::new(r"cargo-install-update\.exe-v.+").unwrap();
        for old_version in fs::read_dir(env::current_exe().unwrap().parent().unwrap().canonicalize().unwrap())
            .unwrap()
            .map(Result::unwrap)
            .filter(|f| old_version_r.is_match(&f.file_name().into_string().unwrap())) {
            fs::remove_file(old_version.path()).unwrap();
        }
    }

    let crates_file = cargo_update::ops::resolve_crates_file(opts.crates_file.1.clone());
    let configuration = try!(cargo_update::ops::PackageConfig::read(&crates_file.with_file_name(".install_config.toml")));
    let mut packages = cargo_update::ops::installed_main_repo_packages(&crates_file);

    if !opts.to_update.is_empty() {
        packages = cargo_update::ops::intersect_packages(&packages, &opts.to_update, opts.install);
    }

    {
        // Searching for "" will just update the registry
        let search_res = Command::new("cargo").arg("search").arg("").status().unwrap();
        if !search_res.success() {
            return Err(search_res.code().unwrap_or(-1));
        }
        println!("");
    }

    let registry = cargo_update::ops::get_index_path(&opts.cargo_dir.1);
    let registry_repo = try!(Repository::open(&registry).map_err(|_| {
        println!("Failed to open registry repository at {}.", registry.display());
        2
    }));
    let latest_registry = try!(registry_repo.revparse_single("origin/master").map_err(|_| {
        println!("Failed read master branch of registry repositry at {}.", registry.display());
        2
    }));

    for package in &mut packages {
        package.pull_version(&latest_registry.as_commit().unwrap().tree().unwrap(), &registry_repo);
    }

    {
        let mut out = TabWriter::new(stdout());
        writeln!(out, "Package\tInstalled\tLatest\tNeeds update").unwrap();
        for package in packages.iter()
            .sorted_by(|lhs, rhs| (!lhs.needs_update(), &lhs.name).cmp(&(!rhs.needs_update(), &rhs.name))) {
            write!(out, "{}\t", package.name).unwrap();
            if let Some(ref v) = package.version {
                write!(out, "v{}", v).unwrap();
            }
            writeln!(out, "\tv{}\t{}", package.update_to_version(), if package.needs_update() { "Yes" } else { "No" }).unwrap();
        }
        writeln!(out, "").unwrap();
        out.flush().unwrap();
    }

    if opts.update {
        if !opts.force {
            packages.retain(cargo_update::ops::MainRepoPackage::needs_update);
        }

        if !packages.is_empty() {
            let (success_n, errored, result): (usize, Vec<String>, Option<i32>) = packages.into_iter()
                .map(|package| -> Result<(), (i32, String)> {
                    println!("{} {}",
                             if package.version.is_some() {
                                 "Updating"
                             } else {
                                 "Installing"
                             },
                             package.name);

                    if cfg!(target_os = "windows") && package.version.is_some() && package.name == "cargo-update" {
                        save_cargo_update_exec(package.version.as_ref().unwrap());
                    }

                    let install_res = if let Some(cfg) = configuration.get(&package.name) {
                            Command::new("cargo")
                                .args(&cfg.cargo_args()[..])
                                .arg(&package.name)
                                .arg("--vers")
                                .arg(package.update_to_version().to_string())
                                .status()
                        } else {
                            Command::new("cargo")
                                .arg("install")
                                .arg("-f")
                                .arg(&package.name)
                                .arg("--vers")
                                .arg(package.update_to_version().to_string())
                                .status()
                        }
                        .unwrap();

                    println!("");
                    if !install_res.success() {
                        if cfg!(target_os = "windows") && package.version.is_some() && package.name == "cargo-update" {
                            restore_cargo_update_exec(package.version.as_ref().unwrap());
                        }

                        Err((install_res.code().unwrap_or(-1), package.name))
                    } else {
                        Ok(())
                    }
                })
                .fold((0, vec![], None), |(s, mut e, r), p| match p {
                    Ok(()) => (s + 1, e, r),
                    Err((pr, pn)) => {
                        e.push(pn);
                        (s, e, r.or_else(|| Some(pr)))
                    }
                });

            println!("");
            println!("Updated {} package{}.", success_n, if success_n == 1 { "" } else { "s" });
            if !errored.is_empty() && result.is_some() {
                println!("Failed to update {}.", &errored.iter().fold("".to_string(), |s, e| s + ", " + e)[2..]);
                return Err(result.unwrap());
            }
        } else {
            println!("No packages need updating.");
        }
    }

    if opts.update_git {
        let mut packages = cargo_update::ops::installed_git_repo_packages(&crates_file);

        if !opts.to_update.is_empty() {
            packages.retain(|p| opts.to_update.iter().any(|u| p.name == u.0));
        }

        for package in &mut packages {
            package.pull_version(&opts.temp_dir.1);
        }

        {
            let mut out = TabWriter::new(stdout());
            writeln!(out, "Package\tInstalled\tLatest\tNeeds update").unwrap();
            for package in packages.iter()
                .sorted_by(|lhs, rhs| (!lhs.needs_update(), &lhs.name).cmp(&(!rhs.needs_update(), &rhs.name))) {
                writeln!(out,
                         "{}\t{}\t{}\t{}",
                         package.name,
                         package.id,
                         package.newest_id.as_ref().unwrap(),
                         if package.needs_update() { "Yes" } else { "No" })
                    .unwrap();
            }
            writeln!(out, "").unwrap();
            out.flush().unwrap();
        }

        if opts.update {
            if !opts.force {
                packages.retain(cargo_update::ops::GitRepoPackage::needs_update);
            }

            if !packages.is_empty() {
                let (success_n, errored, result): (usize, Vec<String>, Option<i32>) = packages.into_iter()
                    .map(|package| -> Result<(), (i32, String)> {
                        println!("Updating {} from {}", package.name, package.url);

                        if cfg!(target_os = "windows") && package.name == "cargo-update" {
                            save_cargo_update_exec(&package.id.to_string());
                        }

                        let install_res = if let Some(cfg) = configuration.get(&package.name) {
                                Command::new("cargo")
                                    .args(&cfg.cargo_args()[..])
                                    .arg("--git")
                                    .arg(&package.url)
                                    .status()
                            } else {
                                Command::new("cargo")
                                    .arg("install")
                                    .arg("-f")
                                    .arg("--git")
                                    .arg(&package.url)
                                    .status()
                            }
                            .unwrap();

                        println!("");
                        if !install_res.success() {
                            if cfg!(target_os = "windows") && package.name == "cargo-update" {
                                restore_cargo_update_exec(&package.id.to_string());
                            }

                            Err((install_res.code().unwrap_or(-1), package.name))
                        } else {
                            Ok(())
                        }
                    })
                    .fold((0, vec![], None), |(s, mut e, r), p| match p {
                        Ok(()) => (s + 1, e, r),
                        Err((pr, pn)) => {
                            e.push(pn);
                            (s, e, r.or_else(|| Some(pr)))
                        }
                    });

                println!("");
                println!("Updated {} git package{}.", success_n, if success_n == 1 { "" } else { "s" });
                if !errored.is_empty() && result.is_some() {
                    println!("Failed to update {}.", &errored.iter().fold("".to_string(), |s, e| s + ", " + e)[2..]);
                    return Err(result.unwrap());
                }
            } else {
                println!("No git packages need updating.");
            }
        }
    }

    Ok(())
}

/// This way the past-current exec will be "replaced" and we'll get no dupes in .cargo.toml
#[cfg(target_os="windows")]
fn save_cargo_update_exec<D: Display>(version: &D) {
    let cur_exe = env::current_exe().unwrap();
    fs::rename(&cur_exe, cur_exe.with_extension(format!("exe-v{}", version))).unwrap();
    File::create(cur_exe).unwrap();
}

#[cfg(target_os="windows")]
fn restore_cargo_update_exec<D: Display>(version: &D) {
    let cur_exe = env::current_exe().unwrap();
    fs::remove_file(&cur_exe).unwrap();
    fs::rename(cur_exe.with_extension(format!("exe-v{}", version)), cur_exe).unwrap();
}

#[cfg(not(target_os="windows"))]
fn save_cargo_update_exec<D: Display>(_: &D) {}

#[cfg(not(target_os="windows"))]
fn restore_cargo_update_exec<D: Display>(_: &D) {}
<commit_msg>typo repositry -> repository<commit_after>extern crate cargo_update;
extern crate tabwriter;
extern crate lazysort;
extern crate regex;
extern crate git2;

use std::process::{Command, exit};
use std::io::{Write, stdout};
use tabwriter::TabWriter;
use lazysort::SortedBy;
use std::fmt::Display;
use git2::Repository;
#[cfg(target_os="windows")]
use std::fs::File;
use regex::Regex;
use std::env;
use std::fs;

fn main() {
    let result = actual_main().err().unwrap_or(0);
    exit(result);
}

fn actual_main() -> Result<(), i32> {
    let opts = cargo_update::Options::parse();

    if cfg!(target_os = "windows") {
        let old_version_r = Regex::new(r"cargo-install-update\.exe-v.+").unwrap();
        for old_version in fs::read_dir(env::current_exe().unwrap().parent().unwrap().canonicalize().unwrap())
            .unwrap()
            .map(Result::unwrap)
            .filter(|f| old_version_r.is_match(&f.file_name().into_string().unwrap())) {
            fs::remove_file(old_version.path()).unwrap();
        }
    }

    let crates_file = cargo_update::ops::resolve_crates_file(opts.crates_file.1.clone());
    let configuration = try!(cargo_update::ops::PackageConfig::read(&crates_file.with_file_name(".install_config.toml")));
    let mut packages = cargo_update::ops::installed_main_repo_packages(&crates_file);

    if !opts.to_update.is_empty() {
        packages = cargo_update::ops::intersect_packages(&packages, &opts.to_update, opts.install);
    }

    {
        // Searching for "" will just update the registry
        let search_res = Command::new("cargo").arg("search").arg("").status().unwrap();
        if !search_res.success() {
            return Err(search_res.code().unwrap_or(-1));
        }
        println!("");
    }

    let registry = cargo_update::ops::get_index_path(&opts.cargo_dir.1);
    let registry_repo = try!(Repository::open(&registry).map_err(|_| {
        println!("Failed to open registry repository at {}.", registry.display());
        2
    }));
    let latest_registry = try!(registry_repo.revparse_single("origin/master").map_err(|_| {
        println!("Failed to read master branch of registry repository at {}.", registry.display());
        2
    }));

    for package in &mut packages {
        package.pull_version(&latest_registry.as_commit().unwrap().tree().unwrap(), &registry_repo);
    }

    {
        let mut out = TabWriter::new(stdout());
        writeln!(out, "Package\tInstalled\tLatest\tNeeds update").unwrap();
        for package in packages.iter()
            .sorted_by(|lhs, rhs| (!lhs.needs_update(), &lhs.name).cmp(&(!rhs.needs_update(), &rhs.name))) {
            write!(out, "{}\t", package.name).unwrap();
            if let Some(ref v) = package.version {
                write!(out, "v{}", v).unwrap();
            }
            writeln!(out, "\tv{}\t{}", package.update_to_version(), if package.needs_update() { "Yes" } else { "No" }).unwrap();
        }
        writeln!(out, "").unwrap();
        out.flush().unwrap();
    }

    if opts.update {
        if !opts.force {
            packages.retain(cargo_update::ops::MainRepoPackage::needs_update);
        }

        if !packages.is_empty() {
            let (success_n, errored, result): (usize, Vec<String>, Option<i32>) = packages.into_iter()
                .map(|package| -> Result<(), (i32, String)> {
                    println!("{} {}",
                             if package.version.is_some() {
                                 "Updating"
                             } else {
                                 "Installing"
                             },
                             package.name);

                    if cfg!(target_os = "windows") && package.version.is_some() && package.name == "cargo-update" {
                        save_cargo_update_exec(package.version.as_ref().unwrap());
                    }

                    let install_res = if let Some(cfg) = configuration.get(&package.name) {
                            Command::new("cargo")
                                .args(&cfg.cargo_args()[..])
                                .arg(&package.name)
                                .arg("--vers")
                                .arg(package.update_to_version().to_string())
                                .status()
                        } else {
                            Command::new("cargo")
                                .arg("install")
                                .arg("-f")
                                .arg(&package.name)
                                .arg("--vers")
                                .arg(package.update_to_version().to_string())
                                .status()
                        }
                        .unwrap();

                    println!("");
                    if !install_res.success() {
                        if cfg!(target_os = "windows") && package.version.is_some() && package.name == "cargo-update" {
                            restore_cargo_update_exec(package.version.as_ref().unwrap());
                        }

                        Err((install_res.code().unwrap_or(-1), package.name))
                    } else {
                        Ok(())
                    }
                })
                .fold((0, vec![], None), |(s, mut e, r), p| match p {
                    Ok(()) => (s + 1, e, r),
                    Err((pr, pn)) => {
                        e.push(pn);
                        (s, e, r.or_else(|| Some(pr)))
                    }
                });

            println!("");
            println!("Updated {} package{}.", success_n, if success_n == 1 { "" } else { "s" });
            if !errored.is_empty() && result.is_some() {
                println!("Failed to update {}.", &errored.iter().fold("".to_string(), |s, e| s + ", " + e)[2..]);
                return Err(result.unwrap());
            }
        } else {
            println!("No packages need updating.");
        }
    }

    if opts.update_git {
        let mut packages = cargo_update::ops::installed_git_repo_packages(&crates_file);

        if !opts.to_update.is_empty() {
            packages.retain(|p| opts.to_update.iter().any(|u| p.name == u.0));
        }

        for package in &mut packages {
            package.pull_version(&opts.temp_dir.1);
        }

        {
            let mut out = TabWriter::new(stdout());
            writeln!(out, "Package\tInstalled\tLatest\tNeeds update").unwrap();
            for package in packages.iter()
                .sorted_by(|lhs, rhs| (!lhs.needs_update(), &lhs.name).cmp(&(!rhs.needs_update(), &rhs.name))) {
                writeln!(out,
                         "{}\t{}\t{}\t{}",
                         package.name,
                         package.id,
                         package.newest_id.as_ref().unwrap(),
                         if package.needs_update() { "Yes" } else { "No" })
                    .unwrap();
            }
            writeln!(out, "").unwrap();
            out.flush().unwrap();
        }

        if opts.update {
            if !opts.force {
                packages.retain(cargo_update::ops::GitRepoPackage::needs_update);
            }

            if !packages.is_empty() {
                let (success_n, errored, result): (usize, Vec<String>, Option<i32>) = packages.into_iter()
                    .map(|package| -> Result<(), (i32, String)> {
                        println!("Updating {} from {}", package.name, package.url);

                        if cfg!(target_os = "windows") && package.name == "cargo-update" {
                            save_cargo_update_exec(&package.id.to_string());
                        }

                        let install_res = if let Some(cfg) = configuration.get(&package.name) {
                                Command::new("cargo")
                                    .args(&cfg.cargo_args()[..])
                                    .arg("--git")
                                    .arg(&package.url)
                                    .status()
                            } else {
                                Command::new("cargo")
                                    .arg("install")
                                    .arg("-f")
                                    .arg("--git")
                                    .arg(&package.url)
                                    .status()
                            }
                            .unwrap();

                        println!("");
                        if !install_res.success() {
                            if cfg!(target_os = "windows") && package.name == "cargo-update" {
                                restore_cargo_update_exec(&package.id.to_string());
                            }

                            Err((install_res.code().unwrap_or(-1), package.name))
                        } else {
                            Ok(())
                        }
                    })
                    .fold((0, vec![], None), |(s, mut e, r), p| match p {
                        Ok(()) => (s + 1, e, r),
                        Err((pr, pn)) => {
                            e.push(pn);
                            (s, e, r.or_else(|| Some(pr)))
                        }
                    });

                println!("");
                println!("Updated {} git package{}.", success_n, if success_n == 1 { "" } else { "s" });
                if !errored.is_empty() && result.is_some() {
                    println!("Failed to update {}.", &errored.iter().fold("".to_string(), |s, e| s + ", " + e)[2..]);
                    return Err(result.unwrap());
                }
            } else {
                println!("No git packages need updating.");
            }
        }
    }

    Ok(())
}

/// This way the past-current exec will be "replaced" and we'll get no dupes in .cargo.toml
#[cfg(target_os="windows")]
fn save_cargo_update_exec<D: Display>(version: &D) {
    let cur_exe = env::current_exe().unwrap();
    fs::rename(&cur_exe, cur_exe.with_extension(format!("exe-v{}", version))).unwrap();
    File::create(cur_exe).unwrap();
}

#[cfg(target_os="windows")]
fn restore_cargo_update_exec<D: Display>(version: &D) {
    let cur_exe = env::current_exe().unwrap();
    fs::remove_file(&cur_exe).unwrap();
    fs::rename(cur_exe.with_extension(format!("exe-v{}", version)), cur_exe).unwrap();
}

#[cfg(not(target_os="windows"))]
fn save_cargo_update_exec<D: Display>(_: &D) {}

#[cfg(not(target_os="windows"))]
fn restore_cargo_update_exec<D: Display>(_: &D) {}
<|endoftext|>


<commit_before>// Copyright (C) 2016 Mark Lee

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

extern crate env_logger;
extern crate hyper;
#[macro_use]
extern crate log;
extern crate redis;
extern crate regex;
extern crate reqwest;
extern crate rquery;
extern crate semver;
extern crate serde;
extern crate serde_json;

use hyper::{Get, Server};
use hyper::header::{Accept, ContentType};
use hyper::server::{Request, Response};
use hyper::uri::RequestUri::AbsolutePath;
use redis::{Commands, RedisError, RedisResult};
use regex::Regex;
use rquery::Document;
use semver::Version;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::Read;

const DEFAULT_SERVER_PORT: u32 = 9000;
const DEFAULT_MIN_BUNDLER_VERSION: &'static str = "1.12.0";
const RUBY_LANGPACK_RELEASES_URL: &'static str = "https://github.\
                                                  com/heroku/heroku-buildpack-ruby/releases.atom";

fn download_url(url: &str) -> io::Result<String> {
    let mut resp = reqwest::get(url).expect("Could not send HTTP request");
    let mut body = String::new();
    match resp.read_to_string(&mut body) {
        Err(error) => Err(error),
        _ => Ok(body),
    }
}

fn download_latest_buildpack_release() -> String {
    let xml = download_url(RUBY_LANGPACK_RELEASES_URL).expect("Could not download Atom releases!");
    let atom_doc = Document::new_from_xml_string(&xml[..]).expect("Could not parse Atom releases!");
    let latest_tag = atom_doc.select("entry title")
        .expect("Could not find latest buildpack tag!");
    latest_tag.text().clone()
}

fn redis_cache_value(redis: &redis::Connection, key: &str, value: &str) -> String {
    let set_result: RedisResult<()> = redis.set_ex(key, value, 3600);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
    value.to_owned()
}

fn redis_cache_hash_value(redis: &redis::Connection, hash_name: &str, key: &str, value: &str) {
    let set_result: RedisResult<()> = redis.hset_nx(hash_name, key, value);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
}

fn latest_buildpack_release(redis_result: &RedisResult<redis::Connection>) -> String {
    if let Ok(ref redis) = *redis_result {
        if let Ok(buildpack_release) = redis.get("latest_buildpack_release") {
            buildpack_release
        } else {
            let buildpack_release = download_latest_buildpack_release();
            redis_cache_value(redis, "latest_buildpack_release", &buildpack_release[..])
        }
    } else {
        download_latest_buildpack_release()
    }
}

fn cached_version_from_buildpack_release(buildpack_release: &str,
                                         redis_result: &RedisResult<redis::Connection>)
                                         -> Option<String> {
    if let Ok(ref redis) = *redis_result {
        if let Ok(version) = redis.hget("bundler_version", buildpack_release) {
            Some(version)
        } else {
            None
        }
    } else {
        None
    }
}

fn bundler_version_from_ruby_buildpack(redis_result: &RedisResult<redis::Connection>)
                                       -> Option<String> {
    let buildpack_release = latest_buildpack_release(redis_result);
    if let Some(cached_version) =
        cached_version_from_buildpack_release(&buildpack_release[..], redis_result) {
        return Some(cached_version);
    }
    let ruby_langpack_url = format!("https://raw.githubusercontent.com/\
                                     heroku/heroku-buildpack-ruby/{}/lib/language_pack/\
                                     ruby.rb",
                                    buildpack_release);
    let ruby_file = &download_url(&ruby_langpack_url[..]).expect("Could not download ruby.rb!")[..];
    let regex = Regex::new(r#"BUNDLER_VERSION += "(.+?)""#).expect("Invalid regular expression!");
    if regex.is_match(ruby_file) {
        let captures = regex.captures(ruby_file).expect("Could not match?!");
        let version_match = captures.get(1).expect("Capture not found?!");
        let version = version_match.as_str();
        if let Ok(ref redis) = *redis_result {
            redis_cache_hash_value(&redis, "bundler_version", &buildpack_release[..], version);
        }
        Some(version.to_owned())
    } else {
        None
    }
}

fn is_bundler_upgraded(redis_result: RedisResult<redis::Connection>) -> bool {
    let bundler_version_result = bundler_version_from_ruby_buildpack(&redis_result);
    if let Some(buildpack_bundler_version_str) = bundler_version_result {
        let min_version = Version::parse(&min_bundler_version()[..])
            .expect("Could not parse min version!");
        let new_version = Version::parse(&buildpack_bundler_version_str[..])
            .expect("Could not parse new version!");
        min_version < new_version
    } else {
        false
    }
}

fn determine_content_type(req: &Request) -> ContentType {
    if let Some(accept) = req.headers.get::<Accept>() {
        let mut use_json = false;
        for qitem in &accept.0 {
            let qitem_str = &format!("{}", qitem)[..];
            if qitem_str == "application/json" {
                use_json = true;
                break;
            }
        }

        if use_json {
            ContentType::json()
        } else {
            ContentType::html()
        }
    } else {
        ContentType::html()
    }
}

fn result_to_json(result: bool) -> String {
    let mut map = HashMap::new();
    map.insert("result".to_owned(), result);
    serde_json::to_string(&map).expect("Could not serialize result!")
}

fn result_to_html(result: bool) -> String {
    let result_str = if result {
        r#"
Yes
"#
    } else {
        r#"
No
"#
    };
    match File::open("index.html") {
        Ok(mut html_file) => {
            let mut html = String::new();
            html_file.read_to_string(&mut html).expect("Could not read HTML file!");
            html.replace("{{ is_bundler_upgraded }}", result_str)
                .replace("{{ MIN_BUNDLER_VERSION }}", &min_bundler_version()[..])
        }
        Err(error) => format!("HTML NOT FOUND: {}", error),
    }
}

fn min_bundler_version() -> String {
    match env::var("MIN_BUNDLER_VERSION") {
        Ok(version) => version,
        _ => String::from(DEFAULT_MIN_BUNDLER_VERSION),
    }
}

fn server_port() -> String {
    match env::var("PORT") {
        Ok(port) => port,
        _ => format!("{}", DEFAULT_SERVER_PORT),
    }
}

fn connect_to_redis() -> RedisResult<redis::Connection> {
    if let Ok(redis_url) = env::var("REDIS_URL") {
        let client = redis::Client::open(&redis_url[..]).expect("Cannot connect to Redis");
        client.get_connection()
    } else {
        Err(RedisError::from(io::Error::new(io::ErrorKind::Other, "REDIS_URL not found")))
    }
}

fn request_handler(req: Request, mut res: Response) {
    if let AbsolutePath(ref path) = req.uri {
        if *path == "/" {
            if req.method == Get {
                let redis = connect_to_redis();
                let result = is_bundler_upgraded(redis);
                let content_type = determine_content_type(&req);
                let data = match &format!("{}", content_type)[..] {
                    "application/json; charset=utf-8" => result_to_json(result),
                    _ => result_to_html(result),
                };
                res.headers_mut().set(content_type);
                res.send(data.as_bytes()).expect("Could not set response body!");
            } else {
                *res.status_mut() = hyper::status::StatusCode::MethodNotAllowed;
            }
        } else {
            *res.status_mut() = hyper::NotFound;
        }
    } else {
        *res.status_mut() = hyper::BadRequest;
    }
}

fn main() {
    env_logger::init().expect("Could not initialize env_logger!");
    let server = Server::http(&format!("0.0.0.0:{}", server_port())[..])
        .expect("Could not create server!");
    server.handle(request_handler).expect("Could not set up HTTP request handler!");
}
<commit_msg>Rustfmt<commit_after>// Copyright (C) 2016 Mark Lee

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.

// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

extern crate env_logger;
extern crate hyper;
#[macro_use]
extern crate log;
extern crate redis;
extern crate regex;
extern crate reqwest;
extern crate rquery;
extern crate semver;
extern crate serde;
extern crate serde_json;

use hyper::{Get, Server};
use hyper::header::{Accept, ContentType};
use hyper::server::{Request, Response};
use hyper::uri::RequestUri::AbsolutePath;
use redis::{Commands, RedisError, RedisResult};
use regex::Regex;
use rquery::Document;
use semver::Version;
use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::Read;

const DEFAULT_SERVER_PORT: u32 = 9000;
const DEFAULT_MIN_BUNDLER_VERSION: &'static str = "1.12.0";
const RUBY_LANGPACK_RELEASES_URL: &'static str = "https://github.\
                                                  com/heroku/heroku-buildpack-ruby/releases.atom";

fn download_url(url: &str) -> io::Result<String> {
    let mut resp = reqwest::get(url).expect("Could not send HTTP request");
    let mut body = String::new();
    match resp.read_to_string(&mut body) {
        Err(error) => Err(error),
        _ => Ok(body),
    }
}

fn download_latest_buildpack_release() -> String {
    let xml = download_url(RUBY_LANGPACK_RELEASES_URL).expect("Could not download Atom releases!");
    let atom_doc = Document::new_from_xml_string(&xml[..]).expect("Could not parse Atom releases!");
    let latest_tag = atom_doc.select("entry title").expect(
        "Could not find latest buildpack tag!",
    );
    latest_tag.text().clone()
}

fn redis_cache_value(redis: &redis::Connection, key: &str, value: &str) -> String {
    let set_result: RedisResult<()> = redis.set_ex(key, value, 3600);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
    value.to_owned()
}

fn redis_cache_hash_value(redis: &redis::Connection, hash_name: &str, key: &str, value: &str) {
    let set_result: RedisResult<()> = redis.hset_nx(hash_name, key, value);
    if let Err(_) = set_result {
        warn!("Cannot set {} in Redis, ignoring", key);
    }
}

fn latest_buildpack_release(redis_result: &RedisResult<redis::Connection>) -> String {
    if let Ok(ref redis) = *redis_result {
        if let Ok(buildpack_release) = redis.get("latest_buildpack_release") {
            buildpack_release
        } else {
            let buildpack_release = download_latest_buildpack_release();
            redis_cache_value(redis, "latest_buildpack_release", &buildpack_release[..])
        }
    } else {
        download_latest_buildpack_release()
    }
}

fn cached_version_from_buildpack_release(
    buildpack_release: &str,
    redis_result: &RedisResult<redis::Connection>,
) -> Option<String> {
    if let Ok(ref redis) = *redis_result {
        if let Ok(version) = redis.hget("bundler_version", buildpack_release) {
            Some(version)
        } else {
            None
        }
    } else {
        None
    }
}

fn bundler_version_from_ruby_buildpack(
    redis_result: &RedisResult<redis::Connection>,
) -> Option<String> {
    let buildpack_release = latest_buildpack_release(redis_result);
    if let Some(cached_version) =
        cached_version_from_buildpack_release(&buildpack_release[..], redis_result)
    {
        return Some(cached_version);
    }
    let ruby_langpack_url = format!(
        "https://raw.githubusercontent.com/\
                                     heroku/heroku-buildpack-ruby/{}/lib/language_pack/\
                                     ruby.rb",
        buildpack_release
    );
    let ruby_file = &download_url(&ruby_langpack_url[..]).expect("Could not download ruby.rb!")[..];
    let regex = Regex::new(r#"BUNDLER_VERSION += "(.+?)""#).expect("Invalid regular expression!");
    if regex.is_match(ruby_file) {
        let captures = regex.captures(ruby_file).expect("Could not match?!");
        let version_match = captures.get(1).expect("Capture not found?!");
        let version = version_match.as_str();
        if let Ok(ref redis) = *redis_result {
            redis_cache_hash_value(&redis, "bundler_version", &buildpack_release[..], version);
        }
        Some(version.to_owned())
    } else {
        None
    }
}

fn is_bundler_upgraded(redis_result: RedisResult<redis::Connection>) -> bool {
    let bundler_version_result = bundler_version_from_ruby_buildpack(&redis_result);
    if let Some(buildpack_bundler_version_str) = bundler_version_result {
        let min_version =
            Version::parse(&min_bundler_version()[..]).expect("Could not parse min version!");
        let new_version = Version::parse(&buildpack_bundler_version_str[..]).expect(
            "Could not parse new version!",
        );
        min_version < new_version
    } else {
        false
    }
}

fn determine_content_type(req: &Request) -> ContentType {
    if let Some(accept) = req.headers.get::<Accept>() {
        let mut use_json = false;
        for qitem in &accept.0 {
            let qitem_str = &format!("{}", qitem)[..];
            if qitem_str == "application/json" {
                use_json = true;
                break;
            }
        }

        if use_json {
            ContentType::json()
        } else {
            ContentType::html()
        }
    } else {
        ContentType::html()
    }
}

fn result_to_json(result: bool) -> String {
    let mut map = HashMap::new();
    map.insert("result".to_owned(), result);
    serde_json::to_string(&map).expect("Could not serialize result!")
}

fn result_to_html(result: bool) -> String {
    let result_str = if result {
        r#"
Yes
"#
    } else {
        r#"
No
"#
    };
    match File::open("index.html") {
        Ok(mut html_file) => {
            let mut html = String::new();
            html_file.read_to_string(&mut html).expect(
                "Could not read HTML file!",
            );
            html.replace("{{ is_bundler_upgraded }}", result_str)
                .replace("{{ MIN_BUNDLER_VERSION }}", &min_bundler_version()[..])
        }
        Err(error) => format!("HTML NOT FOUND: {}", error),
    }
}

fn min_bundler_version() -> String {
    match env::var("MIN_BUNDLER_VERSION") {
        Ok(version) => version,
        _ => String::from(DEFAULT_MIN_BUNDLER_VERSION),
    }
}

fn server_port() -> String {
    match env::var("PORT") {
        Ok(port) => port,
        _ => format!("{}", DEFAULT_SERVER_PORT),
    }
}

fn connect_to_redis() -> RedisResult<redis::Connection> {
    if let Ok(redis_url) = env::var("REDIS_URL") {
        let client = redis::Client::open(&redis_url[..]).expect("Cannot connect to Redis");
        client.get_connection()
    } else {
        Err(RedisError::from(
            io::Error::new(io::ErrorKind::Other, "REDIS_URL not found"),
        ))
    }
}

fn request_handler(req: Request, mut res: Response) {
    if let AbsolutePath(ref path) = req.uri {
        if *path == "/" {
            if req.method == Get {
                let redis = connect_to_redis();
                let result = is_bundler_upgraded(redis);
                let content_type = determine_content_type(&req);
                let data = match &format!("{}", content_type)[..] {
                    "application/json; charset=utf-8" => result_to_json(result),
                    _ => result_to_html(result),
                };
                res.headers_mut().set(content_type);
                res.send(data.as_bytes()).expect(
                    "Could not set response body!",
                );
            } else {
                *res.status_mut() = hyper::status::StatusCode::MethodNotAllowed;
            }
        } else {
            *res.status_mut() = hyper::NotFound;
        }
    } else {
        *res.status_mut() = hyper::BadRequest;
    }
}

fn main() {
    env_logger::init().expect("Could not initialize env_logger!");
    let server = Server::http(&format!("0.0.0.0:{}", server_port())[..])
        .expect("Could not create server!");
    server.handle(request_handler).expect(
        "Could not set up HTTP request handler!",
    );
}
<|endoftext|>