<commit_before>// Copyright (c) 2017-2018 Rene van der Meer
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//! Interface for the GPIO peripheral.
//!
//! To ensure fast performance, RPPAL interfaces with the GPIO peripheral by
//! directly accessing the registers through either `/dev/gpiomem` or `/dev/mem`.
//! GPIO interrupts are controlled using the `/dev/gpiochipN` (where N=0, 1 and 2)
//! character device.
//!
//! ## Pins
//!
//! Pins are addressed by their BCM numbers, rather than their
//! physical location.
//!
//! By default, pins are reset to their original state when they go out of scope.
//! Use [`InputPin::set_clear_on_drop(false)`], [`OutputPin::set_clear_on_drop(false)`]
//! or [`AltPin::set_clear_on_drop(false)`], respecively, to disable this behavior.
//! Note that `drop` methods aren't called when a program is abnormally terminated (for
//! instance when a SIGINT isn't caught).
//!
//! ## Permission denied
//!
//! In recent releases of Raspbian (December 2017 or later), users that are part of the
//! `gpio` group (like the default `pi` user) can access `/dev/gpiomem` and
//! `/dev/gpiochipN` without needing additional permissions. If you encounter any
//! Permission Denied errors when creating a new [`Gpio`] instance, either the current
//! user isn't a member of the `gpio` group, or your Raspbian distribution isn't
//! up-to-date and doesn't automatically configure permissions for the above-mentioned
//! files. Updating Raspbian to the latest release should fix any permission issues.
//! Alternatively, although not recommended, you can run your application with superuser
//! privileges by using `sudo`.
//!
//! If you're unable to update Raspbian and its packages (namely `raspberrypi-sys-mods`) to
//! the latest available release, or updating hasn't fixed the issue, you might be able to
//! manually update your udev rules to set the appropriate permissions. More information
//! can be found at [raspberrypi/linux#1225] and [raspberrypi/linux#2289].
//!
//! [raspberrypi/linux#1225]: https://github.com/raspberrypi/linux/issues/1225
//! [raspberrypi/linux#2289]: https://github.com/raspberrypi/linux/issues/2289
//! [`Gpio`]: struct.Gpio.html
//! [`InputPin::set_clear_on_drop(false)`]: struct.InputPin.html#method.set_clear_on_drop
//! [`OutputPin::set_clear_on_drop(false)`]: struct.InputPin.html#method.set_clear_on_drop
//! [`AltPin::set_clear_on_drop(false)`]: struct.InputPin.html#method.set_clear_on_drop
//! [`Error::InstanceExists`]: enum.Error.html#variant.InstanceExists
//!
//! ## Examples
//!
//! Basic example:
//!
//! ```
//! use std::thread::sleep;
//! use std::time::Duration;
//!
//! use rppal::gpio::Gpio;
//!
//! # fn main() -> rppal::gpio::Result<()> {
//! let gpio = Gpio::new()?;
//! let mut pin = gpio.get(23).unwrap().into_output();
//!
//! pin.set_high();
//! sleep(Duration::from_secs(1));
//! pin.set_low();
//! # Ok(())
//! # }
//! ```

use std::fmt;
use std::io;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;

use lazy_static::lazy_static;
use quick_error::quick_error;

mod epoll;
mod interrupt;
mod ioctl;
mod mem;
mod pin;

pub use self::pin::{AltPin, InputPin, OutputPin, Pin};

quick_error! {
/// Errors that can occur when accessing the GPIO peripheral.
    #[derive(Debug)]
    pub enum Error {
/// Unknown SoC.
///
/// It wasn't possible to automatically identify the Raspberry Pi's SoC.
        UnknownSoC { description("unknown SoC") }
/// Permission denied when opening `/dev/gpiomem` and/or `/dev/mem` for read/write access.
///
/// Make sure the user has read and write access to `/dev/gpiomem`.
/// Common causes are either incorrect file permissions on `/dev/gpiomem`, or
/// the user isn't a member of the `gpio` group. If `/dev/gpiomem` is missing, upgrade to a more
/// recent version of Raspbian.
///
/// `/dev/mem` is a fallback when `/dev/gpiomem` can't be accessed. Getting read and write
/// access to `/dev/mem` is typically accomplished by executing the program as a
/// privileged user through `sudo`. A better solution that doesn't require `sudo` would be
/// to upgrade to a version of Raspbian that implements `/dev/gpiomem`.
        PermissionDenied { description("/dev/gpiomem and/or /dev/mem insufficient permissions") }
/// IO error.
        Io(err: io::Error) { description(err.description()) from() }
/// Interrupt polling thread panicked.
        ThreadPanic { description("interrupt polling thread panicked") }
    }
}

/// Result type returned from methods that can have `rppal::gpio::Error`s.
pub type Result<T> = result::Result<T, Error>;

/// Pin modes.
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum Mode {
    Input = 0b000,
    Output = 0b001,
    Alt5 = 0b010, // PWM
    Alt4 = 0b011, // SPI
    Alt0 = 0b100, // PCM
    Alt1 = 0b101, // SMI
    Alt2 = 0b110, // ---
    Alt3 = 0b111, // BSC-SPI
}

impl fmt::Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Mode::Input => write!(f, "In"),
            Mode::Output => write!(f, "Out"),
            Mode::Alt0 => write!(f, "Alt0"),
            Mode::Alt1 => write!(f, "Alt1"),
            Mode::Alt2 => write!(f, "Alt2"),
            Mode::Alt3 => write!(f, "Alt3"),
            Mode::Alt4 => write!(f, "Alt4"),
            Mode::Alt5 => write!(f, "Alt5"),
        }
    }
}

/// Pin logic levels.
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum Level {
    Low = 0,
    High = 1,
}

impl fmt::Display for Level {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Level::Low => write!(f, "Low"),
            Level::High => write!(f, "High"),
        }
    }
}

/// Built-in pull-up/pull-down resistor states.
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum PullUpDown {
    Off = 0b00,
    PullDown = 0b01,
    PullUp = 0b10,
}

impl fmt::Display for PullUpDown {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            PullUpDown::Off => write!(f, "Off"),
            PullUpDown::PullDown => write!(f, "PullDown"),
            PullUpDown::PullUp => write!(f, "PullUp"),
        }
    }
}

/// Interrupt trigger conditions.
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Trigger {
    Disabled = 0,
    RisingEdge = 1,
    FallingEdge = 2,
    Both = 3,
}

impl fmt::Display for Trigger {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Trigger::Disabled => write!(f, "Disabled"),
            Trigger::RisingEdge => write!(f, "RisingEdge"),
            Trigger::FallingEdge => write!(f, "FallingEdge"),
            Trigger::Both => write!(f, "Both"),
        }
    }
}

// Store Gpio's state separately, so we can conveniently share it through
// a cloned Arc.
pub struct GpioState {
    gpio_mem: mem::GpioMem,
    cdev: std::fs::File,
    sync_interrupts: Mutex<interrupt::EventLoop>,
    pins_taken: [AtomicBool; pin::MAX],
}

impl fmt::Debug for GpioState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("EventLoop")
            .field("gpio_mem", &self.gpio_mem)
            .field("cdev", &self.cdev)
            .field("sync_interrupts", &self.sync_interrupts)
            .field("pins_taken", &format_args!("{{ .. }}"))
            .finish()
    }
}

// Share state between Gpio and Pin instances. GpioState is dropped after
// all Gpio and Pin instances go out of scope, guaranteeing we won't have
// any pins simultaneously using different EventLoop or GpioMem instances.
lazy_static! {
    static ref GPIO_STATE: Mutex<Weak<GpioState>> = Mutex::new(Weak::new());
}

/// Provides access to the Raspberry Pi's GPIO peripheral.
#[derive(Clone, Debug)]
pub struct Gpio {
    inner: Arc<GpioState>,
}

impl Gpio {
    /// Constructs a new `Gpio`.
    pub fn new() -> Result<Gpio> {
        let mut static_state = GPIO_STATE.lock().unwrap();

        // Create a strong reference if a GpioState instance already exists,
        // otherwise initialize it here so we can return any relevant errors.
        if let Some(ref state) = static_state.upgrade() {
            Ok(Gpio {
                inner: state.clone(),
            })
        } else {
            let gpio_mem = mem::GpioMem::open()?;
            let cdev = ioctl::find_gpiochip()?;
            let sync_interrupts =
                Mutex::new(interrupt::EventLoop::new(cdev.as_raw_fd(), pin::MAX)?);
            let pins_taken = init_array!(AtomicBool::new(false), pin::MAX);

            let gpio_state = Arc::new(GpioState {
                gpio_mem,
                cdev,
                sync_interrupts,
                pins_taken,
            });

            // Store a weak reference to our state. This gets dropped when
            // all Gpio and Pin instances go out of scope.
            *static_state = Arc::downgrade(&gpio_state);

            Ok(Gpio { inner: gpio_state })
        }
    }

    /// Returns a [`Pin`] for the specified GPIO pin number.
    ///
    /// Retrieving a GPIO pin using `get` grants exclusive access to the GPIO
    /// pin through an owned [`Pin`]. If the selected pin number is already
    /// in use, `get` returns `None`. After a [`Pin`] goes out of scope, it can be retrieved
    /// again using `get`.
    ///
    /// [`Pin`]: struct.Pin.html
    pub fn get(&self, pin: u8) -> Option<pin::Pin> {
        if pin as usize >= pin::MAX {
            return None;
        }

        // Returns true if the pin is currently taken, otherwise atomically sets
        // it to true here
        if self.inner.pins_taken[pin as usize].compare_and_swap(false, true, Ordering::SeqCst) {
            // Pin is currently taken
            None
        } else {
            // Return an owned Pin
            let pin_instance = pin::Pin::new(pin, self.inner.clone());

            Some(pin_instance)
        }
    }

