<commit_before>#![feature(globs, macro_rules)]

extern crate debug;
extern crate piston;
extern crate sdl2_game_window;
extern crate gl;
extern crate hgl;
extern crate image;
extern crate libc;
extern crate cgmath;

use sdl2_game_window::GameWindowSDL2 as Window;
use piston::input;
use piston::{
    AssetStore,
    GameIterator,
    GameIteratorSettings,
    GameWindow,
    GameWindowSettings,
    Input,
    Render
};

use array::*;
use cam::{Camera, CameraSettings};
use fps_controller::FPSController;
use texture::Texture;

pub mod array;
pub mod shader;
pub mod cube;
pub mod cam;
pub mod fps_controller;
pub mod texture;
pub mod vecmath;

fn main() {
    let mut window = Window::new(
        piston::shader_version::opengl::OpenGL_3_3,
        GameWindowSettings {
            title: "Hematite".to_string(),
            size: [854, 480],
            fullscreen: false,
            exit_on_esc: true,
        }
    );

    let asset_store = AssetStore::from_folder("../assets");

    // Load texture.
    let texture = asset_store.path("texture.png").unwrap();
    let texture = Texture::from_path(&texture).unwrap();

    let game_iter_settings = GameIteratorSettings {
        updates_per_second: 120,
        max_frames_per_second: 60,
    };

    let shader = shader::Shader::new();

    shader.set_projection(CameraSettings {
        fov: 70.0,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: {
            let (w, h) = window.get_size();
            (w as f32) / (h as f32)
        }
    }.projection());

    let mut camera = Camera::new(0.5, 0.5, 4.0);
    let mut fps_controller = FPSController::new();
    camera.set_yaw_pitch(fps_controller.yaw, fps_controller.pitch);

    let mut capture_cursor = false;
    println!("Press C to capture mouse");

    let buffer = shader::Buffer::new();
    let mut events = GameIterator::new(&mut window, &game_iter_settings);
    for e in events {
        match e {
            Render(_args) => {
                let mut tri = vec![];
                for face in cube::FaceIterator::new() {
                    let xyz = face.vertices([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
                    let [u0, v1, u1, v0] = [0.0, 0.75, 0.25, 1.0];
                    let uv = [
                        [u1, v0],
                        [u0, v0],
                        [u0, v1],
                        [u1, v1]
                    ];
                    let v = [
                        (xyz[0], uv[0], [1.0, 0.0, 0.0]),
                        (xyz[1], uv[1], [0.0, 1.0, 0.0]),
                        (xyz[2], uv[2], [0.0, 0.0, 1.0]),
                        (xyz[3], uv[3], [1.0, 0.0, 1.0])
                    ].map(|(xyz, uv, rgb)| shader::Vertex { xyz: xyz, uv: uv, rgb: rgb });

                    // Split the clockwise quad into two clockwise triangles.
                    tri.push([v[0], v[1], v[2]]);
                    tri.push([v[2], v[3], v[0]]);
                }
                buffer.load_data(tri.as_slice());

                shader.set_view(camera.orthogonal());
                shader.bind();
                gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
                shader.render(&buffer);
            }
            Input(input::KeyPress { key: input::keyboard::C }) => {
                println!("Turned cursor capture {}", if capture_cursor { "off" } else { "on" });
                capture_cursor = !capture_cursor;

                events.game_window.capture_cursor(capture_cursor);
            }
            Input(input::MouseRelativeMove { .. }) => {
                if !capture_cursor {
                    // Don't send the mouse event to the FPS controller.
                    continue;
                }
            }
            _ => {}
        }
        fps_controller.event(&e, &mut camera);
    }
}
<commit_msg>Reordered mod declarations alphabetically in main.rs.<commit_after>#![feature(globs, macro_rules)]

extern crate debug;
extern crate piston;
extern crate sdl2_game_window;
extern crate gl;
extern crate hgl;
extern crate image;
extern crate libc;
extern crate cgmath;

use sdl2_game_window::GameWindowSDL2 as Window;
use piston::input;
use piston::{
    AssetStore,
    GameIterator,
    GameIteratorSettings,
    GameWindow,
    GameWindowSettings,
    Input,
    Render
};

use array::*;
use cam::{Camera, CameraSettings};
use fps_controller::FPSController;
use texture::Texture;

pub mod array;
pub mod cam;
pub mod cube;
pub mod fps_controller;
pub mod shader;
pub mod texture;
pub mod vecmath;

fn main() {
    let mut window = Window::new(
        piston::shader_version::opengl::OpenGL_3_3,
        GameWindowSettings {
            title: "Hematite".to_string(),
            size: [854, 480],
            fullscreen: false,
            exit_on_esc: true,
        }
    );

    let asset_store = AssetStore::from_folder("../assets");

    // Load texture.
    let texture = asset_store.path("texture.png").unwrap();
    let texture = Texture::from_path(&texture).unwrap();

    let game_iter_settings = GameIteratorSettings {
        updates_per_second: 120,
        max_frames_per_second: 60,
    };

    let shader = shader::Shader::new();

    shader.set_projection(CameraSettings {
        fov: 70.0,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: {
            let (w, h) = window.get_size();
            (w as f32) / (h as f32)
        }
    }.projection());

    let mut camera = Camera::new(0.5, 0.5, 4.0);
    let mut fps_controller = FPSController::new();
    camera.set_yaw_pitch(fps_controller.yaw, fps_controller.pitch);

    let mut capture_cursor = false;
    println!("Press C to capture mouse");

    let buffer = shader::Buffer::new();
    let mut events = GameIterator::new(&mut window, &game_iter_settings);
    for e in events {
        match e {
            Render(_args) => {
                let mut tri = vec![];
                for face in cube::FaceIterator::new() {
                    let xyz = face.vertices([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
                    let [u0, v1, u1, v0] = [0.0, 0.75, 0.25, 1.0];
                    let uv = [
                        [u1, v0],
                        [u0, v0],
                        [u0, v1],
                        [u1, v1]
                    ];
                    let v = [
                        (xyz[0], uv[0], [1.0, 0.0, 0.0]),
                        (xyz[1], uv[1], [0.0, 1.0, 0.0]),
                        (xyz[2], uv[2], [0.0, 0.0, 1.0]),
                        (xyz[3], uv[3], [1.0, 0.0, 1.0])
                    ].map(|(xyz, uv, rgb)| shader::Vertex { xyz: xyz, uv: uv, rgb: rgb });

                    // Split the clockwise quad into two clockwise triangles.
                    tri.push([v[0], v[1], v[2]]);
                    tri.push([v[2], v[3], v[0]]);
                }
                buffer.load_data(tri.as_slice());

                shader.set_view(camera.orthogonal());
                shader.bind();
                gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);
                shader.render(&buffer);
            }
            Input(input::KeyPress { key: input::keyboard::C }) => {
                println!("Turned cursor capture {}", if capture_cursor { "off" } else { "on" });
                capture_cursor = !capture_cursor;

                events.game_window.capture_cursor(capture_cursor);
            }
            Input(input::MouseRelativeMove { .. }) => {
                if !capture_cursor {
                    // Don't send the mouse event to the FPS controller.
                    continue;
                }
            }
            _ => {}
        }
        fps_controller.event(&e, &mut camera);
    }
}
<|endoftext|>


<commit_before>extern crate piston_window;
#[macro_use]
extern crate clap;
extern crate rand;
extern crate find_folder;

mod game;
mod field;
mod ui;
mod common;

use piston_window::*;
use clap::{Arg, App};

fn main() {
    let matches = App::new("Minesweeper")
        .author("Alexander Kuvaev <alexander@kuvaev.me>")
        .version(&*format!("v{}", crate_version!()))
        .about("Simple minesweeper in Rust ")
        .arg(Arg::from_usage("-s, --size [size] 'window size \"width\"x\"height\"'")
            .validator(|val| {
                let mut s = val.split("x");
                match s.next() {
                    Some(w) => {
                        match w.parse::<u32>() {
                            Ok(r) => {
                                if (r < 100) || (r > 4096) {
                                    return Err(format!("window width must be between 100 and 4096"))
                                }
                            },
                            Err(e) => return Err(format!("window width arg error: {}", e))
                        }
                    }
                    None => return Err(format!("window size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(h) => {
                        match h.parse::<u32>() {
                            Ok(r) => {
                                if (r < 100) || (r > 3072) {
                                    return Err(format!("window height must be between 100 and 3072"))
                                }
                            },
                            Err(e) => return Err(format!("window height arg error: {}", e))
                        }
                    }
                    None => return Err(format!("window size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(_r) => Err(format!("window size arg expected \"width\"x\"height\", got {}", val)),
                    None => Ok(())
                }
            }))
        .arg(Arg::from_usage("-f, --field [field] 'field size \"width\"x\"height\"'")
            .validator(|val| {
                let mut s = val.split("x");
                match s.next() {
                    Some(w) => {
                        match w.parse::<u32>() {
                            Ok(r) => {
                                if (r < 5) || (r > 100) {
                                    return Err(format!("field width must be between 5 and 100"))
                                }
                            },
                            Err(e) => return Err(format!("field width arg error: {}", e))
                        }
                    }
                    None => return Err(format!("field size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(h) => {
                        match h.parse::<u32>() {
                            Ok(r) => {
                                if (r < 5) || (r > 100) {
                                    return Err(format!("field height must be between 5 and 100"))
                                }
                            },
                            Err(e) => return Err(format!("field height arg error: {}", e))
                        }
                    }
                    None => return Err(format!("field size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(_r) => Err(format!("field size arg expected \"width\"x\"height\", got {}", val)),
                    None => Ok(())
                }
            }))
        .arg(Arg::from_usage("-m, --mines [mines] 'max mines'")
            .min_values(1))
        .get_matches();

    let mut width = 1024;
    let mut height = 768;
    let mut f_width = 20;
    let mut f_height = 20;
    let mut mines = 50;
    if let Some(size) = matches.value_of("size") {
        let mut s = size.split("x");
        width = s.next().unwrap().parse().unwrap_or(width);
        height = s.next().unwrap().parse().unwrap_or(height);
    }
    if let Some(field) = matches.value_of("field") {
        let mut s = field.split("x");
        f_width = s.next().unwrap().parse().unwrap_or(f_width);
        f_height = s.next().unwrap().parse().unwrap_or(f_height);
    }
    if let Some(m) = matches.value_of("mines") {
        mines = m.parse().unwrap_or(mines);
    }

    let window: PistonWindow =
        WindowSettings::new("Minesweeper", [width, height])
        .opengl(OpenGL::V2_1)
        .exit_on_esc(true)
        .build()
        .unwrap();

    let assets = find_folder::Search::ParentsThenKids(3, 3)
        .for_folder("assets").unwrap();
    let ref font = assets.join("FiraSans-Regular.ttf");
    let factory = window.factory.borrow().clone();
    let glyphs = Glyphs::new(font, factory).unwrap();

    let mut game = game::Game::new(glyphs, f_width, f_height, mines);

    for e in window {
        game.render(&e);

        if let Some(mouse_rel) = e.mouse_cursor_args() {
            game.mouse_move(mouse_rel);
        }

        if let Some(button) = e.press_args() {
            game.proc_key(button, &e);
        }
    }
}<commit_msg>fix: fixed problems with OpenGL version<commit_after>extern crate piston_window;
#[macro_use]
extern crate clap;
extern crate rand;
extern crate find_folder;

mod game;
mod field;
mod ui;
mod common;

use piston_window::*;
use clap::{Arg, App};

fn main() {
    let matches = App::new("Minesweeper")
        .author("Alexander Kuvaev <alexander@kuvaev.me>")
        .version(&*format!("v{}", crate_version!()))
        .about("Simple minesweeper in Rust ")
        .arg(Arg::from_usage("-s, --size [size] 'window size \"width\"x\"height\"'")
            .validator(|val| {
                let mut s = val.split("x");
                match s.next() {
                    Some(w) => {
                        match w.parse::<u32>() {
                            Ok(r) => {
                                if (r < 100) || (r > 4096) {
                                    return Err(format!("window width must be between 100 and 4096"))
                                }
                            },
                            Err(e) => return Err(format!("window width arg error: {}", e))
                        }
                    }
                    None => return Err(format!("window size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(h) => {
                        match h.parse::<u32>() {
                            Ok(r) => {
                                if (r < 100) || (r > 3072) {
                                    return Err(format!("window height must be between 100 and 3072"))
                                }
                            },
                            Err(e) => return Err(format!("window height arg error: {}", e))
                        }
                    }
                    None => return Err(format!("window size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(_r) => Err(format!("window size arg expected \"width\"x\"height\", got {}", val)),
                    None => Ok(())
                }
            }))
        .arg(Arg::from_usage("-f, --field [field] 'field size \"width\"x\"height\"'")
            .validator(|val| {
                let mut s = val.split("x");
                match s.next() {
                    Some(w) => {
                        match w.parse::<u32>() {
                            Ok(r) => {
                                if (r < 5) || (r > 100) {
                                    return Err(format!("field width must be between 5 and 100"))
                                }
                            },
                            Err(e) => return Err(format!("field width arg error: {}", e))
                        }
                    }
                    None => return Err(format!("field size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(h) => {
                        match h.parse::<u32>() {
                            Ok(r) => {
                                if (r < 5) || (r > 100) {
                                    return Err(format!("field height must be between 5 and 100"))
                                }
                            },
                            Err(e) => return Err(format!("field height arg error: {}", e))
                        }
                    }
                    None => return Err(format!("field size expected \"width\"x\"height\", got {}", val))
                }
                match s.next() {
                    Some(_r) => Err(format!("field size arg expected \"width\"x\"height\", got {}", val)),
                    None => Ok(())
                }
            }))
        .arg(Arg::from_usage("-m, --mines [mines] 'max mines'"))
        .arg(Arg::from_usage("--oldOGL 'set OpenGL version to 2.1'"))
        .get_matches();

    let mut width = 1024;
    let mut height = 768;
    let mut f_width = 20;
    let mut f_height = 20;
    let mut mines = 50;
    let mut opengl = OpenGL::V3_2;
    if let Some(size) = matches.value_of("size") {
        let mut s = size.split("x");
        width = s.next().unwrap().parse().unwrap_or(width);
        height = s.next().unwrap().parse().unwrap_or(height);
    }
    if let Some(field) = matches.value_of("field") {
        let mut s = field.split("x");
        f_width = s.next().unwrap().parse().unwrap_or(f_width);
        f_height = s.next().unwrap().parse().unwrap_or(f_height);
    }
    if let Some(m) = matches.value_of("mines") {
        mines = m.parse().unwrap_or(mines);
    }
    if matches.is_present("oldOGL") {
        opengl = OpenGL::V2_1;
    }

    let window: PistonWindow =
        WindowSettings::new("Minesweeper", [width, height])
        .opengl(opengl)
        .exit_on_esc(true)
        .build()
        .unwrap();

    let assets = find_folder::Search::ParentsThenKids(3, 3)
        .for_folder("assets").unwrap();
    let ref font = assets.join("FiraSans-Regular.ttf");
    let factory = window.factory.borrow().clone();
    let glyphs = Glyphs::new(font, factory).unwrap();

    let mut game = game::Game::new(glyphs, f_width, f_height, mines);

    for e in window {
        game.render(&e);

        if let Some(mouse_rel) = e.mouse_cursor_args() {
            game.mouse_move(mouse_rel);
        }

        if let Some(button) = e.press_args() {
            game.proc_key(button, &e);
        }
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]

use std::thread;
use std::time::Duration;

use mio::{self, NotifyError};

pub mod store;
pub mod errors;
pub mod server;
pub use self::errors::{Result, Error, other};

const MAX_SEND_RETRY_CNT: i32 = 20;

// send_msg wraps Sender and retries some times if queue is full.
pub fn send_msg<M: Send>(sender: &mio::Sender<M>, msg: M) -> Result<()> {
    let mut value: M = msg;
    for _ in 0..MAX_SEND_RETRY_CNT {
        let r = sender.send(value);
        if r.is_ok() {
            return Ok(());
        }

        match r.unwrap_err() {
            NotifyError::Full(m) => {
                warn!("notify queue is full, sleep and retry");
                thread::sleep(Duration::from_millis(100));
                value = m;
                continue;
            }
            e => {
                return Err(other(format!("{:?}", e)));
            }
        }
    }

    // Now sender only supports Send trait, no Sync, so we can' use `try!` directly
    // for outer use because our result error supports Sync + Send.
    // So here returns result instead of Err(NotifyError::Full(value)).
    // TODO: We can refactor it later if using quick_error.
    Err(other("notify channel is full"))
}
<commit_msg>raftserver: Address comment.<commit_after>#![allow(dead_code)]

use std::thread;
use std::time::Duration;

use mio::{self, NotifyError};

pub mod store;
pub mod errors;
pub mod server;
pub use self::errors::{Result, Error, other};

const MAX_SEND_RETRY_CNT: i32 = 20;

// send_msg wraps Sender and retries some times if queue is full.
pub fn send_msg<M: Send>(sender: &mio::Sender<M>, msg: M) -> Result<()> {
    let mut value: M = msg;
    for _ in 0..MAX_SEND_RETRY_CNT {
        let r = sender.send(value);
        if r.is_ok() {
            return Ok(());
        }

        match r.unwrap_err() {
            NotifyError::Full(m) => {
                warn!("notify queue is full, sleep and retry");
                thread::sleep(Duration::from_millis(100));
                value = m;
                continue;
            }
            e => {
                return Err(other(format!("{:?}", e)));
            }
        }
    }

    // Now sender only supports Send trait, no Sync, so we can' use `try!` directly
    // for outer use because our result error supports Sync + Send.
    // TODO: We can refactor it later if using quick_error.
    Err(other("notify channel is full"))
}
<|endoftext|>


<commit_before>extern crate gtk;
extern crate nix;
mod battery;

use std::env;

use gtk::prelude::*;
use gtk::{ButtonsType, DialogFlags, MessageType, MessageDialog, Window};

use nix::unistd::{fork, ForkResult};

use battery::Battery;
use battery::BatteryStatus;

fn console_output(bat: &Battery) {
    // Icons
    let plug          = '\u{f1e6}';
    let check         = '\u{f00c}';
    let full          = '\u{f240}';
    let three_quarter = '\u{f241}';
    let half          = '\u{f242}';
    let quarter       = '\u{f243}';
    let empty         = '\u{f244}';

    // Colors
    let green  = "#859900";
    let cyan   = "#2AA198";
    let yellow = "#B58900";
    let orange = "#CB4B16";
    let red    = "#DC322F";

    let mut percent = bat.percent_remaining();

    let (mut icon, mut color) = match percent {
        0  ... 10  => (empty, red),
        11 ... 35  => (quarter, orange),
        36 ... 60  => (half, yellow),
        61 ... 85  => (three_quarter, cyan),
        86 ... 100 => (full, green),
        _          => std::process::exit(1),
    };

    match bat.charge_status {
        BatteryStatus::FULL => {
            icon = check;
            color = green;
            percent = 100;
        },
        BatteryStatus::CHARGING => {
            icon = plug;
        },
        _ => (),
    };

    if bat.charge_status == BatteryStatus::FULL {
        color = green;
        percent = 100;
    }

    let out = format!("{} {}%\n",
                      color, icon, percent);
    print!("{0:}{0:}", out);
    if percent <= 5 {
        std::process::exit(33);
    }
}

/*
 * Displays the GTK MessageDialog with information about the current status of
 * the battery. This contains all the fields that this program collects
 * information on.
 *
 * This function will return 0 on success and 1 on error.
 */
fn show_gtk_dialog(bat: &Battery) -> i32 {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return 1;
    }

    let body_string = format!("Battery name:\t\t{}\n\
                              Battery charge:\t\t{}\n\
                              Charge when full:\t{}\n\
                              Design full:\t\t\t{}\n\
                              Cycle count:\t\t{}\n\
                              Charging status:\t{}\n\
                              Current now:\t\t{}\n\
                              Current avg:\t\t{}\n\
                              % Charged:\t\t\t{}%\n\
                              Time remaining:\t\t{}\n\
                              Battery health:\t\t{}%\n\
                              % Charged (abs):\t{}%\n",
                              bat.name, bat.charge_now, bat.charge_full,
                              bat.charge_full_design, bat.cycle_count,
                              bat.charge_status, bat.current_now,
                              bat.current_avg, bat.percent_remaining(),
                              bat.time_remaining(), bat.health(),
                              bat.abs_percent_remaining());

    MessageDialog::new(None::<&Window>,
                       DialogFlags::empty(),
                       MessageType::Info,
                       ButtonsType::Ok,
                       &body_string).run();
    return 0;
}

fn get_env_var(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(val) => {
            if val.is_empty() {
                None
            } else {
                Some(val)
            }
        },
        Err(_) => None
    }
}

fn main() {
    let battery_name = match get_env_var("BLOCK_INSTANCE") {
        Some(val) => val,
        None => "BAT0".to_owned(),
    };

    let bat = Battery::initialize(&battery_name);

    let button: u32 = match get_env_var("BLOCK_BUTTON") {
        Some(val) => val.trim().parse().unwrap(),
        None => 0
    };

    /*
     * For the purposes of i3blocks, 3 corresponds to a right click. When
     * that happens, fork() and then show a dialog. If we don't fork(), then
     * the dialog prevents this process from exiting and the block doesn't
     * update. The printing to the console is done in the parent thread.
     */
    if button == 3 {
        match fork() {
            Ok(ForkResult::Parent { child, .. }) => (),
            Ok(ForkResult::Child) => std::process::exit(show_gtk_dialog(&bat)),
            Err(_) => println!("Fork failed"),
        }
    }
    console_output(&bat);
}
<commit_msg>Clean up code as I learn more Rust<commit_after>extern crate gtk;
extern crate nix;
mod battery;

use std::env;

use gtk::prelude::*;
use gtk::{ButtonsType, DialogFlags, MessageType, MessageDialog, Window};

use nix::unistd::{fork, ForkResult};

use battery::Battery;
use battery::BatteryStatus;

const RIGHT_CLICK: i32 = 3;
const CRITICAL_EXIT_CODE: i32 = 33;
const CRITICAL_BAT_LEVEL: u32 = 5;

fn console_output(bat: &Battery) {
    // Icons
    let plug          = '\u{f1e6}';
    let check         = '\u{f00c}';
    let full          = '\u{f240}';
    let three_quarter = '\u{f241}';
    let half          = '\u{f242}';
    let quarter       = '\u{f243}';
    let empty         = '\u{f244}';

    // Colors
    let green  = "#859900";
    let cyan   = "#2AA198";
    let yellow = "#B58900";
    let orange = "#CB4B16";
    let red    = "#DC322F";

    let mut percent = bat.percent_remaining();

    let (mut icon, mut color) = match percent {
        0  ... 10  => (empty, red),
        11 ... 35  => (quarter, orange),
        36 ... 60  => (half, yellow),
        61 ... 85  => (three_quarter, cyan),
        86 ... 100 => (full, green),
        _          => std::process::exit(1),
    };

    match bat.charge_status {
        BatteryStatus::FULL => {
            icon = check;
            color = green;
            percent = 100;
        },
        BatteryStatus::CHARGING => {
            icon = plug;
        },
        _ => (),
    };

    let out = format!("{} {}%\n",
                      color, icon, percent);
    print!("{0:}{0:}", out);

}

/*
 * Displays the GTK MessageDialog with information about the current status of
 * the battery. This contains all the fields that this program collects
 * information on.
 *
 * This function will return 0 on success and 1 on error.
 */
fn show_gtk_dialog(bat: &Battery) -> i32 {
    if gtk::init().is_err() {
        println!("Failed to initialize GTK.");
        return 1;
    }

    let body_string = format!("Battery name:\t\t{}\n\
                              Battery charge:\t\t{}\n\
                              Charge when full:\t{}\n\
                              Design full:\t\t\t{}\n\
                              Cycle count:\t\t{}\n\
                              Charging status:\t{}\n\
                              Current now:\t\t{}\n\
                              Current avg:\t\t{}\n\
                              % Charged:\t\t\t{}%\n\
                              Time remaining:\t\t{}\n\
                              Battery health:\t\t{}%\n\
                              % Charged (abs):\t{}%\n",
                              bat.name, bat.charge_now, bat.charge_full,
                              bat.charge_full_design, bat.cycle_count,
                              bat.charge_status, bat.current_now,
                              bat.current_avg, bat.percent_remaining(),
                              bat.time_remaining(), bat.health(),
                              bat.abs_percent_remaining());

    MessageDialog::new(None::<&Window>,
                       DialogFlags::empty(),
                       MessageType::Info,
                       ButtonsType::Ok,
                       &body_string).run();
    return 0;
}

fn get_env_var(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(ref val) if val.is_empty() => None,
        Ok(val) => Some(val),
        Err(_) => None
    }
}

fn main() {
    let battery_name = match get_env_var("BLOCK_INSTANCE") {
        Some(val) => val,
        None => "BAT0".to_owned(),
    };

    let bat = Battery::initialize(&battery_name);

    let button: i32 = match get_env_var("BLOCK_BUTTON") {
        Some(val) => val.trim().parse().unwrap(),
        None => 0
    };

    /*
     * For the purposes of i3blocks, 3 corresponds to a right click. When
     * that happens, fork() and then show a dialog. If we don't fork(), then
     * the dialog prevents this process from exiting and the block doesn't
     * update. The printing to the console is done in the parent thread.
     */
    if button == RIGHT_CLICK {
        match fork() {
            Ok(ForkResult::Parent { child, .. }) => (),
            Ok(ForkResult::Child) => std::process::exit(show_gtk_dialog(&bat)),
            Err(_) => println!("Fork failed"),
        }
    }
    console_output(&bat);

    /*
     * i3blocks shows a block as critical if the process exits with code 33.
     * If the battery is less than 33% charged, the status is critical
     */
    if bat.percent_remaining() <= CRITICAL_BAT_LEVEL {
        std::process::exit(CRITICAL_EXIT_CODE)
    }
}
<|endoftext|>


<commit_before>extern crate crypto;
extern crate flate2;
extern crate num_cpus;

mod padding;

use flate2::Compression;
use flate2::write::ZlibEncoder;

use std::env;
use std::fs;
use std::io::Write;
use std::ops;
use std::process;
use std::str;
use std::sync::mpsc;
use std::thread;
use std::u8;
use crypto::digest::Digest;
use crypto::sha1;

const SHA1_BYTE_LENGTH: usize = 20;

struct HashPrefix {
    data: Vec<u8>,
    half_byte: Option<u8>,
}

impl Clone for HashPrefix {
    fn clone(&self) -> Self {
        HashPrefix {
            data: self.data.to_owned(),
            half_byte: self.half_byte.to_owned()
        }
    }
}

struct SearchParams<'a> {
    current_message: &'a str,
    desired_prefix: &'a HashPrefix,
    counter_range: ops::Range<u64>,
    extension_word_length: usize
}

struct HashMatch {
    data: Vec<u8>,
    hash: [u8; SHA1_BYTE_LENGTH],
}

struct ProcessedCommitMessage {
    full_message: Vec<u8>,
    whitespace_index: usize,
}

fn main() {
    let args: Vec<String> = env::args().collect();

    match args.len() {
        1 => run_lucky_commit(&parse_prefix("0000000").unwrap()),
        2 => match parse_prefix(&args[1]) {
            Some(prefix) => run_lucky_commit(&prefix),
            None => print_usage_and_exit()
        },
        _ => print_usage_and_exit()
    }
}

fn print_usage_and_exit() {
    fail_with_message("Usage: lucky-commit [commit-hash-prefix]");
}

fn fail_with_message(message: &str) {
    eprintln!("{}", message);
    process::exit(1);
}

fn parse_prefix(prefix: &str) -> Option<HashPrefix> {
    let mut data = Vec::new();
    for index in 0..(prefix.len() / 2) {
        match u8::from_str_radix(&prefix[2 * index..2 * index + 2], 16) {
            Ok(value) => data.push(value),
            Err(_) => return None
        }
    }

    let parsed_prefix = HashPrefix {
        data,
        half_byte: if prefix.len() % 2 == 1 {
            match u8::from_str_radix(&prefix[prefix.len() - 1..], 16) {
                Ok(value) => Some(value << 4),
                Err(_) => return None
            }
        } else {
            None
        }
    };

    Some(parsed_prefix)
}

fn run_lucky_commit(desired_prefix: &HashPrefix) {
    let current_message_bytes = run_command("git", &["cat-file", "commit", "HEAD"]);
    let current_message = &String::from_utf8(current_message_bytes)
        .expect("Git commit contains invalid utf8");

    match find_match(current_message, desired_prefix) {
        Some(hash_match) => {
            create_git_object_file(&hash_match);
            git_reset_to_hash(&hash_match.hash);
        },
        None => fail_with_message("Failed to find a match")
    }
}

fn run_command(command: &str, args: &[&str]) -> Vec<u8> {
    let output = process::Command::new(command)
        .args(args)
        .stderr(process::Stdio::inherit())
        .output()
        .expect("Failed to run command");

    if !output.status.success() {
        process::exit(1);
    }

    output.stdout
}

fn find_match(current_message: &str, desired_prefix: &HashPrefix) -> Option<HashMatch> {
    let num_threads = num_cpus::get();
    let (shared_sender, receiver) = mpsc::channel();

    for thread_index in 0..num_threads {
        let thread_sender = mpsc::Sender::clone(&shared_sender);
        let thread_message_copy = current_message.to_owned();
        let thread_prefix_copy = desired_prefix.clone();
        thread::spawn(move || {
            let thread_params = SearchParams {
                current_message: &thread_message_copy,
                desired_prefix: &thread_prefix_copy,
                counter_range: get_u64_range_segment(num_threads, thread_index),
                extension_word_length: 8
            };
            match thread_sender.send(iterate_for_match(&thread_params)) {
                /*
                 * If an error occurs when sending, then the receiver has already received
                 * a match from another thread, so ignore the error.
                 */
                Ok(_) => (),
                Err(_) => ()
            }
        });
    }

    for _ in 0..num_threads {
        let result = receiver.recv().unwrap();
        if result.is_some() {
            return result;
        }
    }

    None
}

fn get_u64_range_segment(total_segments: usize, current_segment: usize) -> ops::Range<u64> {
    let divisor = total_segments as u64;
    let multiplier = current_segment as u64;
    let counter_start = (1u64 << 63) / divisor * multiplier * 2;
    let counter_end = (1u64 << 63) / divisor * (multiplier + 1) * 2 - 1;
    counter_start..counter_end
}

fn iterate_for_match(params: &SearchParams) -> Option<HashMatch> {
    let desired_prefix = params.desired_prefix;
    let extension_length = params.extension_word_length * 8;
    let processed_message = process_commit_message(
        params.current_message,
        extension_length
    );

    let mut hash_data = processed_message.full_message;
    let whitespace_index = processed_message.whitespace_index;

    let mut sha1_hash = sha1::Sha1::new();
    let mut hash_result: [u8; SHA1_BYTE_LENGTH] = [0; SHA1_BYTE_LENGTH];

    for counter in params.counter_range.clone() {
        let mut whitespace_word_index = 0;
        while whitespace_word_index < extension_length {
            let start_index = whitespace_index + whitespace_word_index;
            let padding_index = (counter >> whitespace_word_index) as u8 as usize;
            &mut hash_data[start_index..start_index + 8].copy_from_slice(&padding::PADDING_LIST[padding_index]);
            whitespace_word_index += 8;
        }

        sha1_hash.input(&hash_data);
        sha1_hash.result(&mut hash_result);

        if matches_desired_prefix(&hash_result, desired_prefix) {
            return Some(HashMatch {
                data: hash_data,
                hash: hash_result,
            })
        }

        sha1_hash.reset();
    }

    None
}

fn process_commit_message(original_message: &str, extension_length: usize) -> ProcessedCommitMessage {
    let mut message_object: Vec<u8> = format!("commit {}\x00", original_message.len() + extension_length)
        .into_bytes();

    let commit_split_index = get_commit_message_split_index(original_message);
    let whitespace_index = commit_split_index + message_object.len();

    for character in original_message[..commit_split_index].as_bytes() {
        message_object.push(*character);
    }

    for _ in 0..extension_length {
        message_object.push(padding::SPACE);
    }

    for character in original_message[commit_split_index..].as_bytes() {
        message_object.push(*character);
    }

    ProcessedCommitMessage {
        full_message: message_object,
        whitespace_index,
    }
}

fn get_commit_message_split_index(message: &str) -> usize {
    /*
     * If the commit has a GPG signature (detected by the presence of "gpgsig " at the start
     * of the fifth line), then add the padding whitespace immediately after the text "gpgsig ".
     * Otherwise, add the padding whitespace right before the end of the commit message.
     *
     * If a signature is present, modifying the commit message would make the signature invalid.
     */
    let mut current_line_index = 0;
    const SIGNATURE_MARKER: &str = "gpgsig ";
    for (index, character) in message.chars().enumerate() {
        if current_line_index == 4 {
            if message[index..].starts_with(SIGNATURE_MARKER) {
                return index + SIGNATURE_MARKER.len();
            } else {
                return message.len() - 1;
            }
        }
        if character == '\n' {
            current_line_index += 1;
        }
    }

    message.len() - 1
}

fn matches_desired_prefix(hash: &[u8; SHA1_BYTE_LENGTH], prefix: &HashPrefix) -> bool {
    prefix.data == &hash[..prefix.data.len()] && match prefix.half_byte {
        Some(half_byte) => (hash[prefix.data.len()] & 0xf0) == half_byte,
        None => true,
    }
}

fn create_git_object_file(search_result: &HashMatch) {
    let compressed_object = zlib_compress(&search_result.data);
    let dir_path = format!(".git/objects/{:02x}", search_result.hash[0]);
    let file_path = format!(
        "{}/{}",
        dir_path,
        to_hex_string(&search_result.hash[1..])
    );

    fs::DirBuilder::new()
        .recursive(true)
        .create(dir_path)
        .expect("Failed to create git object directory");

    fs::File::create(file_path)
        .expect("Failed to open git object file")
        .write_all(&compressed_object)
        .expect("Failed to write git object file");
}

fn zlib_compress(data: &[u8]) -> Vec<u8> {
    let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
    encoder.write(data).expect("zlib compression failed");
    encoder.finish().expect("zlib compression failed")
}

fn git_reset_to_hash(hash: &[u8; SHA1_BYTE_LENGTH]) {
    run_command("git", &["reset", &to_hex_string(hash)]);
}

fn to_hex_string(hash: &[u8]) -> String {
    hash.iter().map(|byte| format!("{:02x}", *byte)).collect::<String>()
}
<commit_msg>Make range generation function more general<commit_after>extern crate crypto;
extern crate flate2;
extern crate num_cpus;

mod padding;

use flate2::Compression;
use flate2::write::ZlibEncoder;

use std::env;
use std::fs;
use std::io::Write;
use std::ops;
use std::process;
use std::str;
use std::sync::mpsc;
use std::thread;
use std::u64;
use std::u8;
use crypto::digest::Digest;
use crypto::sha1;

const SHA1_BYTE_LENGTH: usize = 20;

struct HashPrefix {
    data: Vec<u8>,
    half_byte: Option<u8>,
}

impl Clone for HashPrefix {
    fn clone(&self) -> Self {
        HashPrefix {
            data: self.data.to_owned(),
            half_byte: self.half_byte.to_owned()
        }
    }
}

struct SearchParams<'a> {
    current_message: &'a str,
    desired_prefix: &'a HashPrefix,
    counter_range: ops::Range<u64>,
    extension_word_length: usize
}

struct HashMatch {
    data: Vec<u8>,
    hash: [u8; SHA1_BYTE_LENGTH],
}

struct ProcessedCommitMessage {
    full_message: Vec<u8>,
    whitespace_index: usize,
}

fn main() {
    let args: Vec<String> = env::args().collect();

    match args.len() {
        1 => run_lucky_commit(&parse_prefix("0000000").unwrap()),
        2 => match parse_prefix(&args[1]) {
            Some(prefix) => run_lucky_commit(&prefix),
            None => print_usage_and_exit()
        },
        _ => print_usage_and_exit()
    }
}

fn print_usage_and_exit() {
    fail_with_message("Usage: lucky-commit [commit-hash-prefix]");
}

fn fail_with_message(message: &str) {
    eprintln!("{}", message);
    process::exit(1);
}

fn parse_prefix(prefix: &str) -> Option<HashPrefix> {
    let mut data = Vec::new();
    for index in 0..(prefix.len() / 2) {
        match u8::from_str_radix(&prefix[2 * index..2 * index + 2], 16) {
            Ok(value) => data.push(value),
            Err(_) => return None
        }
    }

    let parsed_prefix = HashPrefix {
        data,
        half_byte: if prefix.len() % 2 == 1 {
            match u8::from_str_radix(&prefix[prefix.len() - 1..], 16) {
                Ok(value) => Some(value << 4),
                Err(_) => return None
            }
        } else {
            None
        }
    };

    Some(parsed_prefix)
}

fn run_lucky_commit(desired_prefix: &HashPrefix) {
    let current_message_bytes = run_command("git", &["cat-file", "commit", "HEAD"]);
    let current_message = &String::from_utf8(current_message_bytes)
        .expect("Git commit contains invalid utf8");

    match find_match(current_message, desired_prefix) {
        Some(hash_match) => {
            create_git_object_file(&hash_match);
            git_reset_to_hash(&hash_match.hash);
        },
        None => fail_with_message("Failed to find a match")
    }
}

fn run_command(command: &str, args: &[&str]) -> Vec<u8> {
    let output = process::Command::new(command)
        .args(args)
        .stderr(process::Stdio::inherit())
        .output()
        .expect("Failed to run command");

    if !output.status.success() {
        process::exit(1);
    }

    output.stdout
}

fn find_match(current_message: &str, desired_prefix: &HashPrefix) -> Option<HashMatch> {
    let num_threads = num_cpus::get();
    let (shared_sender, receiver) = mpsc::channel();
    let u64_ranges = split_range(0, u64::MAX, num_threads);

    for thread_index in 0..num_threads {
        let thread_sender = mpsc::Sender::clone(&shared_sender);
        let thread_message_copy = current_message.to_owned();
        let thread_prefix_copy = desired_prefix.clone();
        let counter_range_copy = u64_ranges[thread_index].clone();
        thread::spawn(move || {
            let thread_params = SearchParams {
                current_message: &thread_message_copy,
                desired_prefix: &thread_prefix_copy,
                counter_range: counter_range_copy,
                extension_word_length: 8
            };
            match thread_sender.send(iterate_for_match(&thread_params)) {
                /*
                 * If an error occurs when sending, then the receiver has already received
                 * a match from another thread, so ignore the error.
                 */
                Ok(_) => (),
                Err(_) => ()
            }
        });
    }

    for _ in 0..num_threads {
        let result = receiver.recv().unwrap();
        if result.is_some() {
            return result;
        }
    }

    None
}

fn split_range(min: u64, max: u64, num_segments: usize) -> Vec<ops::Range<u64>> {
    let segment_size = (max - min) / (num_segments as u64);

    let mut segments = Vec::new();
    let mut last_range_end = 0;
    for _ in 0..num_segments {
        segments.push(last_range_end..last_range_end + segment_size);
        last_range_end += segment_size;
    }

    segments
}

fn iterate_for_match(params: &SearchParams) -> Option<HashMatch> {
    let desired_prefix = params.desired_prefix;
    let extension_length = params.extension_word_length * 8;
    let processed_message = process_commit_message(
        params.current_message,
        extension_length
    );

    let mut hash_data = processed_message.full_message;
    let whitespace_index = processed_message.whitespace_index;

    let mut sha1_hash = sha1::Sha1::new();
    let mut hash_result: [u8; SHA1_BYTE_LENGTH] = [0; SHA1_BYTE_LENGTH];

    for counter in params.counter_range.clone() {
        let mut whitespace_word_index = 0;
        while whitespace_word_index < extension_length {
            let start_index = whitespace_index + whitespace_word_index;
            let padding_index = (counter >> whitespace_word_index) as u8 as usize;
            &mut hash_data[start_index..start_index + 8].copy_from_slice(&padding::PADDING_LIST[padding_index]);
            whitespace_word_index += 8;
        }

        sha1_hash.input(&hash_data);
        sha1_hash.result(&mut hash_result);

        if matches_desired_prefix(&hash_result, desired_prefix) {
            return Some(HashMatch {
                data: hash_data,
                hash: hash_result,
            })
        }

        sha1_hash.reset();
    }

    None
}

fn process_commit_message(original_message: &str, extension_length: usize) -> ProcessedCommitMessage {
    let mut message_object: Vec<u8> = format!("commit {}\x00", original_message.len() + extension_length)
        .into_bytes();

    let commit_split_index = get_commit_message_split_index(original_message);
    let whitespace_index = commit_split_index + message_object.len();

    for character in original_message[..commit_split_index].as_bytes() {
        message_object.push(*character);
    }

    for _ in 0..extension_length {
        message_object.push(padding::SPACE);
    }

    for character in original_message[commit_split_index..].as_bytes() {
        message_object.push(*character);
    }

    ProcessedCommitMessage {
        full_message: message_object,
        whitespace_index,
    }
}

fn get_commit_message_split_index(message: &str) -> usize {
    /*
     * If the commit has a GPG signature (detected by the presence of "gpgsig " at the start
     * of the fifth line), then add the padding whitespace immediately after the text "gpgsig ".
     * Otherwise, add the padding whitespace right before the end of the commit message.
     *
     * If a signature is present, modifying the commit message would make the signature invalid.
     */
    let mut current_line_index = 0;
    const SIGNATURE_MARKER: &str = "gpgsig ";
    for (index, character) in message.chars().enumerate() {
        if current_line_index == 4 {
            if message[index..].starts_with(SIGNATURE_MARKER) {
                return index + SIGNATURE_MARKER.len();
            } else {
                return message.len() - 1;
            }
        }
        if character == '\n' {
            current_line_index += 1;
        }
    }

    message.len() - 1
}

fn matches_desired_prefix(hash: &[u8; SHA1_BYTE_LENGTH], prefix: &HashPrefix) -> bool {
    prefix.data == &hash[..prefix.data.len()] && match prefix.half_byte {
        Some(half_byte) => (hash[prefix.data.len()] & 0xf0) == half_byte,
        None => true,
    }
}

fn create_git_object_file(search_result: &HashMatch) {
    let compressed_object = zlib_compress(&search_result.data);
    let dir_path = format!(".git/objects/{:02x}", search_result.hash[0]);
    let file_path = format!(
        "{}/{}",
        dir_path,
        to_hex_string(&search_result.hash[1..])
    );

    fs::DirBuilder::new()
        .recursive(true)
        .create(dir_path)
        .expect("Failed to create git object directory");

    fs::File::create(file_path)
        .expect("Failed to open git object file")
        .write_all(&compressed_object)
        .expect("Failed to write git object file");
}

fn zlib_compress(data: &[u8]) -> Vec<u8> {
    let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
    encoder.write(data).expect("zlib compression failed");
    encoder.finish().expect("zlib compression failed")
}

fn git_reset_to_hash(hash: &[u8; SHA1_BYTE_LENGTH]) {
    run_command("git", &["reset", &to_hex_string(hash)]);
}

fn to_hex_string(hash: &[u8]) -> String {
    hash.iter().map(|byte| format!("{:02x}", *byte)).collect::<String>()
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::env;
use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let config_file = {
        let mut config_file = None;
        let mut arg_count = 0;
        for arg in env::args_os() {
            arg_count = arg_count + 1;
            if arg_count == 1 {
                continue;
            }
            if arg_count > 2 {
                panic!("Expected only a single command-line argument, the JSON configuration file.");
            }
            config_file = Some(arg);
        }
        config_file.expect("Expected a single command-line argument, the JSON configuration file.")
    };

    let server = IrcServer::new(config_file).expect("Couldn't initialize server with given configuration file");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               None,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            let this_channel_data =
                                channel_data
                                    .entry(target.clone())
                                    .or_insert_with(|| ChannelData::new(&options));
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       Some(this_channel_data),
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      options: &HashMap<String, String>,
                      this_channel_data: Option<&mut ChannelData>,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = if response_is_action {
            format!("\x01ACTION {}\x01", line_with_nick)
        } else {
            line_with_nick
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"Topic github: <url>\", or if the \"Topic:\" line is a github URL.");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "bye" {
        match this_channel_data {
            None => {
                send_line(response_username, "'bye' only works in a channel");
            }
            Some(this_channel_data) => {
                this_channel_data.end_topic();
                server
                    .send(Command::PART(String::from(response_target),
                                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                                     response_username.unwrap()))))
                    .unwrap();
            }
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

/// A case-insensitive version of starts_with.
fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

/// Remove a case-insensitive start of the line, and if that prefix is
/// present return the rest of the line.
fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<commit_msg>Slightly simpler way to process arguments.<commit_after>#[macro_use]
extern crate log;
extern crate env_logger;
extern crate irc;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper;
extern crate hubcaps;
extern crate hyper_native_tls;
extern crate serde_json;

use std::env;
use std::fmt;
use std::thread;
use std::collections::HashMap;
use regex::Regex;

use irc::client::prelude::*;
use irc::client::data::command::Command;

use hyper::Client;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use hubcaps::{Credentials, Github};
use hubcaps::comments::CommentOptions;

fn main() {
    env_logger::init().unwrap();

    let config_file =
        {
            let mut args = env::args_os();
            args.next().expect("What, no program name?");
            let config_file = args.next().expect("Expected a single command-line argument, the JSON configuration file.");
            if args.next().is_some() {
                panic!("Expected only a single command-line argument, the JSON configuration file.");
            }
            config_file
        };

    let server = IrcServer::new(config_file).expect("Couldn't initialize server with given configuration file");
    server.identify().unwrap();

    let options = server
        .config()
        .options
        .as_ref()
        .expect("No options property within configuration?");

    // FIXME: Add options to ask the bot which channels it's in, and
    // which channels it currently has buffers in.  (Then we can do
    // things like ask the bot to reboot itself, but it will only do so
    // if it's not busy.)
    let mut channel_data: HashMap<String, ChannelData> = HashMap::new();

    for message in server.iter() {
        let message = message.unwrap(); // panic if there's an error

        match message.command {
            Command::PRIVMSG(ref target, ref msg) => {
                match message.source_nickname() {
                    None => {
                        warn!("PRIVMSG without a source! {}",
                              format!("{}", message).trim());
                    }
                    Some(ref source) => {
                        let source_ = String::from(*source);
                        let line = if msg.starts_with("\x01ACTION ") && msg.ends_with("\x01") {
                            ChannelLine {
                                source: source_,
                                is_action: true,
                                message: String::from(&msg[8..msg.len() - 1]),
                            }
                        } else {
                            ChannelLine {
                                source: source_,
                                is_action: false,
                                message: msg.clone(),
                            }
                        };
                        let mynick = server.current_nickname();
                        if target == mynick {
                            // An actual private message.
                            info!("[{}] {}", source, line);
                            handle_bot_command(&server,
                                               options,
                                               None,
                                               &line.message,
                                               source,
                                               false,
                                               None)
                        } else if target.starts_with('#') {
                            // A message in a channel.
                            info!("[{}] {}", target, line);
                            let this_channel_data =
                                channel_data
                                    .entry(target.clone())
                                    .or_insert_with(|| ChannelData::new(&options));
                            match check_command_in_channel(mynick, &line.message) {
                                Some(ref command) => {
                                    handle_bot_command(&server,
                                                       options,
                                                       Some(this_channel_data),
                                                       command,
                                                       target,
                                                       line.is_action,
                                                       Some(source))
                                }
                                None => {
                                    if let Some(response) = this_channel_data.add_line(line) {
                                        server.send_privmsg(target, &*response).unwrap();
                                    }
                                }
                            }
                        } else {
                            warn!("UNEXPECTED TARGET {} in message {}",
                                  target,
                                  format!("{}", message).trim());
                        }
                    }
                }
            }
            Command::INVITE(ref target, ref channel) => {
                if target == server.current_nickname() {
                    // Join channels when invited.
                    server.send_join(channel).unwrap();
                }
            }
            _ => (),
        }
    }
}

// Take a message in the channel, and see if it was a message sent to
// this bot.
fn check_command_in_channel(mynick: &str, msg: &String) -> Option<String> {
    if !msg.starts_with(mynick) {
        return None;
    }
    let after_nick = &msg[mynick.len()..];
    if !after_nick.starts_with(":") && !after_nick.starts_with(",") {
        return None;
    }
    let after_punct = &after_nick[1..];
    Some(String::from(after_punct.trim_left()))
}

fn handle_bot_command(server: &IrcServer,
                      options: &HashMap<String, String>,
                      this_channel_data: Option<&mut ChannelData>,
                      command: &str,
                      response_target: &str,
                      response_is_action: bool,
                      response_username: Option<&str>) {

    let send_line = |response_username: Option<&str>, line: &str| {
        let line_with_nick = match response_username {
            None => String::from(line),
            Some(username) => String::from(username) + ", " + line,
        };
        let adjusted_line = if response_is_action {
            format!("\x01ACTION {}\x01", line_with_nick)
        } else {
            line_with_nick
        };
        server
            .send_privmsg(response_target, &adjusted_line)
            .unwrap();
    };

    if command == "help" {
        send_line(response_username, "The commands I understand are:");
        send_line(None, "  help     Send this message.");
        send_line(None, "  intro    Send a message describing what I do.");
        send_line(None,
                  "  bye      Leave the channel.  (You can /invite me back.)");
        return;
    }

    if command == "intro" {
        let config = server.config();
        send_line(None,
                  "My job is to leave comments in github when the group discusses github issues and takes minutes in IRC.");
        send_line(None,
                  "I separate discussions by the \"Topic:\" lines, and I know what github issues to use only by lines of the form \"Topic github: <url>\", or if the \"Topic:\" line is a github URL.");
        send_line(None,
                  &*format!("I'm only allowed to comment on issues in the repositories: {}.",
                            options["github_repos_allowed"]));
        let owners = if let Some(v) = config.owners.as_ref() {
            v.join(" ")
        } else {
            String::from("")
        };
        send_line(None,
                  &*format!("My source code is at {} and I'm run by {}.",
                            options["source"],
                            owners));
        return;
    }

    if command == "bye" {
        match this_channel_data {
            None => {
                send_line(response_username, "'bye' only works in a channel");
            }
            Some(this_channel_data) => {
                this_channel_data.end_topic();
                server
                    .send(Command::PART(String::from(response_target),
                                        Some(format!("Leaving at request of {}.  Feel free to /invite me back.",
                                                     response_username.unwrap()))))
                    .unwrap();
            }
        }
        return;
    }

    send_line(response_username,
              "Sorry, I don't understand that command.  Try 'help'.");
}

struct ChannelLine {
    source: String,
    is_action: bool,
    message: String,
}

struct TopicData {
    topic: String,
    github_url: Option<String>,
    lines: Vec<ChannelLine>,
    resolutions: Vec<String>,
}

struct ChannelData<'opts> {
    current_topic: Option<TopicData>,
    options: &'opts HashMap<String, String>,
}

impl fmt::Display for ChannelLine {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.is_action {
            write!(f, "* {} {}", self.source, self.message)
        } else {
            write!(f, "<{}> {}", self.source, self.message)
        }
    }
}

impl TopicData {
    fn new(topic: &str) -> TopicData {
        let topic_ = String::from(topic);
        TopicData {
            topic: topic_,
            github_url: None,
            lines: vec![],
            resolutions: vec![],
        }
    }
}

impl fmt::Display for TopicData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        if self.resolutions.len() == 0 {
            try!(write!(f,
                        "The CSS Working Group just discussed {}.\n\n",
                        self.topic));
        } else {
            try!(write!(f,
                        "The CSS Working Group just discussed {}, and agreed to the following resolutions:\n\n",
                        self.topic));
            for resolution in &self.resolutions {
                try!(write!(f, "```\n{}\n```\n\n", resolution));
            }
        }

        try!(write!(f,
                    "The full IRC log of that discussion\n"));
        try!(write!(f, "\n```\n"));
        for line in &self.lines {
            try!(write!(f, "{}\n", line));
        }
        try!(write!(f, "```\n\n"));
        Ok(())
    }
}

