<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/. */

//! Borders, padding, and margins.

use layout::box_::Box;

use computed = style::computed_values;
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage};
use servo_util::geometry::Au;
use servo_util::geometry;

/// A collapsible margin. See CSS 2.1 § 8.3.1.
pub struct AdjoiningMargins {
    /// The value of the greatest positive margin.
    most_positive: Au,

    /// The actual value (not the absolute value) of the negative margin with the largest absolute
    /// value. Since this is not the absolute value, this is always zero or negative.
    most_negative: Au,
}

impl AdjoiningMargins {
    pub fn new() -> AdjoiningMargins {
        AdjoiningMargins {
            most_positive: Au(0),
            most_negative: Au(0),
        }
    }

    pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
        if margin_value >= Au(0) {
            AdjoiningMargins {
                most_positive: margin_value,
                most_negative: Au(0),
            }
        } else {
            AdjoiningMargins {
                most_positive: Au(0),
                most_negative: margin_value,
            }
        }
    }

    pub fn union(&mut self, other: AdjoiningMargins) {
        self.most_positive = geometry::max(self.most_positive, other.most_positive);
        self.most_negative = geometry::min(self.most_negative, other.most_negative)
    }

    pub fn collapse(&self) -> Au {
        self.most_positive + self.most_negative
    }
}

/// Represents the top and bottom margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
pub enum CollapsibleMargins {
    /// Margins may not collapse with this flow.
    NoCollapsibleMargins(Au, Au),

    /// Both the top and bottom margins (specified here in that order) may collapse, but the
    /// margins do not collapse through this flow.
    MarginsCollapse(AdjoiningMargins, AdjoiningMargins),

    /// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t
    /// have any border, padding, or out-of-flow (floating or positioned) content
    MarginsCollapseThrough(AdjoiningMargins),
}

impl CollapsibleMargins {
    pub fn new() -> CollapsibleMargins {
        NoCollapsibleMargins(Au(0), Au(0))
    }
}

enum FinalMarginState {
    MarginsCollapseThroughFinalMarginState,
    BottomMarginCollapsesFinalMarginState,
}

pub struct MarginCollapseInfo {
    state: MarginCollapseState,
    top_margin: AdjoiningMargins,
    margin_in: AdjoiningMargins,
}

impl MarginCollapseInfo {
    /// TODO(#2012, pcwalton): Remove this method once `box_` is not an `Option`.
    pub fn new() -> MarginCollapseInfo {
        MarginCollapseInfo {
            state: AccumulatingCollapsibleTopMargin,
            top_margin: AdjoiningMargins::new(),
            margin_in: AdjoiningMargins::new(),
        }
    }

    pub fn initialize_top_margin(&mut self,
                                 fragment: &Box,
                                 can_collapse_top_margin_with_kids: bool) {
        if !can_collapse_top_margin_with_kids {
            self.state = AccumulatingMarginIn
        }

        self.top_margin = AdjoiningMargins::from_margin(fragment.margin.get().top)
    }

    pub fn finish_and_compute_collapsible_margins(mut self,
                                                  fragment: &Box,
                                                  can_collapse_bottom_margin_with_kids: bool)
                                                  -> (CollapsibleMargins, Au) {
        let state = match self.state {
            AccumulatingCollapsibleTopMargin => {
                match fragment.style().Box.get().height {
                    LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => {
                        MarginsCollapseThroughFinalMarginState
                    },
                    _ => {
                        // If the box has an explicitly specified height, margins may not collapse
                        // through it.
                        BottomMarginCollapsesFinalMarginState
                    }
                }
            }
            AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState,
        };

        // Different logic is needed here depending on whether this flow can collapse its bottom
        // margin with its children.
        let bottom_margin = fragment.margin.get().bottom;
        if !can_collapse_bottom_margin_with_kids {
            match state {
                MarginsCollapseThroughFinalMarginState => {
                    let advance = self.top_margin.collapse();
                    self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapse(self.top_margin, self.margin_in), advance)
                }
                BottomMarginCollapsesFinalMarginState => {
                    let advance = self.margin_in.collapse();
                    self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapse(self.top_margin, self.margin_in), advance)
                }
            }
        } else {
            match state {
                MarginsCollapseThroughFinalMarginState => {
                    self.top_margin.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapseThrough(self.top_margin), Au(0))
                }
                BottomMarginCollapsesFinalMarginState => {
                    self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapse(self.top_margin, self.margin_in), Au(0))
                }
            }
        }
    }

    pub fn current_float_ceiling(&mut self) -> Au {
        match self.state {
            AccumulatingCollapsibleTopMargin => self.top_margin.collapse(),
            AccumulatingMarginIn => self.margin_in.collapse(),
        }
    }

    /// Adds the child's potentially collapsible top margin to the current margin state and
    /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
    /// that should be added to the Y offset during block layout.
    pub fn advance_top_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
        match (self.state, *child_collapsible_margins) {
            (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(top, _)) => {
                self.state = AccumulatingMarginIn;
                top
            }
            (AccumulatingCollapsibleTopMargin, MarginsCollapse(top, _)) => {
                self.top_margin.union(top);
                self.state = AccumulatingMarginIn;
                Au(0)
            }
            (AccumulatingMarginIn, NoCollapsibleMargins(top, _)) => {
                let previous_margin_value = self.margin_in.collapse();
                self.margin_in = AdjoiningMargins::new();
                previous_margin_value + top
            }
            (AccumulatingMarginIn, MarginsCollapse(top, _)) => {
                self.margin_in.union(top);
                let margin_value = self.margin_in.collapse();
                self.margin_in = AdjoiningMargins::new();
                margin_value
            }
            (_, MarginsCollapseThrough(_)) => {
                // For now, we ignore this; this will be handled by `advance_bottom_margin` below.
                Au(0)
            }
        }
    }

    /// Adds the child's potentially collapsible bottom margin to the current margin state and
    /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
    /// that should be added to the Y offset during block layout.
    pub fn advance_bottom_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
        match (self.state, *child_collapsible_margins) {
            (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) |
            (AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => {
                // Can't happen because the state will have been replaced with
                // `AccumulatingMarginIn` above.
                fail!("should not be accumulating collapsible top margins anymore!")
            }
            (AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => {
                self.top_margin.union(margin);
                Au(0)
            }
            (AccumulatingMarginIn, NoCollapsibleMargins(_, bottom)) => {
                assert_eq!(self.margin_in.most_positive, Au(0));
                assert_eq!(self.margin_in.most_negative, Au(0));
                bottom
            }
            (AccumulatingMarginIn, MarginsCollapse(_, bottom)) |
            (AccumulatingMarginIn, MarginsCollapseThrough(bottom)) => {
                self.margin_in.union(bottom);
                Au(0)
            }
        }
    }
}

enum MarginCollapseState {
    AccumulatingCollapsibleTopMargin,
    AccumulatingMarginIn,
}

/// Intrinsic widths, which consist of minimum and preferred.
pub struct IntrinsicWidths {
    /// The *minimum width* of the content.
    minimum_width: Au,
    /// The *preferred width* of the content.
    preferred_width: Au,
    /// The estimated sum of borders, padding, and margins. Some calculations use this information
    /// when computing intrinsic widths.
    surround_width: Au,
}

impl IntrinsicWidths {
    pub fn new() -> IntrinsicWidths {
        IntrinsicWidths {
            minimum_width: Au(0),
            preferred_width: Au(0),
            surround_width: Au(0),
        }
    }

    pub fn total_minimum_width(&self) -> Au {
        self.minimum_width + self.surround_width
    }

    pub fn total_preferred_width(&self) -> Au {
        self.preferred_width + self.surround_width
    }
}

/// Useful helper data type when computing values for blocks and positioned elements.
pub enum MaybeAuto {
    Auto,
    Specified(Au),
}

impl MaybeAuto {
    #[inline]
    pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au)
                      -> MaybeAuto {
        match length {
            computed::LPA_Auto => Auto,
            computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)),
            computed::LPA_Length(length) => Specified(length)
        }
    }

    #[inline]
    pub fn specified_or_default(&self, default: Au) -> Au {
        match *self {
            Auto => default,
            Specified(value) => value,
        }
    }

    #[inline]
    pub fn specified_or_zero(&self) -> Au {
        self.specified_or_default(Au::new(0))
    }
}