    /// Blocks until an interrupt is triggered on any of the specified pins, or until a timeout occurs.
    ///
    /// This only works for pins that have been configured for synchronous interrupts using
    /// [`InputPin::set_interrupt`]. Asynchronous interrupt triggers are automatically polled on a separate thread.
    ///
    /// If `reset` is set to `false`, returns immediately if an interrupt trigger event was cached in a
    /// previous call to [`InputPin::poll_interrupt`] or `poll_interrupts`.
    /// If `reset` is set to `true`, clears any cached interrupt trigger events before polling.
    ///
    /// The `timeout` duration indicates how long the call to `poll_interrupts` will block while waiting
    /// for interrupt trigger events, after which an `Ok(None))` is returned.
    /// `timeout` can be set to `None` to wait indefinitely.
    ///
    /// When an interrupt event is triggered, `poll_interrupts` returns
    /// `Ok((&`[`InputPin`]`, `[`Level`]`))` containing the corresponding pin and logic level. If multiple events trigger
    /// at the same time, only the first one is returned. The remaining events are cached and will be returned
    /// the next time [`InputPin::poll_interrupt`] or `poll_interrupts` is called.
    ///
    /// [`InputPin::set_interrupt`]: struct.InputPin#method.set_interrupt
    /// [`InputPin::poll_interrupt`]: struct.InputPin#method.poll_interrupt
    /// [`InputPin`]: struct.InputPin
    /// [`Level`]: struct.Level
    pub fn poll_interrupts<'a>(
        &self,
        pins: &[&'a InputPin],
        reset: bool,
        timeout: Option<Duration>,
    ) -> Result<Option<(&'a InputPin, Level)>> {
        (*self.inner.sync_interrupts.lock().unwrap()).poll(pins, reset, timeout)
    }
}
<commit_msg>Change GpioState visibility to pub(crate)<commit_after>// Copyright (c) 2017-2018 Rene van der Meer
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//! Interface for the GPIO peripheral.
//!
//! To ensure fast performance, RPPAL interfaces with the GPIO peripheral by
//! directly accessing the registers through either `/dev/gpiomem` or `/dev/mem`.
//! GPIO interrupts are controlled using the `/dev/gpiochipN` (where N=0, 1 and 2)
//! character device.
//!
//! ## Pins
//!
//! Pins are addressed by their BCM numbers, rather than their
//! physical location.
//!
//! By default, pins are reset to their original state when they go out of scope.
//! Use [`InputPin::set_clear_on_drop(false)`], [`OutputPin::set_clear_on_drop(false)`]
//! or [`AltPin::set_clear_on_drop(false)`], respecively, to disable this behavior.
//! Note that `drop` methods aren't called when a program is abnormally terminated (for
//! instance when a SIGINT isn't caught).
//!
//! ## Permission denied
//!
//! In recent releases of Raspbian (December 2017 or later), users that are part of the
//! `gpio` group (like the default `pi` user) can access `/dev/gpiomem` and
//! `/dev/gpiochipN` without needing additional permissions. If you encounter any
//! Permission Denied errors when creating a new [`Gpio`] instance, either the current
//! user isn't a member of the `gpio` group, or your Raspbian distribution isn't
//! up-to-date and doesn't automatically configure permissions for the above-mentioned
//! files. Updating Raspbian to the latest release should fix any permission issues.
//! Alternatively, although not recommended, you can run your application with superuser
//! privileges by using `sudo`.
//!
//! If you're unable to update Raspbian and its packages (namely `raspberrypi-sys-mods`) to
//! the latest available release, or updating hasn't fixed the issue, you might be able to
//! manually update your udev rules to set the appropriate permissions. More information
//! can be found at [raspberrypi/linux#1225] and [raspberrypi/linux#2289].
//!
//! [raspberrypi/linux#1225]: https://github.com/raspberrypi/linux/issues/1225
//! [raspberrypi/linux#2289]: https://github.com/raspberrypi/linux/issues/2289
//! [`Gpio`]: struct.Gpio.html
//! [`InputPin::set_clear_on_drop(false)`]: struct.InputPin.html#method.set_clear_on_drop
//! [`OutputPin::set_clear_on_drop(false)`]: struct.InputPin.html#method.set_clear_on_drop
//! [`AltPin::set_clear_on_drop(false)`]: struct.InputPin.html#method.set_clear_on_drop
//! [`Error::InstanceExists`]: enum.Error.html#variant.InstanceExists
//!
//! ## Examples
//!
//! Basic example:
//!
//! ```
//! use std::thread::sleep;
//! use std::time::Duration;
//!
//! use rppal::gpio::Gpio;
//!
//! # fn main() -> rppal::gpio::Result<()> {
//! let gpio = Gpio::new()?;
//! let mut pin = gpio.get(23).unwrap().into_output();
//!
//! pin.set_high();
//! sleep(Duration::from_secs(1));
//! pin.set_low();
//! # Ok(())
//! # }
//! ```

use std::fmt;
use std::io;
use std::os::unix::io::AsRawFd;
use std::result;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex, Weak};
use std::time::Duration;

use lazy_static::lazy_static;
use quick_error::quick_error;

mod epoll;
mod interrupt;
mod ioctl;
mod mem;
mod pin;

pub use self::pin::{AltPin, InputPin, OutputPin, Pin};

quick_error! {
/// Errors that can occur when accessing the GPIO peripheral.
    #[derive(Debug)]
    pub enum Error {
/// Unknown SoC.
///
/// It wasn't possible to automatically identify the Raspberry Pi's SoC.
        UnknownSoC { description("unknown SoC") }
/// Permission denied when opening `/dev/gpiomem` and/or `/dev/mem` for read/write access.
///
/// Make sure the user has read and write access to `/dev/gpiomem`.
/// Common causes are either incorrect file permissions on `/dev/gpiomem`, or
/// the user isn't a member of the `gpio` group. If `/dev/gpiomem` is missing, upgrade to a more
/// recent version of Raspbian.
///
/// `/dev/mem` is a fallback when `/dev/gpiomem` can't be accessed. Getting read and write
/// access to `/dev/mem` is typically accomplished by executing the program as a
/// privileged user through `sudo`. A better solution that doesn't require `sudo` would be
/// to upgrade to a version of Raspbian that implements `/dev/gpiomem`.
        PermissionDenied { description("/dev/gpiomem and/or /dev/mem insufficient permissions") }
/// IO error.
        Io(err: io::Error) { description(err.description()) from() }
/// Interrupt polling thread panicked.
        ThreadPanic { description("interrupt polling thread panicked") }
    }
}

/// Result type returned from methods that can have `rppal::gpio::Error`s.
pub type Result<T> = result::Result<T, Error>;

/// Pin modes.
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum Mode {
    Input = 0b000,
    Output = 0b001,
    Alt5 = 0b010, // PWM
    Alt4 = 0b011, // SPI
    Alt0 = 0b100, // PCM
    Alt1 = 0b101, // SMI
    Alt2 = 0b110, // ---
    Alt3 = 0b111, // BSC-SPI
}

impl fmt::Display for Mode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Mode::Input => write!(f, "In"),
            Mode::Output => write!(f, "Out"),
            Mode::Alt0 => write!(f, "Alt0"),
            Mode::Alt1 => write!(f, "Alt1"),
            Mode::Alt2 => write!(f, "Alt2"),
            Mode::Alt3 => write!(f, "Alt3"),
            Mode::Alt4 => write!(f, "Alt4"),
            Mode::Alt5 => write!(f, "Alt5"),
        }
    }
}

/// Pin logic levels.
#[derive(Debug, PartialEq, Copy, Clone)]
#[repr(u8)]
pub enum Level {
    Low = 0,
    High = 1,
}

impl fmt::Display for Level {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Level::Low => write!(f, "Low"),
            Level::High => write!(f, "High"),
        }
    }
}

/// Built-in pull-up/pull-down resistor states.
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum PullUpDown {
    Off = 0b00,
    PullDown = 0b01,
    PullUp = 0b10,
}

impl fmt::Display for PullUpDown {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            PullUpDown::Off => write!(f, "Off"),
            PullUpDown::PullDown => write!(f, "PullDown"),
            PullUpDown::PullUp => write!(f, "PullUp"),
        }
    }
}

/// Interrupt trigger conditions.
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum Trigger {
    Disabled = 0,
    RisingEdge = 1,
    FallingEdge = 2,
    Both = 3,
}

impl fmt::Display for Trigger {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self {
            Trigger::Disabled => write!(f, "Disabled"),
            Trigger::RisingEdge => write!(f, "RisingEdge"),
            Trigger::FallingEdge => write!(f, "FallingEdge"),
            Trigger::Both => write!(f, "Both"),
        }
    }
}

// Store Gpio's state separately, so we can conveniently share it through
// a cloned Arc.
pub(crate) struct GpioState {
    gpio_mem: mem::GpioMem,
    cdev: std::fs::File,
    sync_interrupts: Mutex<interrupt::EventLoop>,
    pins_taken: [AtomicBool; pin::MAX],
}