/// A case-insensitive version of starts_with.
fn ci_starts_with(s: &str, prefix: &str) -> bool {
    assert!(prefix.to_lowercase() == prefix);

    s.len() >= prefix.len() && s[0..prefix.len()].to_lowercase() == prefix
}

/// Remove a case-insensitive start of the line, and if that prefix is
/// present return the rest of the line.
fn strip_ci_prefix(s: &str, prefix: &str) -> Option<String> {
    if ci_starts_with(s, prefix) {
        Some(String::from(s[prefix.len()..].trim_left()))
    } else {
        None
    }
}

impl<'opts> ChannelData<'opts> {
    fn new(options_: &'opts HashMap<String, String>) -> ChannelData {
        ChannelData {
            current_topic: None,
            options: options_,
        }
    }

    // Returns the response that should be sent to the message over IRC.
    fn add_line(&mut self, line: ChannelLine) -> Option<String> {
        if let Some(ref topic) = strip_ci_prefix(&line.message, "topic:") {
            self.start_topic(topic);
        }
        if line.source == "trackbot" && line.is_action == true &&
           line.message == "is ending a teleconference." {
            self.end_topic();
        }
        match self.current_topic {
            None => None,
            Some(ref mut data) => {
                let new_url_option = extract_github_url(&line.message, self.options);
                let response = match (new_url_option.as_ref(), data.github_url.as_ref()) {
                    (None, _) => None,
                    // FIXME: Add and implement instructions to cancel!
                    (Some(new_url), None) => {
                        Some(format!("OK, I'll post this discussion to {}", new_url))
                    }
                    (Some(new_url), Some(old_url)) if old_url == new_url => None,
                    (Some(new_url), Some(old_url)) => {
                        Some(format!("OK, I'll post this discussion to {} instead of {} like you said before",
                                     new_url,
                                     old_url))
                    }
                };

                if let Some(new_url) = new_url_option {
                    data.github_url = Some(new_url);
                }

                if line.message.starts_with("RESOLUTION") || line.message.starts_with("RESOLVED") {
                    data.resolutions.push(line.message.clone());
                }

                data.lines.push(line);

                response
            }
        }
    }

