<commit_before>use std;
use std::ops::Range;
use std::collections::{HashSet, VecDeque};

use rand;
use rand::Rng;

use tcod;
use tcod::console::Console;
use tcod::Color;

use traits::{Position, Renderable};
use unit_type::UnitTypeLists;
use units::Unit;
use item::Item;

use point::Point;
use rectangle::Rectangle;

use SCREEN_WIDTH;
use SCREEN_HEIGHT;

const MAP_MIN_WIDTH: u8 = SCREEN_WIDTH;
const MAP_MIN_HEIGHT: u8 = SCREEN_HEIGHT;
pub const MAP_MAX_WIDTH: u8 = std::u8::MAX;
pub const MAP_MAX_HEIGHT: u8 = std::u8::MAX;

const ROOM_MAX_MONSTERS: u8 = 3;
// Values for I/N chance of generating monsters for a room.
const ROOM_CHANCE_OF_MONSTERS_I: u32 = 2;
const ROOM_CHANCE_OF_MONSTERS_N: u32 = 5;
const ROOM_MAX_SIZE: u8 = 10;
const ROOM_MIN_SIZE: u8 = 6;
// This value determines the maximum number of rooms for a given map size.
// This was calculated by the previous map size (80 x 45) divided by the
// previous max room count of 30.
// This should provide a similar room densiter for each map.
const ROOM_PER_TILE: u8 = 120;

const ERR_MSG_TUNNEL: &str = "Failed to create tunnel.";
const ERR_MSG_ROOM: &str = "Failed to create room.";
const ERR_MSG_ROOM_CMP: &str = "Error comparing rooms.";

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CanMoveResponse {
    Open,
    Scenery,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TileType {
    Floor,
    Wall,
}

impl TileType {
    fn get_color_not_visible(self) -> Color {
        match self {
            TileType::Floor => Color::new(50, 50, 150),
            TileType::Wall => Color::new(0, 0, 100),
        }
    }

    fn get_color_visible(self) -> Color {
        match self {
            TileType::Floor => Color::new(200, 180, 50),
            TileType::Wall => Color::new(130, 110, 50),
        }
    }

    fn get_glyph(self) -> char {
        match self {
            TileType::Floor => '.',
            TileType::Wall => '#',
        }
    }

    pub fn blocks_move(self) -> bool {
        match self {
            TileType::Floor => false,
            TileType::Wall => true,
        }
    }

    pub fn blocks_sight(self) -> bool {
        // NOTE: TCod library input is whether it's transparent, not whether it blocks.
        !match self {
            TileType::Floor => false,
            TileType::Wall => true,
        }
    }
}

#[derive(Debug)]
pub struct Tile {
    position: Point<i16>,
    tile_type: TileType,
    is_explored: bool,
    is_visible: bool,
}

impl Position for Tile {
    fn get_x(&self) -> i16 {
        self.position.x
    }

    fn get_y(&self) -> i16 {
        self.position.y
    }
}

impl Renderable for Tile {
    fn get_color(&self) -> Color {
        if !self.is_explored {
            tcod::colors::BLACK
        } else {
            match self.is_visible {
                true => self.tile_type.get_color_visible(),
                false => self.tile_type.get_color_not_visible(),
            }
        }
    }
    fn get_glyph(&self) -> char {
        self.tile_type.get_glyph()
    }
}

impl Tile {
    pub fn new(pos: Point<i16>, tile_type: TileType) -> Tile {
        Tile{
            position: pos,
            tile_type: tile_type,
            is_explored: false,
            is_visible: false,
        }
    }
}

pub struct Map {
    width: u8,
    height: u8,
    tile_map: Vec<Tile>,
    items: Vec<Item>,
    fov_map: tcod::map::Map,
}

// Init and building.
impl Map {
    pub fn init<'a>(unit_types: &'a UnitTypeLists) -> (Map, VecDeque<Unit<'a>>, Point<i16>) {
        let mut rng = rand::thread_rng();

        let map_width = rng.gen_range(MAP_MIN_WIDTH, MAP_MAX_WIDTH);
        let map_height = rng.gen_range(MAP_MIN_HEIGHT, MAP_MAX_HEIGHT);

        let mut map = vec![];
        for y in 0..map_height {
            for x in 0..map_width {
                map.push( Tile::new(Point{x: x as i16, y: y as i16}, TileType::Wall) );
            }
        }

        let mut map = Map {
            width: map_width,
            height: map_height,
            tile_map: map,
            items: vec![],
            fov_map: tcod::map::Map::new(map_width as i32, map_height as i32),
        };

        let mut npcs = VecDeque::new();

        let (rooms, player_start) = map.build_rooms(&mut rng);
        map.build_coridoors(&rooms, &mut rng);

        for room in rooms {
            if rng.gen_range(0, ROOM_CHANCE_OF_MONSTERS_N) < ROOM_CHANCE_OF_MONSTERS_I {
                map.place_npcs(&room, &unit_types, &mut npcs, &mut rng);
            }
        }

        Map::build_fov_map(&map.tile_map, &mut map.fov_map);

        (map, npcs, player_start)
    }

    fn place_npcs<'a>(&mut self, room: &Rectangle, units: &'a UnitTypeLists, npc_list: &mut VecDeque<Unit<'a>>, rng: &mut rand::ThreadRng) {
        let max_monsters = rng.gen_range(0, ROOM_MAX_MONSTERS);

        for _ in 0..max_monsters {
            let position = room.get_random_position(rng);
            let monster_type = units.get_random_type(rng);

            let monster = Unit::new(position, monster_type);
            npc_list.push_back(monster);
        }
    }

    pub fn place_item(&mut self, item: Item) {
        self.items.push(item);
    }

    fn build_rooms(&mut self, rng: &mut rand::ThreadRng) -> (Vec<Rectangle>, Point<i16>) {
        let mut rooms = vec![];
        let mut player_start = Point{x:0, y:0};

        // Casts are to avoid overflow.
        let max_rooms = self.width as u16 * self.height as u16 / ROOM_PER_TILE as u16;

        for _ in 0..max_rooms {
            let width = rng.gen_range(ROOM_MIN_SIZE, ROOM_MAX_SIZE);
            let height = rng.gen_range(ROOM_MIN_SIZE, ROOM_MAX_SIZE);

            let room = Rectangle::new(
                Point {
                    x: rng.gen_range(0, self.width as i16 - width as i16 - 1),
                    y: rng.gen_range(0, self.height as i16 - height as i16 - 1)
                },
                (width, height)
            );

            if !rooms.iter().any(|r: &Rectangle| r.is_intersecting(&room)) {
                self.create_room(&room).expect(ERR_MSG_ROOM);

                if rooms.len() == 0 {
                    player_start = room.centre();
                }

                rooms.push(room);
            }
        }

        (rooms, player_start)
    }

    fn build_coridoors(&mut self, rooms: &Vec<Rectangle>, rng: &mut rand::ThreadRng) {
        let mut sorted_rooms: Vec<_> = rooms.iter().map(|r| r).collect();
        let mut connected_rooms = HashSet::new();

        for room in rooms {
            sorted_rooms.sort_by(|a,b| (room.centre() - a.centre()).sqr_radius().partial_cmp(&(room.centre() - b.centre()).sqr_radius()).expect(ERR_MSG_ROOM_CMP) );

            for next in sorted_rooms.iter().skip(1).take(3) {
                if connected_rooms.contains(&(*next, room)) {
                    continue;
                }

                connected_rooms.insert((room, *next));

                let r1_centre = room.centre();
                let r2_centre = next.centre();

                // Decide whether to first tunnel horizontally or vertically.
                if rng.gen_weighted_bool(2) {
                    // Horizontal
                    self.create_h_tunnel(r1_centre.x..r2_centre.x, r1_centre.y).expect(ERR_MSG_TUNNEL);
                    self.create_v_tunnel(r2_centre.x, r1_centre.y..r2_centre.y).expect(ERR_MSG_TUNNEL);
                } else {
                    // Vertical
                    self.create_v_tunnel(r1_centre.x, r1_centre.y..r2_centre.y).expect(ERR_MSG_TUNNEL);
                    self.create_h_tunnel(r1_centre.x..r2_centre.x, r2_centre.y).expect(ERR_MSG_TUNNEL);
                }
            }
        }
    }

    fn create_v_tunnel(&mut self, x: i16, y_r: Range<i16>) -> Result<(),()> {
        // Need to handle the case where start > end.
        // I decided to do it here to make it easier to call the function.
        // Because this is a half-open range, we need to adjust the start
        // and end to account for that.
        let y_r = if y_r.start > y_r.end {
            y_r.end+1 .. y_r.start+1
        } else {
            y_r
        };

        for y in y_r {
            let pos = Point{x:x, y:y};
            self.set_tile_type(pos, TileType::Floor)?;
        }

        Ok(())
    }

    fn create_h_tunnel(&mut self, x_r: Range<i16>, y: i16) -> Result<(),()> {
        // Need to handle the case where start > end.
        // I decided to do it here to make it easier to call the function.
        // Because this is a half-open range, we need to adjust the start
        // and end to account for that.
        let x_r = if x_r.start > x_r.end {
            x_r.end+1 .. x_r.start+1
        } else {
            x_r
        };

        for x in x_r {
            let pos = Point{x:x, y:y};
            self.set_tile_type(pos, TileType::Floor)?;
        }

        Ok(())
    }

    fn create_room(&mut self, rect: &Rectangle) -> Result<(),()> {
        for y in (rect.top_left.y+1)..rect.bottom_right.y {
            for x in (rect.top_left.x+1)..rect.bottom_right.x {
                let pos = Point{x:x, y:y};
                self.set_tile_type(pos, TileType::Floor)?;
            }
        }

        Ok(())
    }
}

// Rendering
impl Map {
    pub fn render_map<T: Console>(&self, cons: &mut T) {
        for tile in self.tile_map.iter() {
            tile.render(cons);
        }

        for item in self.items.iter() {
            item.render(cons);
        }
    }
}

// Queries
impl Map {
    pub fn point_in_map(&self, Point{x,y}: Point<i16>) -> bool {
        x >= 0 && x < self.width as i16 && y >= 0 && y < self.height as i16
    }

    pub fn get_tile_type(&self, pos: Point<i16>) -> Result<TileType,()> {
        if !self.point_in_map(pos) {
            Err(())
        } else {
            let Point{x, y} = pos;
            Ok(self.tile_map[y as usize * self.width as usize + x as usize].tile_type)
        }
    }

    pub fn can_move_to(&self, pos: Point<i16>) -> CanMoveResponse {
        match self.get_tile_type(pos) {
            Ok(tile) if tile.blocks_move() => CanMoveResponse::Scenery,
            _ => CanMoveResponse::Open
        }
    }

    pub fn get_map_size(&self) -> (u8, u8) {
        (self.width, self.height)
    }

    pub fn point_in_fov(&self, Point{x,y}: Point<i16>) -> bool {
        self.fov_map.is_in_fov(x as i32, y as i32)
    }