impl fmt::Debug for GpioState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("EventLoop")
            .field("gpio_mem", &self.gpio_mem)
            .field("cdev", &self.cdev)
            .field("sync_interrupts", &self.sync_interrupts)
            .field("pins_taken", &format_args!("{{ .. }}"))
            .finish()
    }
}

// Share state between Gpio and Pin instances. GpioState is dropped after
// all Gpio and Pin instances go out of scope, guaranteeing we won't have
// any pins simultaneously using different EventLoop or GpioMem instances.
lazy_static! {
    static ref GPIO_STATE: Mutex<Weak<GpioState>> = Mutex::new(Weak::new());
}

/// Provides access to the Raspberry Pi's GPIO peripheral.
#[derive(Clone, Debug)]
pub struct Gpio {
    inner: Arc<GpioState>,
}

impl Gpio {
    /// Constructs a new `Gpio`.
    pub fn new() -> Result<Gpio> {
        let mut static_state = GPIO_STATE.lock().unwrap();

        // Create a strong reference if a GpioState instance already exists,
        // otherwise initialize it here so we can return any relevant errors.
        if let Some(ref state) = static_state.upgrade() {
            Ok(Gpio {
                inner: state.clone(),
            })
        } else {
            let gpio_mem = mem::GpioMem::open()?;
            let cdev = ioctl::find_gpiochip()?;
            let sync_interrupts =
                Mutex::new(interrupt::EventLoop::new(cdev.as_raw_fd(), pin::MAX)?);
            let pins_taken = init_array!(AtomicBool::new(false), pin::MAX);

            let gpio_state = Arc::new(GpioState {
                gpio_mem,
                cdev,
                sync_interrupts,
                pins_taken,
            });

            // Store a weak reference to our state. This gets dropped when
            // all Gpio and Pin instances go out of scope.
            *static_state = Arc::downgrade(&gpio_state);

            Ok(Gpio { inner: gpio_state })
        }
    }

    /// Returns a [`Pin`] for the specified GPIO pin number.
    ///
    /// Retrieving a GPIO pin using `get` grants exclusive access to the GPIO
    /// pin through an owned [`Pin`]. If the selected pin number is already
    /// in use, `get` returns `None`. After a [`Pin`] goes out of scope, it can be retrieved
    /// again using `get`.
    ///
    /// [`Pin`]: struct.Pin.html
    pub fn get(&self, pin: u8) -> Option<pin::Pin> {
        if pin as usize >= pin::MAX {
            return None;
        }

        // Returns true if the pin is currently taken, otherwise atomically sets
        // it to true here
        if self.inner.pins_taken[pin as usize].compare_and_swap(false, true, Ordering::SeqCst) {
            // Pin is currently taken
            None
        } else {
            // Return an owned Pin
            let pin_instance = pin::Pin::new(pin, self.inner.clone());

            Some(pin_instance)
        }
    }

    /// Blocks until an interrupt is triggered on any of the specified pins, or until a timeout occurs.
    ///
    /// This only works for pins that have been configured for synchronous interrupts using
    /// [`InputPin::set_interrupt`]. Asynchronous interrupt triggers are automatically polled on a separate thread.
    ///
    /// If `reset` is set to `false`, returns immediately if an interrupt trigger event was cached in a
    /// previous call to [`InputPin::poll_interrupt`] or `poll_interrupts`.
    /// If `reset` is set to `true`, clears any cached interrupt trigger events before polling.
    ///
    /// The `timeout` duration indicates how long the call to `poll_interrupts` will block while waiting
    /// for interrupt trigger events, after which an `Ok(None))` is returned.
    /// `timeout` can be set to `None` to wait indefinitely.
    ///
    /// When an interrupt event is triggered, `poll_interrupts` returns
    /// `Ok((&`[`InputPin`]`, `[`Level`]`))` containing the corresponding pin and logic level. If multiple events trigger
    /// at the same time, only the first one is returned. The remaining events are cached and will be returned
    /// the next time [`InputPin::poll_interrupt`] or `poll_interrupts` is called.
    ///
    /// [`InputPin::set_interrupt`]: struct.InputPin#method.set_interrupt
    /// [`InputPin::poll_interrupt`]: struct.InputPin#method.poll_interrupt
    /// [`InputPin`]: struct.InputPin
    /// [`Level`]: struct.Level
    pub fn poll_interrupts<'a>(
        &self,
        pins: &[&'a InputPin],
        reset: bool,
        timeout: Option<Duration>,
    ) -> Result<Option<(&'a InputPin, Level)>> {
        (*self.inner.sync_interrupts.lock().unwrap()).poll(pins, reset, timeout)
    }
}
<|endoftext|>


<commit_before>use http::{is_valid_url, resolve_url, retrieve_asset};
use std::default::Default;
use std::io;
use utils::data_to_dataurl;

use html5ever::parse_document;
use html5ever::rcdom::{Handle, NodeData, RcDom};
use html5ever::serialize::{serialize, SerializeOpts};
use html5ever::tendril::TendrilSink;

enum NodeMatch {
    Icon,
    Image,
    Source,
    StyleSheet,
    Anchor,
    Script,
    Form,
    IFrame,
    Other,
}

const TRANSPARENT_PIXEL: &str = "data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";

const JS_DOM_EVENT_ATTRS: [&str; 21] = [
    // Input
    "onfocus",
    "onblur",
    "onselect",
    "onchange",
    "onsubmit",
    "onreset",
    "onkeydown",
    "onkeypress",
    "onkeyup",
    // Mouse
    "onmouseover",
    "onmouseout",
    "onmousedown",
    "onmouseup",
    "onmousemove",
    // Click
    "onclick",
    "ondblclick",
    // Load
    "onload",
    "onunload",
    "onabort",
    "onerror",
    "onresize",
];

fn get_parent_node_name(node: &Handle) -> String {
    let parent = node.parent.take().clone();
    let parent_node = parent.and_then(|node| node.upgrade()).unwrap();

    match &parent_node.data {
        NodeData::Document => {"".to_string()}
        NodeData::Doctype { .. } => {"".to_string()}
        NodeData::Text { .. } => {"".to_string()}
        NodeData::Comment { .. } => {"".to_string()}
        NodeData::Element {
            ref name,
            attrs: _,
            ..
        } => {
            name.local.as_ref().to_string()
        }
        NodeData::ProcessingInstruction { .. } => unreachable!()
    }
}

