<commit_before>use backend::input::KeyState;
use std::io::{Error as IoError, Write};
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use tempfile::tempfile;
use wayland_server::{Liveness, Resource};
use wayland_server::protocol::{wl_keyboard, wl_surface};
use xkbcommon::xkb;
pub use xkbcommon::xkb::{keysyms, Keysym};

/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
///
/// For some modifiers, this means that the key is currently pressed, others are toggled
/// (like caps lock).
#[derive(Copy, Clone, Debug)]
pub struct ModifiersState {
    /// The "control" key
    pub ctrl: bool,
    /// The "alt" key
    pub alt: bool,
    /// The "shift" key
    pub shift: bool,
    /// The "Caps lock" key
    pub caps_lock: bool,
    /// The "logo" key
    ///
    /// Also known as the "windows" key on most keyboards
    pub logo: bool,
    /// The "Num lock" key
    pub num_lock: bool,
}

impl ModifiersState {
    fn new() -> ModifiersState {
        ModifiersState {
            ctrl: false,
            alt: false,
            shift: false,
            caps_lock: false,
            logo: false,
            num_lock: false,
        }
    }

    fn update_with(&mut self, state: &xkb::State) {
        self.ctrl = state.mod_name_is_active(&xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
        self.alt = state.mod_name_is_active(&xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
        self.shift = state.mod_name_is_active(&xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
        self.caps_lock = state.mod_name_is_active(&xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE);
        self.logo = state.mod_name_is_active(&xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
        self.num_lock = state.mod_name_is_active(&xkb::MOD_NAME_NUM, xkb::STATE_MODS_EFFECTIVE);
    }
}

struct KbdInternal {
    known_kbds: Vec<wl_keyboard::WlKeyboard>,
    focus: Option<wl_surface::WlSurface>,
    pressed_keys: Vec<u32>,
    mods_state: ModifiersState,
    keymap: xkb::Keymap,
    state: xkb::State,
    repeat_rate: i32,
    repeat_delay: i32,
}

impl KbdInternal {
    fn new(
        rules: &str, model: &str, layout: &str, variant: &str, options: Option<String>, repeat_rate: i32,
        repeat_delay: i32,
    ) -> Result<KbdInternal, ()> {
        // we create a new contex for each keyboard because libxkbcommon is actually NOT threadsafe
        // so confining it inside the KbdInternal allows us to use Rusts mutability rules to make
        // sure nothing goes wrong.
        //
        // FIXME: This is an issue with the xkbcommon-rs crate that does not reflect this
        // non-threadsafety properly.
        let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
        let keymap = xkb::Keymap::new_from_names(
            &context,
            &rules,
            &model,
            &layout,
            &variant,
            options,
            xkb::KEYMAP_COMPILE_NO_FLAGS,
        ).ok_or(())?;
        let state = xkb::State::new(&keymap);
        Ok(KbdInternal {
            known_kbds: Vec::new(),
            focus: None,
            pressed_keys: Vec::new(),
            mods_state: ModifiersState::new(),
            keymap: keymap,
            state: state,
            repeat_rate: repeat_rate,
            repeat_delay: repeat_delay,
        })
    }

    // return true if modifier state has changed
    fn key_input(&mut self, keycode: u32, state: KeyState) -> bool {
        // track pressed keys as xkbcommon does not seem to expose it :(
        let direction = match state {
            KeyState::Pressed => {
                self.pressed_keys.push(keycode);
                xkb::KeyDirection::Down
            }
            KeyState::Released => {
                self.pressed_keys.retain(|&k| k != keycode);
                xkb::KeyDirection::Up
            }
        };

        // update state
        // Offset the keycode by 8, as the evdev XKB rules reflect X's
        // broken keycode system, which starts at 8.
        let state_components = self.state.update_key(keycode + 8, direction);

        if state_components != 0 {
            self.mods_state.update_with(&self.state);
            true
        } else {
            false
        }
    }

    fn serialize_modifiers(&self) -> (u32, u32, u32, u32) {
        let mods_depressed = self.state.serialize_mods(xkb::STATE_MODS_DEPRESSED);
        let mods_latched = self.state.serialize_mods(xkb::STATE_MODS_LATCHED);
        let mods_locked = self.state.serialize_mods(xkb::STATE_MODS_LOCKED);
        let layout_locked = self.state.serialize_layout(xkb::STATE_LAYOUT_LOCKED);

        (mods_depressed, mods_latched, mods_locked, layout_locked)
    }

    fn serialize_pressed_keys(&self) -> Vec<u8> {
        let serialized = unsafe {
            ::std::slice::from_raw_parts(
                self.pressed_keys.as_ptr() as *const u8,
                self.pressed_keys.len() * 4,
            )
        };
        serialized.into()
    }

    fn with_focused_kbds<F>(&self, mut f: F)
    where
        F: FnMut(&wl_keyboard::WlKeyboard, &wl_surface::WlSurface),
    {
        if let Some(ref surface) = self.focus {
            for kbd in &self.known_kbds {
                if kbd.same_client_as(surface) {
                    f(kbd, surface);
                }
            }
        }
    }
}

/// Errors that can be encountered when creating a keyboard handler
#[derive(Debug)]
pub enum Error {
    /// libxkbcommon could not load the specified keymap
    BadKeymap,
    /// Smithay could not create a tempfile to share the keymap with clients
    IoError(IoError),
}

/// Create a keyboard handler from a set of RMLVO rules
pub(crate) fn create_keyboard_handler(
    rules: &str, model: &str, layout: &str, variant: &str, options: Option<String>, repeat_delay: i32,
    repeat_rate: i32, logger: &::slog::Logger,
) -> Result<KeyboardHandle, Error> {
    let log = logger.new(o!("smithay_module" => "xkbcommon_handler"));
    info!(log, "Initializing a xkbcommon handler with keymap query";
        "rules" => rules, "model" => model, "layout" => layout, "variant" => variant,
        "options" => &options
    );
    let internal = KbdInternal::new(
        rules,
        model,
        layout,
        variant,
        options,
        repeat_rate,
        repeat_delay,
    ).map_err(|_| {
        debug!(log, "Loading keymap failed");
        Error::BadKeymap
    })?;

    info!(log, "Loaded Keymap"; "name" => internal.keymap.layouts().next());

    // prepare a tempfile with the keymap, to send it to clients
    let mut keymap_file = tempfile().map_err(Error::IoError)?;
    let keymap_data = internal.keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1);
    keymap_file
        .write_all(keymap_data.as_bytes())
        .map_err(Error::IoError)?;
    keymap_file.flush().map_err(Error::IoError)?;

    trace!(log, "Keymap loaded and copied to tempfile.";
        "fd" => keymap_file.as_raw_fd(), "len" => keymap_data.as_bytes().len()
    );

    Ok(KeyboardHandle {
        arc: Arc::new(KbdArc {
            internal: Mutex::new(internal),
            keymap_file: keymap_file,
            keymap_len: keymap_data.as_bytes().len() as u32,
            logger: log,
        }),
    })
}

struct KbdArc {
    internal: Mutex<KbdInternal>,
    keymap_file: ::std::fs::File,
    keymap_len: u32,
    logger: ::slog::Logger,
}

/// An handle to a keyboard handler
///
/// It can be cloned and all clones manipulate the same internal state. Clones
/// can also be sent across threads.
///
/// This handle gives you 2 main ways to interact with the keyboard handling:
///
/// - set the current focus for this keyboard: designing the surface that will receive the key inputs
///   using the `KeyboardHandle::set_focus` method.
/// - process key inputs from the input backend, allowing them to be catched at the compositor-level
///   or forwarded to the client. See the documentation of the `KeyboardHandle::input` method for
///   details.
#[derive(Clone)]
pub struct KeyboardHandle {
    arc: Arc<KbdArc>,
}

impl KeyboardHandle {
    /// Handle a keystroke
    ///
    /// All keystrokes from the input backend should be fed _in order_ to this method of the
    /// keyboard handler. It will internally track the state of the keymap.
    ///
    /// The `filter` argument is expected to be a closure which will peek at the generated input
    /// as interpreted by the keymap before it is forwarded to the focused client. If this closure
    /// returns false, the input will not be sent to the client. This mechanism can be used to
    /// implement compositor-level key bindings for example.
    ///
    /// The module `smithay::keyboard::keysyms` exposes definitions of all possible keysyms
    /// to be compared against. This includes non-characted keysyms, such as XF86 special keys.
    pub fn input<F>(&self, keycode: u32, state: KeyState, serial: u32, filter: F)
    where
        F: FnOnce(&ModifiersState, Keysym) -> bool,
    {
        trace!(self.arc.logger, "Handling keystroke"; "keycode" => keycode, "state" => format_args!("{:?}", state));
        let mut guard = self.arc.internal.lock().unwrap();

        // Offset the keycode by 8, as the evdev XKB rules reflect X's
        // broken keycode system, which starts at 8.
        let sym = guard.state.key_get_one_sym(keycode + 8);

        let mods_changed = guard.key_input(keycode, state);

        trace!(self.arc.logger, "Calling input filter";
            "mods_state" => format_args!("{:?}", guard.mods_state), "sym" => xkb::keysym_get_name(sym)
        );

        if !filter(&guard.mods_state, sym) {
            // the filter returned false, we do not forward to client
            trace!(self.arc.logger, "Input was intercepted by filter");
            return;
        }

        // forward to client if no keybinding is triggered
        let modifiers = if mods_changed {
            Some(guard.serialize_modifiers())
        } else {
            None
        };
        let wl_state = match state {
            KeyState::Pressed => wl_keyboard::KeyState::Pressed,
            KeyState::Released => wl_keyboard::KeyState::Released,
        };
        guard.with_focused_kbds(|kbd, _| {
            if let Some((dep, la, lo, gr)) = modifiers {
                kbd.modifiers(serial, dep, la, lo, gr);
            }
            kbd.key(serial, 0, keycode, wl_state);
        });
        if guard.focus.is_some() {
            trace!(self.arc.logger, "Input forwarded to client");
        } else {
            trace!(self.arc.logger, "No client currently focused");
        }
    }

    /// Set the current focus of this keyboard
    ///
    /// If the ne focus is different from the previous one, any previous focus
    /// will be sent a `wl_keyboard::leave` event, and if the new focus is not `None`,
    /// a `wl_keyboard::enter` event will be sent.
    pub fn set_focus(&self, focus: Option<&wl_surface::WlSurface>, serial: u32) {
        let mut guard = self.arc.internal.lock().unwrap();

        let same = guard
            .focus
            .as_ref()
            .and_then(|f| focus.map(|s| s.equals(f)))
            .unwrap_or(false);

        if !same {
            // unset old focus
            guard.with_focused_kbds(|kbd, s| {
                kbd.leave(serial, s);
            });

            // set new focus
            guard.focus = focus.and_then(|s| s.clone());
            let (dep, la, lo, gr) = guard.serialize_modifiers();
            let keys = guard.serialize_pressed_keys();
            guard.with_focused_kbds(|kbd, s| {
                kbd.modifiers(serial, dep, la, lo, gr);
                kbd.enter(serial, s, keys.clone());
            });
            if guard.focus.is_some() {
                trace!(self.arc.logger, "Focus set to new surface");
            } else {
                trace!(self.arc.logger, "Focus unset");
            }
        } else {
            trace!(self.arc.logger, "Focus unchanged");
        }
    }

    /// Register a new keyboard to this handler
    ///
    /// The keymap will automatically be sent to it
    ///
    /// This should be done first, before anything else is done with this keyboard.
    pub(crate) fn new_kbd(&self, kbd: wl_keyboard::WlKeyboard) {
        trace!(self.arc.logger, "Sending keymap to client");
        kbd.keymap(
            wl_keyboard::KeymapFormat::XkbV1,
            self.arc.keymap_file.as_raw_fd(),
            self.arc.keymap_len,
        );
        let mut guard = self.arc.internal.lock().unwrap();
        if kbd.version() >= 4 {
            kbd.repeat_info(guard.repeat_rate, guard.repeat_delay);
        }
        guard.known_kbds.push(kbd);
    }

    /// Change the repeat info configured for this keyboard
    pub fn change_repeat_info(&self, rate: i32, delay: i32) {
        let mut guard = self.arc.internal.lock().unwrap();
        guard.repeat_delay = delay;
        guard.repeat_rate = rate;
        for kbd in &guard.known_kbds {
            kbd.repeat_info(rate, delay);
        }
    }

    /// Performs an internal cleanup of known kbds
    ///
    /// Drops any wl_keyboard that is no longer alive
    pub(crate) fn cleanup_old_kbds(&self) {
        let mut guard = self.arc.internal.lock().unwrap();
        guard
            .known_kbds
            .retain(|kbd| kbd.status() != Liveness::Dead);
    }
}
<commit_msg>Derive PartialEq,Eq,Hash for ModifiersState<commit_after>use backend::input::KeyState;
use std::io::{Error as IoError, Write};
use std::os::unix::io::AsRawFd;
use std::sync::{Arc, Mutex};
use tempfile::tempfile;
use wayland_server::{Liveness, Resource};
use wayland_server::protocol::{wl_keyboard, wl_surface};
use xkbcommon::xkb;
pub use xkbcommon::xkb::{keysyms, Keysym};

/// Represents the current state of the keyboard modifiers
///
/// Each field of this struct represents a modifier and is `true` if this modifier is active.
///
/// For some modifiers, this means that the key is currently pressed, others are toggled
/// (like caps lock).
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct ModifiersState {
    /// The "control" key
    pub ctrl: bool,
    /// The "alt" key
    pub alt: bool,
    /// The "shift" key
    pub shift: bool,
    /// The "Caps lock" key
    pub caps_lock: bool,
    /// The "logo" key
    ///
    /// Also known as the "windows" key on most keyboards
    pub logo: bool,
    /// The "Num lock" key
    pub num_lock: bool,
}

impl ModifiersState {
    fn new() -> ModifiersState {
        ModifiersState {
            ctrl: false,
            alt: false,
            shift: false,
            caps_lock: false,
            logo: false,
            num_lock: false,
        }
    }

    fn update_with(&mut self, state: &xkb::State) {
        self.ctrl = state.mod_name_is_active(&xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
        self.alt = state.mod_name_is_active(&xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
        self.shift = state.mod_name_is_active(&xkb::MOD_NAME_SHIFT, xkb::STATE_MODS_EFFECTIVE);
        self.caps_lock = state.mod_name_is_active(&xkb::MOD_NAME_CAPS, xkb::STATE_MODS_EFFECTIVE);
        self.logo = state.mod_name_is_active(&xkb::MOD_NAME_LOGO, xkb::STATE_MODS_EFFECTIVE);
        self.num_lock = state.mod_name_is_active(&xkb::MOD_NAME_NUM, xkb::STATE_MODS_EFFECTIVE);
    }
}

struct KbdInternal {
    known_kbds: Vec<wl_keyboard::WlKeyboard>,
    focus: Option<wl_surface::WlSurface>,
    pressed_keys: Vec<u32>,
    mods_state: ModifiersState,
    keymap: xkb::Keymap,
    state: xkb::State,
    repeat_rate: i32,
    repeat_delay: i32,
}

impl KbdInternal {
    fn new(
        rules: &str, model: &str, layout: &str, variant: &str, options: Option<String>, repeat_rate: i32,
        repeat_delay: i32,
    ) -> Result<KbdInternal, ()> {
        // we create a new contex for each keyboard because libxkbcommon is actually NOT threadsafe
        // so confining it inside the KbdInternal allows us to use Rusts mutability rules to make
        // sure nothing goes wrong.
        //
        // FIXME: This is an issue with the xkbcommon-rs crate that does not reflect this
        // non-threadsafety properly.
        let context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
        let keymap = xkb::Keymap::new_from_names(
            &context,
            &rules,
            &model,
            &layout,
            &variant,
            options,
            xkb::KEYMAP_COMPILE_NO_FLAGS,
        ).ok_or(())?;
        let state = xkb::State::new(&keymap);
        Ok(KbdInternal {
            known_kbds: Vec::new(),
            focus: None,
            pressed_keys: Vec::new(),
            mods_state: ModifiersState::new(),
            keymap: keymap,
            state: state,
            repeat_rate: repeat_rate,
            repeat_delay: repeat_delay,
        })
    }

    // return true if modifier state has changed
    fn key_input(&mut self, keycode: u32, state: KeyState) -> bool {
        // track pressed keys as xkbcommon does not seem to expose it :(
        let direction = match state {
            KeyState::Pressed => {
                self.pressed_keys.push(keycode);
                xkb::KeyDirection::Down
            }
            KeyState::Released => {
                self.pressed_keys.retain(|&k| k != keycode);
                xkb::KeyDirection::Up
            }
        };

        // update state
        // Offset the keycode by 8, as the evdev XKB rules reflect X's
        // broken keycode system, which starts at 8.
        let state_components = self.state.update_key(keycode + 8, direction);

        if state_components != 0 {
            self.mods_state.update_with(&self.state);
            true
        } else {
            false
        }
    }

    fn serialize_modifiers(&self) -> (u32, u32, u32, u32) {
        let mods_depressed = self.state.serialize_mods(xkb::STATE_MODS_DEPRESSED);
        let mods_latched = self.state.serialize_mods(xkb::STATE_MODS_LATCHED);
        let mods_locked = self.state.serialize_mods(xkb::STATE_MODS_LOCKED);
        let layout_locked = self.state.serialize_layout(xkb::STATE_LAYOUT_LOCKED);

        (mods_depressed, mods_latched, mods_locked, layout_locked)
    }

    fn serialize_pressed_keys(&self) -> Vec<u8> {
        let serialized = unsafe {
            ::std::slice::from_raw_parts(
                self.pressed_keys.as_ptr() as *const u8,
                self.pressed_keys.len() * 4,
            )
        };
        serialized.into()
    }

    fn with_focused_kbds<F>(&self, mut f: F)
    where
        F: FnMut(&wl_keyboard::WlKeyboard, &wl_surface::WlSurface),
    {
        if let Some(ref surface) = self.focus {
            for kbd in &self.known_kbds {
                if kbd.same_client_as(surface) {
                    f(kbd, surface);
                }
            }
        }
    }
}

/// Errors that can be encountered when creating a keyboard handler
#[derive(Debug)]
pub enum Error {
    /// libxkbcommon could not load the specified keymap
    BadKeymap,
    /// Smithay could not create a tempfile to share the keymap with clients
    IoError(IoError),
}

/// Create a keyboard handler from a set of RMLVO rules
pub(crate) fn create_keyboard_handler(
    rules: &str, model: &str, layout: &str, variant: &str, options: Option<String>, repeat_delay: i32,
    repeat_rate: i32, logger: &::slog::Logger,
) -> Result<KeyboardHandle, Error> {
    let log = logger.new(o!("smithay_module" => "xkbcommon_handler"));
    info!(log, "Initializing a xkbcommon handler with keymap query";
        "rules" => rules, "model" => model, "layout" => layout, "variant" => variant,
        "options" => &options
    );
    let internal = KbdInternal::new(
        rules,
        model,
        layout,
        variant,
        options,
        repeat_rate,
        repeat_delay,
    ).map_err(|_| {
        debug!(log, "Loading keymap failed");
        Error::BadKeymap
    })?;

    info!(log, "Loaded Keymap"; "name" => internal.keymap.layouts().next());

    // prepare a tempfile with the keymap, to send it to clients
    let mut keymap_file = tempfile().map_err(Error::IoError)?;
    let keymap_data = internal.keymap.get_as_string(xkb::KEYMAP_FORMAT_TEXT_V1);
    keymap_file
        .write_all(keymap_data.as_bytes())
        .map_err(Error::IoError)?;
    keymap_file.flush().map_err(Error::IoError)?;

    trace!(log, "Keymap loaded and copied to tempfile.";
        "fd" => keymap_file.as_raw_fd(), "len" => keymap_data.as_bytes().len()
    );

    Ok(KeyboardHandle {
        arc: Arc::new(KbdArc {
            internal: Mutex::new(internal),
            keymap_file: keymap_file,
            keymap_len: keymap_data.as_bytes().len() as u32,
            logger: log,
        }),
    })
}

struct KbdArc {
    internal: Mutex<KbdInternal>,
    keymap_file: ::std::fs::File,
    keymap_len: u32,
    logger: ::slog::Logger,
}

/// An handle to a keyboard handler
///
/// It can be cloned and all clones manipulate the same internal state. Clones
/// can also be sent across threads.
///
/// This handle gives you 2 main ways to interact with the keyboard handling:
///
/// - set the current focus for this keyboard: designing the surface that will receive the key inputs
///   using the `KeyboardHandle::set_focus` method.
/// - process key inputs from the input backend, allowing them to be catched at the compositor-level
///   or forwarded to the client. See the documentation of the `KeyboardHandle::input` method for
///   details.
#[derive(Clone)]
pub struct KeyboardHandle {
    arc: Arc<KbdArc>,
}

impl KeyboardHandle {
    /// Handle a keystroke
    ///
    /// All keystrokes from the input backend should be fed _in order_ to this method of the
    /// keyboard handler. It will internally track the state of the keymap.
    ///
    /// The `filter` argument is expected to be a closure which will peek at the generated input
    /// as interpreted by the keymap before it is forwarded to the focused client. If this closure
    /// returns false, the input will not be sent to the client. This mechanism can be used to
    /// implement compositor-level key bindings for example.
    ///
    /// The module `smithay::keyboard::keysyms` exposes definitions of all possible keysyms
    /// to be compared against. This includes non-characted keysyms, such as XF86 special keys.
    pub fn input<F>(&self, keycode: u32, state: KeyState, serial: u32, filter: F)
    where
        F: FnOnce(&ModifiersState, Keysym) -> bool,
    {
        trace!(self.arc.logger, "Handling keystroke"; "keycode" => keycode, "state" => format_args!("{:?}", state));
        let mut guard = self.arc.internal.lock().unwrap();

        // Offset the keycode by 8, as the evdev XKB rules reflect X's
        // broken keycode system, which starts at 8.
        let sym = guard.state.key_get_one_sym(keycode + 8);

        let mods_changed = guard.key_input(keycode, state);

        trace!(self.arc.logger, "Calling input filter";
            "mods_state" => format_args!("{:?}", guard.mods_state), "sym" => xkb::keysym_get_name(sym)
        );

        if !filter(&guard.mods_state, sym) {
            // the filter returned false, we do not forward to client
            trace!(self.arc.logger, "Input was intercepted by filter");
            return;
        }

        // forward to client if no keybinding is triggered
        let modifiers = if mods_changed {
            Some(guard.serialize_modifiers())
        } else {
            None
        };
        let wl_state = match state {
            KeyState::Pressed => wl_keyboard::KeyState::Pressed,
            KeyState::Released => wl_keyboard::KeyState::Released,
        };
        guard.with_focused_kbds(|kbd, _| {
            if let Some((dep, la, lo, gr)) = modifiers {
                kbd.modifiers(serial, dep, la, lo, gr);
            }
            kbd.key(serial, 0, keycode, wl_state);
        });
        if guard.focus.is_some() {
            trace!(self.arc.logger, "Input forwarded to client");
        } else {
            trace!(self.arc.logger, "No client currently focused");
        }
    }

    /// Set the current focus of this keyboard
    ///
    /// If the ne focus is different from the previous one, any previous focus
    /// will be sent a `wl_keyboard::leave` event, and if the new focus is not `None`,
    /// a `wl_keyboard::enter` event will be sent.
    pub fn set_focus(&self, focus: Option<&wl_surface::WlSurface>, serial: u32) {
        let mut guard = self.arc.internal.lock().unwrap();

        let same = guard
            .focus
            .as_ref()
            .and_then(|f| focus.map(|s| s.equals(f)))
            .unwrap_or(false);

        if !same {
            // unset old focus
            guard.with_focused_kbds(|kbd, s| {
                kbd.leave(serial, s);
            });

            // set new focus
            guard.focus = focus.and_then(|s| s.clone());
            let (dep, la, lo, gr) = guard.serialize_modifiers();
            let keys = guard.serialize_pressed_keys();
            guard.with_focused_kbds(|kbd, s| {
                kbd.modifiers(serial, dep, la, lo, gr);
                kbd.enter(serial, s, keys.clone());
            });
            if guard.focus.is_some() {
                trace!(self.arc.logger, "Focus set to new surface");
            } else {
                trace!(self.arc.logger, "Focus unset");
            }
        } else {
            trace!(self.arc.logger, "Focus unchanged");
        }
    }

    /// Register a new keyboard to this handler
    ///
    /// The keymap will automatically be sent to it
    ///
    /// This should be done first, before anything else is done with this keyboard.
    pub(crate) fn new_kbd(&self, kbd: wl_keyboard::WlKeyboard) {
        trace!(self.arc.logger, "Sending keymap to client");
        kbd.keymap(
            wl_keyboard::KeymapFormat::XkbV1,
            self.arc.keymap_file.as_raw_fd(),
            self.arc.keymap_len,
        );
        let mut guard = self.arc.internal.lock().unwrap();
        if kbd.version() >= 4 {
            kbd.repeat_info(guard.repeat_rate, guard.repeat_delay);
        }
        guard.known_kbds.push(kbd);
    }

    /// Change the repeat info configured for this keyboard
    pub fn change_repeat_info(&self, rate: i32, delay: i32) {
        let mut guard = self.arc.internal.lock().unwrap();
        guard.repeat_delay = delay;
        guard.repeat_rate = rate;
        for kbd in &guard.known_kbds {
            kbd.repeat_info(rate, delay);
        }
    }

    /// Performs an internal cleanup of known kbds
    ///
    /// Drops any wl_keyboard that is no longer alive
    pub(crate) fn cleanup_old_kbds(&self) {
        let mut guard = self.arc.internal.lock().unwrap();
        guard
            .known_kbds
            .retain(|kbd| kbd.status() != Liveness::Dead);
    }
}
<|endoftext|>


<commit_before><commit_msg>Remove the workaround for thiserror issue<commit_after><|endoftext|>


<commit_before><commit_msg>Avoid allocations by using TreeMap::find_with()<commit_after><|endoftext|>


<commit_before>use std::collections::LinkedList;
use std::collections::borrow::Borrow;

use token::Token;

use std::cell::Cell;
use std::rc::Rc;
use regex::Regex;
use context::Context;
use token::Posn;

pub trait ESCharExt {
    fn is_es_newline(self) -> bool;
    fn is_es_whitespace(self) -> bool;
}

impl ESCharExt for char {
    fn is_es_newline(self) -> bool {
        match self {
            '\u{000a}' | '\u{000d}' | '\u{2028}' | '\u{2029}' => true,
            _ => false
        }
    }

    fn is_es_whitespace(self) -> bool {
        match self {
              '\u{0009}' | '\u{000b}' | '\u{000c}' | '\u{0020}' | '\u{00a0}'
            | '\u{1680}' | '\u{2000}' | '\u{2001}' | '\u{2002}' | '\u{2003}' | '\u{2004}'
            | '\u{2005}' | '\u{2006}' | '\u{2009}' | '\u{200a}' | '\u{202f}' | '\u{205f}'
            | '\u{3000}' | '\u{feff}' => true,
            _ => false
        }
    }
}

struct LineOrientedReader<I> {
    chars: I,
    curr_char: Option<char>,
    next_char: Option<char>,
    curr_posn: Posn
}

impl<I> LineOrientedReader<I> where I: Iterator<Item=char> {
    pub fn new(mut chars: I) -> LineOrientedReader<I> {
        let curr_char = chars.next();
        let next_char = if curr_char.is_some() { chars.next() } else { None };
        LineOrientedReader {
            chars: chars,
            curr_char: curr_char,
            next_char: next_char,
            curr_posn: Posn {
                offset: 0,
                line: 0,
                column: 0
            }
        }
    }

    pub fn curr_char(&mut self) -> Option<char> { self.curr_char }
    pub fn curr_posn(&mut self) -> Posn { self.curr_posn }
    pub fn next_char(&mut self) -> Option<char> { self.next_char }

    pub fn bump(&mut self) {
        let curr_char = self.next_char;
        let next_char = if curr_char.is_some() { self.chars.next() } else { None };

        self.curr_char = curr_char;
        self.next_char = next_char;

        if (curr_char == Some('\r') && next_char != Some('\n')) ||
           curr_char == Some('\n') ||
           curr_char == Some('\u{2028}') ||
           curr_char == Some('\u{2029}') {
            self.curr_posn.line += 1;
            self.curr_posn.column = 0;
        } else {
            self.curr_posn.column += 1;
        }

        self.curr_posn.offset += 1;
    }
}

struct TokenBuffer {
    tokens: LinkedList<Token>
}

impl TokenBuffer {
    fn new() -> TokenBuffer {
        TokenBuffer {
            tokens: LinkedList::new()
        }
    }

    fn is_empty(&mut self) -> bool {
        self.tokens.len() == 0
    }

    fn push_token(&mut self, token: Token) {
        assert!(self.tokens.len() == 0);
        self.tokens.push_back(token);
    }

    fn read_token(&mut self) -> Token {
        assert!(self.tokens.len() > 0);
        self.tokens.pop_front().unwrap()
    }

    fn peek_token(&mut self) -> &Token {
        assert!(self.tokens.len() > 0);
        self.tokens.front().unwrap()
    }

    fn unread_token(&mut self, token: Token) {
        assert!(self.tokens.len() >= 0);
        assert!(self.tokens.len() < 3);
        self.tokens.push_front(token);
    }
}

pub struct Lexer<I> {
    reader: LineOrientedReader<I>,
    cx: Rc<Cell<Context>>,
    lookahead: TokenBuffer
}

impl<I> Lexer<I> where I: Iterator<Item=char> {
    // constructor

    pub fn new(chars: I, cx: Rc<Cell<Context>>) -> Lexer<I> {
        Lexer {
            reader: LineOrientedReader::new(chars),
            cx: cx,
            lookahead: TokenBuffer::new()
        }
    }

    // public methods

    pub fn is_eof(&mut self) -> bool {
        match *self.peek_token() {
            Token::EOF => true,
            _ => false
        }
    }

    pub fn peek_token(&mut self) -> &Token {
        if self.lookahead.is_empty() {
            let token = self.read_next_token();
            self.lookahead.push_token(token);
        }
        self.lookahead.peek_token()
    }

    pub fn read_token(&mut self) -> Token {
        if self.lookahead.is_empty() {
            self.read_next_token()
        } else {
            self.lookahead.read_token()
        }
    }

    pub fn unread_token(&mut self, token: Token) {
        self.lookahead.unread_token(token);
    }

    // private methods

    fn skip_line_comment(&mut self) {
    }

    fn skip_block_comment(&mut self) {
    }

    fn div_or_regexp(&mut self) -> Token {
        if self.cx.borrow().get().operator {
            unimplemented!()
        } else {
            unimplemented!()
        }
    }

    fn read_next_token(&mut self) -> Token {
        self.skip_whitespace();
        loop {
            match self.reader.curr_char() {
                None => return Token::EOF,
                Some(ch) => {
                    if ch == '/' {
                        match self.reader.next_char() {
                            Some('/') => self.skip_line_comment(),
                            Some('*') => self.skip_block_comment(),
                            _ => return self.div_or_regexp()
                        }
                    }
                    unimplemented!()
                }
            }
        }
    }

    fn is_whitespace(&mut self) -> bool {
        match self.reader.curr_char() {
            Some(ch) => ch.is_es_whitespace(),
            None => false
        }
    }

    fn eat(&mut self) -> Option<char> {
        let ch = self.reader.curr_char();
        self.reader.bump();
        ch
    }

    fn skip_whitespace(&mut self) {
        while self.is_whitespace() {
            self.reader.bump();
        }
    }
}

impl<I> Iterator for Lexer<I> where I: Iterator<Item=char> {
    type Item = Token;

    fn next(&mut self) -> Option<Token> {
        self.skip_whitespace();
        self.eat()
            .map(|ch| {
                match ch {
                    '1' => Token::DecimalInt(String::from_str("1")),
                    '+' => Token::Plus,
                    _ => Token::Error(ch)
                }
            })
    }
}
<commit_msg>ok maybe collections::borrow::Borrow like it suggested? nope src/lexer.rs:2:5: 2:16 error: unresolved import `collections::borrow::Borrow`. Maybe a missing `extern crate collections`? src/lexer.rs:2 use collections::borrow::Borrow;                    ^~~~~~~~~~~<commit_after>use std::collections::LinkedList;
use collections::borrow::Borrow;

use token::Token;

use std::cell::Cell;
use std::rc::Rc;
use regex::Regex;
use context::Context;
use token::Posn;

pub trait ESCharExt {
    fn is_es_newline(self) -> bool;
    fn is_es_whitespace(self) -> bool;
}

impl ESCharExt for char {
    fn is_es_newline(self) -> bool {
        match self {
            '\u{000a}' | '\u{000d}' | '\u{2028}' | '\u{2029}' => true,
            _ => false
        }
    }

    fn is_es_whitespace(self) -> bool {
        match self {
              '\u{0009}' | '\u{000b}' | '\u{000c}' | '\u{0020}' | '\u{00a0}'
            | '\u{1680}' | '\u{2000}' | '\u{2001}' | '\u{2002}' | '\u{2003}' | '\u{2004}'
            | '\u{2005}' | '\u{2006}' | '\u{2009}' | '\u{200a}' | '\u{202f}' | '\u{205f}'
            | '\u{3000}' | '\u{feff}' => true,
            _ => false
        }
    }
}

struct LineOrientedReader<I> {
    chars: I,
    curr_char: Option<char>,
    next_char: Option<char>,
    curr_posn: Posn
}

impl<I> LineOrientedReader<I> where I: Iterator<Item=char> {
    pub fn new(mut chars: I) -> LineOrientedReader<I> {
        let curr_char = chars.next();
        let next_char = if curr_char.is_some() { chars.next() } else { None };
        LineOrientedReader {
            chars: chars,
            curr_char: curr_char,
            next_char: next_char,
            curr_posn: Posn {
                offset: 0,
                line: 0,
                column: 0
            }
        }
    }

    pub fn curr_char(&mut self) -> Option<char> { self.curr_char }
    pub fn curr_posn(&mut self) -> Posn { self.curr_posn }
    pub fn next_char(&mut self) -> Option<char> { self.next_char }

    pub fn bump(&mut self) {
        let curr_char = self.next_char;
        let next_char = if curr_char.is_some() { self.chars.next() } else { None };

        self.curr_char = curr_char;
        self.next_char = next_char;

        if (curr_char == Some('\r') && next_char != Some('\n')) ||
           curr_char == Some('\n') ||
           curr_char == Some('\u{2028}') ||
           curr_char == Some('\u{2029}') {
            self.curr_posn.line += 1;
            self.curr_posn.column = 0;
        } else {
            self.curr_posn.column += 1;
        }

        self.curr_posn.offset += 1;
    }
}

struct TokenBuffer {
    tokens: LinkedList<Token>
}

impl TokenBuffer {
    fn new() -> TokenBuffer {
        TokenBuffer {
            tokens: LinkedList::new()
        }
    }

    fn is_empty(&mut self) -> bool {
        self.tokens.len() == 0
    }

    fn push_token(&mut self, token: Token) {
        assert!(self.tokens.len() == 0);
        self.tokens.push_back(token);
    }

    fn read_token(&mut self) -> Token {
        assert!(self.tokens.len() > 0);
        self.tokens.pop_front().unwrap()
    }

    fn peek_token(&mut self) -> &Token {
        assert!(self.tokens.len() > 0);
        self.tokens.front().unwrap()
    }

    fn unread_token(&mut self, token: Token) {
        assert!(self.tokens.len() >= 0);
        assert!(self.tokens.len() < 3);
        self.tokens.push_front(token);
    }
}

pub struct Lexer<I> {
    reader: LineOrientedReader<I>,
    cx: Rc<Cell<Context>>,
    lookahead: TokenBuffer
}

impl<I> Lexer<I> where I: Iterator<Item=char> {
    // constructor

    pub fn new(chars: I, cx: Rc<Cell<Context>>) -> Lexer<I> {
        Lexer {
            reader: LineOrientedReader::new(chars),
            cx: cx,
            lookahead: TokenBuffer::new()
        }
    }

    // public methods

    pub fn is_eof(&mut self) -> bool {
        match *self.peek_token() {
            Token::EOF => true,
            _ => false
        }
    }

    pub fn peek_token(&mut self) -> &Token {
        if self.lookahead.is_empty() {
            let token = self.read_next_token();
            self.lookahead.push_token(token);
        }
        self.lookahead.peek_token()
    }

    pub fn read_token(&mut self) -> Token {
        if self.lookahead.is_empty() {
            self.read_next_token()
        } else {
            self.lookahead.read_token()
        }
    }

    pub fn unread_token(&mut self, token: Token) {
        self.lookahead.unread_token(token);
    }

    // private methods

    fn skip_line_comment(&mut self) {
    }

    fn skip_block_comment(&mut self) {
    }

    fn div_or_regexp(&mut self) -> Token {
        if self.cx.borrow().get().operator {
            unimplemented!()
        } else {
            unimplemented!()
        }
    }

    fn read_next_token(&mut self) -> Token {
        self.skip_whitespace();
        loop {
            match self.reader.curr_char() {
                None => return Token::EOF,
                Some(ch) => {
                    if ch == '/' {
                        match self.reader.next_char() {
                            Some('/') => self.skip_line_comment(),
                            Some('*') => self.skip_block_comment(),
                            _ => return self.div_or_regexp()
                        }
                    }
                    unimplemented!()
                }
            }
        }
    }

    fn is_whitespace(&mut self) -> bool {
        match self.reader.curr_char() {
            Some(ch) => ch.is_es_whitespace(),
            None => false
        }
    }

    fn eat(&mut self) -> Option<char> {
        let ch = self.reader.curr_char();
        self.reader.bump();
        ch
    }

    fn skip_whitespace(&mut self) {
        while self.is_whitespace() {
            self.reader.bump();
        }
    }
}

impl<I> Iterator for Lexer<I> where I: Iterator<Item=char> {
    type Item = Token;

    fn next(&mut self) -> Option<Token> {
        self.skip_whitespace();
        self.eat()
            .map(|ch| {
                match ch {
                    '1' => Token::DecimalInt(String::from_str("1")),
                    '+' => Token::Plus,
                    _ => Token::Error(ch)
                }
            })
    }
}
<|endoftext|>


<commit_before>use std::marker::ContravariantLifetime;
use std::mem;
use std::ptr;
use libc::c_ulong;

use objc::Id;
use objc::runtime::Object;

use INSObject;

pub struct NSEnumerator<'a, T> {
    id: Id<Object>,
    marker: ContravariantLifetime<'a>,
}

impl<'a, T> NSEnumerator<'a, T> where T: INSObject {
    pub unsafe fn from_ptr(ptr: *mut Object) -> NSEnumerator<'a, T> {
        NSEnumerator { id: Id::from_ptr(ptr), marker: ContravariantLifetime }
    }
}

impl<'a, T> Iterator for NSEnumerator<'a, T> where T: INSObject {
    type Item = &'a T;

    fn next(&mut self) -> Option<&'a T> {
        unsafe {
            let obj: *mut T = msg_send![self.id, nextObject];
            obj.as_ref()
        }
    }
}

pub trait INSFastEnumeration: INSObject {
    type Item: INSObject;

    fn enumerator(&self) -> NSFastEnumerator<Self::Item> {
        NSFastEnumerator::<Self::Item>::new(self)
    }
}

#[repr(C)]
struct NSFastEnumerationState<T> {
    state: c_ulong,
    items_ptr: *const *const T,
    mutations_ptr: *mut c_ulong,
    extra: [c_ulong; 5],
}

const FAST_ENUM_BUF_SIZE: usize = 16;

pub struct NSFastEnumerator<'a, T> {
    object: &'a Object,

    ptr: *const *const T,
    end: *const *const T,

    state: NSFastEnumerationState<T>,
    buf: [*const T; FAST_ENUM_BUF_SIZE],
}

impl<'a, T> NSFastEnumerator<'a, T> where T: INSObject {
    fn new<C: INSFastEnumeration>(object: &C) -> NSFastEnumerator<C::Item> {
        NSFastEnumerator {
            object: unsafe { &*(object as *const C as *const Object) },

            ptr: ptr::null(),
            end: ptr::null(),

            state: unsafe { mem::zeroed() },
            buf: [ptr::null(); FAST_ENUM_BUF_SIZE],
        }
    }

