<commit_before>use cursive::traits::*;
use cursive::views::*;
use cursive::Cursive;
use failure::{format_err, Error};
use gbhwdb_tools::config::{BoardLayout, GameConfig, GamePlatform};
use gbhwdb_tools::dat::DatFile;
use glob::glob;
use itertools::Itertools;
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use std::rc::Rc;
use std::sync::atomic::{self, AtomicBool};
use strsim::jaro;

#[derive(Clone, Debug)]
struct Dats {
    gb: DatFile,
    gbc: DatFile,
}

impl Dats {
    pub fn get_platform(&self, name: &str) -> Option<GamePlatform> {
        match (self.gb.names.contains(name), self.gbc.names.contains(name)) {
            (true, false) => Some(GamePlatform::Gb),
            (false, true) => Some(GamePlatform::Gbc),
            _ => None
        }
    }
}

fn load_dats() -> Result<Dats, Error> {
    let mut gb_dat = None;
    let mut gbc_dat = None;
    for entry in glob("*.dat")
        .expect("Invalid glob pattern")
        .filter_map(Result::ok)
    {
        match gbhwdb_tools::dat::from_path(&entry) {
            Ok(dat) => match dat.header.as_str() {
                "Nintendo - Game Boy" => gb_dat = Some(dat),
                "Nintendo - Game Boy Color" => gbc_dat = Some(dat),
                _ => (),
            },
            Err(e) => eprintln!("Failed to read {}: {}", entry.to_string_lossy(), e),
        }
    }
    match (gb_dat, gbc_dat) {
        (Some(gb), Some(gbc)) => Ok(Dats { gb, gbc }),
        (None, Some(_)) => Err(format_err!("No GB dat found")),
        (None, None) => Err(format_err!("No GBC dat found")),
        _ => Err(format_err!("No dats found")),
    }
}

fn load_cfgs<P: AsRef<Path>>(path: P) -> Result<BTreeMap<String, GameConfig>, Error> {
    let file = File::open(path)?;
    let file = BufReader::new(file);
    let cfgs = serde_json::from_reader(file)?;
    Ok(cfgs)
}

fn write_cfgs<P: AsRef<Path>>(path: P, cfgs: &BTreeMap<String, GameConfig>) -> Result<(), Error> {
    let file = File::create(path)?;
    let file = BufWriter::new(file);
    serde_json::to_writer_pretty(file, cfgs)?;
    Ok(())
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Command {
    Sync,
    Add,
    SaveQuit,
    Quit,
}

fn main_menu(siv: &mut Cursive, cfgs: &BTreeMap<String, GameConfig>, dats: &Dats) -> Command {
    let mut select = SelectView::new();
    select.add_item("Synchronize", Command::Sync);
    select.add_item("Add a game", Command::Add);
    select.add_item("Save and quit", Command::SaveQuit);
    let choice = Rc::new(Cell::new(Command::Quit));
    {
        let choice = choice.clone();
        select.set_on_submit(move |s, &selected| {
            choice.set(selected);
            s.quit();
        });
    }
    siv.add_layer(
        Dialog::new().title("gbhwdb-dat").content(
            LinearLayout::vertical()
                .child(TextView::new(format!(
                    " GB dat version: {}",
                    dats.gb.version
                )))
                .child(TextView::new(format!(
                    "GBC dat version: {}",
                    dats.gbc.version
                )))
                .child(TextView::new(format!("Number of games: {}", cfgs.len())))
                .child(DummyView.fixed_height(1))
                .child(select),
        ),
    );
    siv.run();
    siv.pop_layer();
    choice.get()
}

static QUIT: AtomicBool = AtomicBool::new(false);

fn main() -> Result<(), Error> {
    let mut cfgs = load_cfgs("../config/games.json")?;
    let dats = load_dats()?;
    let mut siv = Cursive::default();
    siv.add_global_callback('q', |s| {
        QUIT.store(true, atomic::Ordering::SeqCst);
        s.quit();
    });
    while !QUIT.load(atomic::Ordering::SeqCst) {
        let cmd = main_menu(&mut siv, &cfgs, &dats);
        match cmd {
            Command::Sync => sync(&mut siv, &mut cfgs, &dats),
            Command::Add => add(&mut siv, &mut cfgs, &dats),
            Command::SaveQuit => {
                write_cfgs("../config/games.json", &cfgs)?;
                break;
            }
            Command::Quit => {
                break;
            }
        }
    }
    Ok(())
}

#[derive(Clone, Debug)]
struct Candidate {
    name: String,
    rating: f64,
}

impl Candidate {
    pub fn new(current_name: &str, name: &str) -> Candidate {
        Candidate {
            name: name.to_owned(),
            rating: jaro(&current_name.to_lowercase(), &name.to_lowercase()),
        }
    }
}

fn sync(siv: &mut Cursive, cfgs: &mut BTreeMap<String, GameConfig>, dats: &Dats) {
    let gb_names = dats.gb.names.iter();
    let gbc_names = dats.gbc.names.iter();
    let names = gb_names.chain(gbc_names).cloned().collect::<HashSet<_>>();
    let name_problems = cfgs
        .iter_mut()
        .filter(|(_, game_cfg)| !names.contains(&game_cfg.name))
        .collect::<Vec<_>>();
    if name_problems.len() > 0 {
        let total = name_problems.len();
        for (idx, (code, game_cfg)) in name_problems.into_iter().enumerate() {
            let candidates = names
                .iter()
                .map(|name| Candidate::new(&game_cfg.name, &name))
                .sorted_by(|a, b| b.rating.partial_cmp(&a.rating).unwrap_or(Ordering::Equal))
                .take(5)
                .map(|c| (format!("Score {:.02}: {}", c.rating, c.name), Some(c.name)));
            let mut select = SelectView::new();
            select.add_item("(skip)", None);
            select.add_all(candidates);
            let choice = Rc::new(Cell::new(None));
            {
                let choice = choice.clone();
                select.set_on_submit(move |s, selected| {
                    choice.set(selected.as_ref().cloned());
                    s.quit();
                });
            }
            {
                let current_name = game_cfg.name.clone();
                select.set_on_select(move |s, selected| {
                    let content = match selected {
                        Some(candidate) => format!("After:  {}", candidate),
                        None => format!("After:  {}", &current_name),
                    };
                    s.call_on_id("selection", |view: &mut TextView| {
                        view.set_content(content);
                    });
                });
            }
            siv.add_layer(
                Dialog::new()
                    .title(format!("Fix name problem {}/{}", idx + 1, total))
                    .padding((2, 2, 1, 1))
                    .content(
                        LinearLayout::vertical()
                            .child(TextView::new(format!("Game code: {}", code)))
                            .child(TextView::new(format!("Before: {}", game_cfg.name)))
                            .child(
                                TextView::new(format!("After:  {}", game_cfg.name))
                                    .with_id("selection"),
                            )
                            .child(DummyView.fixed_height(1))
                            .child(select),
                    ),
            );
            siv.run();
            siv.pop_layer();
            if QUIT.load(atomic::Ordering::SeqCst) {
                return;
            }
            if let Some(name) = choice.replace(None) {
                game_cfg.name = name;
            }
        }
    }

    let platform_problems = cfgs
        .iter_mut()
        .filter_map(|(code, cfg)| match dats.get_platform(&cfg.name) {
            Some(platform) if platform != cfg.platform => Some((code, cfg, platform)),
            _ => None,
        })
        .collect::<Vec<_>>();
    if platform_problems.len() > 0 {
        let total = platform_problems.len();
        for (idx, (code, cfg, platform)) in platform_problems.into_iter().enumerate() {
            let choice = Rc::new(Cell::new(false));
            let mut dialog = Dialog::new()
                .title(format!("Fix platform problem {}/{}", idx + 1, total))
                .content(
                    LinearLayout::vertical()
                        .child(TextView::new(format!("Game code: {}", code)))
                        .child(TextView::new(format!("Before: {}", cfg.platform)))
                        .child(TextView::new(format!("After:  {}", platform))),
                );
            {
                let choice = choice.clone();
                dialog.add_button("Ok", move |s| {
                    choice.set(true);
                    s.quit();
                });
            }
            {
                let choice = choice.clone();
                dialog.add_button("Cancel", move |s| {
                    choice.set(false);
                    s.quit();
                });
            }
            siv.add_layer(dialog);
            siv.run();
            siv.pop_layer();
            if QUIT.load(atomic::Ordering::SeqCst) {
                return;
            }
            if choice.get() {
                cfg.platform = platform;
            }
        }
    }

    siv.add_layer(
        Dialog::around(TextView::new("Synchronization complete")).button("Ok", |s| s.quit()),
    );
    siv.run();
    siv.pop_layer();
}

fn add(siv: &mut Cursive, cfgs: &mut BTreeMap<String, GameConfig>, dats: &Dats) {
    let existing_names = cfgs
        .values()
        .map(|cfg| cfg.name.clone())
        .collect::<HashSet<_>>();
    let gb_games = dats
        .gb
        .names
        .iter()
        .filter(|name| !existing_names.contains(name.as_str()))
        .cloned()
        .map(|name| (GamePlatform::Gb, name));
    let gbc_games = dats
        .gbc
        .names
        .iter()
        .filter(|name| !existing_names.contains(name.as_str()))
        .cloned()
        .map(|name| (GamePlatform::Gbc, name));
    let games = gb_games.chain(gbc_games).collect::<Vec<_>>();
    let mut search = EditView::new();
    search.set_on_edit(move |s, text, _| {
        s.call_on_id(
            "search_results",
            |results: &mut SelectView<(GamePlatform, String)>| {
                results.clear();
                if text.len() > 0 {
                    let candidates = games
                        .iter()
                        .map(|(platform, name)| (*platform, Candidate::new(text, &name)))
                        .sorted_by(|(_, a), (_, b)| {
                            b.rating.partial_cmp(&a.rating).unwrap_or(Ordering::Equal)
                        })
                        .take(10)
                        .map(|(platform, c)| {
                            (
                                format!("Score {:.02}: {}", c.rating, c.name),
                                (platform, c.name),
                            )
                        });
                    results.add_all(candidates);
                }
            },
        );
    });
    let choice = Rc::new(Cell::<Option<(GamePlatform, String)>>::new(None));
    {
        let choice = choice.clone();
        search.set_on_submit(move |s, _| {
            if let Some(Some(selection)) = s.call_on_id(
                "search_results",
                |results: &mut SelectView<(GamePlatform, String)>| results.selection(),
            ) {
                choice.set(Some((selection.0, selection.1.clone())));
                s.quit();
            }
        });
    }
    let mut select = SelectView::<(GamePlatform, String)>::new();
    {
        let choice = choice.clone();
        select.set_on_submit(move |s, selection: &(GamePlatform, String)| {
            choice.set(Some(selection.clone()));
            s.quit();
        });
    }
    siv.add_layer(
        Dialog::new()
            .title("Select game to add")
            .content(
                LinearLayout::vertical()
                    .child(TextView::new("Search:"))
                    .child(search)
                    .child(DummyView.fixed_height(1))
                    .child(TextView::new("Results:"))
                    .child(select.with_id("search_results").fixed_height(10)),
            )
            .fixed_width(150),
    );
    siv.run();
    siv.pop_layer();
    let (platform, name) = choice
        .replace(None)
        .unwrap_or((GamePlatform::Gb, String::new()));
    if name.len() == 0 || QUIT.load(atomic::Ordering::SeqCst) {
        return;
    }
    let code = EditView::new();
    let mut layout = RadioGroup::new();
    let layout_choice = Rc::new(Cell::new(BoardLayout::RomMapperRam));
    let layout_container = LinearLayout::vertical()
        .child(layout.button(BoardLayout::Rom, "Rom"))
        .child(layout.button(BoardLayout::RomMapper, "Rom + mapper"))
        .child(
            layout
                .button(BoardLayout::RomMapperRam, "Rom + mapper + ram")
                .selected(),
        )
        .child(layout.button(
            BoardLayout::RomMapperRamXtal,
            "Rom + mapper + ram + crystal",
        ))
        .child(layout.button(BoardLayout::Mbc2, "MBC2"))
        .child(layout.button(BoardLayout::Mbc6, "MBC6"))
        .child(layout.button(BoardLayout::Mbc7, "MBC7"))
        .child(layout.button(BoardLayout::Type15, "Type 15 (MBC5 + dual ROM)"))
        .child(layout.button(BoardLayout::Huc3, "HuC-3"))
        .child(layout.button(BoardLayout::Tama, "Tamagotchi 3"));
    {
        let layout_choice = layout_choice.clone();
        layout.set_on_change(move |_, &layout| layout_choice.set(layout));
    }
    let mut dialog = Dialog::new().title("Add a game").content(
        LinearLayout::vertical()
            .child(TextView::new("Name:"))
            .child(TextView::new(name.as_str()))
            .child(TextView::new("Platform:"))
            .child(TextView::new(format!("{}", platform)))
            .child(TextView::new("Code:"))
            .child(code.with_id("code"))
            .child(TextView::new("Board layout:"))
            .child(layout_container),
    );
    let code_value = Rc::new(Cell::new(None));
    {
        let code_value = code_value.clone();
        dialog.add_button("Ok", move |s| {
            s.call_on_id("code", |code: &mut EditView| {
                code_value.set(Some(code.get_content().to_string()));
            });
            s.quit();
        });
    }
    siv.add_layer(dialog.fixed_width(150));
    siv.run();
    siv.pop_layer();
    if QUIT.load(atomic::Ordering::SeqCst) {
        return;
    }
    if let Some(code) = code_value.replace(None) {
        if code.len() > 0 {
            cfgs.insert(
                code,
                GameConfig {
                    name,
                    platform,
                    layout: layout_choice.get(),
                },
            );
        }
    }
}
<commit_msg>Show candidate platform in select lists<commit_after>use cursive::traits::*;
use cursive::views::*;
use cursive::Cursive;
use failure::{format_err, Error};
use gbhwdb_tools::config::{BoardLayout, GameConfig, GamePlatform};
use gbhwdb_tools::dat::DatFile;
use glob::glob;
use itertools::Itertools;
use std::cell::Cell;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::fmt;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use std::rc::Rc;
use std::sync::atomic::{self, AtomicBool};
use strsim::jaro;

#[derive(Clone, Debug)]
struct Dats {
    gb: DatFile,
    gbc: DatFile,
}

impl Dats {
    pub fn get_platform(&self, name: &str) -> Option<GamePlatform> {
        match (self.gb.names.contains(name), self.gbc.names.contains(name)) {
            (true, false) => Some(GamePlatform::Gb),
            (false, true) => Some(GamePlatform::Gbc),
            _ => None,
        }
    }
}

fn load_dats() -> Result<Dats, Error> {
    let mut gb_dat = None;
    let mut gbc_dat = None;
    for entry in glob("*.dat")
        .expect("Invalid glob pattern")
        .filter_map(Result::ok)
    {
        match gbhwdb_tools::dat::from_path(&entry) {
            Ok(dat) => match dat.header.as_str() {
                "Nintendo - Game Boy" => gb_dat = Some(dat),
                "Nintendo - Game Boy Color" => gbc_dat = Some(dat),
                _ => (),
            },
            Err(e) => eprintln!("Failed to read {}: {}", entry.to_string_lossy(), e),
        }
    }
    match (gb_dat, gbc_dat) {
        (Some(gb), Some(gbc)) => Ok(Dats { gb, gbc }),
        (None, Some(_)) => Err(format_err!("No GB dat found")),
        (None, None) => Err(format_err!("No GBC dat found")),
        _ => Err(format_err!("No dats found")),
    }
}

fn load_cfgs<P: AsRef<Path>>(path: P) -> Result<BTreeMap<String, GameConfig>, Error> {
    let file = File::open(path)?;
    let file = BufReader::new(file);
    let cfgs = serde_json::from_reader(file)?;
    Ok(cfgs)
}

fn write_cfgs<P: AsRef<Path>>(path: P, cfgs: &BTreeMap<String, GameConfig>) -> Result<(), Error> {
    let file = File::create(path)?;
    let file = BufWriter::new(file);
    serde_json::to_writer_pretty(file, cfgs)?;
    Ok(())
}

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
enum Command {
    Sync,
    Add,
    SaveQuit,
    Quit,
}

fn main_menu(siv: &mut Cursive, cfgs: &BTreeMap<String, GameConfig>, dats: &Dats) -> Command {
    let mut select = SelectView::new();
    select.add_item("Synchronize", Command::Sync);
    select.add_item("Add a game", Command::Add);
    select.add_item("Save and quit", Command::SaveQuit);
    let choice = Rc::new(Cell::new(Command::Quit));
    {
        let choice = choice.clone();
        select.set_on_submit(move |s, &selected| {
            choice.set(selected);
            s.quit();
        });
    }
    siv.add_layer(
        Dialog::new().title("gbhwdb-dat").content(
            LinearLayout::vertical()
                .child(TextView::new(format!(
                    " GB dat version: {}",
                    dats.gb.version
                )))
                .child(TextView::new(format!(
                    "GBC dat version: {}",
                    dats.gbc.version
                )))
                .child(TextView::new(format!("Number of games: {}", cfgs.len())))
                .child(DummyView.fixed_height(1))
                .child(select),
        ),
    );
    siv.run();
    siv.pop_layer();
    choice.get()
}

static QUIT: AtomicBool = AtomicBool::new(false);

fn main() -> Result<(), Error> {
    let mut cfgs = load_cfgs("../config/games.json")?;
    let dats = load_dats()?;
    let mut siv = Cursive::default();
    siv.add_global_callback('q', |s| {
        QUIT.store(true, atomic::Ordering::SeqCst);
        s.quit();
    });
    while !QUIT.load(atomic::Ordering::SeqCst) {
        let cmd = main_menu(&mut siv, &cfgs, &dats);
        match cmd {
            Command::Sync => sync(&mut siv, &mut cfgs, &dats),
            Command::Add => add(&mut siv, &mut cfgs, &dats),
            Command::SaveQuit => {
                write_cfgs("../config/games.json", &cfgs)?;
                break;
            }
            Command::Quit => {
                break;
            }
        }
    }
    Ok(())
}

#[derive(Clone, Debug)]
struct Candidate {
    platform: GamePlatform,
    name: String,
    rating: f64,
}

impl Candidate {
    pub fn new(platform: GamePlatform, current_name: &str, name: &str) -> Candidate {
        Candidate {
            platform,
            name: name.to_owned(),
            rating: jaro(&current_name.to_lowercase(), &name.to_lowercase()),
        }
    }
}

impl fmt::Display for Candidate {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "Score {:.02}: {} [{}]",
            self.rating, self.name, self.platform
        )
    }
}