pub fn walk_and_embed_assets(
    url: &str,
    node: &Handle,
    opt_no_js: bool,
    opt_no_images: bool,
    opt_user_agent: &str,
) {
    match node.data {
        NodeData::Document => {
            // Dig deeper
            for child in node.children.borrow().iter() {
                walk_and_embed_assets(
                    &url, child,
                    opt_no_js,
                    opt_no_images,
                    opt_user_agent,
                );
            }
        }

        NodeData::Doctype { .. } => {}

        NodeData::Text { .. } => {}

        NodeData::Comment { .. } => {
            // Note: in case of opt_no_js being set to true, there's no need to worry about
            //       getting rid of comments that may contain scripts, e.g. <!--[if IE]><script>...
            //       since that's not part of W3C standard and therefore gets ignored
            //       by browsers other than IE [5, 9]
        }

        NodeData::Element {
            ref name,
            ref attrs,
            ..
        } => {
            let attrs_mut = &mut attrs.borrow_mut();
            let mut found = NodeMatch::Other;

            match name.local.as_ref() {
                "link" => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "rel" {
                            if is_icon(&attr.value.to_string()) {
                                found = NodeMatch::Icon;
                                break;
                            } else if attr.value.to_string() == "stylesheet" {
                                found = NodeMatch::StyleSheet;
                                break;
                            }
                        }
                    }
                }
                "img" => { found = NodeMatch::Image; }
                "source" => { found = NodeMatch::Source; }
                "a" => { found = NodeMatch::Anchor; }
                "script" => { found = NodeMatch::Script; }
                "form" => { found = NodeMatch::Form; }
                "iframe" => { found = NodeMatch::IFrame; }
                _ => {}
            }

            match found {
                NodeMatch::Icon => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "href" {
                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            let favicon_datauri = retrieve_asset(
                                &href_full_url.unwrap(),
                                true,
                                "",
                                opt_user_agent,
                            );
                            attr.value.clear();
                            attr.value.push_slice(favicon_datauri.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::Image => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "src" {
                            if opt_no_images {
                                attr.value.clear();
                                attr.value.push_slice(TRANSPARENT_PIXEL);
                            } else {
                                let src_full_url = resolve_url(&url, &attr.value.to_string());
                                let img_datauri = retrieve_asset(
                                    &src_full_url.unwrap(),
                                    true,
                                    "",
                                    opt_user_agent,
                                );
                                attr.value.clear();
                                attr.value.push_slice(img_datauri.unwrap().as_str());
                            }
                        }
                    }
                }
                NodeMatch::Source => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "srcset" {
                            if get_parent_node_name(&node) == "picture" {
                                if opt_no_images {
                                    attr.value.clear();
                                    attr.value.push_slice(TRANSPARENT_PIXEL);
                                } else {
                                    let srcset_full_url = resolve_url(&url, &attr.value.to_string());
                                    let source_datauri = retrieve_asset(
                                        &srcset_full_url.unwrap(),
                                        true,
                                        "",
                                        opt_user_agent,
                                    );
                                    attr.value.clear();
                                    attr.value.push_slice(source_datauri.unwrap().as_str());
                                }
                            }
                        }
                    }
                }
                NodeMatch::Anchor => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "href" {
                            // Don't touch email links or hrefs which begin with a hash sign
                            if attr.value.starts_with('#') || attr.value.starts_with("mailto:") {
                                continue;
                            }

                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            attr.value.clear();
                            attr.value.push_slice(href_full_url.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::StyleSheet => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "href" {
                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            let css_datauri = retrieve_asset(
                                &href_full_url.unwrap(),
                                true,
                                "text/css",
                                opt_user_agent,
                            );
                            attr.value.clear();
                            attr.value.push_slice(css_datauri.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::Script => {
                    if opt_no_js {
                        // Get rid of src and inner content of SCRIPT tags
                        for attr in attrs_mut.iter_mut() {
                            if &attr.name.local == "src" {
                                attr.value.clear();
                            }
                        }
                        node.children.borrow_mut().clear();
                    } else {
                        for attr in attrs_mut.iter_mut() {
                            if &attr.name.local == "src" {
                                let src_full_url = resolve_url(&url, &attr.value.to_string());
                                let js_datauri = retrieve_asset(
                                    &src_full_url.unwrap(),
                                    true,
                                    "application/javascript",
                                    opt_user_agent,
                                );
                                attr.value.clear();
                                attr.value.push_slice(js_datauri.unwrap().as_str());
                            }
                        }
                    }
                }
                NodeMatch::Form => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "action" {
                            // Do not touch action props which are set to a URL
                            if is_valid_url(&attr.value) {
                                continue;
                            }

                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            attr.value.clear();
                            attr.value.push_slice(href_full_url.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::IFrame => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "src" {
                            let src_full_url = resolve_url(&url, &attr.value.to_string()).unwrap();
                            let iframe_data = retrieve_asset(
                                &src_full_url,
                                false,
                                "text/html",
                                opt_user_agent,
                            );
                            let dom = html_to_dom(&iframe_data.unwrap());
                            walk_and_embed_assets(
                                &src_full_url,
                                &dom.document,
                                opt_no_js,
                                opt_no_images,
                                opt_user_agent,
                            );
                            let mut buf: Vec<u8> = Vec::new();
                            serialize(&mut buf, &dom.document, SerializeOpts::default()).unwrap();
                            let iframe_datauri = data_to_dataurl("text/html", &buf);
                            attr.value.clear();
                            attr.value.push_slice(iframe_datauri.as_str());
                        }
                    }
                }
                NodeMatch::Other => {}
            }

            if opt_no_js {
                // Get rid of JS event attributes
                for attr in attrs_mut.iter_mut() {
                    if JS_DOM_EVENT_ATTRS.contains(&attr.name.local.to_lowercase().as_str()) {
                        attr.value.clear();
                    }
                }
            }

            // Dig deeper
            for child in node.children.borrow().iter() {
                walk_and_embed_assets(
                    &url,
                    child,
                    opt_no_js,
                    opt_no_images,
                    opt_user_agent,
                );
            }
        }

        NodeData::ProcessingInstruction { .. } => unreachable!()
    }
}

pub fn html_to_dom(data: &str) -> html5ever::rcdom::RcDom {
    parse_document(RcDom::default(), Default::default())
        .from_utf8()
        .read_from(&mut data.as_bytes())
        .unwrap()
}

pub fn print_dom(handle: &Handle) {
    serialize(&mut io::stdout(), handle, SerializeOpts::default()).unwrap();
}

fn is_icon(attr_value: &str) -> bool {
    attr_value == "icon"
        || attr_value == "shortcut icon"
        || attr_value == "mask-icon"
        || attr_value == "apple-touch-icon"
}

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

    #[test]
    fn test_is_icon() {
        assert_eq!(is_icon("icon"), true);
        assert_eq!(is_icon("stylesheet"), false);
    }
}
<commit_msg>Avoid modifying non-HTTP anchor hrefs<commit_after>use html5ever::parse_document;
use html5ever::rcdom::{Handle, NodeData, RcDom};
use html5ever::serialize::{serialize, SerializeOpts};
use html5ever::tendril::TendrilSink;
use http::{is_valid_url, resolve_url, retrieve_asset};
use regex::Regex;
use std::default::Default;
use std::io;
use utils::data_to_dataurl;

lazy_static! {
    static ref HAS_PROTOCOL: Regex = Regex::new(r"^[a-z0-9]+:").unwrap();
}

enum NodeMatch {
    Icon,
    Image,
    Source,
    StyleSheet,
    Anchor,
    Script,
    Form,
    IFrame,
    Other,
}

const TRANSPARENT_PIXEL: &str = "data:image/png;base64,\
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";

const JS_DOM_EVENT_ATTRS: [&str; 21] = [
    // Input
    "onfocus",
    "onblur",
    "onselect",
    "onchange",
    "onsubmit",
    "onreset",
    "onkeydown",
    "onkeypress",
    "onkeyup",
    // Mouse
    "onmouseover",
    "onmouseout",
    "onmousedown",
    "onmouseup",
    "onmousemove",
    // Click
    "onclick",
    "ondblclick",
    // Load
    "onload",
    "onunload",
    "onabort",
    "onerror",
    "onresize",
];

fn get_parent_node_name(node: &Handle) -> String {
    let parent = node.parent.take().clone();
    let parent_node = parent.and_then(|node| node.upgrade()).unwrap();

    match &parent_node.data {
        NodeData::Document => {"".to_string()}
        NodeData::Doctype { .. } => {"".to_string()}
        NodeData::Text { .. } => {"".to_string()}
        NodeData::Comment { .. } => {"".to_string()}
        NodeData::Element {
            ref name,
            attrs: _,
            ..
        } => {
            name.local.as_ref().to_string()
        }
        NodeData::ProcessingInstruction { .. } => unreachable!()
    }
}

pub fn walk_and_embed_assets(
    url: &str,
    node: &Handle,
    opt_no_js: bool,
    opt_no_images: bool,
    opt_user_agent: &str,
) {
    match node.data {
        NodeData::Document => {
            // Dig deeper
            for child in node.children.borrow().iter() {
                walk_and_embed_assets(
                    &url, child,
                    opt_no_js,
                    opt_no_images,
                    opt_user_agent,
                );
            }
        }

        NodeData::Doctype { .. } => {}

        NodeData::Text { .. } => {}

        NodeData::Comment { .. } => {
            // Note: in case of opt_no_js being set to true, there's no need to worry about
            //       getting rid of comments that may contain scripts, e.g. <!--[if IE]><script>...
            //       since that's not part of W3C standard and therefore gets ignored
            //       by browsers other than IE [5, 9]
        }

        NodeData::Element {
            ref name,
            ref attrs,
            ..
        } => {
            let attrs_mut = &mut attrs.borrow_mut();
            let mut found = NodeMatch::Other;

            match name.local.as_ref() {
                "link" => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "rel" {
                            if is_icon(&attr.value.to_string()) {
                                found = NodeMatch::Icon;
                                break;
                            } else if attr.value.to_string() == "stylesheet" {
                                found = NodeMatch::StyleSheet;
                                break;
                            }
                        }
                    }
                }
                "img" => { found = NodeMatch::Image; }
                "source" => { found = NodeMatch::Source; }
                "a" => { found = NodeMatch::Anchor; }
                "script" => { found = NodeMatch::Script; }
                "form" => { found = NodeMatch::Form; }
                "iframe" => { found = NodeMatch::IFrame; }
                _ => {}
            }

            match found {
                NodeMatch::Icon => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "href" {
                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            let favicon_datauri = retrieve_asset(
                                &href_full_url.unwrap(),
                                true,
                                "",
                                opt_user_agent,
                            );
                            attr.value.clear();
                            attr.value.push_slice(favicon_datauri.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::Image => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "src" {
                            if opt_no_images {
                                attr.value.clear();
                                attr.value.push_slice(TRANSPARENT_PIXEL);
                            } else {
                                let src_full_url = resolve_url(&url, &attr.value.to_string());
                                let img_datauri = retrieve_asset(
                                    &src_full_url.unwrap(),
                                    true,
                                    "",
                                    opt_user_agent,
                                );
                                attr.value.clear();
                                attr.value.push_slice(img_datauri.unwrap().as_str());
                            }
                        }
                    }
                }
                NodeMatch::Source => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "srcset" {
                            if get_parent_node_name(&node) == "picture" {
                                if opt_no_images {
                                    attr.value.clear();
                                    attr.value.push_slice(TRANSPARENT_PIXEL);
                                } else {
                                    let srcset_full_url = resolve_url(&url, &attr.value.to_string());
                                    let source_datauri = retrieve_asset(
                                        &srcset_full_url.unwrap(),
                                        true,
                                        "",
                                        opt_user_agent,
                                    );
                                    attr.value.clear();
                                    attr.value.push_slice(source_datauri.unwrap().as_str());
                                }
                            }
                        }
                    }
                }
                NodeMatch::Anchor => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "href" {
                            // Don't touch email links or hrefs which begin with a hash sign
                            if attr.value.starts_with('#') || has_protocol(&attr.value) {
                                continue;
                            }

                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            attr.value.clear();
                            attr.value.push_slice(href_full_url.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::StyleSheet => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "href" {
                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            let css_datauri = retrieve_asset(
                                &href_full_url.unwrap(),
                                true,
                                "text/css",
                                opt_user_agent,
                            );
                            attr.value.clear();
                            attr.value.push_slice(css_datauri.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::Script => {
                    if opt_no_js {
                        // Get rid of src and inner content of SCRIPT tags
                        for attr in attrs_mut.iter_mut() {
                            if &attr.name.local == "src" {
                                attr.value.clear();
                            }
                        }
                        node.children.borrow_mut().clear();
                    } else {
                        for attr in attrs_mut.iter_mut() {
                            if &attr.name.local == "src" {
                                let src_full_url = resolve_url(&url, &attr.value.to_string());
                                let js_datauri = retrieve_asset(
                                    &src_full_url.unwrap(),
                                    true,
                                    "application/javascript",
                                    opt_user_agent,
                                );
                                attr.value.clear();
                                attr.value.push_slice(js_datauri.unwrap().as_str());
                            }
                        }
                    }
                }
                NodeMatch::Form => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "action" {
                            // Do not touch action props which are set to a URL
                            if is_valid_url(&attr.value) {
                                continue;
                            }

                            let href_full_url = resolve_url(&url, &attr.value.to_string());
                            attr.value.clear();
                            attr.value.push_slice(href_full_url.unwrap().as_str());
                        }
                    }
                }
                NodeMatch::IFrame => {
                    for attr in attrs_mut.iter_mut() {
                        if &attr.name.local == "src" {
                            let src_full_url = resolve_url(&url, &attr.value.to_string()).unwrap();
                            let iframe_data = retrieve_asset(
                                &src_full_url,
                                false,
                                "text/html",
                                opt_user_agent,
                            );
                            let dom = html_to_dom(&iframe_data.unwrap());
                            walk_and_embed_assets(
                                &src_full_url,
                                &dom.document,
                                opt_no_js,
                                opt_no_images,
                                opt_user_agent,
                            );
                            let mut buf: Vec<u8> = Vec::new();
                            serialize(&mut buf, &dom.document, SerializeOpts::default()).unwrap();
                            let iframe_datauri = data_to_dataurl("text/html", &buf);
                            attr.value.clear();
                            attr.value.push_slice(iframe_datauri.as_str());
                        }
                    }
                }
                NodeMatch::Other => {}
            }

            if opt_no_js {
                // Get rid of JS event attributes
                for attr in attrs_mut.iter_mut() {
                    if JS_DOM_EVENT_ATTRS.contains(&attr.name.local.to_lowercase().as_str()) {
                        attr.value.clear();
                    }
                }
            }

            // Dig deeper
            for child in node.children.borrow().iter() {
                walk_and_embed_assets(
                    &url,
                    child,
                    opt_no_js,
                    opt_no_images,
                    opt_user_agent,
                );
            }
        }

        NodeData::ProcessingInstruction { .. } => unreachable!()
    }
}