pub fn specified_or_none(length: computed::LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> {
    match length {
        computed::LPN_None => None,
        computed::LPN_Percentage(percent) => Some(containing_length.scale_by(percent)),
        computed::LPN_Length(length) => Some(length),
    }
}

pub fn specified(length: computed::LengthOrPercentage, containing_length: Au) -> Au {
    match length {
        computed::LP_Length(length) => length,
        computed::LP_Percentage(p) => containing_length.scale_by(p)
    }
}
<commit_msg>'min-height' can prevent margin collapsing through an element, like 'height'<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/. */

//! Borders, padding, and margins.

use layout::box_::Box;

use computed = style::computed_values;
use style::computed_values::{LPA_Auto, LPA_Length, LPA_Percentage, LP_Length, LP_Percentage};
use servo_util::geometry::Au;
use servo_util::geometry;

/// A collapsible margin. See CSS 2.1 § 8.3.1.
pub struct AdjoiningMargins {
    /// The value of the greatest positive margin.
    most_positive: Au,

    /// The actual value (not the absolute value) of the negative margin with the largest absolute
    /// value. Since this is not the absolute value, this is always zero or negative.
    most_negative: Au,
}

impl AdjoiningMargins {
    pub fn new() -> AdjoiningMargins {
        AdjoiningMargins {
            most_positive: Au(0),
            most_negative: Au(0),
        }
    }

    pub fn from_margin(margin_value: Au) -> AdjoiningMargins {
        if margin_value >= Au(0) {
            AdjoiningMargins {
                most_positive: margin_value,
                most_negative: Au(0),
            }
        } else {
            AdjoiningMargins {
                most_positive: Au(0),
                most_negative: margin_value,
            }
        }
    }

    pub fn union(&mut self, other: AdjoiningMargins) {
        self.most_positive = geometry::max(self.most_positive, other.most_positive);
        self.most_negative = geometry::min(self.most_negative, other.most_negative)
    }

    pub fn collapse(&self) -> Au {
        self.most_positive + self.most_negative
    }
}

/// Represents the top and bottom margins of a flow with collapsible margins. See CSS 2.1 § 8.3.1.
pub enum CollapsibleMargins {
    /// Margins may not collapse with this flow.
    NoCollapsibleMargins(Au, Au),

    /// Both the top and bottom margins (specified here in that order) may collapse, but the
    /// margins do not collapse through this flow.
    MarginsCollapse(AdjoiningMargins, AdjoiningMargins),

    /// Margins collapse *through* this flow. This means, essentially, that the flow doesn’t
    /// have any border, padding, or out-of-flow (floating or positioned) content
    MarginsCollapseThrough(AdjoiningMargins),
}

impl CollapsibleMargins {
    pub fn new() -> CollapsibleMargins {
        NoCollapsibleMargins(Au(0), Au(0))
    }
}

enum FinalMarginState {
    MarginsCollapseThroughFinalMarginState,
    BottomMarginCollapsesFinalMarginState,
}

pub struct MarginCollapseInfo {
    state: MarginCollapseState,
    top_margin: AdjoiningMargins,
    margin_in: AdjoiningMargins,
}

impl MarginCollapseInfo {
    /// TODO(#2012, pcwalton): Remove this method once `box_` is not an `Option`.
    pub fn new() -> MarginCollapseInfo {
        MarginCollapseInfo {
            state: AccumulatingCollapsibleTopMargin,
            top_margin: AdjoiningMargins::new(),
            margin_in: AdjoiningMargins::new(),
        }
    }

    pub fn initialize_top_margin(&mut self,
                                 fragment: &Box,
                                 can_collapse_top_margin_with_kids: bool) {
        if !can_collapse_top_margin_with_kids {
            self.state = AccumulatingMarginIn
        }

        self.top_margin = AdjoiningMargins::from_margin(fragment.margin.get().top)
    }

    pub fn finish_and_compute_collapsible_margins(mut self,
                                                  fragment: &Box,
                                                  can_collapse_bottom_margin_with_kids: bool)
                                                  -> (CollapsibleMargins, Au) {
        let state = match self.state {
            AccumulatingCollapsibleTopMargin => {
                match fragment.style().Box.get().height {
                    LPA_Auto | LPA_Length(Au(0)) | LPA_Percentage(0.) => {
                        match fragment.style().Box.get().min_height {
                            LP_Length(Au(0)) | LP_Percentage(0.) => {
                                MarginsCollapseThroughFinalMarginState
                            },
                            _ => {
                                // If the box has non-zero min-height, margins may not collapse
                                // through it.
                                BottomMarginCollapsesFinalMarginState
                            }
                        }
                    },
                    _ => {
                        // If the box has an explicitly specified height, margins may not collapse
                        // through it.
                        BottomMarginCollapsesFinalMarginState
                    }
                }
            }
            AccumulatingMarginIn => BottomMarginCollapsesFinalMarginState,
        };

        // Different logic is needed here depending on whether this flow can collapse its bottom
        // margin with its children.
        let bottom_margin = fragment.margin.get().bottom;
        if !can_collapse_bottom_margin_with_kids {
            match state {
                MarginsCollapseThroughFinalMarginState => {
                    let advance = self.top_margin.collapse();
                    self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapse(self.top_margin, self.margin_in), advance)
                }
                BottomMarginCollapsesFinalMarginState => {
                    let advance = self.margin_in.collapse();
                    self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapse(self.top_margin, self.margin_in), advance)
                }
            }
        } else {
            match state {
                MarginsCollapseThroughFinalMarginState => {
                    self.top_margin.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapseThrough(self.top_margin), Au(0))
                }
                BottomMarginCollapsesFinalMarginState => {
                    self.margin_in.union(AdjoiningMargins::from_margin(bottom_margin));
                    (MarginsCollapse(self.top_margin, self.margin_in), Au(0))
                }
            }
        }
    }

    pub fn current_float_ceiling(&mut self) -> Au {
        match self.state {
            AccumulatingCollapsibleTopMargin => self.top_margin.collapse(),
            AccumulatingMarginIn => self.margin_in.collapse(),
        }
    }

    /// Adds the child's potentially collapsible top margin to the current margin state and
    /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
    /// that should be added to the Y offset during block layout.
    pub fn advance_top_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
        match (self.state, *child_collapsible_margins) {
            (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(top, _)) => {
                self.state = AccumulatingMarginIn;
                top
            }
            (AccumulatingCollapsibleTopMargin, MarginsCollapse(top, _)) => {
                self.top_margin.union(top);
                self.state = AccumulatingMarginIn;
                Au(0)
            }
            (AccumulatingMarginIn, NoCollapsibleMargins(top, _)) => {
                let previous_margin_value = self.margin_in.collapse();
                self.margin_in = AdjoiningMargins::new();
                previous_margin_value + top
            }
            (AccumulatingMarginIn, MarginsCollapse(top, _)) => {
                self.margin_in.union(top);
                let margin_value = self.margin_in.collapse();
                self.margin_in = AdjoiningMargins::new();
                margin_value
            }
            (_, MarginsCollapseThrough(_)) => {
                // For now, we ignore this; this will be handled by `advance_bottom_margin` below.
                Au(0)
            }
        }
    }

    /// Adds the child's potentially collapsible bottom margin to the current margin state and
    /// advances the Y offset by the appropriate amount to handle that margin. Returns the amount
    /// that should be added to the Y offset during block layout.
    pub fn advance_bottom_margin(&mut self, child_collapsible_margins: &CollapsibleMargins) -> Au {
        match (self.state, *child_collapsible_margins) {
            (AccumulatingCollapsibleTopMargin, NoCollapsibleMargins(..)) |
            (AccumulatingCollapsibleTopMargin, MarginsCollapse(..)) => {
                // Can't happen because the state will have been replaced with
                // `AccumulatingMarginIn` above.
                fail!("should not be accumulating collapsible top margins anymore!")
            }
            (AccumulatingCollapsibleTopMargin, MarginsCollapseThrough(margin)) => {
                self.top_margin.union(margin);
                Au(0)
            }
            (AccumulatingMarginIn, NoCollapsibleMargins(_, bottom)) => {
                assert_eq!(self.margin_in.most_positive, Au(0));
                assert_eq!(self.margin_in.most_negative, Au(0));
                bottom
            }
            (AccumulatingMarginIn, MarginsCollapse(_, bottom)) |
            (AccumulatingMarginIn, MarginsCollapseThrough(bottom)) => {
                self.margin_in.union(bottom);
                Au(0)
            }
        }
    }
}

enum MarginCollapseState {
    AccumulatingCollapsibleTopMargin,
    AccumulatingMarginIn,
}

/// Intrinsic widths, which consist of minimum and preferred.
pub struct IntrinsicWidths {
    /// The *minimum width* of the content.
    minimum_width: Au,
    /// The *preferred width* of the content.
    preferred_width: Au,
    /// The estimated sum of borders, padding, and margins. Some calculations use this information
    /// when computing intrinsic widths.
    surround_width: Au,
}

impl IntrinsicWidths {
    pub fn new() -> IntrinsicWidths {
        IntrinsicWidths {
            minimum_width: Au(0),
            preferred_width: Au(0),
            surround_width: Au(0),
        }
    }

    pub fn total_minimum_width(&self) -> Au {
        self.minimum_width + self.surround_width
    }

    pub fn total_preferred_width(&self) -> Au {
        self.preferred_width + self.surround_width
    }
}

/// Useful helper data type when computing values for blocks and positioned elements.
pub enum MaybeAuto {
    Auto,
    Specified(Au),
}

impl MaybeAuto {
    #[inline]
    pub fn from_style(length: computed::LengthOrPercentageOrAuto, containing_length: Au)
                      -> MaybeAuto {
        match length {
            computed::LPA_Auto => Auto,
            computed::LPA_Percentage(percent) => Specified(containing_length.scale_by(percent)),
            computed::LPA_Length(length) => Specified(length)
        }
    }

    #[inline]
    pub fn specified_or_default(&self, default: Au) -> Au {
        match *self {
            Auto => default,
            Specified(value) => value,
        }
    }

    #[inline]
    pub fn specified_or_zero(&self) -> Au {
        self.specified_or_default(Au::new(0))
    }
}

pub fn specified_or_none(length: computed::LengthOrPercentageOrNone, containing_length: Au) -> Option<Au> {
    match length {
        computed::LPN_None => None,
        computed::LPN_Percentage(percent) => Some(containing_length.scale_by(percent)),
        computed::LPN_Length(length) => Some(length),
    }
}

pub fn specified(length: computed::LengthOrPercentage, containing_length: Au) -> Au {
    match length {
        computed::LP_Length(length) => length,
        computed::LP_Percentage(p) => containing_length.scale_by(p)
    }
}
<|endoftext|>


<commit_before>//! The state of the game itself.

use message;

use std::collections::HashMap;
use std::sync::mpsc;

use math::distance_between;
use rand::{thread_rng, Rng};

use self::super::Client;
use self::super::WebSocketEvent;

static BULLET_RADIUS: f32 = 5.0;
static PLAYER_RADIUS: f32 = 10.0;
static MAP_WIDTH: f32 = 500.0;
static MAP_HEIGHT: f32 = 500.0;

/// The `GameState` contains the whole state of the game.
///
/// It consists of both players, and all the clients which are currently connected.
#[derive(Debug)]
pub struct GameState {
    players: HashMap<u32, message::Player>,
    bullets: HashMap<u32, message::Bullet>,
    clients: HashMap<u32, Client>,
    next_bullet_id: u32,
}

impl GameState {
    /// Create a new game state.
    pub fn new() -> GameState {
        GameState {
            players: HashMap::new(),
            bullets: HashMap::new(),
            clients: HashMap::new(),
            next_bullet_id: 0,
        }
    }