fn sync(siv: &mut Cursive, cfgs: &mut BTreeMap<String, GameConfig>, dats: &Dats) {
    let gb_names = dats.gb.names.iter();
    let gbc_names = dats.gbc.names.iter();
    let names = gb_names.chain(gbc_names).cloned().collect::<HashSet<_>>();
    let gb_games = dats
        .gb
        .names
        .iter()
        .cloned()
        .map(|name| (GamePlatform::Gb, name));
    let gbc_games = dats
        .gbc
        .names
        .iter()
        .cloned()
        .map(|name| (GamePlatform::Gbc, name));
    let games = gb_games.chain(gbc_games).collect::<Vec<_>>();
    let name_problems = cfgs
        .iter_mut()
        .filter(|(_, game_cfg)| !names.contains(&game_cfg.name))
        .collect::<Vec<_>>();
    if name_problems.len() > 0 {
        let total = name_problems.len();
        for (idx, (code, game_cfg)) in name_problems.into_iter().enumerate() {
            let candidates = games
                .iter()
                .map(|(platform, name)| Candidate::new(*platform, &game_cfg.name, &name))
                .sorted_by(|a, b| b.rating.partial_cmp(&a.rating).unwrap_or(Ordering::Equal))
                .take(5)
                .map(|c| (format!("{}", c), Some(c.name)));
            let mut select = SelectView::new();
            select.add_item("(skip)", None);
            select.add_all(candidates);
            let choice = Rc::new(Cell::new(None));
            {
                let choice = choice.clone();
                select.set_on_submit(move |s, selected| {
                    choice.set(selected.as_ref().cloned());
                    s.quit();
                });
            }
            {
                let current_name = game_cfg.name.clone();
                select.set_on_select(move |s, selected| {
                    let content = match selected {
                        Some(candidate) => format!("After:  {}", candidate),
                        None => format!("After:  {}", &current_name),
                    };
                    s.call_on_id("selection", |view: &mut TextView| {
                        view.set_content(content);
                    });
                });
            }
            siv.add_layer(
                Dialog::new()
                    .title(format!("Fix name problem {}/{}", idx + 1, total))
                    .padding((2, 2, 1, 1))
                    .content(
                        LinearLayout::vertical()
                            .child(TextView::new(format!("Game code: {}", code)))
                            .child(TextView::new(format!("Before: {}", game_cfg.name)))
                            .child(
                                TextView::new(format!("After:  {}", game_cfg.name))
                                    .with_id("selection"),
                            )
                            .child(DummyView.fixed_height(1))
                            .child(select),
                    ),
            );
            siv.run();
            siv.pop_layer();
            if QUIT.load(atomic::Ordering::SeqCst) {
                return;
            }
            if let Some(name) = choice.replace(None) {
                game_cfg.name = name;
            }
        }
    }

    let platform_problems = cfgs
        .iter_mut()
        .filter_map(|(code, cfg)| match dats.get_platform(&cfg.name) {
            Some(platform) if platform != cfg.platform => Some((code, cfg, platform)),
            _ => None,
        })
        .collect::<Vec<_>>();
    if platform_problems.len() > 0 {
        let total = platform_problems.len();
        for (idx, (code, cfg, platform)) in platform_problems.into_iter().enumerate() {
            let choice = Rc::new(Cell::new(false));
            let mut dialog = Dialog::new()
                .title(format!("Fix platform problem {}/{}", idx + 1, total))
                .content(
                    LinearLayout::vertical()
                        .child(TextView::new(format!("Game code: {}", code)))
                        .child(TextView::new(format!("Before: {}", cfg.platform)))
                        .child(TextView::new(format!("After:  {}", platform))),
                );
            {
                let choice = choice.clone();
                dialog.add_button("Ok", move |s| {
                    choice.set(true);
                    s.quit();
                });
            }
            {
                let choice = choice.clone();
                dialog.add_button("Cancel", move |s| {
                    choice.set(false);
                    s.quit();
                });
            }
            siv.add_layer(dialog);
            siv.run();
            siv.pop_layer();
            if QUIT.load(atomic::Ordering::SeqCst) {
                return;
            }
            if choice.get() {
                cfg.platform = platform;
            }
        }
    }

    siv.add_layer(
        Dialog::around(TextView::new("Synchronization complete")).button("Ok", |s| s.quit()),
    );
    siv.run();
    siv.pop_layer();
}