    fn update_buf(&mut self) -> bool {
        // If this isn't our first time enumerating, record the previous value
        // from the mutations pointer.
        let mutations = if !self.ptr.is_null() {
            Some(unsafe { *self.state.mutations_ptr })
        } else {
            None
        };

        let count: usize = unsafe {
            msg_send![self.object, countByEnumeratingWithState:&mut self.state
                                                       objects:self.buf.as_mut_ptr()
                                                         count:self.buf.len()]
        };

        if count > 0 {
            // Check if the collection was mutated
            if let Some(mutations) = mutations {
                assert!(mutations == unsafe { *self.state.mutations_ptr },
                    "Mutation detected during enumeration of object {:?}",
                    self.object);
            }

            self.ptr = self.state.items_ptr;
            self.end = unsafe { self.ptr.offset(count as isize) };
            true
        } else {
            self.ptr = ptr::null();
            self.end = ptr::null();
            false
        }
    }
}

impl<'a, T> Iterator for NSFastEnumerator<'a, T> where T: INSObject {
    type Item = &'a T;

    fn next(&mut self) -> Option<&'a T> {
        if self.ptr == self.end && !self.update_buf() {
            None
        } else {
            unsafe {
                let obj = *self.ptr;
                self.ptr = self.ptr.offset(1);
                Some(&*obj)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use objc::Id;
    use {INSArray, INSValue, NSArray, NSValue};

    #[test]
    fn test_enumerator() {
        let vec: Vec<Id<NSValue<u32>>> = (0..4).map(INSValue::from_value).collect();
        let array: Id<NSArray<_>> = INSArray::from_vec(vec);

        let enumerator = array.object_enumerator();
        assert!(enumerator.count() == 4);

        let enumerator = array.object_enumerator();
        assert!(enumerator.enumerate().all(|(i, obj)| obj.value() == i as u32));
    }
}
<commit_msg>Added fast enumeration test.<commit_after>use std::marker::ContravariantLifetime;
use std::mem;
use std::ptr;
use libc::c_ulong;

use objc::Id;
use objc::runtime::Object;

use INSObject;

pub struct NSEnumerator<'a, T> {
    id: Id<Object>,
    marker: ContravariantLifetime<'a>,
}

impl<'a, T> NSEnumerator<'a, T> where T: INSObject {
    pub unsafe fn from_ptr(ptr: *mut Object) -> NSEnumerator<'a, T> {
        NSEnumerator { id: Id::from_ptr(ptr), marker: ContravariantLifetime }
    }
}

impl<'a, T> Iterator for NSEnumerator<'a, T> where T: INSObject {
    type Item = &'a T;