fn has_protocol(url: &str) -> bool {
    HAS_PROTOCOL.is_match(&url.to_lowercase())
}

pub fn html_to_dom(data: &str) -> html5ever::rcdom::RcDom {
    parse_document(RcDom::default(), Default::default())
        .from_utf8()
        .read_from(&mut data.as_bytes())
        .unwrap()
}

pub fn print_dom(handle: &Handle) {
    serialize(&mut io::stdout(), handle, SerializeOpts::default()).unwrap();
}

fn is_icon(attr_value: &str) -> bool {
    attr_value == "icon"
        || attr_value == "shortcut icon"
        || attr_value == "mask-icon"
        || attr_value == "apple-touch-icon"
}

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

    #[test]
    fn test_is_icon() {
        assert_eq!(is_icon("icon"), true);
        assert_eq!(is_icon("stylesheet"), false);
    }

    #[test]
    fn test_has_protocol() {
        assert_eq!(has_protocol("mailto:somebody@somewhere.com?subject=hello"), true);
        assert_eq!(has_protocol("tel:5551234567"), true);
        assert_eq!(has_protocol("ftp:user:password@some-ftp-server.com"), true);
        assert_eq!(has_protocol("javascript:void(0)"), true);
        assert_eq!(has_protocol("http://news.ycombinator.com"), true);
        assert_eq!(has_protocol("https://github.com"), true);
        assert_eq!(has_protocol("//some-hostname.com/some-file.html"), false);
        assert_eq!(has_protocol("some-hostname.com/some-file.html"), false);
        assert_eq!(has_protocol("/some-file.html"), false);
        assert_eq!(has_protocol(""), false);
        assert_eq!(has_protocol("MAILTO:somebody@somewhere.com?subject=hello"), true);
    }
}
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

extern crate getopts;

use getopts::Options;
use std::io::Error;
use std::fs;
use std::env;
use std::process;

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    println!("fsgen: Generates an HDFS fsimage.\n");
    println!("By using the fsgen tool, you can quickly generate large HDFS");
    println!("fsimages for testing purposes.  fsgen has two main outputs,");
    println!("an fsimage.xml file and a block directory.");
    print!("{}", opts.usage(&brief));
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut opts = Options::new();
    let program = args[0].clone();

    opts.optopt("d", "num_datanodes", "set the number of datanodes to generate", "NUM_DATANODES");
    opts.optflag("h", "help", "print this help menu");
    opts.optopt("n", "num_inodes", "set the number of inodes to generate", "NUM_INODES");
    opts.optopt("o", "out", "set the output directory", "NAME");
    opts.optopt("r", "repl", "set the replication factor to use", "REPL_FACTOR");
    opts.optopt("s", "seed", "set the random seed to use", "RAND_SEED");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    let num_datanodes = match matches.opt_str("d") {
        None => 4 as u64,
        Some(val) => val.parse::<u64>().unwrap(),
    };
    let num_inodes = match matches.opt_str("n") {
        None => 10000 as u64,
        Some(val) => val.parse::<u64>().unwrap(),
    };
    let out_dir = matches.opt_str("o").unwrap_or("".to_owned());
    if out_dir == "" {
        println!("You must specify an output directory with -o.  -h for help.");
        process::exit(1);
    };
    let repl = match matches.opt_str("r") {
        None => 3 as u16,
        Some(val) => val.parse::<u16>().unwrap(),
    };
    let seed = match matches.opt_str("s") {
        None => 0xdeadbeef as u64,
        Some(val) => val.parse::<u64>().unwrap(),
    };
    // The statements here will be executed when the compiled binary is called
    // Print text to the console
    println!("** fsgen: Generating fsimage with num_datanodes={}, num_inodes={}, \
        out_dir={}, repl={}, seed={}.", num_datanodes, num_inodes,
        out_dir, repl, seed);
    match run(&out_dir) {
        Ok(_) => println!("** Done."),
        Err(err) => {
            println!("** ERROR: {:?}", err);
            process::exit(1);
        }
    }
}

fn run(out_dir: &String) -> Result<(), std::io::Error> {
    if fs::metadata(out_dir).is_ok() {
        try!(fs::remove_dir_all(out_dir));
        println!("** deleted existing output directory {}", out_dir)
    }
    return Result::Ok(());
}

//fn writeImageXml() -> Result<(), io::Error> {
//    let mut f = try!(File::create("fsimage.xml"));
//}
<commit_msg>Move configuration into a struct<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

extern crate getopts;

use getopts::Options;
use std::io::Error;
use std::fs;
use std::env;
use std::process;

struct Config {
    num_datanodes: u32,
    num_inodes: u32,
    out_dir: String,
    repl: u16,
    seed: u64,
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    println!("fsgen: Generates an HDFS fsimage.\n");
    println!("By using the fsgen tool, you can quickly generate large HDFS");
    println!("fsimages for testing purposes.  fsgen has two main outputs,");
    println!("an fsimage.xml file and a block directory.");
    print!("{}", opts.usage(&brief));
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut opts = Options::new();
    let program = args[0].clone();

    opts.optopt("d", "num_datanodes", "set the number of datanodes to generate", "NUM_DATANODES");
    opts.optflag("h", "help", "print this help menu");
    opts.optopt("n", "num_inodes", "set the number of inodes to generate", "NUM_INODES");
    opts.optopt("o", "out", "set the output directory", "NAME");
    opts.optopt("r", "repl", "set the replication factor to use", "REPL_FACTOR");
    opts.optopt("s", "seed", "set the random seed to use", "RAND_SEED");
    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m }
        Err(f) => { panic!(f.to_string()) }
    };
    if matches.opt_present("h") {
        print_usage(&program, opts);
        return;
    }
    let num_datanodes = match matches.opt_str("d") {
        None => 4 as u32,
        Some(val) => val.parse::<u32>().unwrap(),
    };
    let num_inodes = match matches.opt_str("n") {
        None => 10000 as u32,
        Some(val) => val.parse::<u32>().unwrap(),
    };
    let out_dir = matches.opt_str("o").unwrap_or("".to_owned());
    if out_dir == "" {
        println!("You must specify an output directory with -o.  -h for help.");
        process::exit(1);
    };
    let repl = match matches.opt_str("r") {
        None => 3 as u16,
        Some(val) => val.parse::<u16>().unwrap(),
    };
    let seed = match matches.opt_str("s") {
        None => 0xdeadbeef as u64,
        Some(val) => val.parse::<u64>().unwrap(),
    };
    // The statements here will be executed when the compiled binary is called
    // Print text to the console
    let config = Config{num_datanodes: num_datanodes, num_inodes: num_inodes,
        out_dir: out_dir, repl: repl, seed:seed};
    println!("** fsgen: Generating fsimage with num_datanodes={}, num_inodes={}, \
        out_dir={}, repl={}, seed={}.", config.num_datanodes, config.num_inodes,
        config.out_dir, config.repl, config.seed);
    match run(&config) {
        Ok(_) => println!("** Done."),
        Err(err) => {
            println!("** ERROR: {:?}", err);
            process::exit(1);
        }
    }
}