    fn start_topic(&mut self, topic: &str) {
        self.end_topic();
        self.current_topic = Some(TopicData::new(topic));
    }

    fn end_topic(&mut self) {
        // TODO: Test the topic boundary code.
        if let Some(topic) = self.current_topic.take() {
            if topic.github_url.is_some() {
                let task = GithubCommentTask::new(topic, self.options);
                task.run();
            }
        }
    }
}

fn extract_github_url(message: &str, options: &HashMap<String, String>) -> Option<String> {
    lazy_static! {
        static ref GITHUB_URL_RE: Regex =
            Regex::new(r"^https://github.com/(?P<repo>[^/]*/[^/]*)/issues/(?P<number>[0-9]+)$")
            .unwrap();
    }
    let ref allowed_repos = options["github_repos_allowed"];
    for prefix in ["topic:", "github topic:"].into_iter() {
        if let Some(ref maybe_url) = strip_ci_prefix(&message, prefix) {
            if let Some(ref caps) = GITHUB_URL_RE.captures(maybe_url) {
                for repo in allowed_repos.split_whitespace() {
                    if caps["repo"] == *repo {
                        return Some(maybe_url.clone());
                    }
                }
            }
        }
    }
    None
}

struct GithubCommentTask {
    data: TopicData,
    github: Github,
}