    /// Tries to process every available websocket event without blocking.
    pub fn process_websocket_events(&mut self, game_messages: &mpsc::Receiver<WebSocketEvent>) {
        loop {
            match game_messages.try_recv() {
                Ok(message) => self.process_websocket_event(message),
                Err(mpsc::TryRecvError::Empty) => return,
                Err(mpsc::TryRecvError::Disconnected) => panic!("Now I am disconnected?"),
            }
        }
    }

    /// Updates the game state in one tick.
    pub fn process_game_update(&mut self) {
        // Do a normal position update
        let player_ids: Vec<_> = self.players.keys().map(|i| *i).collect();
        for cur_player_id in &player_ids {
            {
                let cur_player = self.players.get(cur_player_id).unwrap();
                match (cur_player.move_x, cur_player.move_y) {
                    (None, None) => continue,
                    (Some(move_x), Some(move_y)) => {
                        let mut collides = false;
                        for cmp_player_id in &player_ids {
                            if cmp_player_id != cur_player_id {
                                let cmp_player = self.players.get(cmp_player_id).unwrap();
                                if distance_between(cur_player.x + move_x,
                                                    cur_player.y + move_y,
                                                    cmp_player.x,
                                                    cmp_player.y) <
                                   2.0 * PLAYER_RADIUS {
                                    collides = true;
                                    break;
                                }
                            }
                        }
                        if collides {
                            continue;
                        }
                    }
                    _ => panic!("Invariant not met: player moves only in one direction"),
                }
            }

            let mut player = self.players.get_mut(cur_player_id).unwrap();
            player.x = (player.x + player.move_x.unwrap_or(0.0))
                           .max(PLAYER_RADIUS)
                           .min(MAP_WIDTH - PLAYER_RADIUS);
            player.y = (player.y + player.move_y.unwrap_or(0.0))
                           .max(PLAYER_RADIUS)
                           .min(MAP_HEIGHT - PLAYER_RADIUS);
        }

        let mut destroyed_bullets = Vec::new();
        let mut destroyed_players = Vec::new();

        for (_, bullet) in &mut self.bullets {
            bullet.x += bullet.move_x.unwrap_or(0.0);
            bullet.y += bullet.move_y.unwrap_or(0.0);

            if bullet.x < 0.0 || bullet.x > MAP_WIDTH || bullet.y < 0.0 || bullet.y > MAP_HEIGHT {
                destroyed_bullets.push(bullet.id);
            }
        }

        // Check for collisions
        for (_, bullet) in &mut self.bullets {
            for (_, player) in &mut self.players {
                if distance_between(bullet.x, bullet.y, player.x, player.y) <
                   BULLET_RADIUS + PLAYER_RADIUS {
                    destroyed_bullets.push(bullet.id);
                    destroyed_players.push(player.id);
                }
            }
        }

        // Process destroy requests
        for bullet_id in destroyed_bullets {
            let _ = self.bullets.remove(&bullet_id);
        }

        let mut rng = thread_rng();
        for player_id in destroyed_players {
            let (new_x, new_y) = self.random_free_spot(&mut rng);
            let dead_player = self.players.get_mut(&player_id).unwrap();
            dead_player.x = new_x;
            dead_player.y = new_y;
        }
    }

    /// Send the current state to each client.
    pub fn send_state_updates(&self) {
        let value = self.serialize();

        for (_, client) in &self.clients {
            // Always ignore if the send fails.
            // We will eventually get a disconnect WebSocketMessage where we will cleanly do the disconnect.
            let _ = client.send(value.clone());
        }
    }

    /// Process a web socket event.
    fn process_websocket_event(&mut self, message: WebSocketEvent) {
        match message {
            WebSocketEvent::ClientCreated { client } => {
                let welcome_message = message::Message::Welcome {
                    id: client.id,
                    speed: 0.0,
                    size: PLAYER_RADIUS,
                    bullet_speed: 0.0,
                    bullet_size: BULLET_RADIUS,
                };

                let _ = client.send(welcome_message.to_string());
                let _ = client.send(self.serialize());

                let (x, y) = self.random_free_spot(&mut thread_rng());
                let _ = self.players
                            .insert(client.id, message::Player::not_moving(client.id, x, y));

                let _ = self.clients.insert(client.id, client);
            }
            WebSocketEvent::ClientClosed { client_id } => {
                let _ = self.players.remove(&client_id);
                let _ = self.clients.remove(&client_id);
            }
            WebSocketEvent::ClientMessage { client_id, message } => {
                self.process_client_message(client_id, message);
            }
        }
    }

    /// Serialize the entire game state into one json string.
    fn serialize(&self) -> String {
        let players: Vec<_> = self.players
                                  .values()
                                  .cloned()
                                  .collect();
        let bullets: Vec<_> = self.bullets
                                  .values()
                                  .cloned()
                                  .collect();
        let state = message::Message::WorldState {
            player_count: players.len() as u32,
            alive_players: players,
            alive_bullets: bullets,
        };
        state.to_string()
    }

    /// Process a simple string message from the client.
    fn process_client_message(&mut self, client_id: u32, message: message::Message) {
        match message {
            message::Message::StartMoving { move_x, move_y } => {
                let player = self.players.get_mut(&client_id).unwrap();
                player.move_x = Some(move_x);
                player.move_y = Some(move_y);
            }
            message::Message::StopMoving => {
                let player = self.players.get_mut(&client_id).unwrap();
                player.move_x = None;
                player.move_y = None;
            }
            message::Message::Fire { move_x, move_y } => {
                let player = self.players.get(&client_id).unwrap();

                // Have to move the bullet out of the way of the player to avoid an instant collision.
                let start_x = player.x + move_x * (BULLET_RADIUS + PLAYER_RADIUS + 1.0);
                let start_y = player.y + move_y * (BULLET_RADIUS + PLAYER_RADIUS + 1.0);

                let _ = self.bullets.insert(self.next_bullet_id,
                                            message::Bullet::moving(self.next_bullet_id,
                                                                    start_x,
                                                                    start_y,
                                                                    move_x,
                                                                    move_y));

                self.next_bullet_id += 1;
            }
            _ => panic!("Unprocessed message! {}", message.to_string()),
        }
    }

    fn random_free_spot<R: Rng>(&self, rng: &mut R) -> (f32, f32) {
        static MAX_ITERATIONS: u32 = 100;

        let min_vial_x = PLAYER_RADIUS;
        let min_vial_y = PLAYER_RADIUS;
        let max_vial_x = MAP_WIDTH - PLAYER_RADIUS;
        let max_vial_y = MAP_HEIGHT - PLAYER_RADIUS;

        for _ in 1..MAX_ITERATIONS {
            let x: f32 = rng.gen_range(min_vial_x, max_vial_x);
            let y: f32 = rng.gen_range(min_vial_y, max_vial_y);

            let mut collides = false;

            for (_, player) in &self.players {
                if distance_between(x, y, player.x, player.y) < 2.0 * PLAYER_RADIUS {
                    collides = true;
                    break;
                }
            }

            for (_, bullet) in &self.bullets {
                if distance_between(x, y, bullet.x, bullet.y) < PLAYER_RADIUS + BULLET_RADIUS {
                    collides = true;
                    break;
                }
            }

            if !collides {
                return (x, y);
            }
        }
        println!("Failed to find a random empty spot for player after {} iterations",
                 MAX_ITERATIONS);

        (rng.gen_range(0.0, MAP_WIDTH), rng.gen_range(0.0, MAP_HEIGHT))
    }
}
<commit_msg>Support speeds on the server-side<commit_after>//! The state of the game itself.

use message;

use std::collections::HashMap;
use std::sync::mpsc;

use math::distance_between;
use rand::{thread_rng, Rng};

use self::super::Client;
use self::super::WebSocketEvent;

static BULLET_RADIUS: f32 = 5.0;
static PLAYER_RADIUS: f32 = 10.0;
static BULLET_SPEED: f32 = 3.0;
static PLAYER_SPEED: f32 = 2.0;
static MAP_HEIGHT: f32 = 500.0;
static MAP_WIDTH: f32 = 500.0;

/// The `GameState` contains the whole state of the game.
///
/// It consists of both players, and all the clients which are currently connected.
#[derive(Debug)]
pub struct GameState {
    players: HashMap<u32, message::Player>,
    bullets: HashMap<u32, message::Bullet>,
    clients: HashMap<u32, Client>,
    next_bullet_id: u32,
}

impl GameState {
    /// Create a new game state.
    pub fn new() -> GameState {
        GameState {
            players: HashMap::new(),
            bullets: HashMap::new(),
            clients: HashMap::new(),
            next_bullet_id: 0,
        }
    }

    /// Tries to process every available websocket event without blocking.
    pub fn process_websocket_events(&mut self, game_messages: &mpsc::Receiver<WebSocketEvent>) {
        loop {
            match game_messages.try_recv() {
                Ok(message) => self.process_websocket_event(message),
                Err(mpsc::TryRecvError::Empty) => return,
                Err(mpsc::TryRecvError::Disconnected) => panic!("Now I am disconnected?"),
            }
        }
    }