    fn build_fov_map(tiles: &Vec<Tile>, fov_map: &mut tcod::map::Map) {
        for tile in tiles.iter() {
            let pos = tile.get_position();
            fov_map.set(pos.x as i32, pos.y as i32, tile.tile_type.blocks_sight(), tile.tile_type.blocks_move());
        }
    }

    pub fn get_pathfinding_map(&self) -> tcod::Map {
        let mut path_map = tcod::Map::new(self.width as i32, self.height as i32);
        Map::build_fov_map(&self.tile_map, &mut path_map);
        path_map
    }
}

// Updating
impl Map {
    pub fn update_fov(&mut self, Point{x, y}: Point<i16>, light_radius: u8) {
        self.fov_map.compute_fov(x as i32, y as i32, light_radius as i32, true, tcod::map::FovAlgorithm::Permissive0);

        // I've opted to update the tile map here, because it doesn't make sense that
        // a function for rendering should need to mutate the object.
        // It does make this function more expensive to run, but it won't be run
        // too frequently.
        for tile in self.tile_map.iter_mut() {
            tile.is_visible = self.fov_map.is_in_fov(tile.get_x() as i32, tile.get_y() as i32);

            if tile.is_visible {
                tile.is_explored = true;
            }
        }
    }

    fn set_tile_type(&mut self, pos: Point<i16>, new_tile: TileType) -> Result<(),()> {
        if !self.point_in_map(pos) {
            Err(())
        } else {
            let Point{x,y} = pos;
            self.tile_map[y as usize * self.width as usize + x as usize].tile_type = new_tile;

            Ok(())
        }
    }
}<commit_msg>Moved blocks_sight inversion to call to tcod library.<commit_after>use std;
use std::ops::Range;
use std::collections::{HashSet, VecDeque};

use rand;
use rand::Rng;

use tcod;
use tcod::console::Console;
use tcod::Color;

use traits::{Position, Renderable};
use unit_type::UnitTypeLists;
use units::Unit;
use item::Item;

use point::Point;
use rectangle::Rectangle;

use SCREEN_WIDTH;
use SCREEN_HEIGHT;

const MAP_MIN_WIDTH: u8 = SCREEN_WIDTH;
const MAP_MIN_HEIGHT: u8 = SCREEN_HEIGHT;
pub const MAP_MAX_WIDTH: u8 = std::u8::MAX;
pub const MAP_MAX_HEIGHT: u8 = std::u8::MAX;

const ROOM_MAX_MONSTERS: u8 = 3;
// Values for I/N chance of generating monsters for a room.
const ROOM_CHANCE_OF_MONSTERS_I: u32 = 2;
const ROOM_CHANCE_OF_MONSTERS_N: u32 = 5;
const ROOM_MAX_SIZE: u8 = 10;
const ROOM_MIN_SIZE: u8 = 6;
// This value determines the maximum number of rooms for a given map size.
// This was calculated by the previous map size (80 x 45) divided by the
// previous max room count of 30.
// This should provide a similar room densiter for each map.
const ROOM_PER_TILE: u8 = 120;

const ERR_MSG_TUNNEL: &str = "Failed to create tunnel.";
const ERR_MSG_ROOM: &str = "Failed to create room.";
const ERR_MSG_ROOM_CMP: &str = "Error comparing rooms.";

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CanMoveResponse {
    Open,
    Scenery,
}

#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum TileType {
    Floor,
    Wall,
}

impl TileType {
    fn get_color_not_visible(self) -> Color {
        match self {
            TileType::Floor => Color::new(50, 50, 150),
            TileType::Wall => Color::new(0, 0, 100),
        }
    }

    fn get_color_visible(self) -> Color {
        match self {
            TileType::Floor => Color::new(200, 180, 50),
            TileType::Wall => Color::new(130, 110, 50),
        }
    }

    fn get_glyph(self) -> char {
        match self {
            TileType::Floor => '.',
            TileType::Wall => '#',
        }
    }

    pub fn blocks_move(self) -> bool {
        match self {
            TileType::Floor => false,
            TileType::Wall => true,
        }
    }

    pub fn blocks_sight(self) -> bool {
        match self {
            TileType::Floor => false,
            TileType::Wall => true,
        }
    }
}

#[derive(Debug)]
pub struct Tile {
    position: Point<i16>,
    tile_type: TileType,
    is_explored: bool,
    is_visible: bool,
}

impl Position for Tile {
    fn get_x(&self) -> i16 {
        self.position.x
    }

    fn get_y(&self) -> i16 {
        self.position.y
    }
}

impl Renderable for Tile {
    fn get_color(&self) -> Color {
        if !self.is_explored {
            tcod::colors::BLACK
        } else {
            match self.is_visible {
                true => self.tile_type.get_color_visible(),
                false => self.tile_type.get_color_not_visible(),
            }
        }
    }
    fn get_glyph(&self) -> char {
        self.tile_type.get_glyph()
    }
}

impl Tile {
    pub fn new(pos: Point<i16>, tile_type: TileType) -> Tile {
        Tile{
            position: pos,
            tile_type: tile_type,
            is_explored: false,
            is_visible: false,
        }
    }
}

pub struct Map {
    width: u8,
    height: u8,
    tile_map: Vec<Tile>,
    items: Vec<Item>,
    fov_map: tcod::map::Map,
}

// Init and building.
impl Map {
    pub fn init<'a>(unit_types: &'a UnitTypeLists) -> (Map, VecDeque<Unit<'a>>, Point<i16>) {
        let mut rng = rand::thread_rng();

        let map_width = rng.gen_range(MAP_MIN_WIDTH, MAP_MAX_WIDTH);
        let map_height = rng.gen_range(MAP_MIN_HEIGHT, MAP_MAX_HEIGHT);

        let mut map = vec![];
        for y in 0..map_height {
            for x in 0..map_width {
                map.push( Tile::new(Point{x: x as i16, y: y as i16}, TileType::Wall) );
            }
        }

        let mut map = Map {
            width: map_width,
            height: map_height,
            tile_map: map,
            items: vec![],
            fov_map: tcod::map::Map::new(map_width as i32, map_height as i32),
        };

        let mut npcs = VecDeque::new();

        let (rooms, player_start) = map.build_rooms(&mut rng);
        map.build_coridoors(&rooms, &mut rng);

        for room in rooms {
            if rng.gen_range(0, ROOM_CHANCE_OF_MONSTERS_N) < ROOM_CHANCE_OF_MONSTERS_I {
                map.place_npcs(&room, &unit_types, &mut npcs, &mut rng);
            }
        }

        Map::build_fov_map(&map.tile_map, &mut map.fov_map);

        (map, npcs, player_start)
    }

    fn place_npcs<'a>(&mut self, room: &Rectangle, units: &'a UnitTypeLists, npc_list: &mut VecDeque<Unit<'a>>, rng: &mut rand::ThreadRng) {
        let max_monsters = rng.gen_range(0, ROOM_MAX_MONSTERS);

        for _ in 0..max_monsters {
            let position = room.get_random_position(rng);
            let monster_type = units.get_random_type(rng);

            let monster = Unit::new(position, monster_type);
            npc_list.push_back(monster);
        }
    }

    pub fn place_item(&mut self, item: Item) {
        self.items.push(item);
    }

    fn build_rooms(&mut self, rng: &mut rand::ThreadRng) -> (Vec<Rectangle>, Point<i16>) {
        let mut rooms = vec![];
        let mut player_start = Point{x:0, y:0};

        // Casts are to avoid overflow.
        let max_rooms = self.width as u16 * self.height as u16 / ROOM_PER_TILE as u16;

        for _ in 0..max_rooms {
            let width = rng.gen_range(ROOM_MIN_SIZE, ROOM_MAX_SIZE);
            let height = rng.gen_range(ROOM_MIN_SIZE, ROOM_MAX_SIZE);

            let room = Rectangle::new(
                Point {
                    x: rng.gen_range(0, self.width as i16 - width as i16 - 1),
                    y: rng.gen_range(0, self.height as i16 - height as i16 - 1)
                },
                (width, height)
            );

            if !rooms.iter().any(|r: &Rectangle| r.is_intersecting(&room)) {
                self.create_room(&room).expect(ERR_MSG_ROOM);

                if rooms.len() == 0 {
                    player_start = room.centre();
                }

                rooms.push(room);
            }
        }

        (rooms, player_start)
    }

    fn build_coridoors(&mut self, rooms: &Vec<Rectangle>, rng: &mut rand::ThreadRng) {
        let mut sorted_rooms: Vec<_> = rooms.iter().map(|r| r).collect();
        let mut connected_rooms = HashSet::new();

        for room in rooms {
            sorted_rooms.sort_by(|a,b| (room.centre() - a.centre()).sqr_radius().partial_cmp(&(room.centre() - b.centre()).sqr_radius()).expect(ERR_MSG_ROOM_CMP) );

            for next in sorted_rooms.iter().skip(1).take(3) {
                if connected_rooms.contains(&(*next, room)) {
                    continue;
                }

                connected_rooms.insert((room, *next));

                let r1_centre = room.centre();
                let r2_centre = next.centre();

                // Decide whether to first tunnel horizontally or vertically.
                if rng.gen_weighted_bool(2) {
                    // Horizontal
                    self.create_h_tunnel(r1_centre.x..r2_centre.x, r1_centre.y).expect(ERR_MSG_TUNNEL);
                    self.create_v_tunnel(r2_centre.x, r1_centre.y..r2_centre.y).expect(ERR_MSG_TUNNEL);
                } else {
                    // Vertical
                    self.create_v_tunnel(r1_centre.x, r1_centre.y..r2_centre.y).expect(ERR_MSG_TUNNEL);
                    self.create_h_tunnel(r1_centre.x..r2_centre.x, r2_centre.y).expect(ERR_MSG_TUNNEL);
                }
            }
        }
    }

    fn create_v_tunnel(&mut self, x: i16, y_r: Range<i16>) -> Result<(),()> {
        // Need to handle the case where start > end.
        // I decided to do it here to make it easier to call the function.
        // Because this is a half-open range, we need to adjust the start
        // and end to account for that.
        let y_r = if y_r.start > y_r.end {
            y_r.end+1 .. y_r.start+1
        } else {
            y_r
        };

        for y in y_r {
            let pos = Point{x:x, y:y};
            self.set_tile_type(pos, TileType::Floor)?;
        }

        Ok(())
    }

    fn create_h_tunnel(&mut self, x_r: Range<i16>, y: i16) -> Result<(),()> {
        // Need to handle the case where start > end.
        // I decided to do it here to make it easier to call the function.
        // Because this is a half-open range, we need to adjust the start
        // and end to account for that.
        let x_r = if x_r.start > x_r.end {
            x_r.end+1 .. x_r.start+1
        } else {
            x_r
        };

        for x in x_r {
            let pos = Point{x:x, y:y};
            self.set_tile_type(pos, TileType::Floor)?;
        }

        Ok(())
    }

    fn create_room(&mut self, rect: &Rectangle) -> Result<(),()> {
        for y in (rect.top_left.y+1)..rect.bottom_right.y {
            for x in (rect.top_left.x+1)..rect.bottom_right.x {
                let pos = Point{x:x, y:y};
                self.set_tile_type(pos, TileType::Floor)?;
            }
        }

        Ok(())
    }
}