impl GithubCommentTask {
    fn new(data_: TopicData, options: &HashMap<String, String>) -> GithubCommentTask {
        let github_ =
            Github::new(&*options["github_uastring"],
                        Client::with_connector(HttpsConnector::new(NativeTlsClient::new()
                                                                       .unwrap())),
                        Credentials::Token(options["github_access_token"].clone()));
        GithubCommentTask {
            data: data_,
            github: github_,
        }
    }
    fn run(self) {
        thread::spawn(move || { self.main(); });
    }
    fn main(&self) {
        lazy_static! {
            static ref GITHUB_URL_RE: Regex =
                Regex::new(r"^https://github.com/(?P<owner>[^/]*)/(?P<repo>[^/]*)/issues/(?P<number>[0-9]+)$")
                .unwrap();
        }

        if let Some(ref github_url) = self.data.github_url {
            if let Some(ref caps) = GITHUB_URL_RE.captures(github_url) {
                let repo = self.github
                    .repo(String::from(&caps["owner"]), String::from(&caps["repo"]));
                let issue = repo.issue(caps["number"].parse::<u64>().unwrap());
                let comments = issue.comments();

                let comment_text = format!("{}", self.data);
                comments
                    .create(&CommentOptions { body: comment_text })
                    .unwrap();
            } else {
                warn!("How does {} fail to match now when it matched before?",
                      github_url)
            }
        }
    }
}
<|endoftext|>