    /// Updates the game state in one tick.
    pub fn process_game_update(&mut self) {
        // Do a normal position update
        let player_ids: Vec<_> = self.players.keys().map(|i| *i).collect();
        for cur_player_id in &player_ids {
            {
                let cur_player = self.players.get(cur_player_id).unwrap();
                match (cur_player.move_x, cur_player.move_y) {
                    (None, None) => continue,
                    (Some(move_x), Some(move_y)) => {
                        let mut collides = false;
                        for cmp_player_id in &player_ids {
                            if cmp_player_id != cur_player_id {
                                let cmp_player = self.players.get(cmp_player_id).unwrap();
                                if distance_between(cur_player.x + move_x,
                                                    cur_player.y + move_y,
                                                    cmp_player.x,
                                                    cmp_player.y) <
                                   2.0 * PLAYER_RADIUS {
                                    collides = true;
                                    break;
                                }
                            }
                        }
                        if collides {
                            continue;
                        }
                    }
                    _ => panic!("Invariant not met: player moves only in one direction"),
                }
            }

            let mut player = self.players.get_mut(cur_player_id).unwrap();
            player.x = (player.x + player.move_x.unwrap_or(0.0) * PLAYER_SPEED)
                           .max(PLAYER_RADIUS)
                           .min(MAP_WIDTH - PLAYER_RADIUS);
            player.y = (player.y + player.move_y.unwrap_or(0.0) * PLAYER_SPEED)
                           .max(PLAYER_RADIUS)
                           .min(MAP_HEIGHT - PLAYER_RADIUS);
        }

        let mut destroyed_bullets = Vec::new();
        let mut destroyed_players = Vec::new();

        for (_, bullet) in &mut self.bullets {
            bullet.x += bullet.move_x.unwrap_or(0.0) * BULLET_SPEED;
            bullet.y += bullet.move_y.unwrap_or(0.0) * BULLET_SPEED;

            if bullet.x < 0.0 || bullet.x > MAP_WIDTH || bullet.y < 0.0 || bullet.y > MAP_HEIGHT {
                destroyed_bullets.push(bullet.id);
            }
        }

        // Check for collisions
        for (_, bullet) in &mut self.bullets {
            for (_, player) in &mut self.players {
                if distance_between(bullet.x, bullet.y, player.x, player.y) <
                   BULLET_RADIUS + PLAYER_RADIUS {
                    destroyed_bullets.push(bullet.id);
                    destroyed_players.push(player.id);
                }
            }
        }

        // Process destroy requests
        for bullet_id in destroyed_bullets {
            let _ = self.bullets.remove(&bullet_id);
        }

        let mut rng = thread_rng();
        for player_id in destroyed_players {
            let (new_x, new_y) = self.random_free_spot(&mut rng);
            let dead_player = self.players.get_mut(&player_id).unwrap();
            dead_player.x = new_x;
            dead_player.y = new_y;
        }
    }

    /// Send the current state to each client.
    pub fn send_state_updates(&self) {
        let value = self.serialize();

        for (_, client) in &self.clients {
            // Always ignore if the send fails.
            // We will eventually get a disconnect WebSocketMessage where we will cleanly do the disconnect.
            let _ = client.send(value.clone());
        }
    }

    /// Process a web socket event.
    fn process_websocket_event(&mut self, message: WebSocketEvent) {
        match message {
            WebSocketEvent::ClientCreated { client } => {
                let welcome_message = message::Message::Welcome {
                    id: client.id,
                    speed: PLAYER_SPEED,
                    size: PLAYER_RADIUS,
                    bullet_speed: BULLET_SPEED,
                    bullet_size: BULLET_RADIUS,
                };

                let _ = client.send(welcome_message.to_string());
                let _ = client.send(self.serialize());

                let (x, y) = self.random_free_spot(&mut thread_rng());
                let _ = self.players
                            .insert(client.id, message::Player::not_moving(client.id, x, y));

                let _ = self.clients.insert(client.id, client);
            }
            WebSocketEvent::ClientClosed { client_id } => {
                let _ = self.players.remove(&client_id);
                let _ = self.clients.remove(&client_id);
            }
            WebSocketEvent::ClientMessage { client_id, message } => {
                self.process_client_message(client_id, message);
            }
        }
    }

    /// Serialize the entire game state into one json string.
    fn serialize(&self) -> String {
        let players: Vec<_> = self.players
                                  .values()
                                  .cloned()
                                  .collect();
        let bullets: Vec<_> = self.bullets
                                  .values()
                                  .cloned()
                                  .collect();
        let state = message::Message::WorldState {
            player_count: players.len() as u32,
            alive_players: players,
            alive_bullets: bullets,
        };
        state.to_string()
    }

    /// Process a simple string message from the client.
    fn process_client_message(&mut self, client_id: u32, message: message::Message) {
        match message {
            message::Message::StartMoving { move_x, move_y } => {
                let player = self.players.get_mut(&client_id).unwrap();
                player.move_x = Some(move_x);
                player.move_y = Some(move_y);
            }
            message::Message::StopMoving => {
                let player = self.players.get_mut(&client_id).unwrap();
                player.move_x = None;
                player.move_y = None;
            }
            message::Message::Fire { move_x, move_y } => {
                let player = self.players.get(&client_id).unwrap();

                // Have to move the bullet out of the way of the player to avoid an instant collision.
                let start_x = player.x + move_x * (BULLET_RADIUS + PLAYER_RADIUS + 1.0);
                let start_y = player.y + move_y * (BULLET_RADIUS + PLAYER_RADIUS + 1.0);

                let _ = self.bullets.insert(self.next_bullet_id,
                                            message::Bullet::moving(self.next_bullet_id,
                                                                    start_x,
                                                                    start_y,
                                                                    move_x,
                                                                    move_y));

                self.next_bullet_id += 1;
            }
            _ => panic!("Unprocessed message! {}", message.to_string()),
        }
    }

    fn random_free_spot<R: Rng>(&self, rng: &mut R) -> (f32, f32) {
        static MAX_ITERATIONS: u32 = 100;

        let min_vial_x = PLAYER_RADIUS;
        let min_vial_y = PLAYER_RADIUS;
        let max_vial_x = MAP_WIDTH - PLAYER_RADIUS;
        let max_vial_y = MAP_HEIGHT - PLAYER_RADIUS;

        for _ in 1..MAX_ITERATIONS {
            let x: f32 = rng.gen_range(min_vial_x, max_vial_x);
            let y: f32 = rng.gen_range(min_vial_y, max_vial_y);

            let mut collides = false;

            for (_, player) in &self.players {
                if distance_between(x, y, player.x, player.y) < 2.0 * PLAYER_RADIUS {
                    collides = true;
                    break;
                }
            }

            for (_, bullet) in &self.bullets {
                if distance_between(x, y, bullet.x, bullet.y) < PLAYER_RADIUS + BULLET_RADIUS {
                    collides = true;
                    break;
                }
            }

            if !collides {
                return (x, y);
            }
        }
        println!("Failed to find a random empty spot for player after {} iterations",
                 MAX_ITERATIONS);

        (rng.gen_range(0.0, MAP_WIDTH), rng.gen_range(0.0, MAP_HEIGHT))
    }
}
<|endoftext|>


<commit_before>use webcore::value::{Value, Reference, ConversionError};
use webcore::try_from::{TryFrom, TryInto};
use webcore::unsafe_typed_array::UnsafeTypedArray;
use webapi::event_target::{IEventTarget, EventTarget};
use webapi::blob::Blob;
use webapi::array_buffer::ArrayBuffer;
use webapi::dom_exception::{InvalidAccessError, SecurityError, SyntaxError};
use private::TODO;

/// Wrapper type around a CloseEvent code, indicating why the WebSocket was closed
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SocketCloseCode(pub u16);

// Close codes are defined here:
// https://tools.ietf.org/html/rfc6455#section-7.4
newtype_enum!(SocketCloseCode {
    /// Normal closure; the connection successfully completed whatever purpose for which it was
    /// created.
    NORMAL_CLOSURE = 1000,
    /// The endpoint is going away, either because of a server failure or because the browser is
    /// navigating away from the page that opened the connection.
    GOING_AWAY = 1001,
    /// The endpoint is terminating the connection due to a protocol error.
    PROTOCOL_ERROR = 1002,
    /// The connection is being terminated because the endpoint received data of a type it cannot
    /// accept (for example, a text-only endpoint received binary data).
    UNSUPPORTED_DATA = 1003,
    /// Reserved. Indicates that no status code was provided even though one was expected.
    NO_STATUS_RECEIVED = 1005,
    /// Reserved. Used to indicate that a connection was closed abnormally (that is, with no close
    /// frame being sent) when a status code is expected.
    ABNORMAL_CLOSURE = 1006,
    /// The endpoint is terminating the connection because a message was received that contained
    /// inconsistent data (e.g., non-UTF-8 data within a text message).
    INVALID_FRAME_PAYLOAD_DATA = 1007,
    /// The endpoint is terminating the connection because it received a message that violates its
    /// policy. This is a generic status code, used when codes 1003 and 1009 are not suitable.
    POLICY_VIOLATION = 1008,
    /// The endpoint is terminating the connection because a data frame was received that is too
    /// large.
    MESSAGE_TOO_BIG = 1009,
    /// The client is terminating the connection because it expected the server to negotiate one or
    /// more extensions, but the server didn't.
    MISSING_EXTENSION = 1010,
    /// The server is terminating the connection because it encountered an unexpected condition
    /// that prevented it from fulfilling the request.
    INTERNAL_ERROR = 1011,
    /// The server is terminating the connection because it is restarting.
    SERVICE_RESTART = 1012,
    /// The server is terminating the connection due to a temporary condition, e.g. it is
    /// overloaded and is casting off some of its clients.
    TRY_AGAIN_LATER = 1013,
    /// The server was acting as a gateway or proxy and received an invalid response from the
    /// upstream server. This is similar to 502 HTTP Status Code.
    BAD_GATEWAY = 1014,
    /// Reserved. Indicates that the connection was closed due to a failure to perform a TLS
    /// handshake (e.g., the server certificate can't be verified).
    TLS_HANDSHAKE = 1015,
});

/// The WebSocket object provides the API for creating and managing a WebSocket connection to a
/// server, as well as for sending and receiving data on the connection.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
// https://html.spec.whatwg.org/#websocket
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "WebSocket")]
#[reference(subclass_of(EventTarget))]
pub struct WebSocket( Reference );

impl IEventTarget for WebSocket {}

/// The type of binary data being transmitted by the WebSocket connection.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Attributes)
// https://html.spec.whatwg.org/#dom-websocket-binarytype
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SocketBinaryType {
    /// A Blob object represents a file-like object of immutable, raw data.
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
    Blob,
    /// The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer.
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)
    ArrayBuffer
}

impl SocketBinaryType {
    fn to_str(self) -> &'static str {
        match self {
            SocketBinaryType::Blob => "blob",
            SocketBinaryType::ArrayBuffer => "arraybuffer",
        }
    }
    fn from_str(s: &str) -> Self {
        match s {
            "blob" => SocketBinaryType::Blob,
            "arraybuffer" => SocketBinaryType::ArrayBuffer,
            other => panic!("Invalid binary type: {:?}", other)
        }
    }
}