// Rendering
impl Map {
    pub fn render_map<T: Console>(&self, cons: &mut T) {
        for tile in self.tile_map.iter() {
            tile.render(cons);
        }

        for item in self.items.iter() {
            item.render(cons);
        }
    }
}

// Queries
impl Map {
    pub fn point_in_map(&self, Point{x,y}: Point<i16>) -> bool {
        x >= 0 && x < self.width as i16 && y >= 0 && y < self.height as i16
    }

    pub fn get_tile_type(&self, pos: Point<i16>) -> Result<TileType,()> {
        if !self.point_in_map(pos) {
            Err(())
        } else {
            let Point{x, y} = pos;
            Ok(self.tile_map[y as usize * self.width as usize + x as usize].tile_type)
        }
    }

    pub fn can_move_to(&self, pos: Point<i16>) -> CanMoveResponse {
        match self.get_tile_type(pos) {
            Ok(tile) if tile.blocks_move() => CanMoveResponse::Scenery,
            _ => CanMoveResponse::Open
        }
    }

    pub fn get_map_size(&self) -> (u8, u8) {
        (self.width, self.height)
    }

    pub fn point_in_fov(&self, Point{x,y}: Point<i16>) -> bool {
        self.fov_map.is_in_fov(x as i32, y as i32)
    }

    fn build_fov_map(tiles: &Vec<Tile>, fov_map: &mut tcod::map::Map) {
        for tile in tiles.iter() {
            let pos = tile.get_position();
            fov_map.set(pos.x as i32, pos.y as i32, !tile.tile_type.blocks_sight(), !tile.tile_type.blocks_move());
        }
    }

    pub fn get_pathfinding_map(&self) -> tcod::Map {
        let mut path_map = tcod::Map::new(self.width as i32, self.height as i32);
        Map::build_fov_map(&self.tile_map, &mut path_map);
        path_map
    }
}

// Updating
impl Map {
    pub fn update_fov(&mut self, Point{x, y}: Point<i16>, light_radius: u8) {
        self.fov_map.compute_fov(x as i32, y as i32, light_radius as i32, true, tcod::map::FovAlgorithm::Permissive0);

        // I've opted to update the tile map here, because it doesn't make sense that
        // a function for rendering should need to mutate the object.
        // It does make this function more expensive to run, but it won't be run
        // too frequently.
        for tile in self.tile_map.iter_mut() {
            tile.is_visible = self.fov_map.is_in_fov(tile.get_x() as i32, tile.get_y() as i32);

            if tile.is_visible {
                tile.is_explored = true;
            }
        }
    }

    fn set_tile_type(&mut self, pos: Point<i16>, new_tile: TileType) -> Result<(),()> {
        if !self.point_in_map(pos) {
            Err(())
        } else {
            let Point{x,y} = pos;
            self.tile_map[y as usize * self.width as usize + x as usize].tile_type = new_tile;

            Ok(())
        }
    }
}<|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/.

// Our current on-disk metadata format uses a JSON-based structure. We
// are using serde-rs, which allows us to implement serialization (to
// JSON) and deserialization (from JSON) by deriving Serialize and
// Deserialize traits.  But, our in-memory structs are too complex for
// serde to handle, and in any case it's best to not tie our real
// structs to what will be saved on disk. Therefore we have *Save
// structs. These contain simple, serde-friendly data types, and we
// can convert to or from them when saving our current state, or
// restoring state from saved metadata.
//
// Look for "to_save" and "setup" methods, that either return or take
// the below structs as parameters.

use std::collections::HashMap;
use std::path::PathBuf;

use devicemapper::Sectors;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockDevSave {
    pub devnode: PathBuf,
    pub total_size: Sectors,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StratSave {
    pub name: String,
    pub id: String,
    pub block_devs: HashMap<String, BlockDevSave>,
}
<commit_msg>Add a trait indicating (de)serializability<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/.

// Our current on-disk metadata format uses a JSON-based structure. We
// are using serde-rs, which allows us to implement serialization (to
// JSON) and deserialization (from JSON) by deriving Serialize and
// Deserialize traits.  But, our in-memory structs are too complex for
// serde to handle, and in any case it's best to not tie our real
// structs to what will be saved on disk. Therefore we have *Save
// structs. These contain simple, serde-friendly data types, and we
// can convert to or from them when saving our current state, or
// restoring state from saved metadata.

use std::collections::HashMap;
use std::marker::Sized;
use std::path::PathBuf;

use devicemapper::Sectors;

/// Implements saving and restoring from metadata.
pub trait DSerializable<T> {
    fn to_save(&self) -> T;
    fn setup(T) -> Self
        where Self: Sized
    {
        unimplemented!()
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockDevSave {
    pub devnode: PathBuf,
    pub total_size: Sectors,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StratSave {
    pub name: String,
    pub id: String,
    pub block_devs: HashMap<String, BlockDevSave>,
}
<|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/.

// Manage the linear volume that stores metadata on pool levels 5-7.

use std::{
    convert::From,
    fs::{create_dir, create_dir_all, read_dir, remove_dir, remove_file, rename, OpenOptions},
    io::{prelude::*, ErrorKind},
    path::{Path, PathBuf},
};

use nix::mount::{mount, umount, MsFlags};
use retry::{delay::Fixed, retry_with_index};

use devicemapper::{DmDevice, DmOptions, LinearDev, LinearDevTargetParams, Sectors, TargetLine};

use crate::{
    engine::{
        strat_engine::{
            cmd::create_fs,
            dm::get_dm,
            serde_structs::FilesystemSave,
            thinpool::filesystem::{fs_usage, StratFilesystem},
        },
        types::{FilesystemUuid, Name, PoolUuid, StratisUuid},
    },
    stratis::StratisResult,
};

// TODO: Monitor fs size and extend linear and fs if needed
// TODO: Document format of stuff on MDV in SWDD (currently ad-hoc)

const RUN_DIR: &str = "/run/stratisd";
const FILESYSTEM_DIR: &str = "filesystems";

#[derive(Debug)]
pub struct MetadataVol {
    dev: LinearDev,
    mount_pt: PathBuf,
}

/// A helper struct that borrows the MetadataVol and ensures that the MDV is
/// mounted as long as it is borrowed, and then unmounted.
#[derive(Debug)]
struct MountedMDV<'a> {
    mdv: &'a MetadataVol,
}

impl<'a> MountedMDV<'a> {
    /// Borrow the MDV and ensure it's mounted.
    fn mount(mdv: &MetadataVol) -> StratisResult<MountedMDV<'_>> {
        if let Err(err) = create_dir_all(&mdv.mount_pt) {
            if err.kind() != ErrorKind::AlreadyExists {
                return Err(From::from(err));
            }
        }

        match mount(
            Some(&mdv.dev.devnode()),
            &mdv.mount_pt,
            Some("xfs"),
            MsFlags::empty(),
            None as Option<&str>,
        ) {
            Err(nix::Error::EBUSY) => {
                // The device is already mounted at the specified mount point
                Ok(())
            }
            Err(err) => Err(err),
            Ok(_) => Ok(()),
        }?;

        Ok(MountedMDV { mdv })
    }

    fn mount_pt(&self) -> &Path {
        &self.mdv.mount_pt
    }
}

impl<'a> Drop for MountedMDV<'a> {
    fn drop(&mut self) {
        if let Err(e) = retry_with_index(Fixed::from_millis(100).take(2), |i| {
            trace!("MDV unmount attempt {}", i);
            umount(&self.mdv.mount_pt)
        }) {
            warn!("Unmounting MDV failed: {}", e);
            return;
        }
        if let Err(err) = remove_dir(&self.mdv.mount_pt) {
            warn!("Could not remove MDV mount point: {}", err);
        }
    }
}

impl MetadataVol {
    /// Minimum allocation size for a file is a block which will be 4k in this
    /// set up.
    const XFS_MIN_FILE_ALLOC_SIZE: Sectors = Sectors(8);

    /// Initialize a new Metadata Volume.
    pub fn initialize(pool_uuid: PoolUuid, dev: LinearDev) -> StratisResult<MetadataVol> {
        create_fs(&dev.devnode(), Some(StratisUuid::Pool(pool_uuid)), true)?;
        MetadataVol::setup(pool_uuid, dev)
    }

    /// Set up an existing Metadata Volume.
    pub fn setup(pool_uuid: PoolUuid, dev: LinearDev) -> StratisResult<MetadataVol> {
        let filename = format!(".mdv-{}", uuid_to_string!(pool_uuid));
        let mount_pt: PathBuf = vec![RUN_DIR, &filename].iter().collect();

        let mdv = MetadataVol { dev, mount_pt };

        {
            let mount = MountedMDV::mount(&mdv)?;
            let filesystem_path = mount.mount_pt().join(FILESYSTEM_DIR);

            if let Err(err) = create_dir(&filesystem_path) {
                if err.kind() != ErrorKind::AlreadyExists {
                    return Err(From::from(err));
                }
            }

            let _ = remove_temp_files(&filesystem_path)?;
        }

        Ok(mdv)
    }

    /// Save info on a new filesystem to persistent storage, or update
    /// the existing info on a filesystem.
    // Write to a temp file and then rename to actual filename, to
    // ensure file contents are not truncated if operation is
    // interrupted.
    pub fn save_fs(
        &self,
        name: &Name,
        uuid: FilesystemUuid,
        fs: &StratFilesystem,
    ) -> StratisResult<()> {
        let data = serde_json::to_string(&fs.record(name, uuid))?;
        let path = self
            .mount_pt
            .join(FILESYSTEM_DIR)
            .join(uuid_to_string!(uuid))
            .with_extension("json");

        let temp_path = path.with_extension("temp");

        let _mount = MountedMDV::mount(self)?;

        // Braces to ensure f is closed before renaming
        {
            let mut f = OpenOptions::new()
                .write(true)
                .create(true)
                .open(&temp_path)?;
            f.write_all(data.as_bytes())?;

            // Try really hard to make sure it goes to disk
            f.sync_all()?;
        }

        rename(temp_path, path)?;

        Ok(())
    }

    /// Remove info on a filesystem from persistent storage.
    pub fn rm_fs(&self, fs_uuid: FilesystemUuid) -> StratisResult<()> {
        let fs_path = self
            .mount_pt
            .join(FILESYSTEM_DIR)
            .join(uuid_to_string!(fs_uuid))
            .with_extension("json");

        let _mount = MountedMDV::mount(self)?;

        if let Err(err) = remove_file(fs_path) {
            if err.kind() != ErrorKind::NotFound {
                return Err(From::from(err));
            }
        }

        Ok(())
    }

    /// Get list of filesystems stored on the MDV.
    pub fn filesystems(&self) -> StratisResult<Vec<FilesystemSave>> {
        let mut filesystems = Vec::new();

        let mount = MountedMDV::mount(self)?;

        for dir_e in read_dir(mount.mount_pt().join(FILESYSTEM_DIR))? {
            let dir_e = dir_e?;

            if dir_e.path().ends_with(".temp") {
                continue;
            }

            let mut f = OpenOptions::new().read(true).open(&dir_e.path())?;
            let mut data = Vec::new();
            f.read_to_end(&mut data)?;

            filesystems.push(serde_json::from_slice(&data)?);
        }

        Ok(filesystems)
    }

    /// Tear down a Metadata Volume.
    pub fn teardown(&mut self) -> StratisResult<()> {
        self.dev.teardown(get_dm())?;

        Ok(())
    }

    /// Suspend the metadata volume DM devices
    pub fn suspend(&mut self) -> StratisResult<()> {
        self.dev.suspend(get_dm(), DmOptions::default())?;
        Ok(())
    }

    /// Resume the metadata volume DM devices
    pub fn resume(&mut self) -> StratisResult<()> {
        self.dev.resume(get_dm())?;
        Ok(())
    }

    /// Get a reference to the backing device
    pub fn device(&self) -> &LinearDev {
        &self.dev
    }

    /// Set the table of the backing device
    pub fn set_table(
        &mut self,
        table: Vec<TargetLine<LinearDevTargetParams>>,
    ) -> StratisResult<()> {
        self.dev.set_table(get_dm(), table)?;
        Ok(())
    }

    /// The maximum number of filesystems that can be recorded in the MDV.
    pub fn max_fs_limit(&self) -> StratisResult<u64> {
        let mounted = MountedMDV::mount(self)?;
        let (total_size, _) = fs_usage(mounted.mount_pt())?;
        Ok(total_size.sectors() / Self::XFS_MIN_FILE_ALLOC_SIZE)
    }
}

/// Remove temp files from the designated directory.
/// Returns an error if the directory can not be read.
/// Persists if an individual directory entry can not be read due to an
/// intermittent IO error.
/// Returns the following summary values:
///  * the number of temp files found
///  * paths of those unremoved, if any
fn remove_temp_files(dir: &Path) -> StratisResult<(u64, Vec<PathBuf>)> {
    let mut found = 0;
    let mut failed = Vec::new();
    for path in read_dir(dir)?
        .filter_map(|e| e.ok()) // Just ignore entry on intermittent IO error
        .map(|e| e.path())
        .filter(|p| p.ends_with(".temp"))
    {
        found += 1;
        remove_file(&path).unwrap_or_else(|_| failed.push(path));
    }
    Ok((found, failed))
}
<commit_msg>Remove some obsolete TODOs<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/.

// Manage the linear volume that stores metadata on pool levels 5-7.

use std::{
    convert::From,
    fs::{create_dir, create_dir_all, read_dir, remove_dir, remove_file, rename, OpenOptions},
    io::{prelude::*, ErrorKind},
    path::{Path, PathBuf},
};

use nix::mount::{mount, umount, MsFlags};
use retry::{delay::Fixed, retry_with_index};

use devicemapper::{DmDevice, DmOptions, LinearDev, LinearDevTargetParams, Sectors, TargetLine};

use crate::{
    engine::{
        strat_engine::{
            cmd::create_fs,
            dm::get_dm,
            serde_structs::FilesystemSave,
            thinpool::filesystem::{fs_usage, StratFilesystem},
        },
        types::{FilesystemUuid, Name, PoolUuid, StratisUuid},
    },
    stratis::StratisResult,
};

const RUN_DIR: &str = "/run/stratisd";
const FILESYSTEM_DIR: &str = "filesystems";

#[derive(Debug)]
pub struct MetadataVol {
    dev: LinearDev,
    mount_pt: PathBuf,
}

/// A helper struct that borrows the MetadataVol and ensures that the MDV is
/// mounted as long as it is borrowed, and then unmounted.
#[derive(Debug)]
struct MountedMDV<'a> {
    mdv: &'a MetadataVol,
}

impl<'a> MountedMDV<'a> {
    /// Borrow the MDV and ensure it's mounted.
    fn mount(mdv: &MetadataVol) -> StratisResult<MountedMDV<'_>> {
        if let Err(err) = create_dir_all(&mdv.mount_pt) {
            if err.kind() != ErrorKind::AlreadyExists {
                return Err(From::from(err));
            }
        }

        match mount(
            Some(&mdv.dev.devnode()),
            &mdv.mount_pt,
            Some("xfs"),
            MsFlags::empty(),
            None as Option<&str>,
        ) {
            Err(nix::Error::EBUSY) => {
                // The device is already mounted at the specified mount point
                Ok(())
            }
            Err(err) => Err(err),
            Ok(_) => Ok(()),
        }?;

        Ok(MountedMDV { mdv })
    }

    fn mount_pt(&self) -> &Path {
        &self.mdv.mount_pt
    }
}