fn run(config: &Config) -> Result<(), std::io::Error> {
    if fs::metadata(config.out_dir.to_owned()).is_ok() {
        try!(fs::remove_dir_all(config.out_dir.to_owned()));
        println!("** deleted existing output directory {}", config.out_dir)
    }
    return Result::Ok(());
}

//fn writeImageXml() -> Result<(), io::Error> {
//    let mut f = try!(File::create("fsimage.xml"));
//}
<|endoftext|>


<commit_before>#![allow(dead_code)]
// Max number of units of distance a ship can travel in a turn
pub const MAX_SPEED: i32 = 7;
// Radius of a ship
pub const SHIP_RADIUS: f64 = 0.5;
// Starting health of ship, also its max
pub const MAX_SHIP_HEALTH: i32 = 255;
// Starting health of ship, also its max
pub const BASE_SHIP_HEALTH: i32 = 255;
// Weapon cooldown period
pub const WEAPON_COOLDOWN: i32 = 1;
// Weapon damage radius
pub const WEAPON_RADIUS: f64 = 5.0;
// Weapon damage
pub const WEAPON_DAMAGE: i32 = 48;
// Radius in which explosions affect other entities
pub const EXPLOSION_RADIUS: i32 = 5;
// Distance from the edge of the planet at which ships can try to dock
pub const DOCK_RADIUS: f64 = 4.0;
// Number of turns it takes to dock a ship
pub const DOCK_TURNS: i32 = 5;
// Number of turns it takes to create a ship per docked ship
pub const BASE_PRODUCTIVITY: i32 = 8;
// Distance from the planets edge at which new ships are created
pub const SPAWN_RADIUS: i32 = 2;<commit_msg>Update constants.rs<commit_after>#![allow(dead_code)]
// Max number of units of distance a ship can travel in a turn
pub const MAX_SPEED: i32 = 7;
// Radius of a ship
pub const SHIP_RADIUS: f64 = 0.5;
// Starting health of ship, also its max
pub const MAX_SHIP_HEALTH: i32 = 255;
// Starting health of ship, also its max
pub const BASE_SHIP_HEALTH: i32 = 255;
// Weapon cooldown period
pub const WEAPON_COOLDOWN: i32 = 1;
// Weapon damage radius
pub const WEAPON_RADIUS: f64 = 5.0;
// Weapon damage
pub const WEAPON_DAMAGE: i32 = 64;
// Radius in which explosions affect other entities
pub const EXPLOSION_RADIUS: i32 = 10;
// Distance from the edge of the planet at which ships can try to dock
pub const DOCK_RADIUS: f64 = 4.0;
// Number of turns it takes to dock a ship
pub const DOCK_TURNS: i32 = 5;
// Number of turns it takes to create a ship per docked ship
pub const BASE_PRODUCTIVITY: i32 = 6;
// Distance from the planets edge at which new ships are created
pub const SPAWN_RADIUS: i32 = 2;
<|endoftext|>


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

use crate::colour::Colour;
use crate::grid::Grid;

// Represent the state of the game as a graph.  One node for each cell.  There is an edge between
// two cells if and only if they are the same colour and have a distance of 1 according to the
// Manhattan metric.
//
// The game has only one real operation (i.e. not counting level generation): Switching the colour
// of the connected component containing the top left cell to whichever colour the clicked cell
// has.  This results in said component growing by every component it neighbours which has its new
// colour.  In essence, the aim of the gaim is to join connected components until only one is
// left.  Fewer clicks is better.
//
// So, we represent the game state as a collection of connected components, each of which has a
// colour, a list of coordinates of the constituent cells, and a list of it s neighbours.  As we
// do all our operations on connected components, we actually work not with the graph of
// neighbouring cells but with a graph of connected components.  The graph of connected components
// is made up of one node representing each connected component and an edge between any two
// connected components that have cells which share an edge, i.e. two cells which would have been
// connected by an edge in the original graph if they had had the same colour (which they did
// not).

struct Position {
    row: usize,
    column: usize,
}

struct ConnectedComponent {
    colour: Colour,
    cells: Vec<Position>,
}

struct Graph {
    neighours: HashMap<ConnectedComponent, Vec<ConnectedComponent>>,
}
<commit_msg>Store cell positions of connected components in HashSet<commit_after>use std::collections::HashMap;

use crate::colour::Colour;
use crate::grid::Grid;

// Represent the state of the game as a graph.  One node for each cell.  There is an edge between
// two cells if and only if they are the same colour and have a distance of 1 according to the
// Manhattan metric.
//
// The game has only one real operation (i.e. not counting level generation): Switching the colour
// of the connected component containing the top left cell to whichever colour the clicked cell
// has.  This results in said component growing by every component it neighbours which has its new
// colour.  In essence, the aim of the gaim is to join connected components until only one is
// left.  Fewer clicks is better.
//
// So, we represent the game state as a collection of connected components, each of which has a
// colour, a list of coordinates of the constituent cells, and a list of it s neighbours.  As we
// do all our operations on connected components, we actually work not with the graph of
// neighbouring cells but with a graph of connected components.  The graph of connected components
// is made up of one node representing each connected component and an edge between any two
// connected components that have cells which share an edge, i.e. two cells which would have been
// connected by an edge in the original graph if they had had the same colour (which they did
// not).

struct Position {
    row: usize,
    column: usize,
}

struct ConnectedComponent {
    colour: Colour,
    cells: HashSet<Position>,
}

struct Graph {
    neighours: HashMap<ConnectedComponent, Vec<ConnectedComponent>>,
}
<|endoftext|>


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

use vertex::{RawVertex,Vertex};
use traversal::GraphQuery;
use path::Element;
use treepath::TreePath;
use edge::Edge;
use parser::{parse, Arg};

#[derive(Debug)]
pub struct Graph {
    elements: i64,
    // we transmute our Boxed vertex into a pointer later
    vertices: HashMap<i64, *mut RawVertex>,
    queue: Vec<String>,
    treepath: Option<TreePath>,
}

// graph is not thread safe - needs to be wrapped in an Arc<Mutex> when running as server
impl Graph {
    pub fn new() -> Box<Graph> {
        let vertices: HashMap<i64, *mut RawVertex> = HashMap::new();
        Box::new(Graph{elements:0,
                        vertices:vertices,
                        queue:Vec::with_capacity(100),
                        treepath: None })
    }

    // todo actually return a result DERP DERP DERP
    pub fn execute(&self, query: &str) {
        // parse the graph query

        // let mut steps_table : HashMap<&'static str, FnMut() -> Result<&'static str, &'static str> > = HashMap::new();
        // steps_table.insert("outV", traverse_out);
        // steps_table.insert("inV", traverse_in);

        // todo - validate query is using legit steps
        let result = GraphQueryResult::new();

        match parse(query) {
            Ok(query) => {
                // execute each of the steps sequentially
                for step in query.steps.iter() {
                    // lookup step in hash table
                    println!("{}", step);

                    let name : &str = &step.name;

                    // there has to be a better way of doing this?
                    let step_result = match name {
                        "outV" => self.traverse_out_vertex(&step.args),
                        "inV" => self.traverse_in_vertex(&step.args),
                        "outE" => self.traverse_out_edge(&step.args),
                        "inE" => self.traverse_in_edge(&step.args),
                        "v" => self.vertex_query(&result, &step.args),
                        "V" => self.global_query(&step.args),
                        _ => Err("no thingy found")
                    };
                    // execute function, passing step args
                }
            },
            Err(x) => {
                println!("SUCH FAIL!!!!")
            }
                // Result::Err("meh")
        }
        println!("query finished");
        ()
    }

    fn vertex_query(&self, result: &GraphQueryResult, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("vertex query");
        // gather the requested vertices
        unimplemented!();
        Ok("cool")
    }

    fn global_query(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("global query");
        unimplemented!();
        Ok("cool")
    }

    fn traverse_out_vertex(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("outV traversal");

        Ok("cool")
    }

    fn traverse_in_vertex(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("inV traversal");

        unimplemented!();
        Ok("cool")
    }

    fn traverse_out_edge(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("outE traversal");
        unimplemented!();
        Ok("cool")
    }

    fn traverse_in_edge(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("inE traversal");
        unimplemented!();
        Ok("cool")
    }
    pub fn add_vertex(&mut self) -> Vertex {
        let new_id = self.elements + 1;
        self.elements += 1;
        let v = RawVertex::new(new_id);
        let ptr: *mut RawVertex = unsafe { mem::transmute(v) };

        self.vertices.insert(new_id, ptr);

        // return the proxy which knows it's own id
        Vertex{id:new_id, v: ptr}
    }

    pub fn get(&self, vertex_id:i64) -> Option<Vertex>  {
        let vertex_pointer = self.vertices.get(&vertex_id);
        // return a proxy
        match vertex_pointer {
            Some(ptr) =>
                Some(Vertex{id:vertex_id, v:*ptr}),
            None =>
                None
        }
    }