/// A number indicating the state of the `WebSocket`.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants)
// https://html.spec.whatwg.org/#dom-websocket-readystate
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SocketReadyState {
    Connecting = 0,
    Open = 1,
    Closing = 2,
    Closed = 3
}

impl TryFrom<Value> for SocketReadyState {
    type Error = ConversionError;

    /// Performs the conversion.
    fn try_from(v: Value) -> Result<SocketReadyState, ConversionError> {
        match v.try_into()? {
            0 => Ok(SocketReadyState::Connecting),
            1 => Ok(SocketReadyState::Open),
            2 => Ok(SocketReadyState::Closing),
            3 => Ok(SocketReadyState::Closed),
            other => Err(ConversionError::Custom(format!("Unknown ready_state: {}", other)))
        }
    }
}

impl WebSocket {
    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket
    pub fn new(url: &str) -> Result<WebSocket, CreationError> {
        js_try!(
            return new WebSocket(@{url});
        ).unwrap()
    }

    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket
    pub fn new_with_protocols(url: &str, protocols: &[&str]) -> Result<WebSocket, CreationError> {
        js_try!(
            return new WebSocket(@{url}, @{protocols});
        ).unwrap()
    }

    /// Returns the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-binarytype
    pub fn binary_type(&self) -> SocketBinaryType {
        let binary_type: String = js!( return @{self}.binaryType; ).try_into().unwrap();
        SocketBinaryType::from_str(&binary_type)
    }

    /// Sets the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-binarytype
    pub fn set_binary_type(&self, binary_type: SocketBinaryType) {
        js!( @(no_return) @{self}.binaryType = @{binary_type.to_str()}; );
    }

    /// Returns the number of bytes of data that have been queued using calls to send()
    /// but not yet transmitted to the network. This value resets to zero once all queued
    /// data has been sent. This value does not reset to zero when the connection is closed;
    /// if you keep calling send(), this will continue to climb.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-bufferedamount
    pub fn buffered_amount(&self) -> u64 {
        js!( return @{self}.bufferedAmount; ).try_into().unwrap()
    }

    /// Returns the extensions selected by the server. This is currently only the empty
    /// string or a list of extensions as negotiated by the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-extensions
    pub fn extensions(&self) -> String {
        js!( return @{self}.extensions; ).try_into().unwrap()
    }

    /// Returns a string indicating the name of the sub-protocol the server selected;
    /// this will be one of the strings specified in the protocols parameter when
    /// creating the WebSocket object.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-protocol
    pub fn protocol(&self) -> String {
        js!( return @{self}.protocol; ).try_into().unwrap()
    }

    /// Returns the current state of the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-readystate
    pub fn ready_state(&self) -> SocketReadyState {
        js!( return @{self}.protocol; ).try_into().unwrap()
    }

    /// Returns the URL as resolved by the constructor. This is always an absolute URL.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-url
    pub fn url(&self) -> String {
        js!( return @{self}.url; ).try_into().unwrap()
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-close
    pub fn close(&self) {
        js!( @(no_return) @{self}.close(); );
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-close
    pub fn close_with_status(&self, code: SocketCloseCode, reason: &str) -> Result<(), CloseError> {
        js_try!( @(no_return)
            @{self}.close(@{code.0}, @{reason});
        ).unwrap()
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_text(&self, text: &str) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{text}); );
        Ok(())
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_blob(&self, blob: &Blob) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{blob}); );
        Ok(())
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_array_buffer(&self, array_buffer: &ArrayBuffer) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{array_buffer}); );
        Ok(())
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_bytes(&self, bytes: &[u8]) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{ UnsafeTypedArray(bytes) }); );
        Ok(())
    }
}

/// Errors thrown by `WebSocket::new`.
error_enum_boilerplate! {
    CreationError,
    SecurityError, SyntaxError
}

/// Errors thrown by `WebSocket::close_with_status`.
error_enum_boilerplate! {
    CloseError,
    InvalidAccessError, SyntaxError
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_close_codes() {
        assert_eq!(&format!("{:?}", SocketCloseCode::NORMAL_CLOSURE), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode::GOING_AWAY), "SocketCloseCode::GOING_AWAY");
        assert_eq!(&format!("{:?}", SocketCloseCode(1000)), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode(3001)), "SocketCloseCode(3001)");
    }
}

#[cfg(all(test, feature = "web_test"))]
mod web_tests {
    use super::*;

    #[test]
    fn test_new() {
        assert!(WebSocket::new("ws://localhost").is_ok());

        match WebSocket::new("bad url") {
            Err(CreationError::SyntaxError(_)) => (),
            v => panic!("expected SyntaxError, got {:?}", v),
        }
    }

    #[test]
    fn test_close() {
        let socket = WebSocket::new("ws://localhost").unwrap();

        socket.close();

        assert!(socket.close_with_status( SocketCloseCode::NORMAL_CLOSURE, "closed" ).is_ok());

        // Invalid close code
        match socket.close_with_status( SocketCloseCode(12345), "closed" ) {
            Err(CloseError::InvalidAccessError(_)) => (),
            v => panic!("expected InvalidAccessError, got {:?}", v),
        }

        // Close reason too long (>123 bytes according to spec)
        match socket.close_with_status(
            SocketCloseCode::NORMAL_CLOSURE,
            &(0..200).map(|_| "X").collect::<String>()
        ) {
            Err(CloseError::SyntaxError(_)) => (),
            v => panic!("expected SyntaxError, got {:?}", v),
        }
    }
}
<commit_msg>Fix `WebSocket::ready_state` (#158)<commit_after>use webcore::value::{Value, Reference, ConversionError};
use webcore::try_from::{TryFrom, TryInto};
use webcore::unsafe_typed_array::UnsafeTypedArray;
use webapi::event_target::{IEventTarget, EventTarget};
use webapi::blob::Blob;
use webapi::array_buffer::ArrayBuffer;
use webapi::dom_exception::{InvalidAccessError, SecurityError, SyntaxError};
use private::TODO;

/// Wrapper type around a CloseEvent code, indicating why the WebSocket was closed
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent)
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SocketCloseCode(pub u16);

// Close codes are defined here:
// https://tools.ietf.org/html/rfc6455#section-7.4
newtype_enum!(SocketCloseCode {
    /// Normal closure; the connection successfully completed whatever purpose for which it was
    /// created.
    NORMAL_CLOSURE = 1000,
    /// The endpoint is going away, either because of a server failure or because the browser is
    /// navigating away from the page that opened the connection.
    GOING_AWAY = 1001,
    /// The endpoint is terminating the connection due to a protocol error.
    PROTOCOL_ERROR = 1002,
    /// The connection is being terminated because the endpoint received data of a type it cannot
    /// accept (for example, a text-only endpoint received binary data).
    UNSUPPORTED_DATA = 1003,
    /// Reserved. Indicates that no status code was provided even though one was expected.
    NO_STATUS_RECEIVED = 1005,
    /// Reserved. Used to indicate that a connection was closed abnormally (that is, with no close
    /// frame being sent) when a status code is expected.
    ABNORMAL_CLOSURE = 1006,
    /// The endpoint is terminating the connection because a message was received that contained
    /// inconsistent data (e.g., non-UTF-8 data within a text message).
    INVALID_FRAME_PAYLOAD_DATA = 1007,
    /// The endpoint is terminating the connection because it received a message that violates its
    /// policy. This is a generic status code, used when codes 1003 and 1009 are not suitable.
    POLICY_VIOLATION = 1008,
    /// The endpoint is terminating the connection because a data frame was received that is too
    /// large.
    MESSAGE_TOO_BIG = 1009,
    /// The client is terminating the connection because it expected the server to negotiate one or
    /// more extensions, but the server didn't.
    MISSING_EXTENSION = 1010,
    /// The server is terminating the connection because it encountered an unexpected condition
    /// that prevented it from fulfilling the request.
    INTERNAL_ERROR = 1011,
    /// The server is terminating the connection because it is restarting.
    SERVICE_RESTART = 1012,
    /// The server is terminating the connection due to a temporary condition, e.g. it is
    /// overloaded and is casting off some of its clients.
    TRY_AGAIN_LATER = 1013,
    /// The server was acting as a gateway or proxy and received an invalid response from the
    /// upstream server. This is similar to 502 HTTP Status Code.
    BAD_GATEWAY = 1014,
    /// Reserved. Indicates that the connection was closed due to a failure to perform a TLS
    /// handshake (e.g., the server certificate can't be verified).
    TLS_HANDSHAKE = 1015,
});

/// The WebSocket object provides the API for creating and managing a WebSocket connection to a
/// server, as well as for sending and receiving data on the connection.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
// https://html.spec.whatwg.org/#websocket
#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "WebSocket")]
#[reference(subclass_of(EventTarget))]
pub struct WebSocket( Reference );

impl IEventTarget for WebSocket {}

/// The type of binary data being transmitted by the WebSocket connection.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Attributes)
// https://html.spec.whatwg.org/#dom-websocket-binarytype
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SocketBinaryType {
    /// A Blob object represents a file-like object of immutable, raw data.
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
    Blob,
    /// The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer.
    /// [(Javascript docs)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)
    ArrayBuffer
}

impl SocketBinaryType {
    fn to_str(self) -> &'static str {
        match self {
            SocketBinaryType::Blob => "blob",
            SocketBinaryType::ArrayBuffer => "arraybuffer",
        }
    }
    fn from_str(s: &str) -> Self {
        match s {
            "blob" => SocketBinaryType::Blob,
            "arraybuffer" => SocketBinaryType::ArrayBuffer,
            other => panic!("Invalid binary type: {:?}", other)
        }
    }
}

/// A number indicating the state of the `WebSocket`.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants)
// https://html.spec.whatwg.org/#dom-websocket-readystate
#[allow(missing_docs)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SocketReadyState {
    Connecting = 0,
    Open = 1,
    Closing = 2,
    Closed = 3
}

impl TryFrom<Value> for SocketReadyState {
    type Error = ConversionError;

