<commit_before>#![allow(dead_code)]

extern crate csv;
extern crate hex;
extern crate piston;
extern crate graphics;
extern crate glutin_window;
extern crate opengl_graphics;
extern crate image;
extern crate piston_window;

mod cpu;
mod gpu;
mod system;

use piston_window::*;
use image::ImageBuffer;
use image::{RgbaImage, Rgba};
use opengl_graphics::Texture;
use piston::event_loop::*;
use piston::input::*;
use opengl_graphics::{ GlGraphics, OpenGL };
use std::fs::File;
use std::io::prelude::*;
use std::env;
use cpu::cpu::*;
use gpu::gpu::*;
use gpu::gpu_registers::*;
use system::*;
use self::hex::FromHex;

static MAX_SPRITE: u8 = 40;

fn main()
 {
    //Initialize Emulator
    let args: Vec<String> = env::args().collect();
    let emulator_type: String = String::from("CLASSIC");
    let file_name: &String = &args[1];
    let mut system_data : SystemData = get_system_data(&emulator_type);
    system_data.mem_map = read_gb_file(file_name);
    let mut registers: Registers = Registers::new();
    let mut gpu_registers: GPU_Registers = GPU_Registers::new();
    init_emulator_state(&mut system_data, &mut registers);

    //Initialize Screen
    let opengl = OpenGL::V3_2;
    let mut window: PistonWindow = WindowSettings::new("RustBoy", [system_data.width as u32, system_data.height as u32])
                                        .opengl(opengl)
                                        .exit_on_esc(true)
                                        .build()
                                        .unwrap();
    //window.set_max_fps(60);
    let mut app = App
    {
        gl: GlGraphics::new(opengl),
    };
    let mut events = Events::new(EventSettings::new().max_fps(60));
    //events.set_max_fps(60);
    let mut background_tile_map: TileMap = TileMap::new();
    let mut window_tile_map: TileMap = TileMap::new();

    //Operation loop
    let mut emulator_loop = true;

    while let Some(e) = events.next(&mut window)
    {
        if let Some(r) = e.update_args()
        {
            while !gpu_registers.v_blank_draw_flag
            {
                let opcode = system_data.mem_map[registers.program_counter as usize];
                let address = registers.program_counter;
                cpu_continue(&mut system_data, &mut registers);
                update_gpu(&mut system_data, &mut registers, &mut gpu_registers);
            }
        }

        if let Some(r) = e.render_args(){
            gpu_registers.v_blank_draw_flag = false;
            background_tile_map.populate_tile_map(&mut system_data, gpu_registers.lcdc_register.tile_data, gpu_registers.lcdc_register.background_display_select);
            window_tile_map.populate_tile_map(&mut system_data, gpu_registers.lcdc_register.tile_data, gpu_registers.lcdc_register.window_display_select);
            //let background_image: RgbaImage = create_background_img(&background_tile_map);
            let background_image: RgbaImage = create_background_img(&window_tile_map);
            app.render(&background_image, &r);
            // break;
        }
    }
        // if system_data.cycles == 0  //|| registers.program_counter == 0x6d
        // {
        //     emulator_loop = false;
        //     println!("Location: {:04X}\tOpcode: 0x{:02X}  {:08b}", address, opcode, opcode);
        // }
    //Cleanup?
}

pub struct App
{
    gl: GlGraphics
}

impl App
{
    fn render(&mut self, img: &RgbaImage, args: &RenderArgs)
    {
            use graphics::*;
            let BLANK: types::Color = color::hex("9CBD0F");
            let tile = Texture::from_image(&img, &TextureSettings::new());

            self.gl.draw(args.viewport(), |c, gl|
            {
                clear(color::BLACK, gl);
                let transform = c.transform.trans(0.0,0.0);
                image(&tile, transform, gl);
            });
    }
}

fn read_gb_file(file_name: &str) -> Vec<u8>
{
    let mut buffer : Vec<u8> = vec![0; 0x10000];
    let file = File::open(file_name);
    if file.is_ok()
    {
        if file.unwrap().read(&mut buffer).is_ok()
        {
            return buffer;
        }
    }
    return vec![0;0];
}

fn create_background_img(background_tile_map: &TileMap) -> RgbaImage
{

    let mut buffer = ImageBuffer::new(256, 256);
    for tile_y in 0..32
    {
        for tile_x in 0..32
        {
            for pixel_y in 0..8
            {
                for pixel_x in 0..8
                {
                    let tile = background_tile_map.map[(tile_y * 32) + tile_x];
                    let pixel_data = background_tile_map.tiles[tile as usize].data[(pixel_y * 8) + pixel_x];
                    let pixel = pixel_color_map(pixel_data);
                    buffer.put_pixel(((tile_x * 8) + pixel_x) as u32, ((tile_y * 8) + pixel_y) as u32, pixel);
                }
            }
        }
    }
    return buffer;
}

fn pixel_color_map(pixel_data: u8) -> Rgba<u8>
{
    match pixel_data
    {
        0 => return Rgba([156,189,15, 0xFF]),
        1 => return Rgba([140,173,15, 0xFF]),
        2 => return Rgba([48,98,48, 0xFF]),
        3 => return Rgba([15, 56, 15, 0xFF]),
        _ => return Rgba([0, 0, 0, 0xFF]),
    }
}

fn output_mem_selection(mem_map: &Vec<u8>, start: u16, end:u16)
{
    for index in start .. end
    {
        print!("{}-{:x}\t", index, mem_map[index as usize]);
    }
    print!("\n");
}

fn init_emulator_state(system_data: &mut SystemData, registers: &mut Registers)
{
    registers.program_counter = 0x100;

    let states: Vec<u8> = vec![0x00, 0x00, 0x00, 0x80, 0xBF, 0xF3, 0xBF, 0x3F,
                               0x00, 0xBF, 0x7F, 0xFF, 0x9F, 0xBF, 0xFF, 0x00,
                               0x00, 0xBF, 0x77, 0xF3, 0xF1, 0x91, 0x00, 0x00,
                               0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00];

    let mem_locations: Vec<usize> = vec![0xFF05, 0xFF06, 0xFF07, 0xFF10, 0xFF11, 0xFF12,
                                       0xFF14, 0xFF16, 0xFF17, 0xFF19, 0xFF1A, 0xFF1B,
                                       0xFF1C, 0xFF1E, 0xFF20, 0xFF21, 0xFF22, 0xFF23,
                                       0xFF24, 0xFF25, 0xFF26, 0xFF40, 0xFF42, 0xFF43,
                                       0xFF45, 0xFF47, 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFFFF];

    for i in 0..states.len()
    {
        system_data.mem_map[mem_locations[i]] = states[i];
    }

    let register_states: Vec<u16> = vec![0x01B0, 0x0013, 0x00D8, 0x014D, 0xFFFE];
    for i in 0..register_states.len()
    {
        registers.mapped_16_bit_register_setter(i as u8, register_states[i]);
    }
}

#[cfg(test)]
mod main_tests
{
    use read_gb_file;

    #[test]
    fn passing_bad_filename_to_read_gb_file_return_empty_vec()
    {
        let return_vector : Vec<u8> = read_gb_file("");
        assert_eq!(return_vector, vec![0;0]);
    }
}
<commit_msg>Fixed joypad defaults with temp joypad value<commit_after>#![allow(dead_code)]

extern crate csv;
extern crate hex;
extern crate piston;
extern crate graphics;
extern crate glutin_window;
extern crate opengl_graphics;
extern crate image;
extern crate piston_window;

mod cpu;
mod gpu;
mod system;

use piston_window::*;
use image::ImageBuffer;
use image::{RgbaImage, Rgba};
use opengl_graphics::Texture;
use piston::event_loop::*;
use piston::input::*;
use opengl_graphics::{ GlGraphics, OpenGL };
use std::fs::File;
use std::io::prelude::*;
use std::env;
use cpu::cpu::*;
use gpu::gpu::*;
use gpu::gpu_registers::*;
use system::*;
use self::hex::FromHex;

static MAX_SPRITE: u8 = 40;

fn main()
 {
    //Initialize Emulator
    let args: Vec<String> = env::args().collect();
    let emulator_type: String = String::from("CLASSIC");
    let file_name: &String = &args[1];
    let mut system_data : SystemData = get_system_data(&emulator_type);
    system_data.mem_map = read_gb_file(file_name);
    let mut registers: Registers = Registers::new();
    let mut gpu_registers: GPU_Registers = GPU_Registers::new();
    init_emulator_state(&mut system_data, &mut registers);

    //Initialize Screen
    let opengl = OpenGL::V3_2;
    let mut window: PistonWindow = WindowSettings::new("RustBoy", [system_data.width as u32, system_data.height as u32])
                                        .opengl(opengl)
                                        .exit_on_esc(true)
                                        .build()
                                        .unwrap();
    //window.set_max_fps(60);
    let mut app = App
    {
        gl: GlGraphics::new(opengl),
    };
    let mut events = Events::new(EventSettings::new().max_fps(60));
    //events.set_max_fps(60);
    let mut background_tile_map: TileMap = TileMap::new();
    let mut window_tile_map: TileMap = TileMap::new();

    //Operation loop
    let mut emulator_loop = true;

    while let Some(e) = events.next(&mut window)
    {
        if let Some(r) = e.update_args()
        {
            while !gpu_registers.v_blank_draw_flag
            {   //Default until joypad implementation
                system_data.mem_map[0xFF00] = 0xFF;//system_data.mem_map[0xFF00] | 0x0F;
                //^^^^ Delete when joypad implemented ^^^^^^
                let opcode = system_data.mem_map[registers.program_counter as usize];
                let address = registers.program_counter;
                cpu_continue(&mut system_data, &mut registers);
                update_gpu(&mut system_data, &mut registers, &mut gpu_registers);
            }
        }

        if let Some(r) = e.render_args(){
            gpu_registers.v_blank_draw_flag = false;
            background_tile_map.populate_tile_map(&mut system_data, gpu_registers.lcdc_register.tile_data, gpu_registers.lcdc_register.background_display_select);
            window_tile_map.populate_tile_map(&mut system_data, gpu_registers.lcdc_register.tile_data, gpu_registers.lcdc_register.window_display_select);
            //let background_image: RgbaImage = create_background_img(&background_tile_map);
            let background_image: RgbaImage = create_background_img(&window_tile_map);
            app.render(&background_image, &r);
            // break;
        }
    }
        // if system_data.cycles == 0  //|| registers.program_counter == 0x6d
        // {
        //     emulator_loop = false;
        //     println!("Location: {:04X}\tOpcode: 0x{:02X}  {:08b}", address, opcode, opcode);
        // }
    //Cleanup?
}