impl<'a> Drop for MountedMDV<'a> {
    fn drop(&mut self) {
        if let Err(e) = retry_with_index(Fixed::from_millis(100).take(2), |i| {
            trace!("MDV unmount attempt {}", i);
            umount(&self.mdv.mount_pt)
        }) {
            warn!("Unmounting MDV failed: {}", e);
            return;
        }
        if let Err(err) = remove_dir(&self.mdv.mount_pt) {
            warn!("Could not remove MDV mount point: {}", err);
        }
    }
}

impl MetadataVol {
    /// Minimum allocation size for a file is a block which will be 4k in this
    /// set up.
    const XFS_MIN_FILE_ALLOC_SIZE: Sectors = Sectors(8);

    /// Initialize a new Metadata Volume.
    pub fn initialize(pool_uuid: PoolUuid, dev: LinearDev) -> StratisResult<MetadataVol> {
        create_fs(&dev.devnode(), Some(StratisUuid::Pool(pool_uuid)), true)?;
        MetadataVol::setup(pool_uuid, dev)
    }

    /// Set up an existing Metadata Volume.
    pub fn setup(pool_uuid: PoolUuid, dev: LinearDev) -> StratisResult<MetadataVol> {
        let filename = format!(".mdv-{}", uuid_to_string!(pool_uuid));
        let mount_pt: PathBuf = vec![RUN_DIR, &filename].iter().collect();

        let mdv = MetadataVol { dev, mount_pt };

        {
            let mount = MountedMDV::mount(&mdv)?;
            let filesystem_path = mount.mount_pt().join(FILESYSTEM_DIR);

            if let Err(err) = create_dir(&filesystem_path) {
                if err.kind() != ErrorKind::AlreadyExists {
                    return Err(From::from(err));
                }
            }

            let _ = remove_temp_files(&filesystem_path)?;
        }

        Ok(mdv)
    }

    /// Save info on a new filesystem to persistent storage, or update
    /// the existing info on a filesystem.
    // Write to a temp file and then rename to actual filename, to
    // ensure file contents are not truncated if operation is
    // interrupted.
    pub fn save_fs(
        &self,
        name: &Name,
        uuid: FilesystemUuid,
        fs: &StratFilesystem,
    ) -> StratisResult<()> {
        let data = serde_json::to_string(&fs.record(name, uuid))?;
        let path = self
            .mount_pt
            .join(FILESYSTEM_DIR)
            .join(uuid_to_string!(uuid))
            .with_extension("json");

        let temp_path = path.with_extension("temp");

        let _mount = MountedMDV::mount(self)?;

        // Braces to ensure f is closed before renaming
        {
            let mut f = OpenOptions::new()
                .write(true)
                .create(true)
                .open(&temp_path)?;
            f.write_all(data.as_bytes())?;

            // Try really hard to make sure it goes to disk
            f.sync_all()?;
        }

        rename(temp_path, path)?;

        Ok(())
    }

    /// Remove info on a filesystem from persistent storage.
    pub fn rm_fs(&self, fs_uuid: FilesystemUuid) -> StratisResult<()> {
        let fs_path = self
            .mount_pt
            .join(FILESYSTEM_DIR)
            .join(uuid_to_string!(fs_uuid))
            .with_extension("json");

        let _mount = MountedMDV::mount(self)?;

        if let Err(err) = remove_file(fs_path) {
            if err.kind() != ErrorKind::NotFound {
                return Err(From::from(err));
            }
        }

        Ok(())
    }

    /// Get list of filesystems stored on the MDV.
    pub fn filesystems(&self) -> StratisResult<Vec<FilesystemSave>> {
        let mut filesystems = Vec::new();

        let mount = MountedMDV::mount(self)?;

        for dir_e in read_dir(mount.mount_pt().join(FILESYSTEM_DIR))? {
            let dir_e = dir_e?;

            if dir_e.path().ends_with(".temp") {
                continue;
            }

            let mut f = OpenOptions::new().read(true).open(&dir_e.path())?;
            let mut data = Vec::new();
            f.read_to_end(&mut data)?;

            filesystems.push(serde_json::from_slice(&data)?);
        }

        Ok(filesystems)
    }

    /// Tear down a Metadata Volume.
    pub fn teardown(&mut self) -> StratisResult<()> {
        self.dev.teardown(get_dm())?;

        Ok(())
    }

    /// Suspend the metadata volume DM devices
    pub fn suspend(&mut self) -> StratisResult<()> {
        self.dev.suspend(get_dm(), DmOptions::default())?;
        Ok(())
    }

    /// Resume the metadata volume DM devices
    pub fn resume(&mut self) -> StratisResult<()> {
        self.dev.resume(get_dm())?;
        Ok(())
    }

    /// Get a reference to the backing device
    pub fn device(&self) -> &LinearDev {
        &self.dev
    }

    /// Set the table of the backing device
    pub fn set_table(
        &mut self,
        table: Vec<TargetLine<LinearDevTargetParams>>,
    ) -> StratisResult<()> {
        self.dev.set_table(get_dm(), table)?;
        Ok(())
    }

    /// The maximum number of filesystems that can be recorded in the MDV.
    pub fn max_fs_limit(&self) -> StratisResult<u64> {
        let mounted = MountedMDV::mount(self)?;
        let (total_size, _) = fs_usage(mounted.mount_pt())?;
        Ok(total_size.sectors() / Self::XFS_MIN_FILE_ALLOC_SIZE)
    }
}

/// Remove temp files from the designated directory.
/// Returns an error if the directory can not be read.
/// Persists if an individual directory entry can not be read due to an
/// intermittent IO error.
/// Returns the following summary values:
///  * the number of temp files found
///  * paths of those unremoved, if any
fn remove_temp_files(dir: &Path) -> StratisResult<(u64, Vec<PathBuf>)> {
    let mut found = 0;
    let mut failed = Vec::new();
    for path in read_dir(dir)?
        .filter_map(|e| e.ok()) // Just ignore entry on intermittent IO error
        .map(|e| e.path())
        .filter(|p| p.ends_with(".temp"))
    {
        found += 1;
        remove_file(&path).unwrap_or_else(|_| failed.push(path));
    }
    Ok((found, failed))
}
<|endoftext|>


<commit_before>use std::collections::HashMap;
use std::fs::File;
use std::path::Path;