    /// Performs the conversion.
    fn try_from(v: Value) -> Result<SocketReadyState, ConversionError> {
        match v.try_into()? {
            0 => Ok(SocketReadyState::Connecting),
            1 => Ok(SocketReadyState::Open),
            2 => Ok(SocketReadyState::Closing),
            3 => Ok(SocketReadyState::Closed),
            other => Err(ConversionError::Custom(format!("Unknown ready_state: {}", other)))
        }
    }
}

impl WebSocket {
    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket
    pub fn new(url: &str) -> Result<WebSocket, CreationError> {
        js_try!(
            return new WebSocket(@{url});
        ).unwrap()
    }

    /// Returns a newly constructed `WebSocket`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket
    pub fn new_with_protocols(url: &str, protocols: &[&str]) -> Result<WebSocket, CreationError> {
        js_try!(
            return new WebSocket(@{url}, @{protocols});
        ).unwrap()
    }

    /// Returns the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-binarytype
    pub fn binary_type(&self) -> SocketBinaryType {
        let binary_type: String = js!( return @{self}.binaryType; ).try_into().unwrap();
        SocketBinaryType::from_str(&binary_type)
    }

    /// Sets the binary type of the web socket. Only affects received messages.
    /// The default binary type is `Blob`.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-binarytype
    pub fn set_binary_type(&self, binary_type: SocketBinaryType) {
        js!( @(no_return) @{self}.binaryType = @{binary_type.to_str()}; );
    }

    /// Returns the number of bytes of data that have been queued using calls to send()
    /// but not yet transmitted to the network. This value resets to zero once all queued
    /// data has been sent. This value does not reset to zero when the connection is closed;
    /// if you keep calling send(), this will continue to climb.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-bufferedamount
    pub fn buffered_amount(&self) -> u64 {
        js!( return @{self}.bufferedAmount; ).try_into().unwrap()
    }

    /// Returns the extensions selected by the server. This is currently only the empty
    /// string or a list of extensions as negotiated by the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-extensions
    pub fn extensions(&self) -> String {
        js!( return @{self}.extensions; ).try_into().unwrap()
    }

    /// Returns a string indicating the name of the sub-protocol the server selected;
    /// this will be one of the strings specified in the protocols parameter when
    /// creating the WebSocket object.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-protocol
    pub fn protocol(&self) -> String {
        js!( return @{self}.protocol; ).try_into().unwrap()
    }

    /// Returns the current state of the connection.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-readystate
    pub fn ready_state(&self) -> SocketReadyState {
        js!( return @{self}.readyState; ).try_into().unwrap()
    }

    /// Returns the URL as resolved by the constructor. This is always an absolute URL.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-url
    pub fn url(&self) -> String {
        js!( return @{self}.url; ).try_into().unwrap()
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-close
    pub fn close(&self) {
        js!( @(no_return) @{self}.close(); );
    }

    /// Closes the WebSocket connection or connection attempt, if any. If the connection
    /// is already CLOSED, this method does nothing.
    ///
    /// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#close())
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-close
    pub fn close_with_status(&self, code: SocketCloseCode, reason: &str) -> Result<(), CloseError> {
        js_try!( @(no_return)
            @{self}.close(@{code.0}, @{reason});
        ).unwrap()
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_text(&self, text: &str) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{text}); );
        Ok(())
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_blob(&self, blob: &Blob) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{blob}); );
        Ok(())
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_array_buffer(&self, array_buffer: &ArrayBuffer) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{array_buffer}); );
        Ok(())
    }

    /// Enqueues the specified data to be transmitted to the server over the WebSocket
    /// connection, increasing the value of bufferedAmount by the number of bytes needed
    /// to contain the data. If the data can't be sent (for example, because it needs to
    /// be buffered but the buffer is full), the socket is closed automatically.
    // https://html.spec.whatwg.org/#the-websocket-interface:dom-websocket-send
    pub fn send_bytes(&self, bytes: &[u8]) -> Result< (), TODO > {
        js!( @(no_return) @{self}.send(@{ UnsafeTypedArray(bytes) }); );
        Ok(())
    }
}

/// Errors thrown by `WebSocket::new`.
error_enum_boilerplate! {
    CreationError,
    SecurityError, SyntaxError
}

/// Errors thrown by `WebSocket::close_with_status`.
error_enum_boilerplate! {
    CloseError,
    InvalidAccessError, SyntaxError
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_close_codes() {
        assert_eq!(&format!("{:?}", SocketCloseCode::NORMAL_CLOSURE), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode::GOING_AWAY), "SocketCloseCode::GOING_AWAY");
        assert_eq!(&format!("{:?}", SocketCloseCode(1000)), "SocketCloseCode::NORMAL_CLOSURE");
        assert_eq!(&format!("{:?}", SocketCloseCode(3001)), "SocketCloseCode(3001)");
    }
}

#[cfg(all(test, feature = "web_test"))]
mod web_tests {
    use super::*;

    #[test]
    fn test_new() {
        assert!(WebSocket::new("ws://localhost").is_ok());

        match WebSocket::new("bad url") {
            Err(CreationError::SyntaxError(_)) => (),
            v => panic!("expected SyntaxError, got {:?}", v),
        }
    }

    #[test]
    fn test_close() {
        let socket = WebSocket::new("ws://localhost").unwrap();

        socket.close();

        assert!(socket.close_with_status( SocketCloseCode::NORMAL_CLOSURE, "closed" ).is_ok());

        // Invalid close code
        match socket.close_with_status( SocketCloseCode(12345), "closed" ) {
            Err(CloseError::InvalidAccessError(_)) => (),
            v => panic!("expected InvalidAccessError, got {:?}", v),
        }

        // Close reason too long (>123 bytes according to spec)
        match socket.close_with_status(
            SocketCloseCode::NORMAL_CLOSURE,
            &(0..200).map(|_| "X").collect::<String>()
        ) {
            Err(CloseError::SyntaxError(_)) => (),
            v => panic!("expected SyntaxError, got {:?}", v),
        }
    }
}
<|endoftext|>


<commit_before><commit_msg>Missing close tag causes parser to hang indefinitely<commit_after><|endoftext|>


<commit_before><commit_msg>more pretty<commit_after><|endoftext|>


<commit_before>use rayon::prelude::*;
use chardet::detect;

use super::consts::space_fix;
use std::path::Path;
use std::io::{self, Read};
use std::fs::File;

#[derive(Debug, Default)]
pub struct CharDet {
    pub files: Vec<String>,
}

impl CharDet {
    pub fn call(self) {
        dbln!("{:?}",self);
        let max_len= self.files.as_slice().iter().max_by_key(|p|p.len()).unwrap().len();
        // println!("{}{:3}CharSet{:13}Rate{:8}Info", space_fix("File",max_len), "", "", "");
        self.files.par_iter()
         .for_each(|file|
         match chardet(&file) {
             Ok(o)=> {
                 let (charset, rate, info) = o ;
                 // "WINDOWS_1258".len() = 12 -> 12+6 = 18
                 println!("{}: {}  {:.4}{:6}{}", space_fix(file,max_len), space_fix(&charset, 18), rate, "", info);
             }
             Err(e)=> {
                 errln!("{}: {:?}", space_fix(file,max_len), e)
             }
         }
          )
    }
    pub fn check(&self) -> Result<(), String> {
        for path in &self.files {
            let path=Path::new(path);
            if !path.exists() {
                return Err(format!("Args(File): {:?} is not exists", path));
            }
            if !path.is_file() {
                return Err(format!("Args(File): {:?} is not a file", path));
            }
        }
        Ok(())
    }
}

fn chardet(f :&str)->io::Result<(String, f32, String)> {
    let mut file = File::open(f)?;
    let mut bytes = Vec::default();
    let mut buf = [0u8];
    loop {
        let len = file.read(&mut buf)?;
        if len != 0 {
            bytes.push(buf[0]);
        } else {
            break;
        }
    }
    Ok(detect(bytes.as_slice()))
}<commit_msg>empty charset meaning binary.<commit_after>use rayon::prelude::*;
use chardet::detect;

use super::consts::space_fix;
use std::path::Path;
use std::io::{self, Read};
use std::fs::File;

#[derive(Debug, Default)]
pub struct CharDet {
    pub files: Vec<String>,
}

impl CharDet {
    pub fn call(self) {
        dbln!("{:?}",self);
        let max_len= self.files.as_slice().iter().max_by_key(|p|p.len()).unwrap().len();
        // println!("{}{:3}CharSet{:13}Rate{:8}Info", space_fix("File",max_len), "", "", "");
        self.files.par_iter()
         .for_each(|file|
         match chardet(&file) {
             Ok(o)=> {
                 let (mut charset, rate, info) = o ;
                 // "WINDOWS_1258".len() = 12 -> 12+6 = 18
                 if charset.is_empty() {
                     charset="Binary".to_owned();
                 }
                 println!("{}: {}  {:.4}{:6}{}", space_fix(file,max_len), space_fix(&charset, 18), rate, "", info);
             }
             Err(e)=> {
                 errln!("{}: {:?}", space_fix(file,max_len), e)
             }
         }
          )
    }
    pub fn check(&self) -> Result<(), String> {
        for path in &self.files {
            let path=Path::new(path);
            if !path.exists() {
                return Err(format!("Args(File): {:?} is not exists", path));
            }
            if !path.is_file() {
                return Err(format!("Args(File): {:?} is not a file", path));
            }
        }
        Ok(())
    }
}

fn chardet(f :&str)->io::Result<(String, f32, String)> {
    let mut file = File::open(f)?;
    let mut bytes = Vec::default();
    file.read_to_end(&mut bytes)?;
    Ok(detect(bytes.as_slice()))
}<|endoftext|>


<commit_before>// TODO: Make this non public
pub mod file_naming;

use std::collections::HashMap;
use std::fmt::{Display, Error, Formatter};
use self::file_naming::{FileName, FileType, FileNameParser};

pub struct BackupSet {
    pub file_type : FileType,
    pub time : String,
    pub start_time : String,
    pub end_time : String,
    pub compressed : bool,
    pub encrypted : bool,
    pub partial : bool,
    pub manifest_path : String,
    volumes_paths : HashMap<i32, String>,
    info_set : bool,    // true if informations are set
}