    fn next(&mut self) -> Option<&'a T> {
        unsafe {
            let obj: *mut T = msg_send![self.id, nextObject];
            obj.as_ref()
        }
    }
}

pub trait INSFastEnumeration: INSObject {
    type Item: INSObject;

    fn enumerator(&self) -> NSFastEnumerator<Self::Item> {
        NSFastEnumerator::<Self::Item>::new(self)
    }
}

#[repr(C)]
struct NSFastEnumerationState<T> {
    state: c_ulong,
    items_ptr: *const *const T,
    mutations_ptr: *mut c_ulong,
    extra: [c_ulong; 5],
}

const FAST_ENUM_BUF_SIZE: usize = 16;

pub struct NSFastEnumerator<'a, T> {
    object: &'a Object,

    ptr: *const *const T,
    end: *const *const T,

    state: NSFastEnumerationState<T>,
    buf: [*const T; FAST_ENUM_BUF_SIZE],
}

impl<'a, T> NSFastEnumerator<'a, T> where T: INSObject {
    fn new<C: INSFastEnumeration>(object: &C) -> NSFastEnumerator<C::Item> {
        NSFastEnumerator {
            object: unsafe { &*(object as *const C as *const Object) },

            ptr: ptr::null(),
            end: ptr::null(),

            state: unsafe { mem::zeroed() },
            buf: [ptr::null(); FAST_ENUM_BUF_SIZE],
        }
    }