fn add(siv: &mut Cursive, cfgs: &mut BTreeMap<String, GameConfig>, dats: &Dats) {
    let existing_names = cfgs
        .values()
        .map(|cfg| cfg.name.clone())
        .collect::<HashSet<_>>();
    let gb_games = dats
        .gb
        .names
        .iter()
        .filter(|name| !existing_names.contains(name.as_str()))
        .cloned()
        .map(|name| (GamePlatform::Gb, name));
    let gbc_games = dats
        .gbc
        .names
        .iter()
        .filter(|name| !existing_names.contains(name.as_str()))
        .cloned()
        .map(|name| (GamePlatform::Gbc, name));
    let games = gb_games.chain(gbc_games).collect::<Vec<_>>();
    let mut search = EditView::new();
    search.set_on_edit(move |s, text, _| {
        s.call_on_id("search_results", |results: &mut SelectView<Candidate>| {
            results.clear();
            if text.len() > 0 {
                let candidates = games
                    .iter()
                    .map(|(platform, name)| Candidate::new(*platform, text, &name))
                    .sorted_by(|a, b| b.rating.partial_cmp(&a.rating).unwrap_or(Ordering::Equal))
                    .take(10)
                    .map(|c| (format!("{}", c), c));
                results.add_all(candidates);
            }
        });
    });
    let choice = Rc::new(Cell::<Option<Candidate>>::new(None));
    {
        let choice = choice.clone();
        search.set_on_submit(move |s, _| {
            if let Some(Some(selection)) = s
                .call_on_id("search_results", |results: &mut SelectView<Candidate>| {
                    results.selection()
                })
            {
                choice.set(Some(Clone::clone(&selection)));
                s.quit();
            }
        });
    }
    let mut select = SelectView::<Candidate>::new();
    {
        let choice = choice.clone();
        select.set_on_submit(move |s, selection: &Candidate| {
            choice.set(Some(Clone::clone(&selection)));
            s.quit();
        });
    }
    siv.add_layer(
        Dialog::new()
            .title("Select game to add")
            .content(
                LinearLayout::vertical()
                    .child(TextView::new("Search:"))
                    .child(search)
                    .child(DummyView.fixed_height(1))
                    .child(TextView::new("Results:"))
                    .child(select.with_id("search_results").fixed_height(10)),
            )
            .fixed_width(150),
    );
    siv.run();
    siv.pop_layer();
    let (platform, name) = choice
        .replace(None)
        .map(|c| (c.platform, c.name))
        .unwrap_or((GamePlatform::Gb, String::new()));
    if name.len() == 0 || QUIT.load(atomic::Ordering::SeqCst) {
        return;
    }
    let code = EditView::new();
    let mut layout = RadioGroup::new();
    let layout_choice = Rc::new(Cell::new(BoardLayout::RomMapperRam));
    let layout_container = LinearLayout::vertical()
        .child(layout.button(BoardLayout::Rom, "Rom"))
        .child(layout.button(BoardLayout::RomMapper, "Rom + mapper"))
        .child(
            layout
                .button(BoardLayout::RomMapperRam, "Rom + mapper + ram")
                .selected(),
        )
        .child(layout.button(
            BoardLayout::RomMapperRamXtal,
            "Rom + mapper + ram + crystal",
        ))
        .child(layout.button(BoardLayout::Mbc2, "MBC2"))
        .child(layout.button(BoardLayout::Mbc6, "MBC6"))
        .child(layout.button(BoardLayout::Mbc7, "MBC7"))
        .child(layout.button(BoardLayout::Type15, "Type 15 (MBC5 + dual ROM)"))
        .child(layout.button(BoardLayout::Huc3, "HuC-3"))
        .child(layout.button(BoardLayout::Tama, "Tamagotchi 3"));
    {
        let layout_choice = layout_choice.clone();
        layout.set_on_change(move |_, &layout| layout_choice.set(layout));
    }
    let mut dialog = Dialog::new().title("Add a game").content(
        LinearLayout::vertical()
            .child(TextView::new("Name:"))
            .child(TextView::new(name.as_str()))
            .child(TextView::new("Platform:"))
            .child(TextView::new(format!("{}", platform)))
            .child(TextView::new("Code:"))
            .child(code.with_id("code"))
            .child(TextView::new("Board layout:"))
            .child(layout_container),
    );
    let code_value = Rc::new(Cell::new(None));
    {
        let code_value = code_value.clone();
        dialog.add_button("Ok", move |s| {
            s.call_on_id("code", |code: &mut EditView| {
                code_value.set(Some(code.get_content().to_string()));
            });
            s.quit();
        });
    }
    siv.add_layer(dialog.fixed_width(150));
    siv.run();
    siv.pop_layer();
    if QUIT.load(atomic::Ordering::SeqCst) {
        return;
    }
    if let Some(code) = code_value.replace(None) {
        if code.len() > 0 {
            cfgs.insert(
                code,
                GameConfig {
                    name,
                    platform,
                    layout: layout_choice.get(),
                },
            );
        }
    }
}
<|endoftext|>


<commit_before>use std::str;
use std::error::Error;
use std::collections::HashMap;
use std::io::{self, Read, BufRead, Write, BufReader};
use std::net::TcpStream;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use serde::Serialize;
use serde_json::{self, Value};
use parking_lot::Mutex;
use futures::future::{Future, BoxFuture};
use futures::sync::oneshot::{self, Sender};
use fmtutils::Asis;
use protocol::{self, Id, Method};
use protocol::{Request, RequestMessage, Notification, NotificationMessage};
use protocol::{ResponseMessage, ResponseError, Message, MessageError};

fn invalid<E: Into<Box<Error + Send + Sync>>>(e: E) -> io::Error {
    io::Error::new(io::ErrorKind::InvalidData, e)
}

struct ServerInner {
    reader: Mutex<Box<BufRead + Send + Sync>>,
    writer: Mutex<Box<Write + Send + Sync>>,

    next_id: AtomicUsize,

    // Values are deserialized by Receiver
    futures: Mutex<HashMap<Id, Sender<Result<Option<Value>, ResponseError<Value>>>>>,
}

#[derive(Clone)]
pub struct Server {
    inner: Arc<ServerInner>,
}

#[derive(Clone, Debug)]
pub enum Received {
    Request(Id, Request),
    Notification(Notification),
}

impl Server {
    pub fn new(reader: Box<BufRead + Send + Sync>,
               writer: Box<Write + Send + Sync>) -> Server {
        Server {
            inner: Arc::new(ServerInner {
                reader: Mutex::new(reader),
                writer: Mutex::new(writer),
                next_id: AtomicUsize::new(0),
                futures: Mutex::new(HashMap::new()),
            }),
        }
    }

    pub fn from_stdio() -> Server {
        Server::new(Box::new(BufReader::new(io::stdin())) as Box<BufRead + Send + Sync>,
                    Box::new(io::stdout()) as Box<Write + Send + Sync>)
    }

    pub fn from_tcp_stream(stream: TcpStream) -> io::Result<Server> {
        let stream2 = stream.try_clone()?;
        Ok(Server::new(Box::new(BufReader::new(stream)) as Box<BufRead + Send + Sync>,
                       Box::new(stream2) as Box<Write + Send + Sync>))
    }

    fn recv_msg(&self) -> io::Result<Vec<u8>> {
        let mut reader = self.inner.reader.lock();

        let mut line = Vec::new();
        let mut bodylen = None;
        loop {
            line.clear();
            reader.read_until(b'\n', &mut line)?;
            if line.is_empty() { return Err(invalid("premature end of stream")); }
            if line.pop() != Some(b'\n') { return Err(invalid("invalid header line")); }
            if line.pop() != Some(b'\r') { return Err(invalid("invalid header line")); }
            let len = line.len();
            if len == 0 {
                break;
            } else if len > 16 && line[..16] == b"Content-Length: "[..] {
                if bodylen.is_some() {
                    return Err(invalid("duplicate Content-Length header"));
                }
                let value = str::from_utf8(&line[16..]).map_err(invalid)?;
                bodylen = Some(value.parse().map_err(invalid)?);
            } else if len > 14 && line[..14] == b"Content-Type: "[..] {
                // ignored
            } else {
                return Err(invalid("invalid or unexpected header line"));
            }
        }
        if bodylen.is_none() {
            return Err(invalid("Content-Length header is missing"));
        }

        let mut body = vec![0; bodylen.unwrap()];
        reader.read_exact(&mut body)?;
        if cfg!(debug_assertions) {
            trace!("read: {}", Asis(&body));
        }
        Ok(body)
    }

    fn send_msg(&self, buf: &[u8]) -> io::Result<()> {
        let mut writer = self.inner.writer.lock();
        write!(writer, "Content-Length: {}\r\n\r\n", buf.len())?;
        writer.write(buf)?;
        drop(writer);

        if cfg!(debug_assertions) {
            trace!("write: {}", Asis(buf));
        }

        Ok(())
    }

    pub fn recv(&self) -> io::Result<Option<Received>> {
        let body = self.recv_msg()?;

        // now we can recover from the error; any error past this point is represented by None
        let json = match serde_json::from_slice::<Value>(&body) {
            Ok(json) => json,
            Err(e) => {
                self.send_err(None, protocol::error_codes::PARSE_ERROR,
                              format!("parsing failed: {}", e), ())?;
                return Ok(None);
            }
        };
        let msg = match serde_json::from_value::<Message>(json) {
            Ok(msg) => msg,
            Err(e) => {
                self.send_err(None, protocol::error_codes::INVALID_REQUEST,
                              format!("invalid request: {}", e), ())?;
                return Ok(None);
            },
        };

        let ret = match msg {
            Message::Request(msg) => match Request::from_message(msg) {
                (id, Ok(req)) => Ok(Received::Request(id, req)),
                (id, Err(e)) => Err((Some(id), e)),
            },

            Message::Notification(msg) => match Notification::from_message(msg) {
                Ok(req) => Ok(Received::Notification(req)),
                Err(e) => Err((None, e)),
            },

            Message::Response(msg) => {
                if let Some(ref id) = msg.id {
                    if let Some(sender) = self.inner.futures.lock().remove(id) {
                        if let Some(error) = msg.error {
                            sender.complete(Err(error));
                        } else {
                            sender.complete(Ok(msg.result));
                        }
                    } else {
                        warn!("no callback registered for {:?}", msg);
                    }
                } else {
                    // this indicates an error from the server side, and cannot be handled
                    warn!("got and ignored {:?}", msg);
                }
                return Ok(None);
            },
        };

        match ret {
            Ok(received) => Ok(Some(received)),
            Err((id, MessageError::MethodNotFound(method))) => {
                self.send_err(id, protocol::error_codes::METHOD_NOT_FOUND,
                              format!("method not found: {}", method), ())?;
                Ok(None)
            },
            Err((id, MessageError::InvalidParams(estr))) => {
                self.send_err(id, protocol::error_codes::INVALID_PARAMS,
                              format!("invalid parameters: {}", estr), ())?;
                Ok(None)
            },
        }
    }