use regex::Regex;
use serde::{Deserialize, Serialize};

use gerritbot_spark as spark;

use super::BotError;

#[derive(Debug, Clone)]
struct Filter {
    pub regex: Regex,
    pub enabled: bool,
}

#[derive(Serialize, Deserialize)]
struct FilterForSerialize<'a> {
    regex: &'a str,
    enabled: bool,
}

/// Serialize the filter by storing the regex as a string.
fn serialize_filter<S>(filter: &Option<Filter>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    filter
        .as_ref()
        .map(|f| FilterForSerialize {
            regex: f.regex.as_str(),
            enabled: f.enabled,
        })
        .serialize(serializer)
}

/// Deserialize the filter by compiling the regex.
fn deserialize_filter<'de, D>(deserializer: D) -> Result<Option<Filter>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let maybe_filter = Option::<FilterForSerialize>::deserialize(deserializer)?;

    maybe_filter
        .map(|f| {
            Regex::new(f.regex)
                .map(|regex| Filter {
                    regex,
                    enabled: f.enabled,
                })
                .map_err(|e| {
                    <D::Error as serde::de::Error>::custom(format!("invalid regex: {}", e))
                })
        })
        .transpose()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    // Legacy attribute.  Keep so we don't drop it on deserialize, serialize.
    // Should be removed later.
    #[serde(skip_serializing_if = "Option::is_none")]
    spark_person_id: Option<String>,
    /// email of the user; assumed to be the same in Spark and Gerrit
    pub email: spark::Email,
    pub enabled: bool,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "serialize_filter",
        deserialize_with = "deserialize_filter"
    )]
    filter: Option<Filter>,
}

impl User {
    fn new(email: spark::Email) -> Self {
        Self {
            spark_person_id: None,
            email: email,
            filter: None,
            enabled: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct State {
    users: Vec<User>,
    #[serde(skip_serializing, skip_deserializing)]
    email_index: HashMap<spark::Email, usize>,
}

impl State {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn load<P>(filename: P) -> Result<Self, BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::open(filename)?;

        serde_json::from_reader(f)
            .map(|mut state: Self| {
                state.index_users();
                state
            })
            .map_err(BotError::from)
    }

    fn index_users(&mut self) {
        for (user_pos, user) in self.users.iter().enumerate() {
            self.email_index.insert(user.email.clone(), user_pos);
        }
    }

    pub fn num_users(&self) -> usize {
        self.users.len()
    }

    // Note: This method is not idempotent, and in particular, when adding the same user twice,
    // it will completely mess up the indexes.
    pub fn add_user<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let user_pos = self.users.len();
        self.users.push(User::new(email.to_owned()));
        self.email_index.insert(email.to_owned(), user_pos);
        self.users.last_mut().unwrap()
    }

    fn find_or_add_user_by_email<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let pos = self.users.iter().position(|u| u.email == email);
        let user: &'a mut User = match pos {
            Some(pos) => &mut self.users[pos],
            None => self.add_user(email),
        };
        user
    }

    fn find_user_mut<'a, P: ?Sized>(&'a mut self, email: &P) -> Option<&'a mut User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(move |pos| &mut self.users[pos])
    }

    pub fn find_user<'a, P: ?Sized>(&'a self, email: &P) -> Option<&'a User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(|pos| &self.users[pos])
    }

    pub fn find_user_by_email<'a, E: ?Sized>(&self, email: &E) -> Option<&User>
    where
        spark::Email: std::borrow::Borrow<E>,
        E: std::hash::Hash + Eq,
    {
        self.email_index.get(email).map(|pos| &self.users[*pos])
    }

    pub fn enable<'a>(&'a mut self, email: &spark::EmailRef, enabled: bool) -> &'a User {
        let user: &'a mut User = self.find_or_add_user_by_email(email);
        user.enabled = enabled;
        user
    }

    pub fn add_filter(
        &mut self,
        email: &spark::EmailRef,
        filter: &str,
    ) -> Result<(), regex::Error> {
        let user = self.find_or_add_user_by_email(email);
        user.filter = Some(Filter {
            regex: Regex::new(filter)?,
            enabled: true,
        });
        Ok(())
    }

    /// Get the filter for the given user given the user exists and has a filter
    /// configured.
    pub fn get_filter(&self, email: &spark::EmailRef) -> Option<(&str, bool)> {
        self.find_user(email)
            .and_then(|u| u.filter.as_ref())
            .map(|f| (f.regex.as_str(), f.enabled))
    }

    /// Enable or disable the configured filter for the user and return it given
    /// that the user exists and has a filter configured. Error means the user
    /// doesn't exist or doesn't have a filter configured.
    pub fn enable_and_get_filter(
        &mut self,
        email: &spark::EmailRef,
        enabled: bool,
    ) -> Result<&str, ()> {
        self.find_user_mut(email)
            .and_then(|u| u.filter.as_mut())
            .map(|f| {
                f.enabled = enabled;
                f.regex.as_str()
            })
            .ok_or(())
    }

    pub fn users(&self) -> impl Iterator<Item = &User> + Clone {
        self.users.iter()
    }

    pub fn is_filtered(&self, user: &User, msg: &str) -> bool {
        user.filter
            .as_ref()
            .map(|f| f.enabled && f.regex.is_match(msg))
            .unwrap_or(false)
    }
}

#[cfg(test)]
mod test {
    use spark::EmailRef;

    use super::*;

    #[test]
    fn test_add_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        assert_eq!(state.users.len(), 1);
        assert_eq!(state.email_index.len(), 1);
        assert_eq!(state.users[0].email, EmailRef::new("some@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );

        state.add_user(EmailRef::new("some_2@example.com"));
        assert_eq!(state.users.len(), 2);
        assert_eq!(state.email_index.len(), 2);
        assert_eq!(state.users[1].email, EmailRef::new("some_2@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );

        let user = state.find_user(EmailRef::new("some@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some@example.com"));

        let user = state.find_user(EmailRef::new("some_2@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some_2@example.com"));
    }

    #[test]
    fn add_invalid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        let res = state.add_filter(EmailRef::new("some@example.com"), ".some_weard_regex/[");
        assert!(res.is_err());
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com") && u.filter.is_none())
            .is_some());

        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(()));
    }

    #[test]
    fn add_valid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert!(res.is_ok());
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.regex.as_str()) == Some(".*some_word.*"))
            .is_some());

        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Some((".*some_word.*", true)));
        }
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(".*some_word.*"));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(false))
            .is_some());
        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Some((".*some_word.*", false)));
        }
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(".*some_word.*"));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(true))
            .is_some());
        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Some((".*some_word.*", true)));
        }
    }

    #[test]
    fn add_valid_filter_for_non_existing_user() {
        let mut state = State::new();
        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Ok(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(".*some_word.*"));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(".*some_word.*"));
    }

    #[test]
    fn add_valid_filter_for_disabled_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        state.users[0].enabled = false;

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Ok(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(".*some_word.*"));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(".*some_word.*"));
    }

    #[test]
    fn enable_non_configured_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(()));
    }
}
<commit_msg>fix loading users without filters<commit_after>use std::collections::HashMap;
use std::fs::File;
use std::path::Path;

use regex::Regex;
use serde::{Deserialize, Serialize};

use gerritbot_spark as spark;

use super::BotError;

#[derive(Debug, Clone)]
struct Filter {
    pub regex: Regex,
    pub enabled: bool,
}

#[derive(Serialize, Deserialize)]
struct FilterForSerialize<'a> {
    regex: &'a str,
    enabled: bool,
}

/// Serialize the filter by storing the regex as a string.
fn serialize_filter<S>(filter: &Option<Filter>, serializer: S) -> Result<S::Ok, S::Error>
where
    S: serde::Serializer,
{
    filter
        .as_ref()
        .map(|f| FilterForSerialize {
            regex: f.regex.as_str(),
            enabled: f.enabled,
        })
        .serialize(serializer)
}

/// Deserialize the filter by compiling the regex.
fn deserialize_filter<'de, D>(deserializer: D) -> Result<Option<Filter>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let maybe_filter = Option::<FilterForSerialize>::deserialize(deserializer)?;

    maybe_filter
        .map(|f| {
            Regex::new(f.regex)
                .map(|regex| Filter {
                    regex,
                    enabled: f.enabled,
                })
                .map_err(|e| {
                    <D::Error as serde::de::Error>::custom(format!("invalid regex: {}", e))
                })
        })
        .transpose()
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
    // Legacy attribute.  Keep so we don't drop it on deserialize, serialize.
    // Should be removed later.
    #[serde(skip_serializing_if = "Option::is_none")]
    spark_person_id: Option<String>,
    /// email of the user; assumed to be the same in Spark and Gerrit
    pub email: spark::Email,
    pub enabled: bool,
    #[serde(
        skip_serializing_if = "Option::is_none",
        serialize_with = "serialize_filter",
        deserialize_with = "deserialize_filter",
        default
    )]
    filter: Option<Filter>,
}

impl User {
    fn new(email: spark::Email) -> Self {
        Self {
            spark_person_id: None,
            email: email,
            filter: None,
            enabled: true,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct State {
    users: Vec<User>,
    #[serde(skip_serializing, skip_deserializing)]
    email_index: HashMap<spark::Email, usize>,
}

impl State {
    pub fn new() -> Self {
        Default::default()
    }

    pub fn load<P>(filename: P) -> Result<Self, BotError>
    where
        P: AsRef<Path>,
    {
        let f = File::open(filename)?;

        serde_json::from_reader(f)
            .map(|mut state: Self| {
                state.index_users();
                state
            })
            .map_err(BotError::from)
    }

    fn index_users(&mut self) {
        for (user_pos, user) in self.users.iter().enumerate() {
            self.email_index.insert(user.email.clone(), user_pos);
        }
    }

    pub fn num_users(&self) -> usize {
        self.users.len()
    }

    // Note: This method is not idempotent, and in particular, when adding the same user twice,
    // it will completely mess up the indexes.
    pub fn add_user<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let user_pos = self.users.len();
        self.users.push(User::new(email.to_owned()));
        self.email_index.insert(email.to_owned(), user_pos);
        self.users.last_mut().unwrap()
    }

    fn find_or_add_user_by_email<'a>(&'a mut self, email: &spark::EmailRef) -> &'a mut User {
        let pos = self.users.iter().position(|u| u.email == email);
        let user: &'a mut User = match pos {
            Some(pos) => &mut self.users[pos],
            None => self.add_user(email),
        };
        user
    }

    fn find_user_mut<'a, P: ?Sized>(&'a mut self, email: &P) -> Option<&'a mut User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(move |pos| &mut self.users[pos])
    }

    pub fn find_user<'a, P: ?Sized>(&'a self, email: &P) -> Option<&'a User>
    where
        spark::Email: std::borrow::Borrow<P>,
        P: std::hash::Hash + Eq,
    {
        self.email_index
            .get(email)
            .cloned()
            .map(|pos| &self.users[pos])
    }

    pub fn find_user_by_email<'a, E: ?Sized>(&self, email: &E) -> Option<&User>
    where
        spark::Email: std::borrow::Borrow<E>,
        E: std::hash::Hash + Eq,
    {
        self.email_index.get(email).map(|pos| &self.users[*pos])
    }

    pub fn enable<'a>(&'a mut self, email: &spark::EmailRef, enabled: bool) -> &'a User {
        let user: &'a mut User = self.find_or_add_user_by_email(email);
        user.enabled = enabled;
        user
    }

    pub fn add_filter(
        &mut self,
        email: &spark::EmailRef,
        filter: &str,
    ) -> Result<(), regex::Error> {
        let user = self.find_or_add_user_by_email(email);
        user.filter = Some(Filter {
            regex: Regex::new(filter)?,
            enabled: true,
        });
        Ok(())
    }

    /// Get the filter for the given user given the user exists and has a filter
    /// configured.
    pub fn get_filter(&self, email: &spark::EmailRef) -> Option<(&str, bool)> {
        self.find_user(email)
            .and_then(|u| u.filter.as_ref())
            .map(|f| (f.regex.as_str(), f.enabled))
    }

    /// Enable or disable the configured filter for the user and return it given
    /// that the user exists and has a filter configured. Error means the user
    /// doesn't exist or doesn't have a filter configured.
    pub fn enable_and_get_filter(
        &mut self,
        email: &spark::EmailRef,
        enabled: bool,
    ) -> Result<&str, ()> {
        self.find_user_mut(email)
            .and_then(|u| u.filter.as_mut())
            .map(|f| {
                f.enabled = enabled;
                f.regex.as_str()
            })
            .ok_or(())
    }

    pub fn users(&self) -> impl Iterator<Item = &User> + Clone {
        self.users.iter()
    }

    pub fn is_filtered(&self, user: &User, msg: &str) -> bool {
        user.filter
            .as_ref()
            .map(|f| f.enabled && f.regex.is_match(msg))
            .unwrap_or(false)
    }
}