impl BackupSet {
    pub fn new() -> Self {
        BackupSet{
            file_type : FileType::Full,
            time : String::new(),
            start_time : String::new(),
            end_time : String::new(),
            compressed : false,
            encrypted : false,
            partial : false,
            manifest_path : String::new(),
            volumes_paths : HashMap::new(),
            info_set : false
        }
    }

    /// Add a filename to given set. Return true if it fits.
    ///
    /// The filename will match the given set if it has the right
    /// times and is of the right type. The information will be set
    /// from the first filename given.
    pub fn add_filename(&mut self, fname : &str, pr : &FileName) -> bool {
        if !self.info_set {
            self.set_info(pr);
        }
        else {
            // check if the file is from the same backup set
            if self.file_type != pr.file_type ||
                self.time != pr.time ||
                self.start_time != pr.start_time ||
                self.end_time != pr.end_time {
                    return false;
            }
            else {
                // fix encrypted flag
                if self.encrypted != pr.encrypted &&
                    self.partial && pr.encrypted {
                        self.encrypted = pr.encrypted;
                    }
            }
        }
        // set manifest or volume number
        if pr.manifest {
            self.manifest_path = fname.to_owned();
        }
        else {
            self.volumes_paths.insert(pr.volume_number, fname.to_owned());
        }
        true
    }

    pub fn is_complete(&self) -> bool {
        !self.manifest_path.is_empty()
    }

    pub fn get_time(&self) -> &str {
        if self.time.is_empty() {
            self.end_time.as_ref()
        }
        else {
            self.time.as_ref()
        }
    }

    fn set_info(&mut self, fname : &FileName) {
        self.file_type = fname.file_type;
        self.time = fname.time.clone();
        self.start_time = fname.start_time.clone();
        self.end_time = fname.end_time.clone();
        self.compressed = fname.compressed;
        self.encrypted = fname.encrypted;
        self.partial = fname.partial;
        self.info_set = true;
    }
}

impl Display for BackupSet {
    fn fmt(&self, f : &mut Formatter) -> Result<(), Error> {
        match self.file_type {
            FileType::Full => {
                try!(write!(f, "Full, time: {}", self.time));
            },
            FileType::Inc => {
                try!(write!(f, "Incremental, start time: {}, end time: {}", self.start_time, self.end_time));
            },
            _ => { }
        }
        if self.compressed {
            try!(write!(f, ", compressed"));
        }
        if self.encrypted {
            try!(write!(f, ", encrypted"));
        }
        if self.partial {
            try!(write!(f, ", partial"));
        }
        if !self.manifest_path.is_empty() {
            try!(write!(f, "\n manifest: {}", self.manifest_path));
        }
        if !self.volumes_paths.is_empty() {
            try!(write!(f, "\n volumes:\n"));
            for (_, vol) in self.volumes_paths.iter() {
                try!(write!(f, " {}", vol));
            }
        }
        Ok(())
    }
}

pub struct BackupChain {
    pub fullset : BackupSet,
    pub incset_list : Vec<BackupSet>,
    pub start_time : String,
    pub end_time : String
}

impl BackupChain {
    /// Create a new BackupChain starting from a full backup set.
    pub fn new(fullset : BackupSet) -> Self {
        assert_eq!(fullset.file_type, FileType::Full);
        let time = fullset.time.clone();

        BackupChain{
            fullset : fullset,
            incset_list : Vec::new(),
            start_time : time.clone(),
            end_time : time
        }
    }

    /// Adds the given incremental backup element to the backup chain if possible,
    /// returns it back otherwise.
    pub fn add_inc(&mut self, incset : BackupSet) -> Option<BackupSet> {
        if self.end_time == incset.start_time {
            self.end_time = incset.end_time.clone();
            self.incset_list.push(incset);
            None
        }
        else {
            // replace the last element if the end time comes before
            let replace_last = self.incset_list.last().map_or(false,
                |last| incset.start_time == last.start_time && incset.end_time > last.end_time);
            if replace_last {
                self.end_time = incset.end_time.clone();
                self.incset_list.pop();
                self.incset_list.push(incset);
                None
            }
            else {
                // ignore the given incremental backup set
                Some(incset)
            }
        }
    }
}

impl Display for BackupChain {
    fn fmt(&self, f : &mut Formatter) -> Result<(), Error> {
        try!(write!(f, "start time: {}, end time: {}\n{}",
                    &self.start_time, &self.end_time, &self.fullset));
        for inc in self.incset_list.iter() {
            try!(write!(f, "\n{}", inc));
        }
        Ok(())
    }
}

pub type FileNameList<'a> = Vec<&'a str>;
pub type BackupChains = Vec<BackupChain>;
type BackupSetList = Vec<BackupSet>;

pub struct CollectionsStatus {
    pub backup_chains : BackupChains,
}

impl CollectionsStatus {
    pub fn new() -> Self {
        CollectionsStatus {
            backup_chains : Vec::new()
        }
    }

    pub fn from_filename_list(filename_list : &FileNameList) -> Self {
        let mut result = Self::new();
        result.compute_backup_chains(filename_list);
        result
    }

    fn compute_backup_chains(&mut self, filename_list : &FileNameList) {
        let mut sets = self.compute_backup_sets(filename_list);
        self.sort_backup_sets(&mut sets);
        self.add_to_chains(sets);
        self.sort_chains();
    }

    fn compute_backup_sets(&self, filename_list : &FileNameList) -> BackupSetList {
        let mut sets = BackupSetList::new();
        let parser = FileNameParser::new();
        for filename in filename_list.iter() {
            if let Some(filename_info) = parser.parse(filename) {
                let mut inserted = false;
                for set in sets.iter_mut() {
                    if set.add_filename(filename.as_ref(), &filename_info) {
                        inserted = true;
                        break;
                    }
                }
                if !inserted {
                    let mut new_set = BackupSet::new();
                    new_set.add_filename(filename.as_ref(), &filename_info);
                    sets.push(new_set);
                }
            }
        }
        sets
    }

    fn sort_backup_sets(&self, set_list : &mut BackupSetList) {
        set_list.sort_by(|a, b| a.get_time().cmp(b.get_time()));
    }

    fn add_to_chains(&mut self, set_list : BackupSetList) {
        for set in set_list.into_iter() {
            match set.file_type {
                FileType::Full => {
                    let new_chain = BackupChain::new(set);
                    self.backup_chains.push(new_chain);
                }
                FileType::Inc => {
                    let mut rejected_set = Some(set);
                    for chain in self.backup_chains.iter_mut() {
                        rejected_set = chain.add_inc(rejected_set.unwrap());
                        if rejected_set.is_none() {
                            break;
                        }
                    }
                    if let Some(_) = rejected_set {
                        // TODO: add to orphaned sets
                    }
                }
                _ => { continue; }
            }
        }
    }

    fn sort_chains(&mut self) {
        self.backup_chains.sort_by(|a, b| a.end_time.cmp(&b.end_time));
    }
}

impl Display for CollectionsStatus {
    fn fmt(&self, f : &mut Formatter) -> Result<(), Error> {
        for backup_chain in &self.backup_chains {
            try!(backup_chain.fmt(f));
        }
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::{BackupSet, CollectionsStatus};
    use super::file_naming::{FileType, FileNameParser};

    #[test]
    fn parse_and_add() {
        let full1_name = "duplicity-full.20150617T182545Z.vol1.difftar.gz";
        let manifest1_name = "duplicity-full.20150617T182545Z.manifest";
        let inc1_name = "duplicity-inc.20150617T182629Z.to.20150617T182650Z.vol1.difftar.gz";

        let parser = FileNameParser::new();
        let full1 = parser.parse(full1_name).unwrap();
        let manifest1 = parser.parse(manifest1_name).unwrap();
        let inc1 = parser.parse(inc1_name).unwrap();

        let mut set = BackupSet::new();
        // add to set
        assert!(set.add_filename(full1_name, &full1));
        assert!(set.add_filename(manifest1_name, &manifest1));
        assert!(!set.add_filename(inc1_name, &inc1));
        // test results
        assert_eq!(set.file_type, FileType::Full);
        assert_eq!(set.time, "20150617t182545z");
        assert_eq!(set.start_time, "");
        assert_eq!(set.end_time, "");
        assert!(set.compressed);
        assert!(!set.encrypted);
        assert!(!set.partial);
        assert_eq!(set.manifest_path, manifest1_name);
    }

    #[test]
    fn collection_status() {
        let filename_list = vec![
            "duplicity-full.20150617T182545Z.manifest",
            "duplicity-full.20150617T182545Z.vol1.difftar.gz",
            "duplicity-full-signatures.20150617T182545Z.sigtar.gz",
            "duplicity-inc.20150617T182545Z.to.20150617T182629Z.manifest",
            "duplicity-inc.20150617T182545Z.to.20150617T182629Z.vol1.difftar.gz",
            "duplicity-inc.20150617T182629Z.to.20150617T182650Z.manifest",
            "duplicity-inc.20150617T182629Z.to.20150617T182650Z.vol1.difftar.gz",
            "duplicity-new-signatures.20150617T182545Z.to.20150617T182629Z.sigtar.gz",
            "duplicity-new-signatures.20150617T182629Z.to.20150617T182650Z.sigtar.gz"
        ];
        let collection_status = CollectionsStatus::from_filename_list(&filename_list);
        println!("collection: {}", collection_status);
    }
}
<commit_msg>Added SignatureChain<commit_after>// TODO: Make this non public
pub mod file_naming;

use std::collections::HashMap;
use std::fmt::{Display, Error, Formatter};
use self::file_naming::{FileName, FileType, FileNameParser};

pub struct BackupSet {
    pub file_type : FileType,
    pub time : String,
    pub start_time : String,
    pub end_time : String,
    pub compressed : bool,
    pub encrypted : bool,
    pub partial : bool,
    pub manifest_path : String,
    volumes_paths : HashMap<i32, String>,
    info_set : bool,    // true if informations are set
}

impl BackupSet {
    pub fn new() -> Self {
        BackupSet{
            file_type : FileType::Full,
            time : String::new(),
            start_time : String::new(),
            end_time : String::new(),
            compressed : false,
            encrypted : false,
            partial : false,
            manifest_path : String::new(),
            volumes_paths : HashMap::new(),
            info_set : false
        }
    }