    pub fn send_ok<R: Serialize>(&self, id: Id, result: R) -> io::Result<()> {
        let msg = ResponseMessage::<R, ()> {
            version: protocol::Version,
            id: Some(id),
            result: Some(result),
            error: None,
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)
    }

    pub fn send_err<M: Into<String>, E: Serialize>(&self, id: Option<Id>, code: i64,
                                                   message: M, data: E) -> io::Result<()> {
        let msg = ResponseMessage::<(), E> {
            version: protocol::Version,
            id: id,
            result: None,
            error: Some(ResponseError {
                code: code,
                message: message.into(),
                data: Some(data),
            }),
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)
    }

    pub fn send_req<T: Serialize>(&self, method: Method, params: T)
        -> io::Result<BoxFuture<Option<Value>, ResponseError<Value>>>
    {
        let id = Id::Number(self.inner.next_id.fetch_add(1, Ordering::SeqCst) as i64);
        let (sender, receiver) = oneshot::channel();
        let prev = self.inner.futures.lock().insert(id.clone(), sender);
        assert!(prev.is_none(), "duplicate request id?!");

        let msg = RequestMessage::<T> {
            version: protocol::Version,
            id: id,
            method: method.as_str().into(),
            params: Some(params),
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)?;

        Ok(receiver.then(|ret| {
            match ret {
                Ok(Ok(result)) => Ok(result),
                Ok(Err(error)) => Err(error),
                Err(_) => {
                    // possible when the server is shut down before the future is dropped
                    Err(ResponseError {
                        code: protocol::error_codes::UNKNOWN_ERROR_CODE,
                        message: "Canceled".into(), data: None,
                    })
                },
            }
        }).boxed())
    }

    pub fn send_notify<T: Serialize>(&self, method: Method, params: T) -> io::Result<()> {
        let msg = NotificationMessage::<T> {
            version: protocol::Version,
            method: method.as_str().into(),
            params: Some(params),
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)
    }
}

<commit_msg>Added missing flush call. Affects non-Windows platforms.<commit_after>use std::str;
use std::error::Error;
use std::collections::HashMap;
use std::io::{self, Read, BufRead, Write, BufReader};
use std::net::TcpStream;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use serde::Serialize;
use serde_json::{self, Value};
use parking_lot::Mutex;
use futures::future::{Future, BoxFuture};
use futures::sync::oneshot::{self, Sender};
use fmtutils::Asis;
use protocol::{self, Id, Method};
use protocol::{Request, RequestMessage, Notification, NotificationMessage};
use protocol::{ResponseMessage, ResponseError, Message, MessageError};

fn invalid<E: Into<Box<Error + Send + Sync>>>(e: E) -> io::Error {
    io::Error::new(io::ErrorKind::InvalidData, e)
}

struct ServerInner {
    reader: Mutex<Box<BufRead + Send + Sync>>,
    writer: Mutex<Box<Write + Send + Sync>>,

    next_id: AtomicUsize,

    // Values are deserialized by Receiver
    futures: Mutex<HashMap<Id, Sender<Result<Option<Value>, ResponseError<Value>>>>>,
}

#[derive(Clone)]
pub struct Server {
    inner: Arc<ServerInner>,
}

#[derive(Clone, Debug)]
pub enum Received {
    Request(Id, Request),
    Notification(Notification),
}

impl Server {
    pub fn new(reader: Box<BufRead + Send + Sync>,
               writer: Box<Write + Send + Sync>) -> Server {
        Server {
            inner: Arc::new(ServerInner {
                reader: Mutex::new(reader),
                writer: Mutex::new(writer),
                next_id: AtomicUsize::new(0),
                futures: Mutex::new(HashMap::new()),
            }),
        }
    }

    pub fn from_stdio() -> Server {
        Server::new(Box::new(BufReader::new(io::stdin())) as Box<BufRead + Send + Sync>,
                    Box::new(io::stdout()) as Box<Write + Send + Sync>)
    }

    pub fn from_tcp_stream(stream: TcpStream) -> io::Result<Server> {
        let stream2 = stream.try_clone()?;
        Ok(Server::new(Box::new(BufReader::new(stream)) as Box<BufRead + Send + Sync>,
                       Box::new(stream2) as Box<Write + Send + Sync>))
    }

    fn recv_msg(&self) -> io::Result<Vec<u8>> {
        let mut reader = self.inner.reader.lock();

        let mut line = Vec::new();
        let mut bodylen = None;
        loop {
            line.clear();
            reader.read_until(b'\n', &mut line)?;
            if line.is_empty() { return Err(invalid("premature end of stream")); }
            if line.pop() != Some(b'\n') { return Err(invalid("invalid header line")); }
            if line.pop() != Some(b'\r') { return Err(invalid("invalid header line")); }
            let len = line.len();
            if len == 0 {
                break;
            } else if len > 16 && line[..16] == b"Content-Length: "[..] {
                if bodylen.is_some() {
                    return Err(invalid("duplicate Content-Length header"));
                }
                let value = str::from_utf8(&line[16..]).map_err(invalid)?;
                bodylen = Some(value.parse().map_err(invalid)?);
            } else if len > 14 && line[..14] == b"Content-Type: "[..] {
                // ignored
            } else {
                return Err(invalid("invalid or unexpected header line"));
            }
        }
        if bodylen.is_none() {
            return Err(invalid("Content-Length header is missing"));
        }

        let mut body = vec![0; bodylen.unwrap()];
        reader.read_exact(&mut body)?;
        if cfg!(debug_assertions) {
            trace!("read: {}", Asis(&body));
        }
        Ok(body)
    }

    fn send_msg(&self, buf: &[u8]) -> io::Result<()> {
        let mut writer = self.inner.writer.lock();
        write!(writer, "Content-Length: {}\r\n\r\n", buf.len())?;
        writer.write(buf)?;
        writer.flush()?;
        drop(writer);

        if cfg!(debug_assertions) {
            trace!("write: {}", Asis(buf));
        }

        Ok(())
    }

    pub fn recv(&self) -> io::Result<Option<Received>> {
        let body = self.recv_msg()?;

        // now we can recover from the error; any error past this point is represented by None
        let json = match serde_json::from_slice::<Value>(&body) {
            Ok(json) => json,
            Err(e) => {
                self.send_err(None, protocol::error_codes::PARSE_ERROR,
                              format!("parsing failed: {}", e), ())?;
                return Ok(None);
            }
        };
        let msg = match serde_json::from_value::<Message>(json) {
            Ok(msg) => msg,
            Err(e) => {
                self.send_err(None, protocol::error_codes::INVALID_REQUEST,
                              format!("invalid request: {}", e), ())?;
                return Ok(None);
            },
        };

        let ret = match msg {
            Message::Request(msg) => match Request::from_message(msg) {
                (id, Ok(req)) => Ok(Received::Request(id, req)),
                (id, Err(e)) => Err((Some(id), e)),
            },

            Message::Notification(msg) => match Notification::from_message(msg) {
                Ok(req) => Ok(Received::Notification(req)),
                Err(e) => Err((None, e)),
            },

            Message::Response(msg) => {
                if let Some(ref id) = msg.id {
                    if let Some(sender) = self.inner.futures.lock().remove(id) {
                        if let Some(error) = msg.error {
                            sender.complete(Err(error));
                        } else {
                            sender.complete(Ok(msg.result));
                        }
                    } else {
                        warn!("no callback registered for {:?}", msg);
                    }
                } else {
                    // this indicates an error from the server side, and cannot be handled
                    warn!("got and ignored {:?}", msg);
                }
                return Ok(None);
            },
        };

        match ret {
            Ok(received) => Ok(Some(received)),
            Err((id, MessageError::MethodNotFound(method))) => {
                self.send_err(id, protocol::error_codes::METHOD_NOT_FOUND,
                              format!("method not found: {}", method), ())?;
                Ok(None)
            },
            Err((id, MessageError::InvalidParams(estr))) => {
                self.send_err(id, protocol::error_codes::INVALID_PARAMS,
                              format!("invalid parameters: {}", estr), ())?;
                Ok(None)
            },
        }
    }

    pub fn send_ok<R: Serialize>(&self, id: Id, result: R) -> io::Result<()> {
        let msg = ResponseMessage::<R, ()> {
            version: protocol::Version,
            id: Some(id),
            result: Some(result),
            error: None,
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)
    }

    pub fn send_err<M: Into<String>, E: Serialize>(&self, id: Option<Id>, code: i64,
                                                   message: M, data: E) -> io::Result<()> {
        let msg = ResponseMessage::<(), E> {
            version: protocol::Version,
            id: id,
            result: None,
            error: Some(ResponseError {
                code: code,
                message: message.into(),
                data: Some(data),
            }),
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)
    }

    pub fn send_req<T: Serialize>(&self, method: Method, params: T)
        -> io::Result<BoxFuture<Option<Value>, ResponseError<Value>>>
    {
        let id = Id::Number(self.inner.next_id.fetch_add(1, Ordering::SeqCst) as i64);
        let (sender, receiver) = oneshot::channel();
        let prev = self.inner.futures.lock().insert(id.clone(), sender);
        assert!(prev.is_none(), "duplicate request id?!");

        let msg = RequestMessage::<T> {
            version: protocol::Version,
            id: id,
            method: method.as_str().into(),
            params: Some(params),
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)?;

        Ok(receiver.then(|ret| {
            match ret {
                Ok(Ok(result)) => Ok(result),
                Ok(Err(error)) => Err(error),
                Err(_) => {
                    // possible when the server is shut down before the future is dropped
                    Err(ResponseError {
                        code: protocol::error_codes::UNKNOWN_ERROR_CODE,
                        message: "Canceled".into(), data: None,
                    })
                },
            }
        }).boxed())
    }

    pub fn send_notify<T: Serialize>(&self, method: Method, params: T) -> io::Result<()> {
        let msg = NotificationMessage::<T> {
            version: protocol::Version,
            method: method.as_str().into(),
            params: Some(params),
        };
        let buf = serde_json::to_vec(&msg).map_err(invalid)?;
        self.send_msg(&buf)
    }
}

<|endoftext|>


<commit_before>#![crate_name = "triangle"]
#![feature(phase)]