pub struct App
{
    gl: GlGraphics
}

impl App
{
    fn render(&mut self, img: &RgbaImage, args: &RenderArgs)
    {
            use graphics::*;
            let BLANK: types::Color = color::hex("9CBD0F");
            let tile = Texture::from_image(&img, &TextureSettings::new());

            self.gl.draw(args.viewport(), |c, gl|
            {
                clear(color::BLACK, gl);
                let transform = c.transform.trans(0.0,0.0);
                image(&tile, transform, gl);
            });
    }
}

fn read_gb_file(file_name: &str) -> Vec<u8>
{
    let mut buffer : Vec<u8> = vec![0; 0x10000];
    let file = File::open(file_name);
    if file.is_ok()
    {
        if file.unwrap().read(&mut buffer).is_ok()
        {
            return buffer;
        }
    }
    return vec![0;0];
}

fn create_background_img(background_tile_map: &TileMap) -> RgbaImage
{

    let mut buffer = ImageBuffer::new(256, 256);
    for tile_y in 0..32
    {
        for tile_x in 0..32
        {
            for pixel_y in 0..8
            {
                for pixel_x in 0..8
                {
                    let tile = background_tile_map.map[(tile_y * 32) + tile_x];
                    let pixel_data = background_tile_map.tiles[tile as usize].data[(pixel_y * 8) + pixel_x];
                    let pixel = pixel_color_map(pixel_data);
                    buffer.put_pixel(((tile_x * 8) + pixel_x) as u32, ((tile_y * 8) + pixel_y) as u32, pixel);
                }
            }
        }
    }
    return buffer;
}

fn pixel_color_map(pixel_data: u8) -> Rgba<u8>
{
    match pixel_data
    {
        0 => return Rgba([156,189,15, 0xFF]),
        1 => return Rgba([140,173,15, 0xFF]),
        2 => return Rgba([48,98,48, 0xFF]),
        3 => return Rgba([15, 56, 15, 0xFF]),
        _ => return Rgba([0, 0, 0, 0xFF]),
    }
}

fn output_mem_selection(mem_map: &Vec<u8>, start: u16, end:u16)
{
    for index in start .. end
    {
        print!("{}-{:x}\t", index, mem_map[index as usize]);
    }
    print!("\n");
}

fn init_emulator_state(system_data: &mut SystemData, registers: &mut Registers)
{
    registers.program_counter = 0x100;

    let states: Vec<u8> = vec![0x00, 0x00, 0x00, 0x80, 0xBF, 0xF3, 0xBF, 0x3F,
                               0x00, 0xBF, 0x7F, 0xFF, 0x9F, 0xBF, 0xFF, 0x00,
                               0x00, 0xBF, 0x77, 0xF3, 0xF1, 0x91, 0x00, 0x00,
                               0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00];

    let mem_locations: Vec<usize> = vec![0xFF05, 0xFF06, 0xFF07, 0xFF10, 0xFF11, 0xFF12,
                                       0xFF14, 0xFF16, 0xFF17, 0xFF19, 0xFF1A, 0xFF1B,
                                       0xFF1C, 0xFF1E, 0xFF20, 0xFF21, 0xFF22, 0xFF23,
                                       0xFF24, 0xFF25, 0xFF26, 0xFF40, 0xFF42, 0xFF43,
                                       0xFF45, 0xFF47, 0xFF48, 0xFF49, 0xFF4A, 0xFF4B, 0xFFFF];

    for i in 0..states.len()
    {
        system_data.mem_map[mem_locations[i]] = states[i];
    }

    let register_states: Vec<u16> = vec![0x01B0, 0x0013, 0x00D8, 0x014D, 0xFFFE];
    for i in 0..register_states.len()
    {
        registers.mapped_16_bit_register_setter(i as u8, register_states[i]);
    }
}

#[cfg(test)]
mod main_tests
{
    use read_gb_file;

    #[test]
    fn passing_bad_filename_to_read_gb_file_return_empty_vec()
    {
        let return_vector : Vec<u8> = read_gb_file("");
        assert_eq!(return_vector, vec![0;0]);
    }
}
<|endoftext|>


<commit_before>extern crate kafka;
extern crate getopts;
extern crate env_logger;
extern crate time;

use std::ascii::AsciiExt;
use std::env;
use std::fmt;
use std::io::{self, stdout, Write, BufWriter};
use std::process;
use std::thread;
use std::time as stdtime;

use kafka::client::{KafkaClient, FetchOffset, GroupOffsetStorage};

/// A very simple offset monitor for a particular topic able to show
/// the lag for a particular consumer group. Dumps the offset/lag of
/// the monitored topic/group to stdout every few seconds.
fn main() {
    env_logger::init().unwrap();

    let cfg = match Config::from_cmdline() {
        Ok(cfg) => cfg,
        Err(e) => {
            println!("error: {}", e);
            process::exit(1);
        }
    };

    if let Err(e) = run(cfg) {
        println!("error: {}", e);
        process::exit(1);
    }
}

#[derive(Copy, Clone)]
struct Offsets {
    latest: i64,
    group: i64,
}

impl Default for Offsets {
    fn default() -> Self {
        Offsets { latest: -1, group: -1 }
    }
}

fn run(cfg: Config) -> Result<(), Error> {
    let mut client = KafkaClient::new(cfg.brokers);
    client.set_group_offset_storage(cfg.offset_storage);
    try!(client.load_metadata_all());

    let mut stdout = stdout();

    if cfg.topic.is_empty() {
        let ts = client.topics();
        let num_topics = ts.len();
        if num_topics == 0 {
            return Err(Error::Literal("no topics available".to_owned()));
        }
        let mut names: Vec<&str> = Vec::with_capacity(ts.len());
        names.extend(ts.names());
        names.sort();

        let mut buf = BufWriter::with_capacity(4096, stdout);
        for name in names {
            try!(write!(buf, "topic: {}\n", name));
        }
        return Err(Error::Literal("choose a topic".to_owned()));
    }

    let num_partitions = match client.topics().partitions(&cfg.topic) {
        None => return Err(Error::Literal(format!("no such topic: {}", &cfg.topic))),
        Some(partitions) => partitions.len(),
    };
    let mut offs = vec![Offsets::default(); num_partitions];
    let mut offs = offs.as_mut_slice();

    let mut fmt_buf = String::with_capacity(16);
    let mut out_buf = String::with_capacity(128);

    // ~ shall we produce the lag of the specified group?
    let show_lag = !cfg.group.is_empty();

    // ~ print a header
    {
        use std::fmt::Write;

        let _ = write!(out_buf, "{:>8} ", "time");
        for i in 0..num_partitions {
            fmt_buf.clear();
            let _ = write!(fmt_buf, "p-{}", i);
            let _ = write!(out_buf, " {:>10}", fmt_buf);
            if show_lag {
                let _ = write!(out_buf, " {:6}", "(lag)");
            }
        }
        out_buf.push('\n');
        let _ = stdout.write_all(out_buf.as_bytes());
    }
    // ~ now periodically fetch the offsets and dump them to stdout
    loop {
        let t = time::now();
        // ~ latest topic offsets
        let latests = try!(client.fetch_topic_offsets(&cfg.topic, FetchOffset::Latest));
        for l in latests {
            let v = l.offset.unwrap_or(-1);
            offs.get_mut(l.partition as usize).expect("available partition").latest = v;
        }
        if show_lag {
            // ~ group offsets
            let groups = try!(client.fetch_group_topic_offsets(&cfg.group, &cfg.topic));
            for g in groups {
                let v = g.offset.unwrap_or(-1);
                offs.get_mut(g.partition as usize).expect("available partition").group = v;
            }
        }
        // ~ format the output
        {
            use std::fmt::Write;

            out_buf.clear();
            let _ = write!(out_buf, "{} ", t.strftime("%H:%M:%S").unwrap());
            for o in &*offs {
                let _ = write!(out_buf, " {:>10}", o.latest);
                if show_lag {
                    fmt_buf.clear();
                    if o.group < 0 {
                        let _ = write!(fmt_buf, "()");
                    } else {
                        let lag = if cfg.commited_not_consumed {
                            o.latest - o.group
                        } else {
                            o.latest - o.group - 1
                        };
                        let _ = write!(fmt_buf, "({:<})", lag);
                    }
                    let _ = write!(out_buf, " {:6}", fmt_buf);
                }
            }
            out_buf.push('\n');
        }
        // ~ write the output
        let _ = stdout.write_all(out_buf.as_bytes());

        thread::sleep(cfg.period);
    }
}

// --------------------------------------------------------------------

struct Config {
    brokers: Vec<String>,
    topic: String,
    group: String,
    offset_storage: GroupOffsetStorage,
    period: stdtime::Duration,
    commited_not_consumed: bool,
}