<commit_before>extern crate gtk;
extern crate glib;
extern crate hyper;
extern crate humansize;
extern crate serde_json;
#[macro_use] extern crate serde_derive;

#[macro_use] mod gui;
mod json_rpc;
mod nzbget;

use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, Mutex};
use std::thread;
use gui::Interface;
use gtk::prelude::*;
use gtk::{AboutDialog, Builder, Label, ListStore, Statusbar, ToolButton,
    Type, TreeIter, TreeView, Menu, MenuItem, Widget, Window};
use humansize::{FileSize, file_size_opts as options};
use nzbget::{Client, Group, Status};

fn main() {
    gtk::init().unwrap();

    let client = Arc::new(Client::new("http://localhost:6789"));

    let interface = Interface::new();
    let builder = interface.builder.clone();

    let window: Window = builder.get_object("main_window").unwrap();
    let about_item: MenuItem = builder.get_object("about_item").unwrap();
    let about_dialog: AboutDialog = builder.get_object("about_dialog").unwrap();
    let status_bar: Statusbar = builder.get_object("status_bar").unwrap();
    let files_tree = interface.create_files_tree();

    about_item.connect_activate(move |_| {
        about_dialog.run();
        about_dialog.hide();
    });

    let client1 = client.clone();
    let pause_button: ToolButton = builder.get_object("pause_button").unwrap();
    pause_button.connect_clicked(move |button| {
        let widget = button.clone().upcast::<Widget>();

        widget.set_sensitive(false);

        if button.get_stock_id().unwrap() == "gtk-media-pause" {
            client1.pause_download();
        } else {
            client1.resume_download();
        }

        widget.set_sensitive(true);
    });

    let context_id = status_bar.get_context_id("");
    status_bar.push(context_id, "nzbget-ui");

    let files_store = ListStore::new(&[Type::U32, Type::String, Type::String, Type::String, Type::F64]);
    files_tree.set_model(Some(&files_store));

    let widget = files_tree.clone().upcast::<Widget>();
    widget.connect_button_release_event(move |_, event| {
        if event.get_button() != 3 {
            return Inhibit(false)
        }

        let popup_menu = Menu::new();

        let item = MenuItem::new_with_label("resume");
        popup_menu.append(&item);

        let client1 = client.clone();
        let selection = files_tree.get_selection();
        item.connect_activate(move |_| {
            let (paths, model) = selection.get_selected_rows();

            for path in paths {
                let iter = model.get_iter(&path).unwrap();
                let id = model.get_value(&iter, 0).get().unwrap();

                client1.resume_groups(vec![id]);
            }
        });

        let item = MenuItem::new_with_label("pause");
        popup_menu.append(&item);

        let client2 = client.clone();
        let selection = files_tree.get_selection();
        item.connect_activate(move |_| {
            let (paths, model) = selection.get_selected_rows();

            for path in paths {
                let iter = model.get_iter(&path).unwrap();
                let id = model.get_value(&iter, 0).get().unwrap();

                client2.pause_groups(vec![id]);
            }
        });

        popup_menu.append(&gtk::SeparatorMenuItem::new());

        let item = MenuItem::new_with_label("delete");
        popup_menu.append(&item);

        let client3 = client.clone();
        let selection = files_tree.get_selection();
        item.connect_activate(move |_| {
            let (paths, model) = selection.get_selected_rows();

            for path in paths {
                let iter = model.get_iter(&path).unwrap();
                let id = model.get_value(&iter, 0).get().unwrap();

                client3.delete_groups(vec![id]);
            }
        });

        popup_menu.show_all();
        popup_menu.popup_easy(event.get_button(), event.get_time());

        Inhibit(false)
    });

    window.show_all();

    let rendered_groups: Mutex<HashMap<u32, TreeIter>> = Mutex::new(HashMap::new());

    let (tx, rx) = channel();
    GLOBAL.with(move |global| {
        *global.borrow_mut() = Some((builder, files_store, rendered_groups, rx))
    });

    thread::spawn(move || {
        loop {
            let client = Client::new("http://localhost:6789");

            let groups = client.load_groups();
            let status = client.load_status();

            tx.send((groups, status)).unwrap();

            thread::sleep(std::time::Duration::from_millis(1000));
            glib::idle_add(receive);
        }
    });

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    gtk::main();
}