extern crate piston;
extern crate gfx;
#[phase(plugin)]
extern crate gfx_macros;
extern crate sdl2_game_window;
extern crate glfw_game_window;

// use Window = sdl2_game_window::GameWindowSDL2;
use Window = glfw_game_window::GameWindowGLFW;
use piston::{
    GameIterator,
    GameIteratorSettings,
    GameWindowSettings,
    Render,
};
use gfx::{
    Device,
    DeviceHelper,
};

#[vertex_format]
struct Vertex {
    pos: [f32, ..2],
    color: [f32, ..3],
}

static VERTEX_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120
    attribute vec2 pos;
    attribute vec3 color;
    varying vec4 v_Color;
    void main() {
        v_Color = vec4(color, 1.0);
        gl_Position = vec4(pos, 0.0, 1.0);
    }
"
GLSL_150: b"
    #version 150 core
    in vec2 pos;
    in vec3 color;
    out vec4 v_Color;
    void main() {
        v_Color = vec4(color, 1.0);
        gl_Position = vec4(pos, 0.0, 1.0);
    }
"
};

static FRAGMENT_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120
    varying vec4 v_Color;
    void main() {
        gl_FragColor = v_Color;
    }
"
GLSL_150: b"
    #version 150 core
    in vec4 v_Color;
    out vec4 o_Color;
    void main() {
        o_Color = v_Color;
    }
"
};

fn main() {
    let ref mut window = Window::new(
        GameWindowSettings {
            title: "triangle".to_string(),
            size: [300, 300],
            fullscreen: false,
            exit_on_esc: true,
        }
    );
    let (mut device, frontend) = window.gfx();

    let state = gfx::DrawState::new();
    let vertex_data = vec![
        Vertex { pos: [ -0.5, -0.5 ], color: [1.0, 0.0, 0.0] },
        Vertex { pos: [ 0.5, -0.5 ], color: [0.0, 1.0, 0.0] },
        Vertex { pos: [ 0.0, 0.5 ], color: [0.0, 0.0, 1.0] }
    ];
    let mesh = device.create_mesh(vertex_data);
    let program = device.link_program(
            (),
            VERTEX_SRC.clone(),
            FRAGMENT_SRC.clone()
        ).unwrap();

    let mut list = frontend.create_drawlist();
    list.clear(
        gfx::ClearData {
            color: Some(gfx::Color([0.3, 0.3, 0.3, 0.1])),
            depth: None,
            stencil: None,
        },
        frontend.get_main_frame()
    );
    list.draw(
            &mesh,
            mesh.get_slice(),
            frontend.get_main_frame(),
            &program,
            &state
        ).unwrap();

    let mut game_iter = GameIterator::new(
        window,
        &GameIteratorSettings {
            updates_per_second: 120,
            max_frames_per_second: 60,
        }
    );
    for e in game_iter {
        match e {
            Render(_args) => {
                device.submit(list.as_slice());
            },
            _ => {},
        }
    }
}

<commit_msg>Updated to latest gfx-rs<commit_after>#![crate_name = "triangle"]
#![feature(phase)]

extern crate piston;
extern crate gfx;
#[phase(plugin)]
extern crate gfx_macros;
extern crate sdl2_game_window;
extern crate glfw_game_window;

// use Window = sdl2_game_window::GameWindowSDL2;
use Window = glfw_game_window::GameWindowGLFW;
use piston::{
    GameIterator,
    GameIteratorSettings,
    GameWindowSettings,
    Render,
};
use gfx::{
    Device,
    DeviceHelper,
};

#[vertex_format]
struct Vertex {
    pos: [f32, ..2],
    color: [f32, ..3],
}

static VERTEX_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120
    attribute vec2 pos;
    attribute vec3 color;
    varying vec4 v_Color;
    void main() {
        v_Color = vec4(color, 1.0);
        gl_Position = vec4(pos, 0.0, 1.0);
    }
"
GLSL_150: b"
    #version 150 core
    in vec2 pos;
    in vec3 color;
    out vec4 v_Color;
    void main() {
        v_Color = vec4(color, 1.0);
        gl_Position = vec4(pos, 0.0, 1.0);
    }
"
};

static FRAGMENT_SRC: gfx::ShaderSource = shaders! {
GLSL_120: b"
    #version 120
    varying vec4 v_Color;
    void main() {
        gl_FragColor = v_Color;
    }
"
GLSL_150: b"
    #version 150 core
    in vec4 v_Color;
    out vec4 o_Color;
    void main() {
        o_Color = v_Color;
    }
"
};

fn main() {
    let ref mut window = Window::new(
        GameWindowSettings {
            title: "triangle".to_string(),
            size: [300, 300],
            fullscreen: false,
            exit_on_esc: true,
        }
    );
    let (mut device, frame) = window.gfx();

    let state = gfx::DrawState::new();
    let vertex_data = vec![
        Vertex { pos: [ -0.5, -0.5 ], color: [1.0, 0.0, 0.0] },
        Vertex { pos: [ 0.5, -0.5 ], color: [0.0, 1.0, 0.0] },
        Vertex { pos: [ 0.0, 0.5 ], color: [0.0, 0.0, 1.0] }
    ];
    let mesh = device.create_mesh(vertex_data);
    let program = device.link_program(
            (),
            VERTEX_SRC.clone(),
            FRAGMENT_SRC.clone()
        ).unwrap();

    let mut list = device.create_draw_list();
    list.clear(
        gfx::ClearData {
            color: Some(gfx::Color([0.3, 0.3, 0.3, 0.1])),
            depth: None,
            stencil: None,
        },
        &frame
    );
    list.draw(
            &mesh,
            mesh.get_slice(),
            &frame,
            &program,
            &state
        ).unwrap();

    let mut game_iter = GameIterator::new(
        window,
        &GameIteratorSettings {
            updates_per_second: 120,
            max_frames_per_second: 60,
        }
    );
    for e in game_iter {
        match e {
            Render(_args) => {
                device.submit(list.as_slice());
            },
            _ => {},
        }
    }
}

<|endoftext|>


<commit_before>use async_trait::async_trait;
use serenity::client::Context;
use serenity::model::id::{ChannelId, GuildId, MessageId};
use serenity::model::user::User;
use serenity::model::Permissions;
use typemap_rev::TypeMap;

use crate::database::settings::Setting;
use crate::discord::message_processors::invite_link::InviteLinkProcessor;
use crate::discord::message_processors::spam_filter::SpamFilterProcessor;
use crate::discord::message_processors::word_filter::WordFilterProcessor;

mod invite_link;
mod spam_filter;
mod word_filter;

pub struct MessageProcessors(pub Vec<Box<dyn MessageProcessor + Send + Sync>>);

pub fn get_all_processors() -> MessageProcessors {
    MessageProcessors(vec![
        Box::new(InviteLinkProcessor),
        Box::new(WordFilterProcessor),
        Box::new(SpamFilterProcessor::new()),
    ])
}

#[async_trait]
pub trait MessageProcessor {
    async fn handle_message(
        &self,
        context: &Context,
        message_content: &str,
        guild_id: GuildId,
        channel_id: ChannelId,
        message_id: MessageId,
        author: &User,
        permissions: Permissions,
        setting: &Setting,
        services: &TypeMap,
    ) -> anyhow::Result<bool>;
}
<commit_msg>Fix spam filter not working when message contains invite links or blocked words<commit_after>use async_trait::async_trait;
use serenity::client::Context;
use serenity::model::id::{ChannelId, GuildId, MessageId};
use serenity::model::user::User;
use serenity::model::Permissions;
use typemap_rev::TypeMap;

use crate::database::settings::Setting;
use crate::discord::message_processors::invite_link::InviteLinkProcessor;
use crate::discord::message_processors::spam_filter::SpamFilterProcessor;
use crate::discord::message_processors::word_filter::WordFilterProcessor;

mod invite_link;
mod spam_filter;
mod word_filter;

pub struct MessageProcessors(pub Vec<Box<dyn MessageProcessor + Send + Sync>>);

pub fn get_all_processors() -> MessageProcessors {
    MessageProcessors(vec![
        // needs to be run first to detect spam even if it contains blocklisted words
        // or a Discord server invite link
        Box::new(SpamFilterProcessor::new()),
        Box::new(InviteLinkProcessor),
        Box::new(WordFilterProcessor),
    ])
}

#[async_trait]
pub trait MessageProcessor {
    async fn handle_message(
        &self,
        context: &Context,
        message_content: &str,
        guild_id: GuildId,
        channel_id: ChannelId,
        message_id: MessageId,
        author: &User,
        permissions: Permissions,
        setting: &Setting,
        services: &TypeMap,
    ) -> anyhow::Result<bool>;
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::io::Write;

use crate::bindgen::config::{Config, Language, LayoutConfig};
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
    AnnotationSet, Cfg, ConditionWrite, Documentation, Field, GenericParams, Item, ItemContainer,
    Path, Repr, ReprAlign, ReprStyle, ToCondition, Type,
};
use crate::bindgen::library::Library;
use crate::bindgen::mangle;
use crate::bindgen::monomorph::Monomorphs;
use crate::bindgen::rename::{IdentifierType, RenameRule};
use crate::bindgen::utilities::IterHelpers;
use crate::bindgen::writer::{ListType, Source, SourceWriter};

#[derive(Debug, Clone)]
pub struct Union {
    pub path: Path,
    pub export_name: String,
    pub generic_params: GenericParams,
    pub fields: Vec<Field>,
    pub tuple_union: bool,
    pub alignment: Option<ReprAlign>,
    pub cfg: Option<Cfg>,
    pub annotations: AnnotationSet,
    pub documentation: Documentation,
}

impl Union {
    pub fn load(
        layout_config: &LayoutConfig,
        item: &syn::ItemUnion,
        mod_cfg: Option<&Cfg>,
    ) -> Result<Union, String> {
        let repr = Repr::load(&item.attrs)?;
        if repr.style != ReprStyle::C {
            return Err("Union is not marked #[repr(C)].".to_owned());
        }

        // Ensure we can safely represent the union given the configuration.
        if let Some(align) = repr.align {
            layout_config.ensure_safe_to_represent(&align)?;
        }

        let path = Path::new(item.ident.to_string());

        let (fields, tuple_union) = {
            let out = item
                .fields
                .named
                .iter()
                .try_skip_map(|field| Field::load(field, &path))?;
            (out, false)
        };

        Ok(Union::new(
            path,
            GenericParams::new(&item.generics),
            fields,
            repr.align,
            tuple_union,
            Cfg::append(mod_cfg, Cfg::load(&item.attrs)),
            AnnotationSet::load(&item.attrs)?,
            Documentation::load(&item.attrs),
        ))
    }

    #[allow(clippy::too_many_arguments)]
    pub fn new(
        path: Path,
        generic_params: GenericParams,
        fields: Vec<Field>,
        alignment: Option<ReprAlign>,
        tuple_union: bool,
        cfg: Option<Cfg>,
        annotations: AnnotationSet,
        documentation: Documentation,
    ) -> Self {
        let export_name = path.name().to_owned();
        Self {
            path,
            export_name,
            generic_params,
            fields,
            alignment,
            tuple_union,
            cfg,
            annotations,
            documentation,
        }
    }