impl Config {
    fn from_cmdline() -> Result<Config, Error> {
        let args: Vec<String> = env::args().collect();
        let mut opts = getopts::Options::new();
        opts.optflag("h", "help", "Print this help screen");
        opts.optopt("", "brokers", "Specify kafka bootstrap brokers (comma separated)", "HOSTS");
        opts.optopt("", "topic", "Specify the topic to monitor", "TOPIC");
        opts.optopt("", "group", "Specify the group to monitor", "GROUP");
        opts.optopt("", "offsets", "Specify offset store [zookeeper, kafka]", "STORE");
        opts.optopt("", "sleep", "Specify the sleep time", "SECS");
        opts.optflag("", "committed-not-yet-consumed",
                     "Assume commited group offsets specify \
                      messages the group will start consuming \
                      (including those at these offsets)");

        let m = match opts.parse(&args[1..]) {
            Ok(m) => m,
            Err(e) => return Err(Error::Literal(e.to_string())),
        };
        if m.opt_present("help") {
            let brief = format!("{} [options]", args[0]);
            return Err(Error::Literal(opts.usage(&brief)));
        }
        let mut offset_storage = GroupOffsetStorage::Zookeeper;
        if let Some(s) = m.opt_str("offsets") {
            if s.eq_ignore_ascii_case("zookeeper") {
                offset_storage = GroupOffsetStorage::Zookeeper;
            } else if s.eq_ignore_ascii_case("kafka") {
                offset_storage = GroupOffsetStorage::Kafka;
            } else {
                return Err(Error::Literal(format!("unknown offset store: {}", s)));
            }
        }
        let mut period = stdtime::Duration::from_secs(5);
        if let Some(s) = m.opt_str("sleep") {
            match s.parse::<u64>() {
                Ok(n) if n != 0 => period = stdtime::Duration::from_secs(n),
                _ => return Err(Error::Literal(format!("not a number greater than zero: {}", s))),
            }
        }
        Ok(Config {
            brokers: m.opt_str("brokers")
                .unwrap_or_else(|| "localhost:9092".to_owned())
                .split(',')
                .map(|s| s.trim().to_owned())
                .collect(),
            topic: m.opt_str("topic")
                .unwrap_or_else(|| String::new()),
            group: m.opt_str("group")
                .unwrap_or_else(|| String::new()),
            offset_storage: offset_storage,
            period: period,
            commited_not_consumed: m.opt_present("committed-not-yet-consumed"),
        })
    }
}

// --------------------------------------------------------------------

enum Error {
    Kafka(kafka::error::Error),
    Io(io::Error),
    Literal(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Error::Kafka(ref e) => write!(f, "kafka-error: {}", e),
            &Error::Io(ref e) => write!(f, "io-error: {}", e),
            &Error::Literal(ref s) => write!(f, "{}", s),
        }
    }
}

impl From<kafka::error::Error> for Error {
    fn from(e: kafka::error::Error) -> Self { Error::Kafka(e) }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self { Error::Io(e) }
}
<commit_msg>offset-monitor: Print an optional summary column<commit_after>extern crate kafka;
extern crate getopts;
extern crate env_logger;
extern crate time;

use std::ascii::AsciiExt;
use std::env;
use std::fmt;
use std::io::{self, stdout, Write, BufWriter};
use std::process;
use std::thread;
use std::time as stdtime;

use kafka::client::{KafkaClient, FetchOffset, GroupOffsetStorage};

/// A very simple offset monitor for a particular topic able to show
/// the lag for a particular consumer group. Dumps the offset/lag of
/// the monitored topic/group to stdout every few seconds.
fn main() {
    env_logger::init().unwrap();

    let cfg = match Config::from_cmdline() {
        Ok(cfg) => cfg,
        Err(e) => {
            println!("error: {}", e);
            process::exit(1);
        }
    };

    if let Err(e) = run(cfg) {
        println!("error: {}", e);
        process::exit(1);
    }
}

#[derive(Copy, Clone)]
struct Offsets {
    latest: i64,
    group: i64,
}

impl Default for Offsets {
    fn default() -> Self {
        Offsets { latest: -1, group: -1 }
    }
}

fn run(cfg: Config) -> Result<(), Error> {
    let mut client = KafkaClient::new(cfg.brokers);
    client.set_group_offset_storage(cfg.offset_storage);
    try!(client.load_metadata_all());

    let mut stdout = stdout();

    if cfg.topic.is_empty() {
        let ts = client.topics();
        let num_topics = ts.len();
        if num_topics == 0 {
            return Err(Error::Literal("no topics available".to_owned()));
        }
        let mut names: Vec<&str> = Vec::with_capacity(ts.len());
        names.extend(ts.names());
        names.sort();

        let mut buf = BufWriter::with_capacity(4096, stdout);
        for name in names {
            try!(write!(buf, "topic: {}\n", name));
        }
        return Err(Error::Literal("choose a topic".to_owned()));
    }

    let num_partitions = match client.topics().partitions(&cfg.topic) {
        None => return Err(Error::Literal(format!("no such topic: {}", &cfg.topic))),
        Some(partitions) => partitions.len(),
    };
    let mut offs = vec![Offsets::default(); num_partitions];
    let mut offs = offs.as_mut_slice();

    let mut fmt_buf = String::with_capacity(16);
    let mut out_buf = String::with_capacity(128);

    // ~ shall we produce the lag of the specified group?
    let show_lag = !cfg.group.is_empty();

    // ~ print a header
    {
        use std::fmt::Write;

        let _ = write!(out_buf, "{:>8} ", "time");
        for i in 0..num_partitions {
            fmt_buf.clear();
            let _ = write!(fmt_buf, "p-{}", i);
            let _ = write!(out_buf, " {:>10}", fmt_buf);
            if show_lag {
                let _ = write!(out_buf, " {:6}", "(lag)");
            }
        }
        if cfg.summary {
            let _ = write!(out_buf, " {:>12}", "summary");
            if show_lag {
                let _ = write!(out_buf, " {:8}", "(lag)");
            }
        }
        out_buf.push('\n');
        let _ = stdout.write_all(out_buf.as_bytes());
    }
    // ~ now periodically fetch the offsets and dump them to stdout
    loop {
        let t = time::now();
        // ~ latest topic offsets
        let latests = try!(client.fetch_topic_offsets(&cfg.topic, FetchOffset::Latest));
        for l in latests {
            let v = l.offset.unwrap_or(-1);
            offs.get_mut(l.partition as usize).expect("available partition").latest = v;
        }
        if show_lag {
            // ~ group offsets
            let groups = try!(client.fetch_group_topic_offsets(&cfg.group, &cfg.topic));
            for g in groups {
                let v = g.offset.unwrap_or(-1);
                offs.get_mut(g.partition as usize).expect("available partition").group = v;
            }
        }
        // ~ format the output
        {
            use std::fmt::Write;

            let mut total_latest = 0;
            let mut total_lag = 0;

            out_buf.clear();
            let _ = write!(out_buf, "{} ", t.strftime("%H:%M:%S").unwrap());
            for o in &*offs {
                total_latest += o.latest;
                let _ = write!(out_buf, " {:>10}", o.latest);
                if show_lag {
                    fmt_buf.clear();
                    if o.group < 0 {
                        let _ = write!(fmt_buf, "()");
                        total_lag = -1;
                    } else {
                        let mut lag = if cfg.commited_not_consumed {
                            o.latest - o.group
                        } else {
                            o.latest - o.group - 1
                        };
                        if lag < 0 {
                            // ~ it's quite likely that we fetch group offsets which
                            // are a bit ahead of the topic's latest offset since we
                            // issued the fetch-latest-offset request earlier than
                            // the request for the group-offsets
                            lag = 0;
                        }
                        if total_lag >= 0 {
                            total_lag += lag;
                        }
                        let _ = write!(fmt_buf, "({:<})", lag);
                    }
                    let _ = write!(out_buf, " {:6}", fmt_buf);
                }
            }
            if cfg.summary {
                let _ = write!(out_buf, " {:12}", total_latest);
                if show_lag {
                    fmt_buf.clear();
                    if total_lag >= 0 {
                        let _ = write!(fmt_buf, "({:<})", total_lag);
                    } else {
                        let _ = write!(fmt_buf, "()");
                    }
                    let _ = write!(out_buf, " {:8}", fmt_buf);
                }
            }
        }
        // ~ write the output
        out_buf.push('\n');
        let _ = stdout.write_all(out_buf.as_bytes());

        thread::sleep(cfg.period);
    }
}

// --------------------------------------------------------------------

struct Config {
    brokers: Vec<String>,
    topic: String,
    group: String,
    offset_storage: GroupOffsetStorage,
    period: stdtime::Duration,
    commited_not_consumed: bool,
    summary: bool,
}