    /// Add a filename to given set. Return true if it fits.
    ///
    /// The filename will match the given set if it has the right
    /// times and is of the right type. The information will be set
    /// from the first filename given.
    pub fn add_filename(&mut self, fname : &str, pr : &FileName) -> bool {
        if !self.info_set {
            self.set_info(pr);
        }
        else {
            // check if the file is from the same backup set
            if self.file_type != pr.file_type ||
                self.time != pr.time ||
                self.start_time != pr.start_time ||
                self.end_time != pr.end_time {
                    return false;
            }
            else {
                // fix encrypted flag
                if self.encrypted != pr.encrypted &&
                    self.partial && pr.encrypted {
                        self.encrypted = pr.encrypted;
                    }
            }
        }
        // set manifest or volume number
        if pr.manifest {
            self.manifest_path = fname.to_owned();
        }
        else {
            self.volumes_paths.insert(pr.volume_number, fname.to_owned());
        }
        true
    }

    pub fn is_complete(&self) -> bool {
        !self.manifest_path.is_empty()
    }

    pub fn get_time(&self) -> &str {
        if self.time.is_empty() {
            self.end_time.as_ref()
        }
        else {
            self.time.as_ref()
        }
    }

    fn set_info(&mut self, fname : &FileName) {
        self.file_type = fname.file_type;
        self.time = fname.time.clone();
        self.start_time = fname.start_time.clone();
        self.end_time = fname.end_time.clone();
        self.compressed = fname.compressed;
        self.encrypted = fname.encrypted;
        self.partial = fname.partial;
        self.info_set = true;
    }
}

impl Display for BackupSet {
    fn fmt(&self, f : &mut Formatter) -> Result<(), Error> {
        match self.file_type {
            FileType::Full => {
                try!(write!(f, "Full, time: {}", self.time));
            },
            FileType::Inc => {
                try!(write!(f, "Incremental, start time: {}, end time: {}", self.start_time, self.end_time));
            },
            _ => { }
        }
        if self.compressed {
            try!(write!(f, ", compressed"));
        }
        if self.encrypted {
            try!(write!(f, ", encrypted"));
        }
        if self.partial {
            try!(write!(f, ", partial"));
        }
        if !self.manifest_path.is_empty() {
            try!(write!(f, "\n manifest: {}", self.manifest_path));
        }
        if !self.volumes_paths.is_empty() {
            try!(write!(f, "\n volumes:\n"));
            for (_, vol) in self.volumes_paths.iter() {
                try!(write!(f, " {}", vol));
            }
        }
        Ok(())
    }
}

pub struct BackupChain {
    pub fullset : BackupSet,
    pub incset_list : Vec<BackupSet>,
    pub start_time : String,
    pub end_time : String
}

impl BackupChain {
    /// Create a new BackupChain starting from a full backup set.
    pub fn new(fullset : BackupSet) -> Self {
        assert_eq!(fullset.file_type, FileType::Full);
        let time = fullset.time.clone();

        BackupChain{
            fullset : fullset,
            incset_list : Vec::new(),
            start_time : time.clone(),
            end_time : time
        }
    }

    /// Adds the given incremental backup element to the backup chain if possible,
    /// returns it back otherwise.
    pub fn add_inc(&mut self, incset : BackupSet) -> Option<BackupSet> {
        if self.end_time == incset.start_time {
            self.end_time = incset.end_time.clone();
            self.incset_list.push(incset);
            None
        }
        else {
            // replace the last element if the end time comes before
            let replace_last = self.incset_list.last().map_or(false,
                |last| incset.start_time == last.start_time && incset.end_time > last.end_time);
            if replace_last {
                self.end_time = incset.end_time.clone();
                self.incset_list.pop();
                self.incset_list.push(incset);
                None
            }
            else {
                // ignore the given incremental backup set
                Some(incset)
            }
        }
    }
}

impl Display for BackupChain {
    fn fmt(&self, f : &mut Formatter) -> Result<(), Error> {
        try!(write!(f, "start time: {}, end time: {}\n{}",
                    &self.start_time, &self.end_time, &self.fullset));
        for inc in self.incset_list.iter() {
            try!(write!(f, "\n{}", inc));
        }
        Ok(())
    }
}

pub struct SignatureChain {
    pub fullsig : String,
    pub inclist : Vec<String>,
    pub start_time : String,
    pub end_time : String
}

impl SignatureChain {
    pub fn new() -> Self {
        SignatureChain {
            fullsig : String::new(),
            inclist : Vec::new(),
            start_time : String::new(),
            end_time : String::new()
        }
    }

    pub fn add_filename(&mut self, fname : &str, pr : &FileName) -> bool {
        match pr.file_type {
            FileType::FullSig if self.fullsig.is_empty() => {
                self.fullsig = fname.to_owned();
                self.start_time = pr.start_time.clone();
                self.end_time = pr.end_time.clone();
                true
            },
            FileType::NewSig if !self.fullsig.is_empty() => {
                self.inclist.push(fname.to_owned());
                self.end_time = pr.end_time.clone();
                true
            }
            _ => false
        }
    }
}

pub type FileNameList<'a> = Vec<&'a str>;
pub type BackupChains = Vec<BackupChain>;
type BackupSetList = Vec<BackupSet>;

pub struct CollectionsStatus {
    pub backup_chains : BackupChains,
}

impl CollectionsStatus {
    pub fn new() -> Self {
        CollectionsStatus {
            backup_chains : Vec::new()
        }
    }

    pub fn from_filename_list(filename_list : &FileNameList) -> Self {
        let mut result = Self::new();
        result.compute_backup_chains(filename_list);
        result
    }

    fn compute_backup_chains(&mut self, filename_list : &FileNameList) {
        let mut sets = self.compute_backup_sets(filename_list);
        self.sort_backup_sets(&mut sets);
        self.add_to_chains(sets);
        self.sort_chains();
    }

    fn compute_backup_sets(&self, filename_list : &FileNameList) -> BackupSetList {
        let mut sets = BackupSetList::new();
        let parser = FileNameParser::new();
        for filename in filename_list.iter() {
            if let Some(filename_info) = parser.parse(filename) {
                let mut inserted = false;
                for set in sets.iter_mut() {
                    if set.add_filename(filename.as_ref(), &filename_info) {
                        inserted = true;
                        break;
                    }
                }
                if !inserted {
                    let mut new_set = BackupSet::new();
                    new_set.add_filename(filename.as_ref(), &filename_info);
                    sets.push(new_set);
                }
            }
        }
        sets
    }

    fn sort_backup_sets(&self, set_list : &mut BackupSetList) {
        set_list.sort_by(|a, b| a.get_time().cmp(b.get_time()));
    }

    fn add_to_chains(&mut self, set_list : BackupSetList) {
        for set in set_list.into_iter() {
            match set.file_type {
                FileType::Full => {
                    let new_chain = BackupChain::new(set);
                    self.backup_chains.push(new_chain);
                }
                FileType::Inc => {
                    let mut rejected_set = Some(set);
                    for chain in self.backup_chains.iter_mut() {
                        rejected_set = chain.add_inc(rejected_set.unwrap());
                        if rejected_set.is_none() {
                            break;
                        }
                    }
                    if let Some(_) = rejected_set {
                        // TODO: add to orphaned sets
                    }
                }
                _ => { continue; }
            }
        }
    }

    fn sort_chains(&mut self) {
        self.backup_chains.sort_by(|a, b| a.end_time.cmp(&b.end_time));
    }
}

impl Display for CollectionsStatus {
    fn fmt(&self, f : &mut Formatter) -> Result<(), Error> {
        for backup_chain in &self.backup_chains {
            try!(backup_chain.fmt(f));
        }
        Ok(())
    }
}

#[cfg(test)]
mod test {
    use super::{BackupSet, CollectionsStatus};
    use super::file_naming::{FileType, FileNameParser};

    #[test]
    fn parse_and_add() {
        let full1_name = "duplicity-full.20150617T182545Z.vol1.difftar.gz";
        let manifest1_name = "duplicity-full.20150617T182545Z.manifest";
        let inc1_name = "duplicity-inc.20150617T182629Z.to.20150617T182650Z.vol1.difftar.gz";

        let parser = FileNameParser::new();
        let full1 = parser.parse(full1_name).unwrap();
        let manifest1 = parser.parse(manifest1_name).unwrap();
        let inc1 = parser.parse(inc1_name).unwrap();

        let mut set = BackupSet::new();
        // add to set
        assert!(set.add_filename(full1_name, &full1));
        assert!(set.add_filename(manifest1_name, &manifest1));
        assert!(!set.add_filename(inc1_name, &inc1));
        // test results
        assert_eq!(set.file_type, FileType::Full);
        assert_eq!(set.time, "20150617t182545z");
        assert_eq!(set.start_time, "");
        assert_eq!(set.end_time, "");
        assert!(set.compressed);
        assert!(!set.encrypted);
        assert!(!set.partial);
        assert_eq!(set.manifest_path, manifest1_name);
    }

    #[test]
    fn collection_status() {
        let filename_list = vec![
            "duplicity-full.20150617T182545Z.manifest",
            "duplicity-full.20150617T182545Z.vol1.difftar.gz",
            "duplicity-full-signatures.20150617T182545Z.sigtar.gz",
            "duplicity-inc.20150617T182545Z.to.20150617T182629Z.manifest",
            "duplicity-inc.20150617T182545Z.to.20150617T182629Z.vol1.difftar.gz",
            "duplicity-inc.20150617T182629Z.to.20150617T182650Z.manifest",
            "duplicity-inc.20150617T182629Z.to.20150617T182650Z.vol1.difftar.gz",
            "duplicity-new-signatures.20150617T182545Z.to.20150617T182629Z.sigtar.gz",
            "duplicity-new-signatures.20150617T182629Z.to.20150617T182650Z.sigtar.gz"
        ];
        let collection_status = CollectionsStatus::from_filename_list(&filename_list);
        println!("collection: {}", collection_status);
    }
}
<|endoftext|>