#[cfg(test)]
mod test {
    use spark::EmailRef;

    use super::*;

    #[test]
    fn test_add_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        assert_eq!(state.users.len(), 1);
        assert_eq!(state.email_index.len(), 1);
        assert_eq!(state.users[0].email, EmailRef::new("some@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some@example.com")),
            Some(&0)
        );

        state.add_user(EmailRef::new("some_2@example.com"));
        assert_eq!(state.users.len(), 2);
        assert_eq!(state.email_index.len(), 2);
        assert_eq!(state.users[1].email, EmailRef::new("some_2@example.com"));
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );
        assert_eq!(
            state.email_index.get(EmailRef::new("some_2@example.com")),
            Some(&1)
        );

        let user = state.find_user(EmailRef::new("some@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some@example.com"));

        let user = state.find_user(EmailRef::new("some_2@example.com"));
        assert!(user.is_some());
        assert_eq!(user.unwrap().email, EmailRef::new("some_2@example.com"));
    }

    #[test]
    fn add_invalid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        let res = state.add_filter(EmailRef::new("some@example.com"), ".some_weard_regex/[");
        assert!(res.is_err());
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com") && u.filter.is_none())
            .is_some());

        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(()));
    }

    #[test]
    fn add_valid_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert!(res.is_ok());
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.regex.as_str()) == Some(".*some_word.*"))
            .is_some());

        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Some((".*some_word.*", true)));
        }
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(".*some_word.*"));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(false))
            .is_some());
        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Some((".*some_word.*", false)));
        }
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(".*some_word.*"));
        assert!(state
            .users
            .iter()
            .position(|u| u.email == EmailRef::new("some@example.com")
                && u.filter.as_ref().map(|f| f.enabled) == Some(true))
            .is_some());
        {
            let filter = state.get_filter(EmailRef::new("some@example.com"));
            assert_eq!(filter, Some((".*some_word.*", true)));
        }
    }

    #[test]
    fn add_valid_filter_for_non_existing_user() {
        let mut state = State::new();
        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Ok(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(".*some_word.*"));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(".*some_word.*"));
    }

    #[test]
    fn add_valid_filter_for_disabled_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));
        state.users[0].enabled = false;

        let res = state.add_filter(EmailRef::new("some@example.com"), ".*some_word.*");
        assert_eq!(res, Ok(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Ok(".*some_word.*"));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Ok(".*some_word.*"));
    }

    #[test]
    fn enable_non_configured_filter_for_existing_user() {
        let mut state = State::new();
        state.add_user(EmailRef::new("some@example.com"));

        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), true);
        assert_eq!(res, Err(()));
        let res = state.enable_and_get_filter(EmailRef::new("some@example.com"), false);
        assert_eq!(res, Err(()));
    }
}
<|endoftext|>


<commit_before>use std::collections::HashMap;

use lber::structure::{PL, StructureTag};
use lber::structures::{ASNTag, Boolean, OctetString, Sequence, Tag};
use lber::universal::Types;

pub mod types {
    //! Control type enum and variant names.
    //!
    //! Variants are individually reexported from the private submodule
    //! to inhibit exhaustive matching.
    pub use self::inner::_ControlType::{PagedResults, RelaxRules};

    /// Recognized control types. Variants can't be named in the namespace
    /// of this type; they must be used through module-level reexports.
    pub type ControlType = self::inner::_ControlType;
    mod inner {
        #[derive(Clone, Copy, Debug)]
        pub enum _ControlType {
            PagedResults,
            RelaxRules,
            #[doc(hidden)]
            _Nonexhaustive,
        }
    }
}
use self::types::ControlType;

mod paged_results;
pub use self::paged_results::PagedResults;

mod proxy_auth;
pub use self::proxy_auth::ProxyAuth;

mod relax_rules;
pub use self::relax_rules::RelaxRules;

lazy_static! {
    static ref CONTROLS: HashMap<&'static str, ControlType> = {
        let mut map = HashMap::new();
        map.insert(self::paged_results::PAGED_RESULTS_OID, types::PagedResults);
        map
    };
}

/// Mark a control as critical.
///
/// Most controls provided by this library implements this trait. All controls
/// are instantiated as non-critical by default, unless dictated otherwise by
/// their specification.
///
/// __Note__: a way to implement this trait for third-party controls will be
/// provided in 0.5.x.
pub trait MakeCritical {
    /// Mark the control instance as critical. This operation consumes the control,
    /// and is irreversible.
    fn critical(self) -> CriticalControl<Self> where Self: Sized {
        CriticalControl {
            control: self,
        }
    }
}

pub struct CriticalControl<T> {
    control: T
}

impl<T> From<CriticalControl<T>> for StructureTag
    where T: Oid, Option<Vec<u8>>: From<T>
{
    fn from(cc: CriticalControl<T>) -> StructureTag {
        let oid = cc.control.oid();
        construct_control(oid, true, cc.control.into())
    }
}

pub trait Oid {
    fn oid(&self) -> &'static str;
}

pub trait ControlParser {
    fn parse(&[u8]) -> Self;
}

/// Parse the raw value of a control.
///
/// The function returns the struct corresponding to control's contents.
/// The type of the struct must be explicitly specified in the binding annotation
/// of a __let__ statement or by using the turbofish.
///
/// __Note__: This function will be removed in 0.5.x, in favor of calling
/// type-qualified `parse()` on `RawControl`.
pub fn parse_control<T: ControlParser>(val: &[u8]) -> T {
    T::parse(val)
}

/// Response control.
///
/// If the OID is recognized as corresponding to one of controls implemented by this
/// library while parsing raw BER data of the response, the first element will have
/// a value, otherwise it will be `None`.
#[derive(Clone, Debug)]
pub struct Control(pub Option<ControlType>, pub RawControl);

/// Generic control.
///
/// This struct can be used both for request and response controls. For requests, an
/// independently implemented control can produce an instance of this type and use it
/// to provide an element of the vector passed to [`with_controls()`](../struct.LdapConn.html#method.with_controls)
/// by calling `into()` on the instance. For responses, an instance is packed into a
/// [`Control`](struct.Control.html).
// future text:
// ... and can be parsed by calling type-qualified [`parse()`](#method.parse) on that
// instance, if a [`ControlParser`](trait.ControlParser.html) implementation exists
// for the specified type.
#[derive(Clone, Debug)]
pub struct RawControl {
    /// OID of the control.
    pub ctype: String,
    /// Criticality, has no meaning on response.
    pub crit: bool,
    /// Raw value of the control, if any.
    pub val: Option<Vec<u8>>,
}

impl From<RawControl> for StructureTag {
    fn from(ctrl: RawControl) -> StructureTag {
        construct_control(&ctrl.ctype, ctrl.crit, ctrl.val)
    }
}

pub fn construct_control(oid: &str, crit: bool, val: Option<Vec<u8>>) -> StructureTag {
    let mut seq = vec![
        Tag::OctetString(OctetString {
            inner: Vec::from(oid.as_bytes()),
            .. Default::default()
        })
    ];
    if crit {
        seq.push(Tag::Boolean(Boolean {
            inner: true,
            .. Default::default()
        }));
    }
    if let Some(val) = val {
        seq.push(Tag::OctetString(OctetString {
            inner: val,
            .. Default::default()
        }));
    }
    Tag::Sequence(Sequence {
        inner: seq,
        .. Default::default()
    }).into_structure()
}

pub fn parse_controls(t: StructureTag) -> Vec<Control> {
    let tags = t.expect_constructed().expect("result sequence").into_iter();
    let mut ctrls = Vec::new();
    for ctrl in tags {
        let mut components = ctrl.expect_constructed().expect("components").into_iter();
        let ctype = String::from_utf8(components.next().expect("element").expect_primitive().expect("octet string")).expect("control type");
        let next = components.next();
        let (crit, maybe_val) = match next {
            None => (false, None),
            Some(c) => match c {
                StructureTag { id, ref payload, .. } if id == Types::Boolean as u64 => match *payload {
                    PL::P(ref v) => (v[0] != 0, components.next()),
                    PL::C(_) => panic!("decoding error"),
                },
                StructureTag { id, .. } if id == Types::OctetString as u64 => (false, Some(c.clone())),
                _ => panic!("decoding error"),
            },
        };
        let val = match maybe_val {
            None => None,
            Some(v) => Some(Vec::from(v.expect_primitive().expect("octet string"))),
        };
        let known_type = match CONTROLS.get(&*ctype) {
            Some(val) => Some(*val),
            None => None,
        };
        ctrls.push(Control(known_type, RawControl { ctype: ctype, crit: crit, val: val }));
    }
    ctrls
}
<commit_msg>Remove RelaxRules from response types<commit_after>use std::collections::HashMap;

use lber::structure::{PL, StructureTag};
use lber::structures::{ASNTag, Boolean, OctetString, Sequence, Tag};
use lber::universal::Types;

pub mod types {
    //! Control type enum and variant names.
    //!
    //! Variants are individually reexported from the private submodule
    //! to inhibit exhaustive matching.
    pub use self::inner::_ControlType::{PagedResults};  // there will be more