impl Config {
    fn from_cmdline() -> Result<Config, Error> {
        let args: Vec<String> = env::args().collect();
        let mut opts = getopts::Options::new();
        opts.optflag("h", "help", "Print this help screen");
        opts.optopt("", "brokers", "Specify kafka bootstrap brokers (comma separated)", "HOSTS");
        opts.optopt("", "topic", "Specify the topic to monitor", "TOPIC");
        opts.optopt("", "group", "Specify the group to monitor", "GROUP");
        opts.optopt("", "offsets", "Specify offset store [zookeeper, kafka]", "STORE");
        opts.optopt("", "sleep", "Specify the sleep time", "SECS");
        opts.optflag("", "summary", "Print a summary for the topic");
        opts.optflag("", "committed-not-yet-consumed",
                     "Assume commited group offsets specify \
                      messages the group will start consuming \
                      (including those at these offsets)");

        let m = match opts.parse(&args[1..]) {
            Ok(m) => m,
            Err(e) => return Err(Error::Literal(e.to_string())),
        };
        if m.opt_present("help") {
            let brief = format!("{} [options]", args[0]);
            return Err(Error::Literal(opts.usage(&brief)));
        }
        let mut offset_storage = GroupOffsetStorage::Zookeeper;
        if let Some(s) = m.opt_str("offsets") {
            if s.eq_ignore_ascii_case("zookeeper") {
                offset_storage = GroupOffsetStorage::Zookeeper;
            } else if s.eq_ignore_ascii_case("kafka") {
                offset_storage = GroupOffsetStorage::Kafka;
            } else {
                return Err(Error::Literal(format!("unknown offset store: {}", s)));
            }
        }
        let mut period = stdtime::Duration::from_secs(5);
        if let Some(s) = m.opt_str("sleep") {
            match s.parse::<u64>() {
                Ok(n) if n != 0 => period = stdtime::Duration::from_secs(n),
                _ => return Err(Error::Literal(format!("not a number greater than zero: {}", s))),
            }
        }
        Ok(Config {
            brokers: m.opt_str("brokers")
                .unwrap_or_else(|| "localhost:9092".to_owned())
                .split(',')
                .map(|s| s.trim().to_owned())
                .collect(),
            topic: m.opt_str("topic")
                .unwrap_or_else(|| String::new()),
            group: m.opt_str("group")
                .unwrap_or_else(|| String::new()),
            offset_storage: offset_storage,
            period: period,
            commited_not_consumed: m.opt_present("committed-not-yet-consumed"),
            summary: m.opt_present("summary"),
        })
    }
}

// --------------------------------------------------------------------

enum Error {
    Kafka(kafka::error::Error),
    Io(io::Error),
    Literal(String),
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Error::Kafka(ref e) => write!(f, "kafka-error: {}", e),
            &Error::Io(ref e) => write!(f, "io-error: {}", e),
            &Error::Literal(ref s) => write!(f, "{}", s),
        }
    }
}

impl From<kafka::error::Error> for Error {
    fn from(e: kafka::error::Error) -> Self { Error::Kafka(e) }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self { Error::Io(e) }
}
<|endoftext|>


<commit_before>use beacon_chain::Bootstrapper;
use clap::ArgMatches;
use client::{BeaconChainStartMethod, ClientConfig, Eth2Config};
use eth2_config::{read_from_file, write_to_file};
use rand::{distributions::Alphanumeric, Rng};
use slog::{crit, info, warn, Logger};
use std::fs;
use std::path::{Path, PathBuf};

pub const DEFAULT_DATA_DIR: &str = ".lighthouse";
pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml";
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml";

type Result<T> = std::result::Result<T, String>;
type Config = (ClientConfig, Eth2Config);

/// Gets the fully-initialized global client and eth2 configuration objects.
pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<Config> {
    let mut builder = ConfigBuilder::new(cli_args, log)?;

    match cli_args.subcommand() {
        ("testnet", Some(sub_cmd_args)) => {
            process_testnet_subcommand(&mut builder, sub_cmd_args, log)?
        }
        // No sub-command assumes a resume operation.
        _ => {
            info!(
                log,
                "Resuming from existing datadir";
                "path" => format!("{:?}", builder.data_dir)
            );

            // If no primary subcommand was given, start the beacon chain from an existing
            // database.
            builder.set_beacon_chain_start_method(BeaconChainStartMethod::Resume);

            // Whilst there is no large testnet or mainnet force the user to specify how they want
            // to start a new chain (e.g., from a genesis YAML file, another node, etc).
            if !builder.data_dir.exists() {
                return Err(
                    "No datadir found. To start a new beacon chain, see `testnet --help`. \
                     Use `--datadir` to specify a different directory"
                        .into(),
                );
            }

            // If the `testnet` command was not provided, attempt to load an existing datadir and
            // continue with an existing chain.
            builder.load_from_datadir()?;
        }
    };

    builder.build(cli_args)
}

/// Process the `testnet` CLI subcommand arguments, updating the `builder`.
fn process_testnet_subcommand(
    builder: &mut ConfigBuilder,
    cli_args: &ArgMatches,
    log: &Logger,
) -> Result<()> {
    if cli_args.is_present("random-datadir") {
        builder.set_random_datadir()?;
    }

    if cli_args.is_present("force") {
        builder.clean_datadir()?;
    }

    if let Some(path_string) = cli_args.value_of("eth2-config") {
        let path = path_string
            .parse::<PathBuf>()
            .map_err(|e| format!("Unable to parse eth2-config path: {:?}", e))?;
        builder.load_eth2_config(path)?;
    } else {
        builder.update_spec_from_subcommand(&cli_args)?;
    }

    if let Some(path_string) = cli_args.value_of("client-config") {
        let path = path_string
            .parse::<PathBuf>()
            .map_err(|e| format!("Unable to parse client config path: {:?}", e))?;
        builder.load_client_config(path)?;
    }

    info!(
        log,
        "Creating new datadir";
        "path" => format!("{:?}", builder.data_dir)
    );

    // Start matching on the second subcommand (e.g., `testnet bootstrap ...`)
    match cli_args.subcommand() {
        ("bootstrap", Some(cli_args)) => {
            let server = cli_args
                .value_of("server")
                .ok_or_else(|| "No bootstrap server specified")?;
            let port: Option<u16> = cli_args
                .value_of("libp2p-port")
                .and_then(|s| s.parse::<u16>().ok());

            builder.import_bootstrap_libp2p_address(server, port)?;

            builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap {
                server: server.to_string(),
                port,
            })
        }
        ("recent", Some(cli_args)) => {
            let validator_count = cli_args
                .value_of("validator_count")
                .ok_or_else(|| "No validator_count specified")?
                .parse::<usize>()
                .map_err(|e| format!("Unable to parse validator_count: {:?}", e))?;

            builder.set_beacon_chain_start_method(BeaconChainStartMethod::RecentGenesis {
                validator_count,
            })
        }
        _ => return Err("No testnet method specified. See 'testnet --help'.".into()),
    };

    builder.write_configs_to_new_datadir()?;

    Ok(())
}

/// Allows for building a set of configurations based upon `clap` arguments.
struct ConfigBuilder<'a> {
    log: &'a Logger,
    pub data_dir: PathBuf,
    eth2_config: Eth2Config,
    client_config: ClientConfig,
}

impl<'a> ConfigBuilder<'a> {
    /// Create a new builder with default settings.
    pub fn new(cli_args: &'a ArgMatches, log: &'a Logger) -> Result<Self> {
        // Read the `--datadir` flag.
        //
        // If it's not present, try and find the home directory (`~`) and push the default data
        // directory onto it.
        let data_dir: PathBuf = cli_args
            .value_of("datadir")
            .map(|string| PathBuf::from(string))
            .or_else(|| {
                dirs::home_dir().map(|mut home| {
                    home.push(DEFAULT_DATA_DIR);
                    home
                })
            })
            .ok_or_else(|| "Unable to find a home directory for the datadir".to_string())?;

        Ok(Self {
            log,
            data_dir,
            eth2_config: Eth2Config::minimal(),
            client_config: ClientConfig::default(),
        })
    }

    /// Clears any configuration files that would interfere with writing new configs.
    ///
    /// Moves the following files in `data_dir` into a backup directory:
    ///
    /// - Client config
    /// - Eth2 config
    /// - The entire database directory
    pub fn clean_datadir(&mut self) -> Result<()> {
        let backup_dir = {
            let mut s = String::from("backup_");
            s.push_str(&random_string(6));
            self.data_dir.join(s)
        };

        fs::create_dir_all(&backup_dir)
            .map_err(|e| format!("Unable to create config backup dir: {:?}", e))?;

        let move_to_backup_dir = |path: &Path| -> Result<()> {
            let file_name = path
                .file_name()
                .ok_or_else(|| "Invalid path found during datadir clean (no filename).")?;

            let mut new = path.to_path_buf();
            new.pop();
            new.push(backup_dir.clone());
            new.push(file_name);

            let _ = fs::rename(path, new);

            Ok(())
        };

        move_to_backup_dir(&self.data_dir.join(CLIENT_CONFIG_FILENAME))?;
        move_to_backup_dir(&self.data_dir.join(ETH2_CONFIG_FILENAME))?;

        if let Some(db_path) = self.client_config.db_path() {
            move_to_backup_dir(&db_path)?;
        }

        Ok(())
    }

    /// Sets the method for starting the beacon chain.
    pub fn set_beacon_chain_start_method(&mut self, method: BeaconChainStartMethod) {
        self.client_config.beacon_chain_start_method = method;
    }

    /// Import the libp2p address for `server` into the list of bootnodes in `self`.
    ///
    /// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`,
    /// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port.
    pub fn import_bootstrap_libp2p_address(
        &mut self,
        server: &str,
        port: Option<u16>,
    ) -> Result<()> {
        let bootstrapper = Bootstrapper::from_server_string(server.to_string())?;

        if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) {
            info!(
                self.log,
                "Estimated bootstrapper libp2p address";
                "multiaddr" => format!("{:?}", server_multiaddr)
            );

            self.client_config
                .network
                .libp2p_nodes
                .push(server_multiaddr);
        } else {
            warn!(
                self.log,
                "Unable to estimate a bootstrapper libp2p address, this node may not find any peers."
            );
        };