    fn update_buf(&mut self) -> bool {
        // If this isn't our first time enumerating, record the previous value
        // from the mutations pointer.
        let mutations = if !self.ptr.is_null() {
            Some(unsafe { *self.state.mutations_ptr })
        } else {
            None
        };

        let count: usize = unsafe {
            msg_send![self.object, countByEnumeratingWithState:&mut self.state
                                                       objects:self.buf.as_mut_ptr()
                                                         count:self.buf.len()]
        };

        if count > 0 {
            // Check if the collection was mutated
            if let Some(mutations) = mutations {
                assert!(mutations == unsafe { *self.state.mutations_ptr },
                    "Mutation detected during enumeration of object {:?}",
                    self.object);
            }

            self.ptr = self.state.items_ptr;
            self.end = unsafe { self.ptr.offset(count as isize) };
            true
        } else {
            self.ptr = ptr::null();
            self.end = ptr::null();
            false
        }
    }
}

impl<'a, T> Iterator for NSFastEnumerator<'a, T> where T: INSObject {
    type Item = &'a T;

    fn next(&mut self) -> Option<&'a T> {
        if self.ptr == self.end && !self.update_buf() {
            None
        } else {
            unsafe {
                let obj = *self.ptr;
                self.ptr = self.ptr.offset(1);
                Some(&*obj)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use objc::Id;
    use {INSArray, INSValue, NSArray, NSValue};
    use super::INSFastEnumeration;

    #[test]
    fn test_enumerator() {
        let vec: Vec<Id<NSValue<u32>>> = (0..4).map(INSValue::from_value).collect();
        let array: Id<NSArray<_>> = INSArray::from_vec(vec);

        let enumerator = array.object_enumerator();
        assert!(enumerator.count() == 4);

        let enumerator = array.object_enumerator();
        assert!(enumerator.enumerate().all(|(i, obj)| obj.value() == i as u32));
    }

    #[test]
    fn test_fast_enumerator() {
        let vec: Vec<Id<NSValue<u32>>> = (0..4).map(INSValue::from_value).collect();
        let array: Id<NSArray<_>> = INSArray::from_vec(vec);

        let enumerator = array.enumerator();
        assert!(enumerator.count() == 4);

        let enumerator = array.enumerator();
        assert!(enumerator.enumerate().all(|(i, obj)| obj.value() == i as u32));
    }
}
<|endoftext|>


<commit_before>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use chrono::Duration;
use clap::{App, Arg, ArgMatches};
use failure::{bail, Error};
use if_chain::if_chain;
use log::info;
use serde::{Deserialize, Serialize};

use crate::api::{Api, NewRelease};
use crate::config::Config;
use crate::utils::args::ArgExt;
use crate::utils::fs::TempFile;
use crate::utils::sourcemaps::{SourceMapProcessor, UploadContext};
use crate::utils::system::propagate_exit_status;
use crate::utils::xcode::{InfoPlist, MayDetach};

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Upload react-native projects in a Xcode build step.")
        .org_project_args()
        // legacy parameter
        .arg(
            Arg::with_name("verbose")
                .long("verbose")
                .short("v")
                .hidden(true),
        )
        .arg(Arg::with_name("force").long("force").short("f").help(
            "Force the script to run, even in debug configuration.{n}This rarely \
             does what you want because the default build script does not actually \
             produce any information that the sentry build tool could pick up on.",
        ))
        .arg(Arg::with_name("allow_fetch").long("allow-fetch").help(
            "Enable sourcemap fetching from the packager.{n}If this is enabled \
             the react native packager needs to run and sourcemaps are downloade \
             from it if the simulator platform is detected.",
        ))
        .arg(
            Arg::with_name("fetch_from")
                .long("fetch-from")
                .value_name("URL")
                .help(
                    "Set the URL to fetch sourcemaps from.{n}\
                     The default is http://127.0.0.1:8081/, where the react-native \
                     packager runs by default.",
                ),
        )
        .arg(
            Arg::with_name("force_foreground")
                .long("force-foreground")
                .help(
                    "Wait for the process to finish.{n}\
                     By default part of the build process will when triggered from Xcode \
                     detach and continue in the background.  When an error happens, \
                     a dialog is shown.  If this parameter is passed, Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
                ),
        )
        .arg(
            Arg::with_name("build_script")
                .value_name("BUILD_SCRIPT")
                .index(1)
                .help(
                    "Optional path to the build script.{n}\
                     This is the path to the `react-native-xcode.sh` script you want \
                     to use.  By default the bundled build script is used.",
                ),
        )
        .arg(
            Arg::with_name("dist")
                .long("dist")
                .value_name("DISTRIBUTION")
                .multiple(true)
                .number_of_values(1)
                .help("The names of the distributions to publish. Can be supplied multiple times."),
        )
        .arg(
            Arg::with_name("args")
                .value_name("ARGS")
                .multiple(true)
                .last(true)
                .help("Optional arguments to pass to the build script."),
        )
        .arg(
            Arg::with_name("wait")
                .long("wait")
                .help("Wait for the server to fully process uploaded files."),
        )
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if !path.is_empty() {
            return path;
        }
    }
    "node".into()
}