    pub fn simplify_standard_types(&mut self, config: &Config) {
        for field in &mut self.fields {
            field.ty.simplify_standard_types(config);
        }
    }

    pub fn is_generic(&self) -> bool {
        self.generic_params.len() > 0
    }

    pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
        // Generic unions can instantiate monomorphs only once they've been
        // instantiated. See `instantiate_monomorph` for more details.
        if self.is_generic() {
            return;
        }

        for field in &self.fields {
            field.ty.add_monomorphs(library, out);
        }
    }

    pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) {
        for field in &mut self.fields {
            field.ty.mangle_paths(monomorphs);
        }
    }
}

impl Item for Union {
    fn path(&self) -> &Path {
        &self.path
    }

    fn export_name(&self) -> &str {
        &self.export_name
    }

    fn cfg(&self) -> Option<&Cfg> {
        self.cfg.as_ref()
    }

    fn annotations(&self) -> &AnnotationSet {
        &self.annotations
    }

    fn annotations_mut(&mut self) -> &mut AnnotationSet {
        &mut self.annotations
    }

    fn container(&self) -> ItemContainer {
        ItemContainer::Union(self.clone())
    }

    fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) {
        resolver.add_union(&self.path);
    }

    fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) {
        for field in &mut self.fields {
            field.ty.resolve_declaration_types(resolver);
        }
    }

    fn rename_for_config(&mut self, config: &Config) {
        config.export.rename(&mut self.export_name);
        for field in &mut self.fields {
            field.ty.rename_for_config(config, &self.generic_params);
        }

        let rules = self
            .annotations
            .parse_atom::<RenameRule>("rename-all")
            .unwrap_or(config.structure.rename_fields);

        if let Some(o) = self.annotations.list("field-names") {
            let mut overriden_fields = Vec::new();

            for (i, field) in self.fields.iter().enumerate() {
                if i >= o.len() {
                    overriden_fields.push(field.clone());
                } else {
                    overriden_fields.push(Field {
                        name: o[i].clone(),
                        ty: field.ty.clone(),
                        cfg: field.cfg.clone(),
                        annotations: field.annotations.clone(),
                        documentation: field.documentation.clone(),
                    });
                }
            }

            self.fields = overriden_fields;
        } else if let Some(r) = rules.not_none() {
            self.fields = self
                .fields
                .iter()
                .map(|field| Field {
                    name: r
                        .apply(&field.name, IdentifierType::StructMember)
                        .into_owned(),
                    ty: field.ty.clone(),
                    cfg: field.cfg.clone(),
                    annotations: field.annotations.clone(),
                    documentation: field.documentation.clone(),
                })
                .collect();
        } else if self.tuple_union {
            // If we don't have any rules for a tuple union, prefix them with
            // an underscore so it still compiles
            for field in &mut self.fields {
                field.name.insert(0, '_');
            }
        }
    }

    fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
        for field in &self.fields {
            field
                .ty
                .add_dependencies_ignoring_generics(&self.generic_params, library, out);
        }
    }

    fn instantiate_monomorph(
        &self,
        generic_values: &[Type],
        library: &Library,
        out: &mut Monomorphs,
    ) {
        assert!(
            self.generic_params.len() > 0,
            "{} is not generic",
            self.path
        );
        assert!(
            self.generic_params.len() == generic_values.len(),
            "{} has {} params but is being instantiated with {} values",
            self.path,
            self.generic_params.len(),
            generic_values.len(),
        );

        let mappings = self
            .generic_params
            .iter()
            .zip(generic_values.iter())
            .collect::<Vec<_>>();

        let mangled_path = mangle::mangle_path(
            &self.path,
            generic_values,
            &library.get_config().export.mangle,
        );

        let monomorph = Union::new(
            mangled_path,
            GenericParams::default(),
            self.fields
                .iter()
                .map(|field| Field {
                    name: field.name.clone(),
                    ty: field.ty.specialize(&mappings),
                    cfg: field.cfg.clone(),
                    annotations: field.annotations.clone(),
                    documentation: field.documentation.clone(),
                })
                .collect(),
            self.alignment,
            self.tuple_union,
            self.cfg.clone(),
            self.annotations.clone(),
            self.documentation.clone(),
        );

        // Instantiate any monomorphs for any generic paths we may have just created.
        monomorph.add_monomorphs(library, out);

        out.insert_union(self, monomorph, generic_values.to_owned());
    }
}

impl Source for Union {
    fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
        let condition = self.cfg.to_condition(config);
        condition.write_before(config, out);

        self.documentation.write(config, out);

        self.generic_params.write(config, out);

        // The following results in
        // C++ or C with Tag as style:
        //   union Name {
        // C with Type only style:
        //   typedef union {
        // C with Both as style:
        //   typedef union Name {
        match config.language {
            Language::C if config.style.generate_typedef() => out.write("typedef "),
            Language::C | Language::Cxx => {}
            Language::Cython => out.write(config.style.cython_def()),
        }

        out.write("union");

        // Cython supports `packed` on structs (see comments there), but not on unions.
        if config.language != Language::Cython {
            if let Some(align) = self.alignment {
                match align {
                    ReprAlign::Packed => {
                        if let Some(ref anno) = config.layout.packed {
                            write!(out, " {}", anno);
                        }
                    }
                    ReprAlign::Align(n) => {
                        if let Some(ref anno) = config.layout.aligned_n {
                            write!(out, " {}({})", anno, n);
                        }
                    }
                }
            }
        }

        if config.language != Language::C || config.style.generate_tag() {
            write!(out, " {}", self.export_name);
        }

        out.open_brace();

        // Emit the pre_body section, if relevant
        if let Some(body) = config.export.pre_body(&self.path) {
            out.write_raw_block(body);
            out.new_line();
        }

        out.write_vertical_source_list(&self.fields, ListType::Cap(";"));
        if config.language == Language::Cython && self.fields.is_empty() {
            out.write("pass");
        }

        // Emit the post_body section, if relevant
        if let Some(body) = config.export.post_body(&self.path) {
            out.new_line();
            out.write_raw_block(body);
        }

        if config.language == Language::C && config.style.generate_typedef() {
            out.close_brace(false);
            write!(out, " {};", self.export_name);
        } else {
            out.close_brace(true);
        }

        condition.write_after(config, out);
    }
}
<commit_msg>union: Fix clippy lint.<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::io::Write;

use crate::bindgen::config::{Config, Language, LayoutConfig};
use crate::bindgen::declarationtyperesolver::DeclarationTypeResolver;
use crate::bindgen::dependencies::Dependencies;
use crate::bindgen::ir::{
    AnnotationSet, Cfg, ConditionWrite, Documentation, Field, GenericParams, Item, ItemContainer,
    Path, Repr, ReprAlign, ReprStyle, ToCondition, Type,
};
use crate::bindgen::library::Library;
use crate::bindgen::mangle;
use crate::bindgen::monomorph::Monomorphs;
use crate::bindgen::rename::{IdentifierType, RenameRule};
use crate::bindgen::utilities::IterHelpers;
use crate::bindgen::writer::{ListType, Source, SourceWriter};

#[derive(Debug, Clone)]
pub struct Union {
    pub path: Path,
    pub export_name: String,
    pub generic_params: GenericParams,
    pub fields: Vec<Field>,
    pub tuple_union: bool,
    pub alignment: Option<ReprAlign>,
    pub cfg: Option<Cfg>,
    pub annotations: AnnotationSet,
    pub documentation: Documentation,
}

impl Union {
    pub fn load(
        layout_config: &LayoutConfig,
        item: &syn::ItemUnion,
        mod_cfg: Option<&Cfg>,
    ) -> Result<Union, String> {
        let repr = Repr::load(&item.attrs)?;
        if repr.style != ReprStyle::C {
            return Err("Union is not marked #[repr(C)].".to_owned());
        }

        // Ensure we can safely represent the union given the configuration.
        if let Some(align) = repr.align {
            layout_config.ensure_safe_to_represent(&align)?;
        }

        let path = Path::new(item.ident.to_string());

        let (fields, tuple_union) = {
            let out = item
                .fields
                .named
                .iter()
                .try_skip_map(|field| Field::load(field, &path))?;
            (out, false)
        };

        Ok(Union::new(
            path,
            GenericParams::new(&item.generics),
            fields,
            repr.align,
            tuple_union,
            Cfg::append(mod_cfg, Cfg::load(&item.attrs)),
            AnnotationSet::load(&item.attrs)?,
            Documentation::load(&item.attrs),
        ))
    }

    #[allow(clippy::too_many_arguments)]
    pub fn new(
        path: Path,
        generic_params: GenericParams,
        fields: Vec<Field>,
        alignment: Option<ReprAlign>,
        tuple_union: bool,
        cfg: Option<Cfg>,
        annotations: AnnotationSet,
        documentation: Documentation,
    ) -> Self {
        let export_name = path.name().to_owned();
        Self {
            path,
            export_name,
            generic_params,
            fields,
            tuple_union,
            alignment,
            cfg,
            annotations,
            documentation,
        }
    }

    pub fn simplify_standard_types(&mut self, config: &Config) {
        for field in &mut self.fields {
            field.ty.simplify_standard_types(config);
        }
    }

    pub fn is_generic(&self) -> bool {
        self.generic_params.len() > 0
    }

    pub fn add_monomorphs(&self, library: &Library, out: &mut Monomorphs) {
        // Generic unions can instantiate monomorphs only once they've been
        // instantiated. See `instantiate_monomorph` for more details.
        if self.is_generic() {
            return;
        }

        for field in &self.fields {
            field.ty.add_monomorphs(library, out);
        }
    }

    pub fn mangle_paths(&mut self, monomorphs: &Monomorphs) {
        for field in &mut self.fields {
            field.ty.mangle_paths(monomorphs);
        }
    }
}

impl Item for Union {
    fn path(&self) -> &Path {
        &self.path
    }

    fn export_name(&self) -> &str {
        &self.export_name
    }

    fn cfg(&self) -> Option<&Cfg> {
        self.cfg.as_ref()
    }

    fn annotations(&self) -> &AnnotationSet {
        &self.annotations
    }

    fn annotations_mut(&mut self) -> &mut AnnotationSet {
        &mut self.annotations
    }

    fn container(&self) -> ItemContainer {
        ItemContainer::Union(self.clone())
    }

    fn collect_declaration_types(&self, resolver: &mut DeclarationTypeResolver) {
        resolver.add_union(&self.path);
    }

    fn resolve_declaration_types(&mut self, resolver: &DeclarationTypeResolver) {
        for field in &mut self.fields {
            field.ty.resolve_declaration_types(resolver);
        }
    }