        Ok(())
    }

    /// Set the config data_dir to be an random directory.
    ///
    /// Useful for easily spinning up ephemeral testnets.
    pub fn set_random_datadir(&mut self) -> Result<()> {
        let mut s = DEFAULT_DATA_DIR.to_string();
        s.push_str("_random_");
        s.push_str(&random_string(6));

        self.data_dir.pop();
        self.data_dir.push(s);

        Ok(())
    }

    /// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag.
    ///
    /// Returns an error if the `--spec` flag is not present in the given `cli_args`.
    pub fn update_spec_from_subcommand(&mut self, cli_args: &ArgMatches) -> Result<()> {
        // Re-initialise the `Eth2Config`.
        //
        // If a CLI parameter is set, overwrite any config file present.
        // If a parameter is not set, use either the config file present or default to minimal.
        let eth2_config = match cli_args.value_of("spec") {
            Some("mainnet") => Eth2Config::mainnet(),
            Some("minimal") => Eth2Config::minimal(),
            Some("interop") => Eth2Config::interop(),
            _ => return Err("Unable to determine specification type.".into()),
        };

        self.client_config.spec_constants = cli_args
            .value_of("spec")
            .expect("Guarded by prior match statement")
            .to_string();
        self.eth2_config = eth2_config;

        Ok(())
    }

    /// Writes the configs in `self` to `self.data_dir`.
    ///
    /// Returns an error if `self.data_dir` already exists.
    pub fn write_configs_to_new_datadir(&mut self) -> Result<()> {
        let db_exists = self
            .client_config
            .db_path()
            .map(|d| d.exists())
            .unwrap_or_else(|| false);

        // Do not permit creating a new config when the datadir exists.
        if db_exists {
            return Err("Database already exists. See `-f` in `testnet --help`".into());
        }

        // Create `datadir` and any non-existing parent directories.
        fs::create_dir_all(&self.data_dir).map_err(|e| {
            crit!(self.log, "Failed to initialize data dir"; "error" => format!("{}", e));
            format!("{}", e)
        })?;

        let client_config_file = self.data_dir.join(CLIENT_CONFIG_FILENAME);
        if client_config_file.exists() {
            return Err(format!(
                "Datadir is not clean, {} exists. See `-f` in `testnet --help`.",
                CLIENT_CONFIG_FILENAME
            ));
        } else {
            // Write the onfig to a TOML file in the datadir.
            write_to_file(
                self.data_dir.join(CLIENT_CONFIG_FILENAME),
                &self.client_config,
            )
            .map_err(|e| format!("Unable to write {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?;
        }

        let eth2_config_file = self.data_dir.join(ETH2_CONFIG_FILENAME);
        if eth2_config_file.exists() {
            return Err(format!(
                "Datadir is not clean, {} exists. See `-f` in `testnet --help`.",
                ETH2_CONFIG_FILENAME
            ));
        } else {
            // Write the config to a TOML file in the datadir.
            write_to_file(self.data_dir.join(ETH2_CONFIG_FILENAME), &self.eth2_config)
                .map_err(|e| format!("Unable to write {} file: {:?}", ETH2_CONFIG_FILENAME, e))?;
        }

        Ok(())
    }

    /// Attempts to load the client and eth2 configs from `self.data_dir`.
    ///
    /// Returns an error if any files are not found or are invalid.
    pub fn load_from_datadir(&mut self) -> Result<()> {
        // Check to ensure the datadir exists.
        //
        // For now we return an error. In the future we may decide to boot a default (e.g.,
        // public testnet or mainnet).
        if !self.data_dir.exists() {
            return Err(
                "No datadir found. Either create a new testnet or specify a different `--datadir`."
                    .into(),
            );
        }

        // If there is a path to a databse in the config, ensure it exists.
        if !self
            .client_config
            .db_path()
            .map(|path| path.exists())
            .unwrap_or_else(|| true)
        {
            return Err(
                "No database found in datadir. Use 'testnet -f' to overwrite the existing \
                 datadir, or specify a different `--datadir`."
                    .into(),
            );
        }

        self.load_eth2_config(self.data_dir.join(ETH2_CONFIG_FILENAME))?;
        self.load_client_config(self.data_dir.join(CLIENT_CONFIG_FILENAME))?;

        Ok(())
    }

    /// Attempts to load the client config from `path`.
    ///
    /// Returns an error if any files are not found or are invalid.
    pub fn load_client_config(&mut self, path: PathBuf) -> Result<()> {
        self.client_config = read_from_file::<ClientConfig>(path)
            .map_err(|e| format!("Unable to parse ClientConfig file: {:?}", e))?
            .ok_or_else(|| "ClientConfig file does not exist".to_string())?;

        Ok(())
    }

    /// Attempts to load the eth2 config from `path`.
    ///
    /// Returns an error if any files are not found or are invalid.
    pub fn load_eth2_config(&mut self, path: PathBuf) -> Result<()> {
        self.eth2_config = read_from_file::<Eth2Config>(path)
            .map_err(|e| format!("Unable to parse Eth2Config file: {:?}", e))?
            .ok_or_else(|| "Eth2Config file does not exist".to_string())?;

        Ok(())
    }

    /// Consumes self, returning the configs.
    ///
    /// The supplied `cli_args` should be the base-level `clap` cli_args (i.e., not a subcommand
    /// cli_args).
    pub fn build(mut self, cli_args: &ArgMatches) -> Result<Config> {
        self.eth2_config.apply_cli_args(cli_args)?;
        self.client_config
            .apply_cli_args(cli_args, &mut self.log.clone())?;

        if self.eth2_config.spec_constants != self.client_config.spec_constants {
            crit!(self.log, "Specification constants do not match.";
                  "client_config" => format!("{}", self.client_config.spec_constants),
                  "eth2_config" => format!("{}", self.eth2_config.spec_constants)
            );
            return Err("Specification constant mismatch".into());
        }

        self.client_config.data_dir = self.data_dir;

        Ok((self.client_config, self.eth2_config))
    }
}

fn random_string(len: usize) -> String {
    rand::thread_rng()
        .sample_iter(&Alphanumeric)
        .take(len)
        .collect::<String>()
}
<commit_msg>Improve CLI error messages<commit_after>use beacon_chain::Bootstrapper;
use clap::ArgMatches;
use client::{BeaconChainStartMethod, ClientConfig, Eth2Config};
use eth2_config::{read_from_file, write_to_file};
use rand::{distributions::Alphanumeric, Rng};
use slog::{crit, info, warn, Logger};
use std::fs;
use std::path::{Path, PathBuf};

pub const DEFAULT_DATA_DIR: &str = ".lighthouse";
pub const CLIENT_CONFIG_FILENAME: &str = "beacon-node.toml";
pub const ETH2_CONFIG_FILENAME: &str = "eth2-spec.toml";

type Result<T> = std::result::Result<T, String>;
type Config = (ClientConfig, Eth2Config);

/// Gets the fully-initialized global client and eth2 configuration objects.
pub fn get_configs(cli_args: &ArgMatches, log: &Logger) -> Result<Config> {
    let mut builder = ConfigBuilder::new(cli_args, log)?;

    match cli_args.subcommand() {
        ("testnet", Some(sub_cmd_args)) => {
            process_testnet_subcommand(&mut builder, sub_cmd_args, log)?
        }
        // No sub-command assumes a resume operation.
        _ => {
            info!(
                log,
                "Resuming from existing datadir";
                "path" => format!("{:?}", builder.data_dir)
            );

            // If no primary subcommand was given, start the beacon chain from an existing
            // database.
            builder.set_beacon_chain_start_method(BeaconChainStartMethod::Resume);

            // Whilst there is no large testnet or mainnet force the user to specify how they want
            // to start a new chain (e.g., from a genesis YAML file, another node, etc).
            if !builder.data_dir.exists() {
                return Err(
                    "No datadir found. To start a new beacon chain, see `testnet --help`. \
                     Use `--datadir` to specify a different directory"
                        .into(),
                );
            }

            // If the `testnet` command was not provided, attempt to load an existing datadir and
            // continue with an existing chain.
            builder.load_from_datadir()?;
        }
    };

    builder.build(cli_args)
}

/// Process the `testnet` CLI subcommand arguments, updating the `builder`.
fn process_testnet_subcommand(
    builder: &mut ConfigBuilder,
    cli_args: &ArgMatches,
    log: &Logger,
) -> Result<()> {
    if cli_args.is_present("random-datadir") {
        builder.set_random_datadir()?;
    }

    if cli_args.is_present("force") {
        builder.clean_datadir()?;
    }

    if let Some(path_string) = cli_args.value_of("eth2-config") {
        let path = path_string
            .parse::<PathBuf>()
            .map_err(|e| format!("Unable to parse eth2-config path: {:?}", e))?;
        builder.load_eth2_config(path)?;
    } else {
        builder.update_spec_from_subcommand(&cli_args)?;
    }

    if let Some(path_string) = cli_args.value_of("client-config") {
        let path = path_string
            .parse::<PathBuf>()
            .map_err(|e| format!("Unable to parse client config path: {:?}", e))?;
        builder.load_client_config(path)?;
    }

    info!(
        log,
        "Creating new datadir";
        "path" => format!("{:?}", builder.data_dir)
    );

    // Start matching on the second subcommand (e.g., `testnet bootstrap ...`)
    match cli_args.subcommand() {
        ("bootstrap", Some(cli_args)) => {
            let server = cli_args
                .value_of("server")
                .ok_or_else(|| "No bootstrap server specified")?;
            let port: Option<u16> = cli_args
                .value_of("libp2p-port")
                .and_then(|s| s.parse::<u16>().ok());

            builder.import_bootstrap_libp2p_address(server, port)?;

            builder.set_beacon_chain_start_method(BeaconChainStartMethod::HttpBootstrap {
                server: server.to_string(),
                port,
            })
        }
        ("recent", Some(cli_args)) => {
            let validator_count = cli_args
                .value_of("validator_count")
                .ok_or_else(|| "No validator_count specified")?
                .parse::<usize>()
                .map_err(|e| format!("Unable to parse validator_count: {:?}", e))?;

            builder.set_beacon_chain_start_method(BeaconChainStartMethod::RecentGenesis {
                validator_count,
            })
        }
        _ => return Err("No testnet method specified. See 'testnet --help'.".into()),
    };

    builder.write_configs_to_new_datadir()?;

    Ok(())
}

/// Allows for building a set of configurations based upon `clap` arguments.
struct ConfigBuilder<'a> {
    log: &'a Logger,
    pub data_dir: PathBuf,
    eth2_config: Eth2Config,
    client_config: ClientConfig,
}