pub fn execute<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;
    let api = Api::current();
    let should_wrap = matches.is_present("force")
        || match env::var("CONFIGURATION") {
            Ok(config) => &config != "Debug",
            Err(_) => bail!("Need to run this from Xcode"),
        };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/scripts/react-native-xcode.sh")
    }
    .canonicalize()?;

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match InfoPlist::discover_from_env()? {
        Some(plist) => plist,
        None => bail!("Could not find info.plist"),
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::create()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let url = url.trim_end_matches('/');
            bundle_file = TempFile::create()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::create()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                bail!("Error: react-native packager did not respond in time");
            }

            api.download(
                &format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                &mut bundle_file.open()?,
            )?;
            api.download(
                &format!("{}/index.ios.map?platform=ios&dev=true", url),
                &mut sourcemap_file.open()?,
            )?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env(
                    "SENTRY_RN_SOURCEMAP_REPORT",
                    report_file.path().to_str().unwrap(),
                )
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report: SourceMapReport = serde_json::from_reader(&mut f)?;
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!(
                "~/{}",
                sourcemap_path.file_name().unwrap().to_string_lossy()
            );
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new();
        processor.add(&bundle_url, &bundle_path)?;
        processor.add(&sourcemap_url, &sourcemap_path)?;
        processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let release = api.new_release(
            &org,
            &NewRelease {
                version: format!(
                    "{}@{}+{}",
                    plist.bundle_id(),
                    plist.version(),
                    plist.build()
                ),
                projects: vec![project.to_string()],
                ..Default::default()
            },
        )?;

        match matches.values_of("dist") {
            None => {
                processor.upload(&UploadContext {
                    org: &org,
                    project: Some(&project),
                    release: &release.version,
                    dist: Some(&plist.build()),
                    wait: matches.is_present("wait"),
                })?;
            }
            Some(dists) => {
                for dist in dists {
                    processor.upload(&UploadContext {
                        org: &org,
                        project: Some(&project),
                        release: &release.version,
                        dist: Some(dist),
                        wait: matches.is_present("wait"),
                    })?;
                }
            }
        }

        Ok(())
    })
}