    /// Recognized control types. Variants can't be named in the namespace
    /// of this type; they must be used through module-level reexports.
    pub type ControlType = self::inner::_ControlType;
    mod inner {
        #[derive(Clone, Copy, Debug)]
        pub enum _ControlType {
            PagedResults,
            #[doc(hidden)]
            _Nonexhaustive,
        }
    }
}
use self::types::ControlType;

mod paged_results;
pub use self::paged_results::PagedResults;

mod proxy_auth;
pub use self::proxy_auth::ProxyAuth;

mod relax_rules;
pub use self::relax_rules::RelaxRules;

lazy_static! {
    static ref CONTROLS: HashMap<&'static str, ControlType> = {
        let mut map = HashMap::new();
        map.insert(self::paged_results::PAGED_RESULTS_OID, types::PagedResults);
        map
    };
}

/// Mark a control as critical.
///
/// Most controls provided by this library implements this trait. All controls
/// are instantiated as non-critical by default, unless dictated otherwise by
/// their specification.
///
/// __Note__: a way to implement this trait for third-party controls will be
/// provided in 0.5.x.
pub trait MakeCritical {
    /// Mark the control instance as critical. This operation consumes the control,
    /// and is irreversible.
    fn critical(self) -> CriticalControl<Self> where Self: Sized {
        CriticalControl {
            control: self,
        }
    }
}

pub struct CriticalControl<T> {
    control: T
}

impl<T> From<CriticalControl<T>> for StructureTag
    where T: Oid, Option<Vec<u8>>: From<T>
{
    fn from(cc: CriticalControl<T>) -> StructureTag {
        let oid = cc.control.oid();
        construct_control(oid, true, cc.control.into())
    }
}

pub trait Oid {
    fn oid(&self) -> &'static str;
}

pub trait ControlParser {
    fn parse(&[u8]) -> Self;
}

/// Parse the raw value of a control.
///
/// The function returns the struct corresponding to control's contents.
/// The type of the struct must be explicitly specified in the binding annotation
/// of a __let__ statement or by using the turbofish.
///
/// __Note__: This function will be removed in 0.5.x, in favor of calling
/// type-qualified `parse()` on `RawControl`.
pub fn parse_control<T: ControlParser>(val: &[u8]) -> T {
    T::parse(val)
}

/// Response control.
///
/// If the OID is recognized as corresponding to one of controls implemented by this
/// library while parsing raw BER data of the response, the first element will have
/// a value, otherwise it will be `None`.
#[derive(Clone, Debug)]
pub struct Control(pub Option<ControlType>, pub RawControl);

/// Generic control.
///
/// This struct can be used both for request and response controls. For requests, an
/// independently implemented control can produce an instance of this type and use it
/// to provide an element of the vector passed to [`with_controls()`](../struct.LdapConn.html#method.with_controls)
/// by calling `into()` on the instance. For responses, an instance is packed into a
/// [`Control`](struct.Control.html).
// future text:
// ... and can be parsed by calling type-qualified [`parse()`](#method.parse) on that
// instance, if a [`ControlParser`](trait.ControlParser.html) implementation exists
// for the specified type.
#[derive(Clone, Debug)]
pub struct RawControl {
    /// OID of the control.
    pub ctype: String,
    /// Criticality, has no meaning on response.
    pub crit: bool,
    /// Raw value of the control, if any.
    pub val: Option<Vec<u8>>,
}

impl From<RawControl> for StructureTag {
    fn from(ctrl: RawControl) -> StructureTag {
        construct_control(&ctrl.ctype, ctrl.crit, ctrl.val)
    }
}

pub fn construct_control(oid: &str, crit: bool, val: Option<Vec<u8>>) -> StructureTag {
    let mut seq = vec![
        Tag::OctetString(OctetString {
            inner: Vec::from(oid.as_bytes()),
            .. Default::default()
        })
    ];
    if crit {
        seq.push(Tag::Boolean(Boolean {
            inner: true,
            .. Default::default()
        }));
    }
    if let Some(val) = val {
        seq.push(Tag::OctetString(OctetString {
            inner: val,
            .. Default::default()
        }));
    }
    Tag::Sequence(Sequence {
        inner: seq,
        .. Default::default()
    }).into_structure()
}

pub fn parse_controls(t: StructureTag) -> Vec<Control> {
    let tags = t.expect_constructed().expect("result sequence").into_iter();
    let mut ctrls = Vec::new();
    for ctrl in tags {
        let mut components = ctrl.expect_constructed().expect("components").into_iter();
        let ctype = String::from_utf8(components.next().expect("element").expect_primitive().expect("octet string")).expect("control type");
        let next = components.next();
        let (crit, maybe_val) = match next {
            None => (false, None),
            Some(c) => match c {
                StructureTag { id, ref payload, .. } if id == Types::Boolean as u64 => match *payload {
                    PL::P(ref v) => (v[0] != 0, components.next()),
                    PL::C(_) => panic!("decoding error"),
                },
                StructureTag { id, .. } if id == Types::OctetString as u64 => (false, Some(c.clone())),
                _ => panic!("decoding error"),
            },
        };
        let val = match maybe_val {
            None => None,
            Some(v) => Some(Vec::from(v.expect_primitive().expect("octet string"))),
        };
        let known_type = match CONTROLS.get(&*ctype) {
            Some(val) => Some(*val),
            None => None,
        };
        ctrls.push(Control(known_type, RawControl { ctype: ctype, crit: crit, val: val }));
    }
    ctrls
}
<|endoftext|>


<commit_before>// Copyright 2016, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::slice;
use std::io::{Read, Write, Error};
use std::process;
use std::thread;

use libc::{c_void, c_uint};

use ffi;
use ffi::enums::Status;
use error::IoError;
use ImageSurface;

struct CallbackGuard;

impl Drop for CallbackGuard {
    fn drop(&mut self) {
        if thread::panicking() {
            process::exit(101);
        }
    }
}

struct ReadEnv<'a, R: 'a + Read> {
    reader: &'a mut R,
    error: Option<Error>,
}

unsafe extern "C" fn read_func<R: Read>(closure: *mut c_void, data: *mut u8, len: c_uint) -> Status {
    let _cbguard = CallbackGuard;
    let read_env: &mut ReadEnv<R> = &mut *(closure as *mut ReadEnv<R>);
    let mut buffer = slice::from_raw_parts_mut(data, len as usize);
    match read_env.reader.read_exact(buffer) {
        Ok(()) => Status::Success,
        Err(error) => {
            read_env.error = Some(error);
            Status::ReadError
        },
    }
}

struct WriteEnv<'a, W: 'a + Write> {
    writer: &'a mut W,
    error: Option<Error>,
}

unsafe extern "C" fn write_func<W: Write>(closure: *mut c_void, data: *mut u8, len: c_uint) -> Status {
    let _cbguard = CallbackGuard;
    let write_env: &mut WriteEnv<W> = &mut *(closure as *mut WriteEnv<W>);
    let buffer = slice::from_raw_parts(data, len as usize);
    match write_env.writer.write_all(buffer) {
        Ok(()) => Status::Success,
        Err(error) => {
            write_env.error = Some(error);
            Status::WriteError
        },
    }
}

impl ImageSurface {
    pub fn create_from_png<R: Read>(stream: &mut R) -> Result<ImageSurface, IoError> {
        let mut env = ReadEnv{ reader: stream, error: None };
        let surface: ImageSurface = unsafe { ImageSurface::from_raw_full(ffi::cairo_image_surface_create_from_png_stream(
            Some(read_func::<R>), &mut env as *mut ReadEnv<R> as *mut c_void)) };
        match env.error {
            None => match surface.as_ref().status() {   // The surface migth still be "nil" if the error occured in Cairo
                Status::Success => Ok(surface),
                st => Err(IoError::Cairo(st)),
            },
            Some(err) => Err(IoError::Io(err)),
        }
    }

    pub fn write_to_png<W: Write>(&self, stream: &mut W) -> Result<(), IoError> {
        let mut env = WriteEnv{ writer: stream, error: None };
        let status = unsafe { ffi::cairo_surface_write_to_png_stream(self.to_raw_none(),
            Some(write_func::<W>), &mut env as *mut WriteEnv<W> as *mut c_void) };
        match env.error {
            None => match status {
                Status::Success => Ok(()),
                st => Err(IoError::Cairo(st)),
            },
            Some(err) => Err(IoError::Io(err)),
        }
    }
}
<commit_msg>ImageSurface::create_from_png() - Process errors from the call to ImageSurface::from_raw_full()<commit_after>// Copyright 2016, The Gtk-rs Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::slice;
use std::io::{Read, Write, Error};
use std::process;
use std::thread;

use libc::{c_void, c_uint};

use ffi;
use ffi::enums::Status;
use error::IoError;
use ImageSurface;

struct CallbackGuard;

impl Drop for CallbackGuard {
    fn drop(&mut self) {
        if thread::panicking() {
            process::exit(101);
        }
    }
}

struct ReadEnv<'a, R: 'a + Read> {
    reader: &'a mut R,
    error: Option<Error>,
}

unsafe extern "C" fn read_func<R: Read>(closure: *mut c_void, data: *mut u8, len: c_uint) -> Status {
    let _cbguard = CallbackGuard;
    let read_env: &mut ReadEnv<R> = &mut *(closure as *mut ReadEnv<R>);
    let mut buffer = slice::from_raw_parts_mut(data, len as usize);
    match read_env.reader.read_exact(buffer) {
        Ok(()) => Status::Success,
        Err(error) => {
            read_env.error = Some(error);
            Status::ReadError
        },
    }
}

struct WriteEnv<'a, W: 'a + Write> {
    writer: &'a mut W,
    error: Option<Error>,
}

unsafe extern "C" fn write_func<W: Write>(closure: *mut c_void, data: *mut u8, len: c_uint) -> Status {
    let _cbguard = CallbackGuard;
    let write_env: &mut WriteEnv<W> = &mut *(closure as *mut WriteEnv<W>);
    let buffer = slice::from_raw_parts(data, len as usize);
    match write_env.writer.write_all(buffer) {
        Ok(()) => Status::Success,
        Err(error) => {
            write_env.error = Some(error);
            Status::WriteError
        },
    }
}

impl ImageSurface {
    pub fn create_from_png<R: Read>(stream: &mut R) -> Result<ImageSurface, IoError> {
        let mut env = ReadEnv{ reader: stream, error: None };
        unsafe {
            let raw_surface = ffi::cairo_image_surface_create_from_png_stream(
                Some(read_func::<R>),
                &mut env as *mut ReadEnv<R> as *mut c_void);

            let surface = ImageSurface::from_raw_full(raw_surface)?;

            match env.error {
                None => Ok(surface),
                Some(err) => Err(IoError::Io(err)),
            }
        }
    }