fn receive() -> glib::Continue {
    GLOBAL.with(|global| {
        if let Some((ref builder, ref files_store, ref rendered_groups, ref rx)) = *global.borrow() {
            if let Ok(data) = rx.try_recv() {
                let (groups, status) = data;

                let pause_button: ToolButton = builder.get_object("pause_button").unwrap();

                if status.DownloadPaused {
                    pause_button.set_stock_id("gtk-media-play");
                } else {
                    pause_button.set_stock_id("gtk-media-pause");
                }

                let mut rendered_groups = rendered_groups.lock().unwrap();

                for group in &groups {
                    let human_size = group.file_size().file_size(options::CONVENTIONAL).unwrap();

                    if rendered_groups.contains_key(&group.NZBID) {
                        let iter = rendered_groups.get(&group.NZBID).unwrap();
                        files_store.set(iter, &[0, 1, 2, 3, 4], &[&group.NZBID, &group.NZBNicename, &group.Status, &human_size, &group.progress()]);
                        continue;
                    }

                    let rendered_group = files_store.insert_with_values(Some(group.NZBID), &[0, 1, 2, 3, 4], &[&group.NZBID, &group.NZBNicename, &group.Status, &human_size, &group.progress()]);
                    rendered_groups.insert(group.NZBID, rendered_group);
                }

                let tree_view: TreeView = builder.get_object("files_tree").unwrap();
                let tree_model = tree_view.get_model().unwrap();

                let iter = tree_model.iter_children(None).unwrap();

                loop {
                    let id: u32 = tree_model.get_value(&iter, 0).get().unwrap();

                    if groups.iter().find(|x| x.NZBID == id).is_none() {
                        files_store.remove(&iter);
                    }

                    if !tree_model.iter_next(&iter) {
                        break;
                    }
                }

                let mut human_speed = status.DownloadRate.file_size(options::CONVENTIONAL).unwrap();
                human_speed.push_str("/s");

                let current_speed: Label = builder.get_object("current_speed").unwrap();
                current_speed.set_label(&human_speed);
            }
        }
    });
    glib::Continue(false)
}