impl<'a> ConfigBuilder<'a> {
    /// Create a new builder with default settings.
    pub fn new(cli_args: &'a ArgMatches, log: &'a Logger) -> Result<Self> {
        // Read the `--datadir` flag.
        //
        // If it's not present, try and find the home directory (`~`) and push the default data
        // directory onto it.
        let data_dir: PathBuf = cli_args
            .value_of("datadir")
            .map(|string| PathBuf::from(string))
            .or_else(|| {
                dirs::home_dir().map(|mut home| {
                    home.push(DEFAULT_DATA_DIR);
                    home
                })
            })
            .ok_or_else(|| "Unable to find a home directory for the datadir".to_string())?;

        Ok(Self {
            log,
            data_dir,
            eth2_config: Eth2Config::minimal(),
            client_config: ClientConfig::default(),
        })
    }

    /// Clears any configuration files that would interfere with writing new configs.
    ///
    /// Moves the following files in `data_dir` into a backup directory:
    ///
    /// - Client config
    /// - Eth2 config
    /// - The entire database directory
    pub fn clean_datadir(&mut self) -> Result<()> {
        let backup_dir = {
            let mut s = String::from("backup_");
            s.push_str(&random_string(6));
            self.data_dir.join(s)
        };

        fs::create_dir_all(&backup_dir)
            .map_err(|e| format!("Unable to create config backup dir: {:?}", e))?;

        let move_to_backup_dir = |path: &Path| -> Result<()> {
            let file_name = path
                .file_name()
                .ok_or_else(|| "Invalid path found during datadir clean (no filename).")?;

            let mut new = path.to_path_buf();
            new.pop();
            new.push(backup_dir.clone());
            new.push(file_name);

            let _ = fs::rename(path, new);

            Ok(())
        };

        move_to_backup_dir(&self.data_dir.join(CLIENT_CONFIG_FILENAME))?;
        move_to_backup_dir(&self.data_dir.join(ETH2_CONFIG_FILENAME))?;

        if let Some(db_path) = self.client_config.db_path() {
            move_to_backup_dir(&db_path)?;
        }

        Ok(())
    }

    /// Sets the method for starting the beacon chain.
    pub fn set_beacon_chain_start_method(&mut self, method: BeaconChainStartMethod) {
        self.client_config.beacon_chain_start_method = method;
    }

    /// Import the libp2p address for `server` into the list of bootnodes in `self`.
    ///
    /// If `port` is `Some`, it is used as the port for the `Multiaddr`. If `port` is `None`,
    /// attempts to connect to the `server` via HTTP and retrieve it's libp2p listen port.
    pub fn import_bootstrap_libp2p_address(
        &mut self,
        server: &str,
        port: Option<u16>,
    ) -> Result<()> {
        let bootstrapper = Bootstrapper::from_server_string(server.to_string())?;

        if let Some(server_multiaddr) = bootstrapper.best_effort_multiaddr(port) {
            info!(
                self.log,
                "Estimated bootstrapper libp2p address";
                "multiaddr" => format!("{:?}", server_multiaddr)
            );

            self.client_config
                .network
                .libp2p_nodes
                .push(server_multiaddr);
        } else {
            warn!(
                self.log,
                "Unable to estimate a bootstrapper libp2p address, this node may not find any peers."
            );
        };

        Ok(())
    }

    /// Set the config data_dir to be an random directory.
    ///
    /// Useful for easily spinning up ephemeral testnets.
    pub fn set_random_datadir(&mut self) -> Result<()> {
        let mut s = DEFAULT_DATA_DIR.to_string();
        s.push_str("_random_");
        s.push_str(&random_string(6));

        self.data_dir.pop();
        self.data_dir.push(s);

        Ok(())
    }

    /// Reads the subcommand and tries to update `self.eth2_config` based up on the `--spec` flag.
    ///
    /// Returns an error if the `--spec` flag is not present in the given `cli_args`.
    pub fn update_spec_from_subcommand(&mut self, cli_args: &ArgMatches) -> Result<()> {
        // Re-initialise the `Eth2Config`.
        //
        // If a CLI parameter is set, overwrite any config file present.
        // If a parameter is not set, use either the config file present or default to minimal.
        let eth2_config = match cli_args.value_of("spec") {
            Some("mainnet") => Eth2Config::mainnet(),
            Some("minimal") => Eth2Config::minimal(),
            Some("interop") => Eth2Config::interop(),
            _ => return Err("Unable to determine specification type.".into()),
        };

        self.client_config.spec_constants = cli_args
            .value_of("spec")
            .expect("Guarded by prior match statement")
            .to_string();
        self.eth2_config = eth2_config;

        Ok(())
    }

    /// Writes the configs in `self` to `self.data_dir`.
    ///
    /// Returns an error if `self.data_dir` already exists.
    pub fn write_configs_to_new_datadir(&mut self) -> Result<()> {
        let db_exists = self
            .client_config
            .db_path()
            .map(|d| d.exists())
            .unwrap_or_else(|| false);

        // Do not permit creating a new config when the datadir exists.
        if db_exists {
            return Err("Database already exists. See `-f` in `testnet --help`".into());
        }

        // Create `datadir` and any non-existing parent directories.
        fs::create_dir_all(&self.data_dir).map_err(|e| {
            crit!(self.log, "Failed to initialize data dir"; "error" => format!("{}", e));
            format!("{}", e)
        })?;

        let client_config_file = self.data_dir.join(CLIENT_CONFIG_FILENAME);
        if client_config_file.exists() {
            return Err(format!(
                "Datadir is not clean, {} exists. See `-f` in `testnet --help`.",
                CLIENT_CONFIG_FILENAME
            ));
        } else {
            // Write the onfig to a TOML file in the datadir.
            write_to_file(
                self.data_dir.join(CLIENT_CONFIG_FILENAME),
                &self.client_config,
            )
            .map_err(|e| format!("Unable to write {} file: {:?}", CLIENT_CONFIG_FILENAME, e))?;
        }

        let eth2_config_file = self.data_dir.join(ETH2_CONFIG_FILENAME);
        if eth2_config_file.exists() {
            return Err(format!(
                "Datadir is not clean, {} exists. See `-f` in `testnet --help`.",
                ETH2_CONFIG_FILENAME
            ));
        } else {
            // Write the config to a TOML file in the datadir.
            write_to_file(self.data_dir.join(ETH2_CONFIG_FILENAME), &self.eth2_config)
                .map_err(|e| format!("Unable to write {} file: {:?}", ETH2_CONFIG_FILENAME, e))?;
        }

        Ok(())
    }

    /// Attempts to load the client and eth2 configs from `self.data_dir`.
    ///
    /// Returns an error if any files are not found or are invalid.
    pub fn load_from_datadir(&mut self) -> Result<()> {
        // Check to ensure the datadir exists.
        //
        // For now we return an error. In the future we may decide to boot a default (e.g.,
        // public testnet or mainnet).
        if !self.data_dir.exists() {
            return Err(
                "No datadir found. Either create a new testnet or specify a different `--datadir`."
                    .into(),
            );
        }

        // If there is a path to a databse in the config, ensure it exists.
        if !self
            .client_config
            .db_path()
            .map(|path| path.exists())
            .unwrap_or_else(|| true)
        {
            return Err(
                "No database found in datadir. Use 'testnet -f' to overwrite the existing \
                 datadir, or specify a different `--datadir`."
                    .into(),
            );
        }

        self.load_eth2_config(self.data_dir.join(ETH2_CONFIG_FILENAME))?;
        self.load_client_config(self.data_dir.join(CLIENT_CONFIG_FILENAME))?;

        Ok(())
    }

    /// Attempts to load the client config from `path`.
    ///
    /// Returns an error if any files are not found or are invalid.
    pub fn load_client_config(&mut self, path: PathBuf) -> Result<()> {
        self.client_config = read_from_file::<ClientConfig>(path.clone())
            .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))?
            .ok_or_else(|| format!("{:?} file does not exist", path))?;

        Ok(())
    }

    /// Attempts to load the eth2 config from `path`.
    ///
    /// Returns an error if any files are not found or are invalid.
    pub fn load_eth2_config(&mut self, path: PathBuf) -> Result<()> {
        self.eth2_config = read_from_file::<Eth2Config>(path.clone())
            .map_err(|e| format!("Unable to parse {:?} file: {:?}", path, e))?
            .ok_or_else(|| format!("{:?} file does not exist", path))?;

        Ok(())
    }

    /// Consumes self, returning the configs.
    ///
    /// The supplied `cli_args` should be the base-level `clap` cli_args (i.e., not a subcommand
    /// cli_args).
    pub fn build(mut self, cli_args: &ArgMatches) -> Result<Config> {
        self.eth2_config.apply_cli_args(cli_args)?;
        self.client_config
            .apply_cli_args(cli_args, &mut self.log.clone())?;

        if self.eth2_config.spec_constants != self.client_config.spec_constants {
            crit!(self.log, "Specification constants do not match.";
                  "client_config" => format!("{}", self.client_config.spec_constants),
                  "eth2_config" => format!("{}", self.eth2_config.spec_constants)
            );
            return Err("Specification constant mismatch".into());
        }

        self.client_config.data_dir = self.data_dir;

        Ok((self.client_config, self.eth2_config))
    }
}

fn random_string(len: usize) -> String {
    rand::thread_rng()
        .sample_iter(&Alphanumeric)
        .take(len)
        .collect::<String>()
}
<|endoftext|>


<commit_before>extern crate gl;
extern crate sdl2;
extern crate yaglw;

use gl::types::*;
use sdl2::event::{Event, EventPump};
use std::mem;