    fn rename_for_config(&mut self, config: &Config) {
        config.export.rename(&mut self.export_name);
        for field in &mut self.fields {
            field.ty.rename_for_config(config, &self.generic_params);
        }

        let rules = self
            .annotations
            .parse_atom::<RenameRule>("rename-all")
            .unwrap_or(config.structure.rename_fields);

        if let Some(o) = self.annotations.list("field-names") {
            let mut overriden_fields = Vec::new();

            for (i, field) in self.fields.iter().enumerate() {
                if i >= o.len() {
                    overriden_fields.push(field.clone());
                } else {
                    overriden_fields.push(Field {
                        name: o[i].clone(),
                        ty: field.ty.clone(),
                        cfg: field.cfg.clone(),
                        annotations: field.annotations.clone(),
                        documentation: field.documentation.clone(),
                    });
                }
            }

            self.fields = overriden_fields;
        } else if let Some(r) = rules.not_none() {
            self.fields = self
                .fields
                .iter()
                .map(|field| Field {
                    name: r
                        .apply(&field.name, IdentifierType::StructMember)
                        .into_owned(),
                    ty: field.ty.clone(),
                    cfg: field.cfg.clone(),
                    annotations: field.annotations.clone(),
                    documentation: field.documentation.clone(),
                })
                .collect();
        } else if self.tuple_union {
            // If we don't have any rules for a tuple union, prefix them with
            // an underscore so it still compiles
            for field in &mut self.fields {
                field.name.insert(0, '_');
            }
        }
    }

    fn add_dependencies(&self, library: &Library, out: &mut Dependencies) {
        for field in &self.fields {
            field
                .ty
                .add_dependencies_ignoring_generics(&self.generic_params, library, out);
        }
    }

    fn instantiate_monomorph(
        &self,
        generic_values: &[Type],
        library: &Library,
        out: &mut Monomorphs,
    ) {
        assert!(
            self.generic_params.len() > 0,
            "{} is not generic",
            self.path
        );
        assert!(
            self.generic_params.len() == generic_values.len(),
            "{} has {} params but is being instantiated with {} values",
            self.path,
            self.generic_params.len(),
            generic_values.len(),
        );

        let mappings = self
            .generic_params
            .iter()
            .zip(generic_values.iter())
            .collect::<Vec<_>>();

        let mangled_path = mangle::mangle_path(
            &self.path,
            generic_values,
            &library.get_config().export.mangle,
        );

        let monomorph = Union::new(
            mangled_path,
            GenericParams::default(),
            self.fields
                .iter()
                .map(|field| Field {
                    name: field.name.clone(),
                    ty: field.ty.specialize(&mappings),
                    cfg: field.cfg.clone(),
                    annotations: field.annotations.clone(),
                    documentation: field.documentation.clone(),
                })
                .collect(),
            self.alignment,
            self.tuple_union,
            self.cfg.clone(),
            self.annotations.clone(),
            self.documentation.clone(),
        );

        // Instantiate any monomorphs for any generic paths we may have just created.
        monomorph.add_monomorphs(library, out);

        out.insert_union(self, monomorph, generic_values.to_owned());
    }
}

impl Source for Union {
    fn write<F: Write>(&self, config: &Config, out: &mut SourceWriter<F>) {
        let condition = self.cfg.to_condition(config);
        condition.write_before(config, out);

        self.documentation.write(config, out);

        self.generic_params.write(config, out);

        // The following results in
        // C++ or C with Tag as style:
        //   union Name {
        // C with Type only style:
        //   typedef union {
        // C with Both as style:
        //   typedef union Name {
        match config.language {
            Language::C if config.style.generate_typedef() => out.write("typedef "),
            Language::C | Language::Cxx => {}
            Language::Cython => out.write(config.style.cython_def()),
        }

        out.write("union");

        // Cython supports `packed` on structs (see comments there), but not on unions.
        if config.language != Language::Cython {
            if let Some(align) = self.alignment {
                match align {
                    ReprAlign::Packed => {
                        if let Some(ref anno) = config.layout.packed {
                            write!(out, " {}", anno);
                        }
                    }
                    ReprAlign::Align(n) => {
                        if let Some(ref anno) = config.layout.aligned_n {
                            write!(out, " {}({})", anno, n);
                        }
                    }
                }
            }
        }

        if config.language != Language::C || config.style.generate_tag() {
            write!(out, " {}", self.export_name);
        }

        out.open_brace();

        // Emit the pre_body section, if relevant
        if let Some(body) = config.export.pre_body(&self.path) {
            out.write_raw_block(body);
            out.new_line();
        }

        out.write_vertical_source_list(&self.fields, ListType::Cap(";"));
        if config.language == Language::Cython && self.fields.is_empty() {
            out.write("pass");
        }

        // Emit the post_body section, if relevant
        if let Some(body) = config.export.post_body(&self.path) {
            out.new_line();
            out.write_raw_block(body);
        }

        if config.language == Language::C && config.style.generate_typedef() {
            out.close_brace(false);
            write!(out, " {};", self.export_name);
        } else {
            out.close_brace(true);
        }

        condition.write_after(config, out);
    }
}
<|endoftext|>


<commit_before>use std::fs::{self, File, OpenOptions};
use std::io::*;
use std::io;
use std::path::{Path, PathBuf, Display};

use term::color::CYAN;
use fs2::{FileExt, lock_contended_error};

use util::{CargoResult, ChainError, Config, human};

pub struct FileLock {
    f: Option<File>,
    path: PathBuf,
    state: State,
}

#[derive(PartialEq)]
enum State {
    Unlocked,
    Shared,
    Exclusive,
}

impl FileLock {
    /// Returns the underlying file handle of this lock.
    pub fn file(&self) -> &File {
        self.f.as_ref().unwrap()
    }

    /// Returns the underlying path that this lock points to.
    ///
    /// Note that special care must be taken to ensure that the path is not
    /// referenced outside the lifetime of this lock.
    pub fn path(&self) -> &Path {
        assert!(self.state != State::Unlocked);
        &self.path
    }

    /// Returns the parent path containing this file
    pub fn parent(&self) -> &Path {
        assert!(self.state != State::Unlocked);
        self.path.parent().unwrap()
    }

    /// Removes all sibling files to this locked file.
    ///
    /// This can be useful if a directory is locked with a sentinel file but it
    /// needs to be cleared out as it may be corrupt.
    pub fn remove_siblings(&self) -> io::Result<()> {
        let path = self.path();
        for entry in try!(path.parent().unwrap().read_dir()) {
            let entry = try!(entry);
            if Some(&entry.file_name()[..]) == path.file_name() {
                continue
            }
            let kind = try!(entry.file_type());
            if kind.is_dir() {
                try!(fs::remove_dir_all(entry.path()));
            } else {
                try!(fs::remove_file(entry.path()));
            }
        }
        Ok(())
    }
}

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

impl Seek for FileLock {
    fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
        self.file().seek(to)
    }
}

impl Write for FileLock {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.file().write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.file().flush()
    }
}

impl Drop for FileLock {
    fn drop(&mut self) {
        if self.state != State::Unlocked {
            if let Some(f) = self.f.take() {
                let _ = f.unlock();
            }
        }
    }
}

/// A "filesystem" is intended to be a globally shared, hence locked, resource
/// in Cargo.
///
/// The `Path` of a filesystem cannot be learned unless it's done in a locked
/// fashion, and otherwise functions on this structure are prepared to handle
/// concurrent invocations across multiple instances of Cargo.
#[derive(Clone, Debug)]
pub struct Filesystem {
    root: PathBuf,
}

impl Filesystem {
    /// Creates a new filesystem to be rooted at the given path.
    pub fn new(path: PathBuf) -> Filesystem {
        Filesystem { root: path }
    }

    /// Like `Path::join`, creates a new filesystem rooted at this filesystem
    /// joined with the given path.
    pub fn join<T: AsRef<Path>>(&self, other: T) -> Filesystem {
        Filesystem::new(self.root.join(other))
    }

    /// Like `Path::push`, pushes a new path component onto this filesystem.
    pub fn push<T: AsRef<Path>>(&mut self, other: T) {
        self.root.push(other);
    }

    /// Consumes this filesystem and returns the underlying `PathBuf`.
    ///
    /// Note that this is a relatively dangerous operation and should be used
    /// with great caution!.
    pub fn into_path_unlocked(self) -> PathBuf {
        self.root
    }

    /// Creates the directory pointed to by this filesystem.
    ///
    /// Handles errors where other Cargo processes are also attempting to
    /// concurrently create this directory.
    pub fn create_dir(&self) -> io::Result<()> {
        return create_dir_all(&self.root);
    }

    /// Returns an adaptor that can be used to print the path of this
    /// filesystem.
    pub fn display(&self) -> Display {
        self.root.display()
    }

    /// Opens exclusive access to a file, returning the locked version of a
    /// file.
    ///
    /// This function will create a file at `path` if it doesn't already exist
    /// (including intermediate directories), and then it will acquire an
    /// exclusive lock on `path`. If the process must block waiting for the
    /// lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has
    /// read/write access to the underlying file.
    pub fn open_rw<P>(&self,
                      path: P,
                      config: &Config,
                      msg: &str) -> CargoResult<FileLock>
        where P: AsRef<Path>
    {
        self.open(path.as_ref(),
                  OpenOptions::new().read(true).write(true).create(true),
                  State::Exclusive,
                  config,
                  msg)
    }

    /// Opens shared access to a file, returning the locked version of a file.
    ///
    /// This function will fail if `path` doesn't already exist, but if it does
    /// then it will acquire a shared lock on `path`. If the process must block
    /// waiting for the lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has read
    /// access to the underlying file. Any writes to the file will return an
    /// error.
    pub fn open_ro<P>(&self,
                      path: P,
                      config: &Config,
                      msg: &str) -> CargoResult<FileLock>
        where P: AsRef<Path>
    {
        self.open(path.as_ref(),
                  OpenOptions::new().read(true),
                  State::Shared,
                  config,
                  msg)
    }

    fn open(&self,
            path: &Path,
            opts: &OpenOptions,
            state: State,
            config: &Config,
            msg: &str) -> CargoResult<FileLock> {
        let path = self.root.join(path);

        // If we want an exclusive lock then if we fail because of NotFound it's
        // likely because an intermediate directory didn't exist, so try to
        // create the directory and then continue.
        let f = try!(opts.open(&path).or_else(|e| {
            if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {
                try!(create_dir_all(path.parent().unwrap()));
                opts.open(&path)
            } else {
                Err(e)
            }
        }).chain_error(|| {
            human(format!("failed to open: {}", path.display()))
        }));
        match state {
            State::Exclusive => {
                try!(acquire(config, msg, &path,
                             &|| f.try_lock_exclusive(),
                             &|| f.lock_exclusive()));
            }
            State::Shared => {
                try!(acquire(config, msg, &path,
                             &|| f.try_lock_shared(),
                             &|| f.lock_shared()));
            }
            State::Unlocked => {}

        }
        Ok(FileLock { f: Some(f), path: path, state: state })
    }
}