thread_local!(
    static GLOBAL: RefCell<Option<(Builder, ListStore, Mutex<HashMap<u32, TreeIter>>, Receiver<(Vec<Group>, Status)>)>> = RefCell::new(None)
);
<commit_msg>Use shared hyper client<commit_after>extern crate gtk;
extern crate glib;
extern crate hyper;
extern crate humansize;
extern crate serde_json;
#[macro_use] extern crate serde_derive;

#[macro_use] mod gui;
mod json_rpc;
mod nzbget;

use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::mpsc::{channel, Receiver};
use std::sync::{Arc, Mutex};
use std::thread;
use gui::Interface;
use gtk::prelude::*;
use gtk::{AboutDialog, Builder, Label, ListStore, Statusbar, ToolButton,
    Type, TreeIter, TreeView, Menu, MenuItem, Widget, Window};
use humansize::{FileSize, file_size_opts as options};
use nzbget::{Client, Group, Status};

fn main() {
    gtk::init().unwrap();

    let shared_client = Arc::new(Client::new("http://localhost:6789"));

    let interface = Interface::new();
    let builder = interface.builder.clone();

    let window: Window = builder.get_object("main_window").unwrap();
    let about_item: MenuItem = builder.get_object("about_item").unwrap();
    let about_dialog: AboutDialog = builder.get_object("about_dialog").unwrap();
    let status_bar: Statusbar = builder.get_object("status_bar").unwrap();
    let files_tree = interface.create_files_tree();

    about_item.connect_activate(move |_| {
        about_dialog.run();
        about_dialog.hide();
    });

    let client = shared_client.clone();
    let pause_button: ToolButton = builder.get_object("pause_button").unwrap();
    pause_button.connect_clicked(move |button| {
        let widget = button.clone().upcast::<Widget>();

        widget.set_sensitive(false);

        if button.get_stock_id().unwrap() == "gtk-media-pause" {
            client.pause_download();
        } else {
            client.resume_download();
        }

        widget.set_sensitive(true);
    });

    let context_id = status_bar.get_context_id("");
    status_bar.push(context_id, "nzbget-ui");

    let files_store = ListStore::new(&[Type::U32, Type::String, Type::String, Type::String, Type::F64]);
    files_tree.set_model(Some(&files_store));

    let widget = files_tree.clone().upcast::<Widget>();
    let client = shared_client.clone();
    widget.connect_button_release_event(move |_, event| {
        if event.get_button() != 3 {
            return Inhibit(false)
        }

        let popup_menu = Menu::new();

        let item = MenuItem::new_with_label("resume");
        popup_menu.append(&item);

        let client1 = client.clone();
        let selection = files_tree.get_selection();
        item.connect_activate(move |_| {
            let (paths, model) = selection.get_selected_rows();

            for path in paths {
                let iter = model.get_iter(&path).unwrap();
                let id = model.get_value(&iter, 0).get().unwrap();

                client1.resume_groups(vec![id]);
            }
        });

        let item = MenuItem::new_with_label("pause");
        popup_menu.append(&item);

        let client2 = client.clone();
        let selection = files_tree.get_selection();
        item.connect_activate(move |_| {
            let (paths, model) = selection.get_selected_rows();

            for path in paths {
                let iter = model.get_iter(&path).unwrap();
                let id = model.get_value(&iter, 0).get().unwrap();

                client2.pause_groups(vec![id]);
            }
        });

        popup_menu.append(&gtk::SeparatorMenuItem::new());

        let item = MenuItem::new_with_label("delete");
        popup_menu.append(&item);

        let client3 = client.clone();
        let selection = files_tree.get_selection();
        item.connect_activate(move |_| {
            let (paths, model) = selection.get_selected_rows();

            for path in paths {
                let iter = model.get_iter(&path).unwrap();
                let id = model.get_value(&iter, 0).get().unwrap();

                client3.delete_groups(vec![id]);
            }
        });

        popup_menu.show_all();
        popup_menu.popup_easy(event.get_button(), event.get_time());

        Inhibit(false)
    });

    window.show_all();

    let rendered_groups: Mutex<HashMap<u32, TreeIter>> = Mutex::new(HashMap::new());

    let (tx, rx) = channel();
    GLOBAL.with(move |global| {
        *global.borrow_mut() = Some((builder, files_store, rendered_groups, rx))
    });

    let client = shared_client.clone();
    thread::spawn(move || {
        loop {
            let groups = client.load_groups();
            let status = client.load_status();

            tx.send((groups, status)).unwrap();

            thread::sleep(std::time::Duration::from_millis(1000));
            glib::idle_add(receive);
        }
    });

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    gtk::main();
}