use yaglw::gl_context::GLContext;
use yaglw::shader::Shader;
use yaglw::vertex_buffer::{GLArray, GLBuffer, GLType, VertexAttribData, DrawMode};

#[repr(C)]
#[derive(Copy, Clone)]
struct Vertex {
  pub position: [GLfloat; 2],
  pub color: [GLfloat; 3],
}

const VERTEX_SHADER: &'static str = "
  #version 130

  in vec2 position;
  in vec3 color;

  out vec3 v_color;

  void main() {
    v_color = color;
    gl_Position = vec4(position, 0, 1);
  }
";

const FRAGMENT_SHADER: &'static str = "
  #version 130

  in vec3 v_color;

  void main() {
    gl_FragColor = vec4(v_color, 1.0);
  }
";

pub fn main() {
  let sdl = sdl2::init(sdl2::INIT_EVERYTHING).unwrap();
  let window = make_window(&sdl);
  let mut event_pump = sdl.event_pump();

  let _sdl_gl_context = window.gl_create_context().unwrap();

  // Load the OpenGL function pointers.
  gl::load_with(|s| unsafe {
    mem::transmute(sdl2::video::gl_get_proc_address(s))
  });

  let mut gl = unsafe {
    GLContext::new()
  };

  let vertices = [
    Vertex {
      position: [0.0, 0.5],
      color: [1.0, 0.0, 0.0],
    },
    Vertex {
      position: [-0.5, -0.5],
      color: [0.0, 1.0, 0.0],
    },
    Vertex {
      position: [0.5, -0.5],
      color: [0.0, 0.0, 1.0],
    },
  ];

  let mut vbo = GLBuffer::new(&mut gl, 3);
  vbo.push(&mut gl, &vertices);

  let attribs = [
    VertexAttribData {
      name: "position",
      size: 2,
      unit: GLType::Float,
    },
    VertexAttribData {
      name: "color",
      size: 3,
      unit: GLType::Float,
    },
  ];

  let components = [
    ((gl::VERTEX_SHADER, VERTEX_SHADER)),
    ((gl::FRAGMENT_SHADER, FRAGMENT_SHADER)),
  ];

  let shader = Shader::new(&gl, components.iter().map(|&(ty, s)| (ty, String::from(s))));
  shader.use_shader(&mut gl);

  let vao =
    GLArray::new(
      &mut gl,
      &shader,
      &attribs,
      DrawMode::Triangles,
      vbo,
    );
  vao.bind(&mut gl);

  match gl.get_error() {
    gl::NO_ERROR => {},
    err => {
      println!("OpenGL error 0x{:x} in setup", err);
      return;
    },
  }

  while !quit_event(&mut event_pump) {
    gl.clear_buffer();
    vao.draw(&mut gl);
    // swap buffers
    window.gl_swap_window();

    std::thread::sleep_ms(10);
  }
}

fn make_window(sdl: &sdl2::Sdl) -> sdl2::video::Window {
  sdl2::video::gl_set_attribute(sdl2::video::GLAttr::GLContextMajorVersion, 3);
  sdl2::video::gl_set_attribute(sdl2::video::GLAttr::GLContextMinorVersion, 0);
  sdl2::video::gl_set_attribute(
    sdl2::video::GLAttr::GLContextProfileMask,
    sdl2::video::GLProfile::GLCoreProfile as i32,
  );

  sdl2::video::Window::new(
    sdl,
    "Triangle",
    sdl2::video::WindowPos::PosCentered,
    sdl2::video::WindowPos::PosCentered,
    800,
    600,
    sdl2::video::OPENGL,
  ).unwrap()
}

fn quit_event(event_pump: &mut EventPump) -> bool {
  loop {
    match event_pump.poll_event() {
      None => {
        return false;
      },
      Some(Event::Quit{..}) => {
        return true;
      }
      Some(Event::AppTerminating{..}) => {
        return true;
      }
      _ => {},
    }
  }
}
<commit_msg>cargo update<commit_after>extern crate gl;
extern crate sdl2;
extern crate yaglw;

use gl::types::*;
use sdl2::event::{Event, EventPump};
use std::mem;

use yaglw::gl_context::GLContext;
use yaglw::shader::Shader;
use yaglw::vertex_buffer::{GLArray, GLBuffer, GLType, VertexAttribData, DrawMode};

#[repr(C)]
#[derive(Copy, Clone)]
struct Vertex {
  pub position: [GLfloat; 2],
  pub color: [GLfloat; 3],
}

const VERTEX_SHADER: &'static str = "
  #version 130

  in vec2 position;
  in vec3 color;

  out vec3 v_color;

  void main() {
    v_color = color;
    gl_Position = vec4(position, 0, 1);
  }
";

const FRAGMENT_SHADER: &'static str = "
  #version 130

  in vec3 v_color;

  void main() {
    gl_FragColor = vec4(v_color, 1.0);
  }
";

pub fn main() {
  let mut sdl = sdl2::init().everything().build().unwrap();

  let window = make_window(&sdl);
  let mut event_pump = sdl.event_pump();

  let _sdl_gl_context = window.gl_create_context().unwrap();

  // Load the OpenGL function pointers.
  gl::load_with(|s| unsafe {
    mem::transmute(sdl2::video::gl_get_proc_address(s))
  });

  let mut gl = unsafe {
    GLContext::new()
  };

  let vertices = [
    Vertex {
      position: [0.0, 0.5],
      color: [1.0, 0.0, 0.0],
    },
    Vertex {
      position: [-0.5, -0.5],
      color: [0.0, 1.0, 0.0],
    },
    Vertex {
      position: [0.5, -0.5],
      color: [0.0, 0.0, 1.0],
    },
  ];

  let mut vbo = GLBuffer::new(&mut gl, 3);
  vbo.push(&mut gl, &vertices);

  let attribs = [
    VertexAttribData {
      name: "position",
      size: 2,
      unit: GLType::Float,
    },
    VertexAttribData {
      name: "color",
      size: 3,
      unit: GLType::Float,
    },
  ];

  let components = [
    ((gl::VERTEX_SHADER, VERTEX_SHADER)),
    ((gl::FRAGMENT_SHADER, FRAGMENT_SHADER)),
  ];

  let shader = Shader::new(&gl, components.iter().map(|&(ty, s)| (ty, String::from(s))));
  shader.use_shader(&mut gl);

  let vao =
    GLArray::new(
      &mut gl,
      &shader,
      &attribs,
      DrawMode::Triangles,
      vbo,
    );
  vao.bind(&mut gl);

  match gl.get_error() {
    gl::NO_ERROR => {},
    err => {
      println!("OpenGL error 0x{:x} in setup", err);
      return;
    },
  }

  while !quit_event(&mut event_pump) {
    gl.clear_buffer();
    vao.draw(&mut gl);
    // swap buffers
    window.gl_swap_window();

    std::thread::sleep_ms(10);
  }
}

fn make_window(sdl: &sdl2::Sdl) -> sdl2::video::Window {
  sdl2::video::gl_attr::set_context_profile(sdl2::video::GLProfile::Core);
  sdl2::video::gl_attr::set_context_version(3, 3);

  // Open the window as fullscreen at the current resolution.
  let mut window =
    sdl2::video::WindowBuilder::new(
      &sdl,
      "Triangle",
      800, 600,
    );

  let window = window.position(0, 0);
  window.opengl();

  window.build().unwrap()
}

fn quit_event(event_pump: &mut EventPump) -> bool {
  loop {
    match event_pump.poll_event() {
      None => {
        return false;
      },
      Some(Event::Quit{..}) => {
        return true;
      }
      Some(Event::AppTerminating{..}) => {
        return true;
      }
      _ => {},
    }
  }
}
<|endoftext|>