    // this will be used to start a graph query
    pub fn v(&self, vertex_id:i64) -> GraphQuery {
        // grab the original vertex
        let vertex = self.get(vertex_id);
        let mut v = Vec::new();
        match vertex {
            Some(result) => {
                v.push(result);
                GraphQuery::new(v)
            }
            None => GraphQuery::empty()
        }
    }
}

pub trait TraversableToVertex {
	fn inV(&self) -> Vec<Vertex>;
	fn outV(&self, &[&str]) -> Vec<Vertex>;
//	fn inE(&self) -> Vec<Edge>;

}

struct GraphQueryResult {
    results: Option<TreePath>
}

impl GraphQueryResult {
    pub fn new() -> GraphQueryResult {
        GraphQueryResult{ results: None }
    }
}
<commit_msg>working on results<commit_after>use std::collections::HashMap;
use std::mem;

use vertex::{RawVertex,Vertex};
use traversal::GraphQuery;
use path::Element;
use treepath::TreePath;
use edge::Edge;
use parser::{parse, Arg};

#[derive(Debug)]
pub struct Graph {
    elements: i64,
    // we transmute our Boxed vertex into a pointer later
    vertices: HashMap<i64, *mut RawVertex>,
    queue: Vec<String>,
    treepath: Option<TreePath>,
}

// graph is not thread safe - needs to be wrapped in an Arc<Mutex> when running as server
impl Graph {
    pub fn new() -> Box<Graph> {
        let vertices: HashMap<i64, *mut RawVertex> = HashMap::new();
        Box::new(Graph{elements:0,
                        vertices:vertices,
                        queue:Vec::with_capacity(100),
                        treepath: None })
    }

    // todo actually return a result DERP DERP DERP
    pub fn execute(&self, query: &str) {
        // parse the graph query

        // let mut steps_table : HashMap<&'static str, FnMut() -> Result<&'static str, &'static str> > = HashMap::new();
        // steps_table.insert("outV", traverse_out);
        // steps_table.insert("inV", traverse_in);

        // todo - validate query is using legit steps
        let result = GraphQueryResult::new();

        match parse(query) {
            Ok(query) => {
                // execute each of the steps sequentially
                for step in query.steps.iter() {
                    // lookup step in hash table
                    println!("{}", step);

                    let name : &str = &step.name;

                    // there has to be a better way of doing this?
                    let step_result = match name {
                        "outV" => self.traverse_out_vertex(&step.args),
                        "inV" => self.traverse_in_vertex(&step.args),
                        "outE" => self.traverse_out_edge(&step.args),
                        "inE" => self.traverse_in_edge(&step.args),
                        "v" => self.vertex_query(&result, &step.args),
                        "V" => self.global_query(&result, &step.args),
                        _ => Err("no thingy found")
                    };
                    // execute function, passing step args
                }
            },
            Err(x) => {
                println!("SUCH FAIL!!!!")
            }
                // Result::Err("meh")
        }
        println!("query finished");
        ()
    }

    fn vertex_query(&self, result: &GraphQueryResult, args: &Vec<Arg>)
        -> Result<&'static str, &'static str>  {
        println!("vertex query");
        // gather the requested vertices
        for arg in args {
            // this better be an integer
            match arg {
                Arg::Integer(i) => {
                    result.
                }
            }

        }
        Ok("cool")
    }

    fn global_query(&self, result: &GraphQueryResult, args: &Vec<Arg>)
        -> Result<&'static str, &'static str>  {
        println!("global query");
        unimplemented!();
        Ok("cool")
    }

    fn traverse_out_vertex(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("outV traversal");

        Ok("cool")
    }

    fn traverse_in_vertex(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("inV traversal");

        unimplemented!();
        Ok("cool")
    }

    fn traverse_out_edge(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("outE traversal");
        unimplemented!();
        Ok("cool")
    }

    fn traverse_in_edge(&self, args: &Vec<Arg>) -> Result<&'static str, &'static str>  {
        println!("inE traversal");
        unimplemented!();
        Ok("cool")
    }
    pub fn add_vertex(&mut self) -> Vertex {
        let new_id = self.elements + 1;
        self.elements += 1;
        let v = RawVertex::new(new_id);
        let ptr: *mut RawVertex = unsafe { mem::transmute(v) };

        self.vertices.insert(new_id, ptr);

        // return the proxy which knows it's own id
        Vertex{id:new_id, v: ptr}
    }

    pub fn get(&self, vertex_id:i64) -> Option<Vertex>  {
        let vertex_pointer = self.vertices.get(&vertex_id);
        // return a proxy
        match vertex_pointer {
            Some(ptr) =>
                Some(Vertex{id:vertex_id, v:*ptr}),
            None =>
                None
        }
    }

    // this will be used to start a graph query
    pub fn v(&self, vertex_id:i64) -> GraphQuery {
        // grab the original vertex
        let vertex = self.get(vertex_id);
        let mut v = Vec::new();
        match vertex {
            Some(result) => {
                GraphQuery::new(v)
            }
            None => GraphQuery::empty()
        }
    }
}

pub trait TraversableToVertex {
	fn inV(&self) -> Vec<Vertex>;
	fn outV(&self, &[&str]) -> Vec<Vertex>;
//	fn inE(&self) -> Vec<Edge>;

}

struct GraphQueryResult {
    tree: Option<TreePath>
}

impl GraphQueryResult {
    pub fn new() -> GraphQueryResult {
        GraphQueryResult{ tree: None }
    }
}
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(rocket_codegen)]
#[macro_use]
extern crate error_chain;
extern crate reqwest;
extern crate rocket;
extern crate toml;
extern crate semver;

use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::process;

use rocket::request::FromParam;
use semver::Version;

mod errors {
    // Create the Error, ErrorKind, ResultExt, and Result types
    error_chain! { }
}

use errors::ResultExt;

#[derive(Eq, PartialEq, PartialOrd, Ord)]
enum Status {
    OutOfDate,
    UpToDate,
    Unknown,
}

impl Display for Status {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            Status::Unknown => write!(f, "unknown"),
            Status::OutOfDate => write!(f, "outofdate"),
            Status::UpToDate => write!(f, "uptodate"),
        }
    }
}

struct MyParam<'r> {
    deps_type: &'r str,
    ext: &'r str,
}

impl<'r> FromParam<'r> for MyParam<'r> {
    type Error = &'r str;

    fn from_param(param: &'r str) -> Result<MyParam<'r>, &'r str> {
        let (status_type, ext) = match param.find('.') {
            Some(i) if i > 0 => (&param[..i], &param[(i + 1)..]),
            _ => return Err(param),
        };

        Ok(MyParam {
            deps_type: match status_type {
                "dev-status" => "dev-dependencies",
                "status" => "dependencies",
                _ => return Err(param),
            },
            ext: match ext {
                "png" => "png",
                "svg" => "svg",
                _ => return Err(param),
            },
        })
    }
}

#[get("/<owner>/<name>/<params>")]
fn index(owner: &str, name: &str, params: MyParam) -> io::Result<File> {
    // TODO: HEADER 'Cache-Control': 'no-cache, no-store, must-revalidate',
    // TODO: HEADER 'Expires': new Date().toUTCString()
    let status = match get_deps_status(owner, name, params.deps_type) {
        Ok(status) => status,
        Err(ref e) => {
            println!("error: {}", e);

            for e in e.iter().skip(1) {
                println!("caused by: {}", e);
            }

            // The backtrace is not always generated. Try to run this example
            // with `RUST_BACKTRACE=1`.
            if let Some(backtrace) = e.backtrace() {
                println!("backtrace: {:?}", backtrace);
            }
            Status::Unknown
        }
    };
    File::open(format!("public/img/status/{}.{}", status, params.ext))
}

fn get_deps_status(owner: &str, name: &str, deps_type: &str) -> errors::Result<Status> {
    let cargo_url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
                            owner,
                            name);

    let mut resp = reqwest::get(cargo_url.as_str())
        .chain_err(|| "Unable to download Cargo.toml")?;

    let mut buffer = String::new();
    match resp.status() {
        &reqwest::StatusCode::Ok => {
            resp.read_to_string(&mut buffer)
                .chain_err(|| "Unable to read Cargo.toml body")?;
            deps_status_from_cargo(owner, name, buffer, deps_type)
                .chain_err(|| "Unable verify status")
        },
        _ => {
            bail!("Bad status code retreiving Cargo.toml");
        }
    }
}