fn receive() -> glib::Continue {
    GLOBAL.with(|global| {
        if let Some((ref builder, ref files_store, ref rendered_groups, ref rx)) = *global.borrow() {
            if let Ok(data) = rx.try_recv() {
                let (groups, status) = data;

                let pause_button: ToolButton = builder.get_object("pause_button").unwrap();

                if status.DownloadPaused {
                    pause_button.set_stock_id("gtk-media-play");
                } else {
                    pause_button.set_stock_id("gtk-media-pause");
                }

                let mut rendered_groups = rendered_groups.lock().unwrap();

                for group in &groups {
                    let human_size = group.file_size().file_size(options::CONVENTIONAL).unwrap();

                    if rendered_groups.contains_key(&group.NZBID) {
                        let iter = rendered_groups.get(&group.NZBID).unwrap();
                        files_store.set(iter, &[0, 1, 2, 3, 4], &[&group.NZBID, &group.NZBNicename, &group.Status, &human_size, &group.progress()]);
                        continue;
                    }

                    let rendered_group = files_store.insert_with_values(Some(group.NZBID), &[0, 1, 2, 3, 4], &[&group.NZBID, &group.NZBNicename, &group.Status, &human_size, &group.progress()]);
                    rendered_groups.insert(group.NZBID, rendered_group);
                }

                let tree_view: TreeView = builder.get_object("files_tree").unwrap();
                let tree_model = tree_view.get_model().unwrap();

                let iter = tree_model.iter_children(None).unwrap();

                loop {
                    let id: u32 = tree_model.get_value(&iter, 0).get().unwrap();

                    if groups.iter().find(|x| x.NZBID == id).is_none() {
                        files_store.remove(&iter);
                    }

                    if !tree_model.iter_next(&iter) {
                        break;
                    }
                }

                let mut human_speed = status.DownloadRate.file_size(options::CONVENTIONAL).unwrap();
                human_speed.push_str("/s");

                let current_speed: Label = builder.get_object("current_speed").unwrap();
                current_speed.set_label(&human_speed);
            }
        }
    });
    glib::Continue(false)
}

thread_local!(
    static GLOBAL: RefCell<Option<(Builder, ListStore, Mutex<HashMap<u32, TreeIter>>, Receiver<(Vec<Group>, Status)>)>> = RefCell::new(None)
);
<|endoftext|>