<commit_before>pub mod v1 {
    pub enum Entity {
        Account,
        Application,
        Attachment,
        Card,
        Context,
        Error,
        Instance,
        Mention,
        Notification,
        Relationship,
        Results,
        Status,
        Tag
    }
    pub enum Method {
        Accounts,
        Apps,
        Blocks,
        Favorites,
        FollowRequests,
        Follows,
        Instances,
        Media,
        Mutes,
        Notifications,
        Reports,
        Search,
        Statuses,
        Timelines
    }

    pub struct Account {
        pub id: i32,
        pub username: String,
        pub acct: String,
        pub display_name: String,
        pub note: String,
        pub url: String,
        pub avatar: String,
        pub header: String,
        pub locked: bool,
        pub created_at: String,
        pub followers_count: i32,
        pub following_count: i32,
        pub statuses_count: i32
    }

    pub struct Application {
        pub name: String,
        pub website: Option<String>
    }

    pub struct Attachment {
        pub id: i32,
        pub attachment_type: String,
        pub url: String,
        pub remote_url: String,
        pub preview_url: String,
        pub text_url: String

    }

    pub struct Card {
        pub url: String,
        pub title: String,
        pub descrption: String,
        pub image: String,
    }

    pub struct Context {
        pub ancestors: Vec<Status>,
        pub descendants: Vec<Status>
    }

    pub struct Error {
        error: String
    }

    pub struct Instance {
        url: String,
        title: String,
        description: String,
        email: String
    }

    pub struct Mention {
        id: i32,
        url: String,
        username: String,
        acct: String
    }

    pub struct Notification {
        id: i32,
        notification_type: String,
        created_at: String,
        account: Account,
        status: Option<Status>
    }

    pub struct Relationship {
        id: i32,
        following: bool,
        followed_by: bool,
        blocking: bool,
        muting: bool,
        requested: bool
    }

    pub struct Report {
        id: i32,
        action_taken: String,
    }

    pub struct Results {
        accounts: Vec<Account>,
        statuses: Vec<Status>,
        hashtags: Vec<String>
    }

    pub struct Status {
        id: i32,
        uri: String,
        url: String,
        account: Account,
        in_reply_to_id: Option<i32>,
        in_reply_to_account_id: Option<i32>,
        reblog: Option<Box<Status>>,
        content: String,
        created_at: String,
        reblogs_count: i32,
        favourites_count: i32,
        reblogged: bool,
        favourited: bool,
        sensitive: bool,
        spoiler_text: String,
        visibility: String,
        media_attachments: Vec<Attachment>,
        mentions: Vec<Mention>,
        tags: Vec<Tag>,
        application: Application
    }

    pub struct Tag {
        name: String,
        url: String
    }
}
<commit_msg>all attributes are private until further notice<commit_after>pub mod v1 {
    pub enum Entity {
        Account,
        Application,
        Attachment,
        Card,
        Context,
        Error,
        Instance,
        Mention,
        Notification,
        Relationship,
        Results,
        Status,
        Tag
    }
    pub enum Method {
        Accounts,
        Apps,
        Blocks,
        Favorites,
        FollowRequests,
        Follows,
        Instances,
        Media,
        Mutes,
        Notifications,
        Reports,
        Search,
        Statuses,
        Timelines
    }

    pub struct Account {
        id: i32,
        username: String,
        acct: String,
        display_name: String,
        note: String,
        url: String,
        avatar: String,
        header: String,
        locked: bool,
        created_at: String,
        followers_count: i32,
        following_count: i32,
        statuses_count: i32
    }

    pub struct Application {
        name: String,
        website: Option<String>
    }

    pub struct Attachment {
        id: i32,
        attachment_type: String,
        url: String,
        remote_url: String,
        preview_url: String,
        text_url: String

    }

    pub struct Card {
        url: String,
        title: String,
        descrption: String,
        image: String,
    }

    pub struct Context {
        ancestors: Vec<Status>,
        descendants: Vec<Status>
    }

    pub struct Error {
        error: String
    }

    pub struct Instance {
        url: String,
        title: String,
        description: String,
        email: String
    }

    pub struct Mention {
        id: i32,
        url: String,
        username: String,
        acct: String
    }

    pub struct Notification {
        id: i32,
        notification_type: String,
        created_at: String,
        account: Account,
        status: Option<Status>
    }

    pub struct Relationship {
        id: i32,
        following: bool,
        followed_by: bool,
        blocking: bool,
        muting: bool,
        requested: bool
    }

    pub struct Report {
        id: i32,
        action_taken: String,
    }

    pub struct Results {
        accounts: Vec<Account>,
        statuses: Vec<Status>,
        hashtags: Vec<String>
    }

    pub struct Status {
        id: i32,
        uri: String,
        url: String,
        account: Account,
        in_reply_to_id: Option<i32>,
        in_reply_to_account_id: Option<i32>,
        reblog: Option<Box<Status>>,
        content: String,
        created_at: String,
        reblogs_count: i32,
        favourites_count: i32,
        reblogged: bool,
        favourited: bool,
        sensitive: bool,
        spoiler_text: String,
        visibility: String,
        media_attachments: Vec<Attachment>,
        mentions: Vec<Mention>,
        tags: Vec<Tag>,
        application: Application
    }

    pub struct Tag {
        name: String,
        url: String
    }
}
<|endoftext|>


<commit_before>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use devices::*;
use requests::*;
use values::Value;

/// An error produced by one of the APIs in this module.
pub enum Error {
    /// There is no such node connected to the Foxbox, even indirectly.
    NoSuchNode(NodeId),

    /// There is no such service connected to the Foxbox, even indirectly.
    NoSuchService(ServiceId),

    /// Attempting to set a value with the wrong type
    TypeError,
}

/// An event during watching.
pub enum WatchEvent {
    /// A new value is available.
    Value(Value),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was connected or
    /// disconnected. Value `SetChanged(n)` means that the set now
    /// holds `n` input services.
    SetChanged(usize),
}

/// The public API.
pub trait API {
    /// Get a list of nodes matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/node/list`
    fn get_nodes(&self, &NodeRequest) -> Vec<Node>;

    /// Add a tag to an existing node.
    ///
    /// Tags can be used to locate nodes.
    ///
    /// # REST API
    ///
    /// `PUT /api/v1/node/tag/$NodeId`
    fn put_node_tag(&self, &NodeId, String) -> Result<(), Error>;

    /// Add a tag to an existing node.
    ///
    /// Tags can be used to locate nodes.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/node/tag/$NodeId`
    fn delete_node_tag(&self, &NodeId, String) -> Result<(), Error>;

    /// Get a list of inputs matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/service/list`
    fn get_input_services(&self, &InputRequest) -> Vec<Service<Input>>;
    fn get_output_services(&self, &OutputRequest) -> Vec<Service<Output>>;

    /// Add a tag to an existing service.
    ///
    /// Tags can be used to locate service.
    ///
    /// # REST API
    ///
    /// `PUT /api/v1/service/tag/$ServiceId`
    fn put_service_tag(&self, &ServiceId, String) -> Result<(), Error>;

    /// Add a tag to an existing service.
    ///
    /// Tags can be used to locate services.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/service/tag/$ServiceId`
    fn delete_service_tag(&self, &ServiceId, String) -> Result<(), Error>;

    /// Read one value from an input enpoint
    ///
    /// # REST API
    ///
    /// GET /api/v1/service/value/$ServiceId
    fn get_service_value(&self, &Service<Input>) -> Result<Value, Error>;

    /// Send one value to an output enpoint
    ///
    /// # REST API
    ///
    /// `PUT /api/v1/service/value/$ServiceId`
    fn put_service_value(&self, &Service<Output>, Value) -> Result<(), Error>;

    /// Watch for any change
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/service/watch/$ServiceId`
    fn register_service_watch<F>(&self, &WatchOptions, cb: F)
                                 -> Result<Self::WatchGuard, Error>
        where F: Fn(WatchEvent) + Send;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}

/// Options for watching changes in one or more services.
pub struct WatchOptions {
    source: InputRequest,
}

impl WatchOptions {
    pub fn new() -> Self {
        WatchOptions {
            source: InputRequest::new(),
        }
    }

    pub fn and(self, other: Self) -> Self {
        WatchOptions {
            source: self.source.and(other.source),
            ..self
        }
    }
}
<commit_msg>Replaced WatchOptions::and with WatchOptions::with_inputs<commit_after>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use devices::*;
use requests::*;
use values::Value;

/// An error produced by one of the APIs in this module.
pub enum Error {
    /// There is no such node connected to the Foxbox, even indirectly.
    NoSuchNode(NodeId),

    /// There is no such service connected to the Foxbox, even indirectly.
    NoSuchService(ServiceId),

    /// Attempting to set a value with the wrong type
    TypeError,
}

/// An event during watching.
pub enum WatchEvent {
    /// A new value is available.
    Value(Value),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was connected or
    /// disconnected. Value `SetChanged(n)` means that the set now
    /// holds `n` input services.
    SetChanged(usize),
}

/// The public API.
pub trait API {
    /// Get a list of nodes matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/node/list`
    fn get_nodes(&self, &NodeRequest) -> Vec<Node>;

    /// Add a tag to an existing node.
    ///
    /// Tags can be used to locate nodes.
    ///
    /// # REST API
    ///
    /// `PUT /api/v1/node/tag/$NodeId`
    fn put_node_tag(&self, &NodeId, String) -> Result<(), Error>;

    /// Add a tag to an existing node.
    ///
    /// Tags can be used to locate nodes.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/node/tag/$NodeId`
    fn delete_node_tag(&self, &NodeId, String) -> Result<(), Error>;

    /// Get a list of inputs matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/service/list`
    fn get_input_services(&self, &InputRequest) -> Vec<Service<Input>>;
    fn get_output_services(&self, &OutputRequest) -> Vec<Service<Output>>;

    /// Add a tag to an existing service.
    ///
    /// Tags can be used to locate service.
    ///
    /// # REST API
    ///
    /// `PUT /api/v1/service/tag/$ServiceId`
    fn put_service_tag(&self, &ServiceId, String) -> Result<(), Error>;

    /// Add a tag to an existing service.
    ///
    /// Tags can be used to locate services.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/service/tag/$ServiceId`
    fn delete_service_tag(&self, &ServiceId, String) -> Result<(), Error>;

    /// Read one value from an input enpoint
    ///
    /// # REST API
    ///
    /// GET /api/v1/service/value/$ServiceId
    fn get_service_value(&self, &Service<Input>) -> Result<Value, Error>;

    /// Send one value to an output enpoint
    ///
    /// # REST API
    ///
    /// `PUT /api/v1/service/value/$ServiceId`
    fn put_service_value(&self, &Service<Output>, Value) -> Result<(), Error>;

    /// Watch for any change
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/service/watch/$ServiceId`
    fn register_service_watch<F>(&self, WatchOptions, cb: F)
                                 -> Result<Self::WatchGuard, Error>
        where F: Fn(WatchEvent) + Send;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}

/// Options for watching changes in one or more services.
#[derive(Clone, Debug, Default)]
pub struct WatchOptions {
    /// The set of inputs to watch. Note that the actual inputs in the
    /// set may change over time.
    source: InputRequest,
}

impl WatchOptions {
    pub fn new() -> Self {
        WatchOptions {
            source: InputRequest::new(),
        }
    }

    /// Restrict to input services in a given set. Note that if all
    /// the inputs do not have the same `ServiceKind`, the call to
    /// `register_service_watch` will fail.
    ///
    /// Also note that the actual input services that are part of the
    /// set may change with time, for instance if devices are added
    /// ore removed.  as it changes.
    pub fn with_inputs(self, req: InputRequest) -> Self {
        WatchOptions {
            source: self.source.and(req),
            ..self
        }
    }
}
<|endoftext|>