pub fn wrap_call() -> Result<(), Error> {
    let mut args: Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && (args[1] == "bundle" || args[1] == "ram-bundle") {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().cloned();
            } else if item.starts_with("--sourcemap-output=") {
                sourcemap_path = Some(item[19..].to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().cloned();
            } else if item.starts_with("--bundle-output=") {
                bundle_path = Some(item[16..].to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let path = format!("{}.map", &bundle_path.as_ref().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path);
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(PathBuf::from);

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<commit_msg>fix: react-native xcode uses regex to detect Debug builds (#665)<commit_after>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use chrono::Duration;
use clap::{App, Arg, ArgMatches};
use failure::{bail, Error};
use if_chain::if_chain;
use lazy_static::lazy_static;
use log::info;
use regex::Regex;
use serde::{Deserialize, Serialize};

use crate::api::{Api, NewRelease};
use crate::config::Config;
use crate::utils::args::ArgExt;
use crate::utils::fs::TempFile;
use crate::utils::sourcemaps::{SourceMapProcessor, UploadContext};
use crate::utils::system::propagate_exit_status;
use crate::utils::xcode::{InfoPlist, MayDetach};

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Upload react-native projects in a Xcode build step.")
        .org_project_args()
        // legacy parameter
        .arg(
            Arg::with_name("verbose")
                .long("verbose")
                .short("v")
                .hidden(true),
        )
        .arg(Arg::with_name("force").long("force").short("f").help(
            "Force the script to run, even in debug configuration.{n}This rarely \
             does what you want because the default build script does not actually \
             produce any information that the sentry build tool could pick up on.",
        ))
        .arg(Arg::with_name("allow_fetch").long("allow-fetch").help(
            "Enable sourcemap fetching from the packager.{n}If this is enabled \
             the react native packager needs to run and sourcemaps are downloade \
             from it if the simulator platform is detected.",
        ))
        .arg(
            Arg::with_name("fetch_from")
                .long("fetch-from")
                .value_name("URL")
                .help(
                    "Set the URL to fetch sourcemaps from.{n}\
                     The default is http://127.0.0.1:8081/, where the react-native \
                     packager runs by default.",
                ),
        )
        .arg(
            Arg::with_name("force_foreground")
                .long("force-foreground")
                .help(
                    "Wait for the process to finish.{n}\
                     By default part of the build process will when triggered from Xcode \
                     detach and continue in the background.  When an error happens, \
                     a dialog is shown.  If this parameter is passed, Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
                ),
        )
        .arg(
            Arg::with_name("build_script")
                .value_name("BUILD_SCRIPT")
                .index(1)
                .help(
                    "Optional path to the build script.{n}\
                     This is the path to the `react-native-xcode.sh` script you want \
                     to use.  By default the bundled build script is used.",
                ),
        )
        .arg(
            Arg::with_name("dist")
                .long("dist")
                .value_name("DISTRIBUTION")
                .multiple(true)
                .number_of_values(1)
                .help("The names of the distributions to publish. Can be supplied multiple times."),
        )
        .arg(
            Arg::with_name("args")
                .value_name("ARGS")
                .multiple(true)
                .last(true)
                .help("Optional arguments to pass to the build script."),
        )
        .arg(
            Arg::with_name("wait")
                .long("wait")
                .help("Wait for the server to fully process uploaded files."),
        )
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if !path.is_empty() {
            return path;
        }
    }
    "node".into()
}

pub fn execute<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    lazy_static! {
        static ref DEBUG_RE: Regex = Regex::new(r"Debug").unwrap();
    }

    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;
    let api = Api::current();
    let should_wrap = matches.is_present("force")
        || match env::var("CONFIGURATION") {
            Ok(config) => !DEBUG_RE.is_match(&config),
            Err(_) => bail!("Need to run this from Xcode"),
        };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/scripts/react-native-xcode.sh")
    }
    .canonicalize()?;

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match InfoPlist::discover_from_env()? {
        Some(plist) => plist,
        None => bail!("Could not find info.plist"),
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::create()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let url = url.trim_end_matches('/');
            bundle_file = TempFile::create()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::create()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                bail!("Error: react-native packager did not respond in time");
            }

            api.download(
                &format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                &mut bundle_file.open()?,
            )?;
            api.download(
                &format!("{}/index.ios.map?platform=ios&dev=true", url),
                &mut sourcemap_file.open()?,
            )?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env(
                    "SENTRY_RN_SOURCEMAP_REPORT",
                    report_file.path().to_str().unwrap(),
                )
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report: SourceMapReport = serde_json::from_reader(&mut f)?;
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!(
                "~/{}",
                sourcemap_path.file_name().unwrap().to_string_lossy()
            );
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new();
        processor.add(&bundle_url, &bundle_path)?;
        processor.add(&sourcemap_url, &sourcemap_path)?;
        processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let release = api.new_release(
            &org,
            &NewRelease {
                version: format!(
                    "{}@{}+{}",
                    plist.bundle_id(),
                    plist.version(),
                    plist.build()
                ),
                projects: vec![project.to_string()],
                ..Default::default()
            },
        )?;

        match matches.values_of("dist") {
            None => {
                processor.upload(&UploadContext {
                    org: &org,
                    project: Some(&project),
                    release: &release.version,
                    dist: Some(&plist.build()),
                    wait: matches.is_present("wait"),
                })?;
            }
            Some(dists) => {
                for dist in dists {
                    processor.upload(&UploadContext {
                        org: &org,
                        project: Some(&project),
                        release: &release.version,
                        dist: Some(dist),
                        wait: matches.is_present("wait"),
                    })?;
                }
            }
        }

        Ok(())
    })
}