    pub fn write_to_png<W: Write>(&self, stream: &mut W) -> Result<(), IoError> {
        let mut env = WriteEnv{ writer: stream, error: None };
        let status = unsafe { ffi::cairo_surface_write_to_png_stream(self.to_raw_none(),
            Some(write_func::<W>), &mut env as *mut WriteEnv<W> as *mut c_void) };
        match env.error {
            None => match status {
                Status::Success => Ok(()),
                st => Err(IoError::Cairo(st)),
            },
            Some(err) => Err(IoError::Io(err)),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::io::ErrorKind;
    use super::*;
    use ffi::enums::Format;

    struct IoErrorReader;

    // A reader that always returns an error
    impl Read for IoErrorReader {
        fn read(&mut self, _: &mut [u8]) -> Result<usize, Error> {
            Err(Error::new(ErrorKind::Other, "yikes!"))
        }
    }

    #[test]
    fn valid_png_reads_correctly() {
        // A 1x1 PNG, RGB, no alpha, with a single pixel with (42, 42, 42) values
        let png_data: Vec<u8> = vec![
            0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,  0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
            0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,  0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
            0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41,  0x54, 0x08, 0xd7, 0x63, 0xd0, 0xd2, 0xd2, 0x02,
            0x00, 0x01, 0x00, 0x00, 0x7f, 0x09, 0xa9, 0x5a,  0x4d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e,
            0x44, 0xae, 0x42, 0x60, 0x82
        ];

        let r = ImageSurface::create_from_png(&mut &png_data[..]);
        assert!(r.is_ok());

        let mut surface = r.unwrap();
        assert!(surface.get_width() == 1);
        assert!(surface.get_height() == 1);
        assert!(surface.get_format() == Format::Rgb24);

        let data = surface.get_data().unwrap();
        assert!(data.len() >= 3);

        let slice = &data[0..3];
        assert!(slice[0] == 42);
        assert!(slice[1] == 42);
        assert!(slice[2] == 42);
    }

    #[test]
    fn invalid_png_yields_error() {
        let png_data: Vec<u8> = vec![
            //      v--- this byte is modified
            0x89, 0x40, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,  0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
            0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,  0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
            0xde, 0x00, 0x00, 0x00, 0x0c, 0x49, 0x44, 0x41,  0x54, 0x08, 0xd7, 0x63, 0xd0, 0xd2, 0xd2, 0x02,
            0x00, 0x01, 0x00, 0x00, 0x7f, 0x09, 0xa9, 0x5a,  0x4d, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e,
            0x44, 0xae, 0x42, 0x60, 0x82
        ];

        match ImageSurface::create_from_png(&mut &png_data[..]) {
            Err(IoError::Cairo(_)) => (),
            _ => unreachable!()
        }
    }

    #[test]
    fn io_error_yields_cairo_read_error() {
        let mut r = IoErrorReader;

        match ImageSurface::create_from_png(&mut r) {
            Err(IoError::Cairo(Status::ReadError)) => (),
            _ => unreachable!()
        }
    }
}
<|endoftext|>


<commit_before>use truetype::{Result, Tape, Value};

/// A device or variation correction.
#[derive(Clone, Debug)]
pub enum Correction {
    /// A device correction.
    Device(Device),
    /// A variation correction.
    Variation(Variation),
}

table! {
    @define
    #[doc = "A device correction."]
    pub Device { // Device
        start_size   (u16     ), // StartSize
        end_size     (u16     ), // EndSize
        delta_format (u16     ), // DeltaFormat
        delta_data   (Vec<u16>), // DeltaValue
    }
}

table! {
    #[doc = "A variation correction."]
    pub Variation { // VariationIndex
        outer_index  (u16), // DeltaSetOuterIndex
        inner_index  (u16), // DeltaSetInnerIndex
        delta_format (u16), // DeltaFormat
    }
}

table! {
    pub Header {
        _dummy (u32),
        format (u16),
    }
}

impl Value for Correction {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        Ok(match try!(tape.peek::<Header>()).format {
            1 | 2 | 3 => Correction::Device(try!(tape.take())),
            0x8000 => Correction::Variation(try!(tape.take())),
            _ => raise!("found an unknown format of the correction table"),
        })
    }
}

impl Value for Device {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        let start_size = try!(tape.take());
        let end_size = try!(tape.take());
        if start_size > end_size {
            raise!("found a malformed device table");
        }
        let delta_format = try!(tape.take());
        if delta_format == 0 || delta_format > 3 {
            raise!("found an unknown format of the device table");
        }
        let count = (end_size - start_size) as usize + 1;
        let bit_count = (1 << delta_format as usize) * count;
        let short_count = (bit_count + 16 - bit_count % 16) >> 4;
        let delta_data = try!(tape.take_given(short_count));
        Ok(Device {
            start_size: start_size,
            end_size: end_size,
            delta_format: delta_format,
            delta_data: delta_data,
        })
    }
}
<commit_msg>Adjust a description<commit_after>use truetype::{Result, Tape, Value};

/// A correction.
#[derive(Clone, Debug)]
pub enum Correction {
    /// A device correction.
    Device(Device),
    /// A variation correction.
    Variation(Variation),
}

table! {
    @define
    #[doc = "A device correction."]
    pub Device { // Device
        start_size   (u16     ), // StartSize
        end_size     (u16     ), // EndSize
        delta_format (u16     ), // DeltaFormat
        delta_data   (Vec<u16>), // DeltaValue
    }
}

table! {
    #[doc = "A variation correction."]
    pub Variation { // VariationIndex
        outer_index  (u16), // DeltaSetOuterIndex
        inner_index  (u16), // DeltaSetInnerIndex
        delta_format (u16), // DeltaFormat
    }
}

table! {
    pub Header {
        _dummy (u32),
        format (u16),
    }
}

impl Value for Correction {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        Ok(match try!(tape.peek::<Header>()).format {
            1 | 2 | 3 => Correction::Device(try!(tape.take())),
            0x8000 => Correction::Variation(try!(tape.take())),
            _ => raise!("found an unknown format of the correction table"),
        })
    }
}

impl Value for Device {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        let start_size = try!(tape.take());
        let end_size = try!(tape.take());
        if start_size > end_size {
            raise!("found a malformed device table");
        }
        let delta_format = try!(tape.take());
        if delta_format == 0 || delta_format > 3 {
            raise!("found an unknown format of the device table");
        }
        let count = (end_size - start_size) as usize + 1;
        let bit_count = (1 << delta_format as usize) * count;
        let short_count = (bit_count + 16 - bit_count % 16) >> 4;
        let delta_data = try!(tape.take_given(short_count));
        Ok(Device {
            start_size: start_size,
            end_size: end_size,
            delta_format: delta_format,
            delta_data: delta_data,
        })
    }
}
<|endoftext|>


<commit_before>use super::Status;
use crate as ion_shell;
use builtins_proc::builtin;
use calc::{eval, eval_polish, CalcError, Value};
use std::io::{self, Write};

const REPL_GUIDE: &str = r#"ion-calc
Type in expressions to have them evaluated.
Type "help" for help."#;

fn calc_or_polish_calc(args: &str) -> Result<Value, CalcError> {
    match eval(args) {
        Ok(t) => Ok(t),
        Err(_) => eval_polish(args),
    }
}

#[builtin(
    desc = "Floating-point calculator",
    man = "
SYNOPSIS
    calc [EXPRESSION]

DESCRIPTION
    Evaluates arithmetic expressions

SPECIAL EXPRESSIONS
    help (only in interactive mode)
        prints this help text

    --help (only in non-interactive mode)
        prints this help text

    exit (only in interactive mode)
        exits the program

NOTATIONS
    infix notation
        e.g. 3 * 4 + 5

    polish notation
        e.g. + * 3 4 5

EXAMPLES
    Add two plus two in infix notation
        calc 2+2

    Add two plus two in polish notation
        calc + 2 2

AUTHOR
    Written by Hunter Goldstein."
)]
pub fn calc(args: &[crate::types::Str], _: &mut crate::Shell<'_>) -> Status {
    let stdout = io::stdout();
    let mut stdout = stdout.lock();
    if args.get(1).is_some() {
        let result = calc_or_polish_calc(&args[1..].join(" "));
        match result {
            Ok(v) => {
                println!("{}", v);
                Status::SUCCESS
            }
            Err(e) => Status::error(format!("{}", e)),
        }
    } else {
        let prompt = b"ion-calc: ";
        println!("{}", REPL_GUIDE);
        loop {
            let _ = stdout.write(prompt);
            let _ = stdout.flush();
            let mut input = String::new();
            let _ = io::stdin().read_line(&mut input);
            if input.is_empty() {
                return Status::SUCCESS;
            } else {
                match input.trim() {
                    "" => (),
                    "exit" => return Status::SUCCESS,
                    s => {
                        let result = calc_or_polish_calc(s);
                        match result {
                            Ok(v) => println!("{}", v),
                            Err(e) => eprintln!("{}", e),
                        }
                    }
                }
            }
        }
    }
}
<commit_msg>Use liner for the calc builtin rather than manipulating stdout directly<commit_after>use super::{EmptyCompleter, Status};
use crate as ion_shell;
use builtins_proc::builtin;
use calc::{eval, eval_polish, CalcError, Value};
use liner::Context;

const REPL_GUIDE: &str = r#"ion-calc
Type in expressions to have them evaluated.
Type "help" for help."#;

fn calc_or_polish_calc(args: &str) -> Result<Value, CalcError> {
    match eval(args) {
        Ok(t) => Ok(t),
        Err(_) => eval_polish(args),
    }
}

#[builtin(
    desc = "Floating-point calculator",
    man = "
SYNOPSIS
    calc [EXPRESSION]

DESCRIPTION
    Evaluates arithmetic expressions

SPECIAL EXPRESSIONS
    help (only in interactive mode)
        prints this help text

    --help (only in non-interactive mode)
        prints this help text

    exit (only in interactive mode)
        exits the program

NOTATIONS
    infix notation
        e.g. 3 * 4 + 5

    polish notation
        e.g. + * 3 4 5

EXAMPLES
    Add two plus two in infix notation
        calc 2+2

    Add two plus two in polish notation
        calc + 2 2

AUTHOR
    Written by Hunter Goldstein."
)]
pub fn calc(args: &[crate::types::Str], _: &mut crate::Shell<'_>) -> Status {
    if args.get(1).is_some() {
        let result = calc_or_polish_calc(&args[1..].join(" "));
        match result {
            Ok(v) => {
                println!("{}", v);
                Status::SUCCESS
            }
            Err(e) => Status::error(format!("{}", e)),
        }
    } else {
        println!("{}", REPL_GUIDE);
        let mut context = Context::new();
        loop {
            match context
                .read_line("ion-calc: ", None, &mut EmptyCompleter)
                .as_ref()
                .map(AsRef::as_ref)
            {
                Ok("") => return Status::SUCCESS,
                Ok(text) if text.trim() == "exit" => return Status::SUCCESS,
                Ok(s) => {
                    let result = calc_or_polish_calc(s);
                    match result {
                        Ok(v) => println!("{}", v),
                        Err(e) => eprintln!("{}", e),
                    }
                }
                Err(err) => {
                    eprintln!("{}", err);
                    return Status::SUCCESS;
                }
            }
        }
    }
}
<|endoftext|>