/// Acquires a lock on a file in a "nice" manner.
///
/// Almost all long-running blocking actions in Cargo have a status message
/// associated with them as we're not sure how long they'll take. Whenever a
/// conflicted file lock happens, this is the case (we're not sure when the lock
/// will be released).
///
/// This function will acquire the lock on a `path`, printing out a nice message
/// to the console if we have to wait for it. It will first attempt to use `try`
/// to acquire a lock on the crate, and in the case of contention it will emit a
/// status message based on `msg` to `config`'s shell, and then use `block` to
/// block waiting to acquire a lock.
///
/// Returns an error if the lock could not be acquired or if any error other
/// than a contention error happens.
fn acquire(config: &Config,
           msg: &str,
           path: &Path,
           try: &Fn() -> io::Result<()>,
           block: &Fn() -> io::Result<()>) -> CargoResult<()> {
    match try() {
        Ok(()) => return Ok(()),
        Err(e) => {
            if e.raw_os_error() != lock_contended_error().raw_os_error() {
                return Err(human(e)).chain_error(|| {
                    human(format!("failed to lock file: {}", path.display()))
                })
            }
        }
    }
    let msg = format!("waiting for file lock on {}", msg);
    try!(config.shell().err().say_status("Blocking", &msg, CYAN, true));

    block().chain_error(|| {
        human(format!("failed to lock file: {}", path.display()))
    })
}

fn create_dir_all(path: &Path) -> io::Result<()> {
    match create_dir(path) {
        Ok(()) => return Ok(()),
        Err(e) => {
            if e.kind() == io::ErrorKind::NotFound {
                if let Some(p) = path.parent() {
                    return create_dir_all(p).and_then(|()| create_dir(path))
                }
            }
            Err(e)
        }
    }
}

fn create_dir(path: &Path) -> io::Result<()> {
    match fs::create_dir(path) {
        Ok(()) => Ok(()),
        Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()),
        Err(e) => Err(e),
    }
}
<commit_msg>Don't use flock on NFS mounts<commit_after>use std::fs::{self, File, OpenOptions};
use std::io::*;
use std::io;
use std::path::{Path, PathBuf, Display};

use term::color::CYAN;
use fs2::{FileExt, lock_contended_error};

use util::{CargoResult, ChainError, Config, human};

pub struct FileLock {
    f: Option<File>,
    path: PathBuf,
    state: State,
}

#[derive(PartialEq)]
enum State {
    Unlocked,
    Shared,
    Exclusive,
}

impl FileLock {
    /// Returns the underlying file handle of this lock.
    pub fn file(&self) -> &File {
        self.f.as_ref().unwrap()
    }

    /// Returns the underlying path that this lock points to.
    ///
    /// Note that special care must be taken to ensure that the path is not
    /// referenced outside the lifetime of this lock.
    pub fn path(&self) -> &Path {
        assert!(self.state != State::Unlocked);
        &self.path
    }

    /// Returns the parent path containing this file
    pub fn parent(&self) -> &Path {
        assert!(self.state != State::Unlocked);
        self.path.parent().unwrap()
    }

    /// Removes all sibling files to this locked file.
    ///
    /// This can be useful if a directory is locked with a sentinel file but it
    /// needs to be cleared out as it may be corrupt.
    pub fn remove_siblings(&self) -> io::Result<()> {
        let path = self.path();
        for entry in try!(path.parent().unwrap().read_dir()) {
            let entry = try!(entry);
            if Some(&entry.file_name()[..]) == path.file_name() {
                continue
            }
            let kind = try!(entry.file_type());
            if kind.is_dir() {
                try!(fs::remove_dir_all(entry.path()));
            } else {
                try!(fs::remove_file(entry.path()));
            }
        }
        Ok(())
    }
}

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

impl Seek for FileLock {
    fn seek(&mut self, to: SeekFrom) -> io::Result<u64> {
        self.file().seek(to)
    }
}

impl Write for FileLock {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.file().write(buf)
    }

    fn flush(&mut self) -> io::Result<()> {
        self.file().flush()
    }
}

impl Drop for FileLock {
    fn drop(&mut self) {
        if self.state != State::Unlocked {
            if let Some(f) = self.f.take() {
                let _ = f.unlock();
            }
        }
    }
}

/// A "filesystem" is intended to be a globally shared, hence locked, resource
/// in Cargo.
///
/// The `Path` of a filesystem cannot be learned unless it's done in a locked
/// fashion, and otherwise functions on this structure are prepared to handle
/// concurrent invocations across multiple instances of Cargo.
#[derive(Clone, Debug)]
pub struct Filesystem {
    root: PathBuf,
}

impl Filesystem {
    /// Creates a new filesystem to be rooted at the given path.
    pub fn new(path: PathBuf) -> Filesystem {
        Filesystem { root: path }
    }

    /// Like `Path::join`, creates a new filesystem rooted at this filesystem
    /// joined with the given path.
    pub fn join<T: AsRef<Path>>(&self, other: T) -> Filesystem {
        Filesystem::new(self.root.join(other))
    }

    /// Like `Path::push`, pushes a new path component onto this filesystem.
    pub fn push<T: AsRef<Path>>(&mut self, other: T) {
        self.root.push(other);
    }

    /// Consumes this filesystem and returns the underlying `PathBuf`.
    ///
    /// Note that this is a relatively dangerous operation and should be used
    /// with great caution!.
    pub fn into_path_unlocked(self) -> PathBuf {
        self.root
    }

    /// Creates the directory pointed to by this filesystem.
    ///
    /// Handles errors where other Cargo processes are also attempting to
    /// concurrently create this directory.
    pub fn create_dir(&self) -> io::Result<()> {
        return create_dir_all(&self.root);
    }

    /// Returns an adaptor that can be used to print the path of this
    /// filesystem.
    pub fn display(&self) -> Display {
        self.root.display()
    }

    /// Opens exclusive access to a file, returning the locked version of a
    /// file.
    ///
    /// This function will create a file at `path` if it doesn't already exist
    /// (including intermediate directories), and then it will acquire an
    /// exclusive lock on `path`. If the process must block waiting for the
    /// lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has
    /// read/write access to the underlying file.
    pub fn open_rw<P>(&self,
                      path: P,
                      config: &Config,
                      msg: &str) -> CargoResult<FileLock>
        where P: AsRef<Path>
    {
        self.open(path.as_ref(),
                  OpenOptions::new().read(true).write(true).create(true),
                  State::Exclusive,
                  config,
                  msg)
    }

    /// Opens shared access to a file, returning the locked version of a file.
    ///
    /// This function will fail if `path` doesn't already exist, but if it does
    /// then it will acquire a shared lock on `path`. If the process must block
    /// waiting for the lock, the `msg` is printed to `config`.
    ///
    /// The returned file can be accessed to look at the path and also has read
    /// access to the underlying file. Any writes to the file will return an
    /// error.
    pub fn open_ro<P>(&self,
                      path: P,
                      config: &Config,
                      msg: &str) -> CargoResult<FileLock>
        where P: AsRef<Path>
    {
        self.open(path.as_ref(),
                  OpenOptions::new().read(true),
                  State::Shared,
                  config,
                  msg)
    }

    fn open(&self,
            path: &Path,
            opts: &OpenOptions,
            state: State,
            config: &Config,
            msg: &str) -> CargoResult<FileLock> {
        let path = self.root.join(path);

        // If we want an exclusive lock then if we fail because of NotFound it's
        // likely because an intermediate directory didn't exist, so try to
        // create the directory and then continue.
        let f = try!(opts.open(&path).or_else(|e| {
            if e.kind() == io::ErrorKind::NotFound && state == State::Exclusive {
                try!(create_dir_all(path.parent().unwrap()));
                opts.open(&path)
            } else {
                Err(e)
            }
        }).chain_error(|| {
            human(format!("failed to open: {}", path.display()))
        }));
        match state {
            State::Exclusive => {
                try!(acquire(config, msg, &path,
                             &|| f.try_lock_exclusive(),
                             &|| f.lock_exclusive()));
            }
            State::Shared => {
                try!(acquire(config, msg, &path,
                             &|| f.try_lock_shared(),
                             &|| f.lock_shared()));
            }
            State::Unlocked => {}

        }
        Ok(FileLock { f: Some(f), path: path, state: state })
    }
}

/// Acquires a lock on a file in a "nice" manner.
///
/// Almost all long-running blocking actions in Cargo have a status message
/// associated with them as we're not sure how long they'll take. Whenever a
/// conflicted file lock happens, this is the case (we're not sure when the lock
/// will be released).
///
/// This function will acquire the lock on a `path`, printing out a nice message
/// to the console if we have to wait for it. It will first attempt to use `try`
/// to acquire a lock on the crate, and in the case of contention it will emit a
/// status message based on `msg` to `config`'s shell, and then use `block` to
/// block waiting to acquire a lock.
///
/// Returns an error if the lock could not be acquired or if any error other
/// than a contention error happens.
fn acquire(config: &Config,
           msg: &str,
           path: &Path,
           try: &Fn() -> io::Result<()>,
           block: &Fn() -> io::Result<()>) -> CargoResult<()> {

    // File locking on Unix is currently implemented via `flock`, which is known
    // to be broken on NFS. We could in theory just ignore errors that happen on
    // NFS, but apparently the failure mode [1] for `flock` on NFS is **blocking
    // forever**, even if the nonblocking flag is passed!
    //
    // As a result, we just skip all file locks entirely on NFS mounts. That
    // should avoid calling any `flock` functions at all, and it wouldn't work
    // there anyway.
    //
    // [1]: https://github.com/rust-lang/cargo/issues/2615
    if is_on_nfs_mount(path) {
        return Ok(())
    }

    match try() {
        Ok(()) => return Ok(()),
        Err(e) => {
            if e.raw_os_error() != lock_contended_error().raw_os_error() {
                return Err(human(e)).chain_error(|| {
                    human(format!("failed to lock file: {}", path.display()))
                })
            }
        }
    }
    let msg = format!("waiting for file lock on {}", msg);
    try!(config.shell().err().say_status("Blocking", &msg, CYAN, true));

    return block().chain_error(|| {
        human(format!("failed to lock file: {}", path.display()))
    });

    #[cfg(target_os = "linux")]
    fn is_on_nfs_mount(path: &Path) -> bool {
        use std::ffi::CString;
        use std::mem;
        use std::os::unix::prelude::*;
        use libc;

        let path = match CString::new(path.as_os_str().as_bytes()) {
            Ok(path) => path,
            Err(_) => return false,
        };

        unsafe {
            let mut buf: libc::statfs = mem::zeroed();
            let r = libc::statfs(path.as_ptr(), &mut buf);

            r == 0 && buf.f_type == libc::NFS_SUPER_MAGIC
        }
    }

    #[cfg(not(target_os = "linux"))]
    fn is_on_nfs_mount(_path: &Path) -> bool {
        false
    }
}

fn create_dir_all(path: &Path) -> io::Result<()> {
    match create_dir(path) {
        Ok(()) => return Ok(()),
        Err(e) => {
            if e.kind() == io::ErrorKind::NotFound {
                if let Some(p) = path.parent() {
                    return create_dir_all(p).and_then(|()| create_dir(path))
                }
            }
            Err(e)
        }
    }
}

fn create_dir(path: &Path) -> io::Result<()> {
    match fs::create_dir(path) {
        Ok(()) => Ok(()),
        Err(ref e) if e.kind() == io::ErrorKind::AlreadyExists => Ok(()),
        Err(e) => Err(e),
    }
}
<|endoftext|>