pub fn wrap_call() -> Result<(), Error> {
    let mut args: Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && (args[1] == "bundle" || args[1] == "ram-bundle") {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().cloned();
            } else if item.starts_with("--sourcemap-output=") {
                sourcemap_path = Some(item[19..].to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().cloned();
            } else if item.starts_with("--bundle-output=") {
                bundle_path = Some(item[16..].to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let path = format!("{}.map", &bundle_path.as_ref().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path);
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(PathBuf::from);

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<|endoftext|>


<commit_before>extern crate serde;
extern crate serde_xml;

use glusterfs_exporter::errors::*;
use glusterfs_exporter::types::*;
use glusterfs_exporter::metrics::*;
use std::process::{Command, Output};
use std::io::Read;
use std::str;

fn fail_if_not_successful(cmd_string: &str, cmd: &Output) -> Result<(), GEError> {
    if !cmd.status.success() {
        err!("Failed to run command {} with exitcode {}.",
             cmd_string,
             cmd.status)
    } else {
        Ok(())
    }
}

pub fn gluster_volumes() -> Result<Vec<String>, GEError> {
    let cmd = try!(Command::new("gluster")
        .arg("volume")
        .arg("list")
        .output());
    try!(fail_if_not_successful("volume list", &cmd));
    let out = try!(str::from_utf8(&cmd.stdout));
    Ok(out.lines().map(str::to_string).collect())
}

// This assumes valid xml output as of Glusterfs 3.8
pub fn gluster_vol_info(vol: &str) -> Result<VolProfile, GEError> {
    let cmd = try!(Command::new("gluster")
        .arg("volume")
        .arg("profile")
        .arg(vol)
        .arg("info")
        .arg("cumulative")
        .arg("--xml")
        .output());
    serde_xml::de::from_iter(cmd.stdout.bytes())
        .map_err(From::from)
        .map(|cli: CliOutput| cli.vol_profile)
}

pub fn collect_stats() -> Result<(), GEError> {
    for vol in try!(gluster_volumes()).iter() {
        info!("collection profile info for volume {}.", vol);
        let VolProfile{profile_op, bricks, ..} = try!(gluster_vol_info(vol));
        PROFILE_OP_GAUGE.with_label_values(&[vol]).set(profile_op as f64);

        for Brick{brick_name, cumulative_stats} in bricks {
            let brick = &brick_name;
            debug!("collecting info for brick {}.", brick_name);
            BLOCKSTATS_TOTAL_COUNTER.with_label_values(&[vol, brick, "read"]).set(cumulative_stats.total_read as f64);
            BLOCKSTATS_TOTAL_COUNTER.with_label_values(&[vol, brick, "write"]).set(cumulative_stats.total_write as f64);
            for blockstat in cumulative_stats.block_stats.blocks {
                let size_bucket = &format!("{}", blockstat.size);
                BLOCKSTATS_COUNTER.with_label_values(&[vol, brick, "read", size_bucket]).set(blockstat.reads as f64);
                BLOCKSTATS_COUNTER.with_label_values(&[vol, brick, "write", size_bucket]).set(blockstat.writes as f64);
            }
            for fopstat in cumulative_stats.fop_stats.fops {
                let fop_name = &fopstat.name;
                FOPSTATS_AVGLATENCY.with_label_values(&[vol, brick, fop_name]).set(fopstat.avg_latency);
                FOPSTATS_MINLATENCY.with_label_values(&[vol, brick, fop_name]).set(fopstat.min_latency);
                FOPSTATS_MAXLATENCY.with_label_values(&[vol, brick, fop_name]).set(fopstat.max_latency);
                FOPSTATS_COUNTER.with_label_values(&[vol, brick, fop_name]).set(fopstat.hits as f64);
            }
        }
    }
    Ok(())
}
<commit_msg>fix typo in logging<commit_after>extern crate serde;
extern crate serde_xml;

use glusterfs_exporter::errors::*;
use glusterfs_exporter::types::*;
use glusterfs_exporter::metrics::*;
use std::process::{Command, Output};
use std::io::Read;
use std::str;

fn fail_if_not_successful(cmd_string: &str, cmd: &Output) -> Result<(), GEError> {
    if !cmd.status.success() {
        err!("Failed to run command {} with exitcode {}.",
             cmd_string,
             cmd.status)
    } else {
        Ok(())
    }
}

pub fn gluster_volumes() -> Result<Vec<String>, GEError> {
    let cmd = try!(Command::new("gluster")
        .arg("volume")
        .arg("list")
        .output());
    try!(fail_if_not_successful("volume list", &cmd));
    let out = try!(str::from_utf8(&cmd.stdout));
    Ok(out.lines().map(str::to_string).collect())
}

// This assumes valid xml output as of Glusterfs 3.8
pub fn gluster_vol_info(vol: &str) -> Result<VolProfile, GEError> {
    let cmd = try!(Command::new("gluster")
        .arg("volume")
        .arg("profile")
        .arg(vol)
        .arg("info")
        .arg("cumulative")
        .arg("--xml")
        .output());
    serde_xml::de::from_iter(cmd.stdout.bytes())
        .map_err(From::from)
        .map(|cli: CliOutput| cli.vol_profile)
}

pub fn collect_stats() -> Result<(), GEError> {
    for vol in try!(gluster_volumes()).iter() {
        info!("collecting profile info for volume {}.", vol);
        let VolProfile{profile_op, bricks, ..} = try!(gluster_vol_info(vol));
        PROFILE_OP_GAUGE.with_label_values(&[vol]).set(profile_op as f64);

        for Brick{brick_name, cumulative_stats} in bricks {
            let brick = &brick_name;
            debug!("collecting info for brick {}.", brick_name);
            BLOCKSTATS_TOTAL_COUNTER.with_label_values(&[vol, brick, "read"]).set(cumulative_stats.total_read as f64);
            BLOCKSTATS_TOTAL_COUNTER.with_label_values(&[vol, brick, "write"]).set(cumulative_stats.total_write as f64);
            for blockstat in cumulative_stats.block_stats.blocks {
                let size_bucket = &format!("{}", blockstat.size);
                BLOCKSTATS_COUNTER.with_label_values(&[vol, brick, "read", size_bucket]).set(blockstat.reads as f64);
                BLOCKSTATS_COUNTER.with_label_values(&[vol, brick, "write", size_bucket]).set(blockstat.writes as f64);
            }
            for fopstat in cumulative_stats.fop_stats.fops {
                let fop_name = &fopstat.name;
                FOPSTATS_AVGLATENCY.with_label_values(&[vol, brick, fop_name]).set(fopstat.avg_latency);
                FOPSTATS_MINLATENCY.with_label_values(&[vol, brick, fop_name]).set(fopstat.min_latency);
                FOPSTATS_MAXLATENCY.with_label_values(&[vol, brick, fop_name]).set(fopstat.max_latency);
                FOPSTATS_COUNTER.with_label_values(&[vol, brick, fop_name]).set(fopstat.hits as f64);
            }
        }
    }
    Ok(())
}
<|endoftext|>