fn deps_status_from_cargo(owner: &str, name: &str, cargo: String, deps_type: &str) -> errors::Result<Status> {

    let root = cargo.as_str()
        .parse::<toml::Value>()
        .chain_err(|| "Unable to parse manifest")?;

    let dependencies = match root.get(deps_type)
        .and_then(|val| val.as_table()) {
        Some(dependencies) => dependencies,
        None => return Ok(Status::UpToDate),
    };

    // TODO:
    // 1- Download the Cargo.toml of the project into /tmp/owner/name/Cargo.toml
    // 2- Create a dummy /tmp/owner/name/src/lib.rs (avoid `cargo update` complaint)
    let tmp_dir = format!("/tmp/{}/{}", owner, name);
    let tmp_manifest = format!("{}/Cargo.toml", tmp_dir);
    let tmp_lockfile = format!("{}/Cargo.lock", tmp_dir);
    let tmp_src_dir = format!("{}/src", tmp_dir);
    let tmp_src_lib = format!("{}/lib.rs", tmp_src_dir);

    fs::create_dir_all(tmp_src_dir.as_str())
        .and_then(|_| File::create(tmp_manifest.as_str()))
        .and_then(|mut file| file.write_all(cargo.as_bytes()))
        .and_then(|_| File::create(tmp_src_lib.as_str()))
        .chain_err(|| "Unable to create tmp file structure")?;

    // 3- `cargo update --manifest-path /tmp/owner/name/Cargo.toml`
    process::Command::new("cargo")
        .arg("update")
        .arg("--manifest-path")
        .arg(tmp_manifest.as_str())
        .output()
        .chain_err(|| "Unable to exec cargo update")?;

    // 4- Parse the /tmp/owner/name/Cargo.lock generated
    let mut buffer = String::new();
    File::open(tmp_lockfile)
        .and_then(|mut f| f.read_to_string(&mut buffer))
        .chain_err(|| "Unable to read Cargo.lock")?;

    let tmp_root_lockfile = buffer.as_str()
        .parse::<toml::Value>()
        .chain_err(|| "Unable to parse Cargo.lock")?;

    let tmp_root_table = match tmp_root_lockfile.get("root") {
        Some(root) => root,
        None => bail!("Unable to find root in lockfile"),
    };

    let updated_raw_deps = match tmp_root_table.get("dependencies") {
        Some(&toml::Value::Array(ref raw_deps)) => raw_deps,
        Some(_) => unreachable!(),
        None => bail!("Unable to find dependencies in lockfile"),
    };

    let mut updated_deps = HashMap::new();
    for updated_raw_dep in updated_raw_deps {
        let raw_dep_vec: Vec<_> = updated_raw_dep.as_str().unwrap_or("").split(' ').collect();
        if raw_dep_vec.len() < 2 {
            bail!("Invalid dependency found");
        }
        updated_deps.insert(raw_dep_vec[0], raw_dep_vec[1]);
    }

    println!("{:?}", updated_deps);

    // 5- Compare each deps with semver
    let status = dependencies.iter().fold(Status::UpToDate, |oldest, (dep, version_value)| {
        let updated_version = match updated_deps.get::<str>(&dep.to_string()) {
            Some(updated_version) => updated_version,
            None => unreachable!(),
        };
        let version = match version_value.as_str() {
            Some(version) => version,
            None => return oldest,
        };
        println!("{:?}", dep);
        println!("{:?}", version);

        if Version::parse(updated_version) > Version::parse(version) {
            Status::OutOfDate
        } else if Status::OutOfDate == oldest {
            Status::OutOfDate
        } else {
            Status::UpToDate
        }
    });

    Ok(status)
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}
<commit_msg>Remove debug print<commit_after>#![feature(plugin)]
#![plugin(rocket_codegen)]
#[macro_use]
extern crate error_chain;
extern crate reqwest;
extern crate rocket;
extern crate toml;
extern crate semver;

use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::process;

use rocket::request::FromParam;
use semver::Version;

mod errors {
    // Create the Error, ErrorKind, ResultExt, and Result types
    error_chain! { }
}

use errors::ResultExt;

#[derive(Eq, PartialEq, PartialOrd, Ord)]
enum Status {
    OutOfDate,
    UpToDate,
    Unknown,
}

impl Display for Status {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match *self {
            Status::Unknown => write!(f, "unknown"),
            Status::OutOfDate => write!(f, "outofdate"),
            Status::UpToDate => write!(f, "uptodate"),
        }
    }
}

struct MyParam<'r> {
    deps_type: &'r str,
    ext: &'r str,
}

impl<'r> FromParam<'r> for MyParam<'r> {
    type Error = &'r str;

    fn from_param(param: &'r str) -> Result<MyParam<'r>, &'r str> {
        let (status_type, ext) = match param.find('.') {
            Some(i) if i > 0 => (&param[..i], &param[(i + 1)..]),
            _ => return Err(param),
        };

        Ok(MyParam {
            deps_type: match status_type {
                "dev-status" => "dev-dependencies",
                "status" => "dependencies",
                _ => return Err(param),
            },
            ext: match ext {
                "png" => "png",
                "svg" => "svg",
                _ => return Err(param),
            },
        })
    }
}

#[get("/<owner>/<name>/<params>")]
fn index(owner: &str, name: &str, params: MyParam) -> io::Result<File> {
    // TODO: HEADER 'Cache-Control': 'no-cache, no-store, must-revalidate',
    // TODO: HEADER 'Expires': new Date().toUTCString()
    let status = match get_deps_status(owner, name, params.deps_type) {
        Ok(status) => status,
        Err(ref e) => {
            println!("error: {}", e);

            for e in e.iter().skip(1) {
                println!("caused by: {}", e);
            }

            // The backtrace is not always generated. Try to run this example
            // with `RUST_BACKTRACE=1`.
            if let Some(backtrace) = e.backtrace() {
                println!("backtrace: {:?}", backtrace);
            }
            Status::Unknown
        }
    };
    File::open(format!("public/img/status/{}.{}", status, params.ext))
}

fn get_deps_status(owner: &str, name: &str, deps_type: &str) -> errors::Result<Status> {
    let cargo_url = format!("https://raw.githubusercontent.com/{}/{}/master/Cargo.toml",
                            owner,
                            name);

    let mut resp = reqwest::get(cargo_url.as_str())
        .chain_err(|| "Unable to download Cargo.toml")?;

    let mut buffer = String::new();
    match resp.status() {
        &reqwest::StatusCode::Ok => {
            resp.read_to_string(&mut buffer)
                .chain_err(|| "Unable to read Cargo.toml body")?;
            deps_status_from_cargo(owner, name, buffer, deps_type)
                .chain_err(|| "Unable verify status")
        },
        _ => {
            bail!("Bad status code retreiving Cargo.toml");
        }
    }
}

fn deps_status_from_cargo(owner: &str, name: &str, cargo: String, deps_type: &str) -> errors::Result<Status> {

    let root = cargo.as_str()
        .parse::<toml::Value>()
        .chain_err(|| "Unable to parse manifest")?;

    let dependencies = match root.get(deps_type)
        .and_then(|val| val.as_table()) {
        Some(dependencies) => dependencies,
        None => return Ok(Status::UpToDate),
    };

    // TODO:
    // 1- Download the Cargo.toml of the project into /tmp/owner/name/Cargo.toml
    // 2- Create a dummy /tmp/owner/name/src/lib.rs (avoid `cargo update` complaint)
    let tmp_dir = format!("/tmp/{}/{}", owner, name);
    let tmp_manifest = format!("{}/Cargo.toml", tmp_dir);
    let tmp_lockfile = format!("{}/Cargo.lock", tmp_dir);
    let tmp_src_dir = format!("{}/src", tmp_dir);
    let tmp_src_lib = format!("{}/lib.rs", tmp_src_dir);

    fs::create_dir_all(tmp_src_dir.as_str())
        .and_then(|_| File::create(tmp_manifest.as_str()))
        .and_then(|mut file| file.write_all(cargo.as_bytes()))
        .and_then(|_| File::create(tmp_src_lib.as_str()))
        .chain_err(|| "Unable to create tmp file structure")?;

    // 3- `cargo update --manifest-path /tmp/owner/name/Cargo.toml`
    process::Command::new("cargo")
        .arg("update")
        .arg("--manifest-path")
        .arg(tmp_manifest.as_str())
        .output()
        .chain_err(|| "Unable to exec cargo update")?;

    // 4- Parse the /tmp/owner/name/Cargo.lock generated
    let mut buffer = String::new();
    File::open(tmp_lockfile)
        .and_then(|mut f| f.read_to_string(&mut buffer))
        .chain_err(|| "Unable to read Cargo.lock")?;

    let tmp_root_lockfile = buffer.as_str()
        .parse::<toml::Value>()
        .chain_err(|| "Unable to parse Cargo.lock")?;

    let tmp_root_table = match tmp_root_lockfile.get("root") {
        Some(root) => root,
        None => bail!("Unable to find root in lockfile"),
    };

    let updated_raw_deps = match tmp_root_table.get("dependencies") {
        Some(&toml::Value::Array(ref raw_deps)) => raw_deps,
        Some(_) => unreachable!(),
        None => bail!("Unable to find dependencies in lockfile"),
    };

    let mut updated_deps = HashMap::new();
    for updated_raw_dep in updated_raw_deps {
        let raw_dep_vec: Vec<_> = updated_raw_dep.as_str().unwrap_or("").split(' ').collect();
        if raw_dep_vec.len() < 2 {
            bail!("Invalid dependency found");
        }
        updated_deps.insert(raw_dep_vec[0], raw_dep_vec[1]);
    }

    // 5- Compare each deps with semver
    let status = dependencies.iter().fold(Status::UpToDate, |oldest, (dep, version_value)| {
        let updated_version = match updated_deps.get::<str>(&dep.to_string()) {
            Some(updated_version) => updated_version,
            None => unreachable!(),
        };
        let version = match version_value.as_str() {
            Some(version) => version,
            None => return oldest,
        };

        if Version::parse(updated_version) > Version::parse(version) {
            Status::OutOfDate
        } else if Status::OutOfDate == oldest {
            Status::OutOfDate
        } else {
            Status::UpToDate
        }
    });

    Ok(status)
}

fn main() {
    rocket::ignite().mount("/", routes![index]).launch();
}
<|endoftext|>