<commit_before>// NOTE need to store the drawable in lua, because it's a reference to a drawable a lua object

use std::default::Default;
use std::fmt::{self, Display, Formatter};
use std::rc::Rc;
use rustwlc::{Geometry, Point, Size};
use rlua::{self, Table, Lua, UserData, ToLua, Value};
use rlua::prelude::LuaInteger;
use super::drawable::Drawable;
use super::property::Property;

use super::class::{self, Class, ClassBuilder};
use super::object::{Object, Objectable, ObjectBuilder};

#[derive(Clone, Debug)]
pub struct DrawinState {
    // Note that the drawable is stored in Lua.
    // TODO WINDOW_OBJECT_HEADER??
    ontop: bool,
    visible: bool,
    cursor: String,
    geometry: Geometry,
    geometry_dirty: bool
}

#[derive(Clone, Debug)]
pub struct Drawin<'lua>(Table<'lua>);

impl UserData for DrawinState {}

impl Default for DrawinState {
    fn default() -> Self {
        DrawinState {
            ontop: false,
            visible: false,
            cursor: String::default(),
            geometry: Geometry::zero(),
            geometry_dirty: false
        }
    }
}

impl Display for DrawinState {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Drawin: {:p}", self)
    }
}

impl <'lua> Drawin<'lua> {
    fn new(lua: &Lua) -> rlua::Result<Object> {
        let class = class::class_setup(lua, "drawin")?;
        Ok(object_setup(lua, Drawin::allocate(lua, class)?)?.build())
    }

    fn update_drawing(&mut self) -> rlua::Result<()> {
        let state = self.state()?;
        let table = &self.0;
        let mut drawable = Drawable::cast(table.get::<_, Table>("drawable")?.into())?;
        drawable.set_geometry(state.geometry)?;
        table.raw_set::<_, Table>("drawable", drawable.get_table())?;
        Ok(())
    }

    fn get_visible(&mut self) -> rlua::Result<bool> {
        let drawin = self.state()?;
        Ok(drawin.visible)
    }

    fn set_visible(&mut self, val: bool) -> rlua::Result<()> {
        let mut drawin = self.state()?;
        drawin.visible = val;
        self.map()?;
        self.set_state(drawin)
    }

    fn map(&mut self) -> rlua::Result<()> {
        // TODO other things
        self.update_drawing()
    }

    fn get_geometry(&self) -> rlua::Result<Geometry> {
        Ok(self.state()?.geometry)
    }

    fn resize(&mut self, geometry: Geometry) -> rlua::Result<()> {
        let mut state = self.state()?;
        let old_geometry = state.geometry;
        state.geometry = geometry;
        if state.geometry.size.w <= 0 {
            state.geometry.size.w = old_geometry.size.w;
        }
        if state.geometry.size.h <= 0 {
            state.geometry.size.h = old_geometry.size.h
        }
        state.geometry_dirty = true;
        self.update_drawing()?;
        // TODO emit signals
        // TODO update screen workareas like in awesome? Might not be necessary
        self.set_state(state)
    }
}

impl <'lua> ToLua<'lua> for Drawin<'lua> {
    fn to_lua(self, lua: &'lua Lua) -> rlua::Result<Value<'lua>> {
        self.0.to_lua(lua)
    }
}

impl_objectable!(Drawin, DrawinState);

pub fn init(lua: &Lua) -> rlua::Result<Class> {
    property_setup(lua, method_setup(lua, Class::builder(lua, Some(Rc::new(Drawin::new)), None, None)?)?)?
        .save_class("drawin")?
        .build()
}

fn method_setup<'lua>(lua: &'lua Lua, builder: ClassBuilder<'lua>) -> rlua::Result<ClassBuilder<'lua>> {
    // TODO Do properly
    use super::dummy;
    builder.method("connect_signal".into(), lua.create_function(dummy))?
            // TODO This should be adding properties, e.g like luaA_class_new
           .method("__call".into(), lua.create_function(|lua, _: Value| Drawin::new(lua)))
}

fn property_setup<'lua>(lua: &'lua Lua, builder: ClassBuilder<'lua>) -> rlua::Result<ClassBuilder<'lua>> {
    builder
        .property(Property::new("x".into(),
                                Some(lua.create_function(set_x)),
                                Some(lua.create_function(get_x)),
                                Some(lua.create_function(set_x))))?
        .property(Property::new("y".into(),
                                Some(lua.create_function(set_y)),
                                Some(lua.create_function(get_y)),
                                Some(lua.create_function(set_y))))?
        .property(Property::new("width".into(),
                                Some(lua.create_function(set_width)),
                                Some(lua.create_function(get_width)),
                                Some(lua.create_function(set_width))))?
        .property(Property::new("height".into(),
                                Some(lua.create_function(set_height)),
                                Some(lua.create_function(get_height)),
                                Some(lua.create_function(set_height))))?
        .property(Property::new("visible".into(),
                                Some(lua.create_function(set_visible)),
                                Some(lua.create_function(get_visible)),
                                Some(lua.create_function(set_visible))))
}

fn object_setup<'lua>(lua: &'lua Lua, builder: ObjectBuilder<'lua>) -> rlua::Result<ObjectBuilder<'lua>> {
    // TODO Do properly
    let table = lua.create_table();
    let drawable_table = Drawable::new(lua)?.to_lua(lua)?;
    table.set("drawable", drawable_table)?;
    table.set("geometry", lua.create_function(drawin_geometry))?;
    table.set("struts", lua.create_function(drawin_struts))?;
    builder.add_to_meta(table)
}

fn set_visible<'lua>(_: &'lua Lua, (table, visible): (Table<'lua>, bool))
                     -> rlua::Result<()> {
    let mut drawin = Drawin::cast(table.into())?;
    drawin.set_visible(visible)
    // TODO signal
}

fn get_visible<'lua>(_: &'lua Lua, table: Table<'lua>) -> rlua::Result<bool> {
    let mut drawin = Drawin::cast(table.into())?;
    drawin.get_visible()
    // TODO signal
}

fn drawin_geometry<'lua>(lua: &'lua Lua, (drawin, geometry): (Table<'lua>, Value<'lua>)) -> rlua::Result<Table<'lua>> {
    let mut drawin = Drawin::cast(drawin.into())?;
    match geometry {
        rlua::Value::Table(geometry) => {
            let w = geometry.get::<_, i32>("width")?;
            let h = geometry.get::<_, i32>("height")?;
            let x = geometry.get::<_, i32>("x")?;
            let y = geometry.get::<_, i32>("y")?;
            if x > 0 && y > 0 {
                let geo = Geometry {
                    origin: Point { x, y },
                    size: Size { w: w as u32, h: h as u32 }
                };
                drawin.resize(geo)?;
            }
        },
        rlua::Value::Nil => {},
        _ => return Err(rlua::Error::RuntimeError("Invalid argument".to_owned()))
    }
    let new_geo = drawin.get_geometry()?;
    let Size { w, h } = new_geo.size;
    let Point { x, y } = new_geo.origin;
    let res = lua.create_table();
    res.set("x", x)?;
    res.set("y", y)?;
    res.set("height", h)?;
    res.set("width", w)?;
    Ok(res)
}

fn get_x<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Point { x, .. } = drawin.get_geometry()?.origin;
    Ok(x as LuaInteger)
}

fn set_x<'lua>(_: &'lua Lua, (drawin, x): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    geo.origin.x = x as i32;
    drawin.resize(geo)?;
    Ok(())
}

fn get_y<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Point { y, .. } = drawin.get_geometry()?.origin;
    Ok(y as LuaInteger)
}

fn set_y<'lua>(_: &'lua Lua, (drawin, y): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    geo.origin.y = y as i32;
    drawin.resize(geo)?;
    Ok(())
}

fn get_width<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Size { w, .. } = drawin.get_geometry()?.size;
    Ok(w as LuaInteger)
}

fn set_width<'lua>(_: &'lua Lua, (drawin, width): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    if width > 0 {
        geo.size.w = width as u32;
        drawin.resize(geo)?;
    }
    Ok(())
}

fn get_height<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Size { h, .. } = drawin.get_geometry()?.size;
    Ok(h as LuaInteger)
}

fn set_height<'lua>(_: &'lua Lua, (drawin, height): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    if height > 0 {
        geo.size.h = height as u32;
        drawin.resize(geo)?;
    }
    Ok(())
}

fn drawin_struts<'lua>(lua: &'lua Lua, _drawin: Table<'lua>) -> rlua::Result<Table<'lua>> {
    // TODO: Implement this properly. Struts means this drawin reserves some space on the screen
    // that it is visible on, shrinking the workarea in the specified directions.
    let res = lua.create_table();
    res.set("left", 0)?;
    res.set("right", 0)?;
    res.set("top", 0)?;
    res.set("bottom", 0)?;
    Ok(res)
}
<commit_msg>Small improvement to drawin_geometry<commit_after>// NOTE need to store the drawable in lua, because it's a reference to a drawable a lua object

use std::default::Default;
use std::fmt::{self, Display, Formatter};
use std::rc::Rc;
use rustwlc::{Geometry, Point, Size};
use rlua::{self, Table, Lua, UserData, ToLua, Value};
use rlua::prelude::LuaInteger;
use super::drawable::Drawable;
use super::property::Property;

use super::class::{self, Class, ClassBuilder};
use super::object::{Object, Objectable, ObjectBuilder};

#[derive(Clone, Debug)]
pub struct DrawinState {
    // Note that the drawable is stored in Lua.
    // TODO WINDOW_OBJECT_HEADER??
    ontop: bool,
    visible: bool,
    cursor: String,
    geometry: Geometry,
    geometry_dirty: bool
}

#[derive(Clone, Debug)]
pub struct Drawin<'lua>(Table<'lua>);

impl UserData for DrawinState {}

impl Default for DrawinState {
    fn default() -> Self {
        DrawinState {
            ontop: false,
            visible: false,
            cursor: String::default(),
            geometry: Geometry::zero(),
            geometry_dirty: false
        }
    }
}

impl Display for DrawinState {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "Drawin: {:p}", self)
    }
}

impl <'lua> Drawin<'lua> {
    fn new(lua: &Lua) -> rlua::Result<Object> {
        let class = class::class_setup(lua, "drawin")?;
        Ok(object_setup(lua, Drawin::allocate(lua, class)?)?.build())
    }

    fn update_drawing(&mut self) -> rlua::Result<()> {
        let state = self.state()?;
        let table = &self.0;
        let mut drawable = Drawable::cast(table.get::<_, Table>("drawable")?.into())?;
        drawable.set_geometry(state.geometry)?;
        table.raw_set::<_, Table>("drawable", drawable.get_table())?;
        Ok(())
    }

    fn get_visible(&mut self) -> rlua::Result<bool> {
        let drawin = self.state()?;
        Ok(drawin.visible)
    }

    fn set_visible(&mut self, val: bool) -> rlua::Result<()> {
        let mut drawin = self.state()?;
        drawin.visible = val;
        self.map()?;
        self.set_state(drawin)
    }

    fn map(&mut self) -> rlua::Result<()> {
        // TODO other things
        self.update_drawing()
    }

    fn get_geometry(&self) -> rlua::Result<Geometry> {
        Ok(self.state()?.geometry)
    }

    fn resize(&mut self, geometry: Geometry) -> rlua::Result<()> {
        let mut state = self.state()?;
        let old_geometry = state.geometry;
        state.geometry = geometry;
        if state.geometry.size.w <= 0 {
            state.geometry.size.w = old_geometry.size.w;
        }
        if state.geometry.size.h <= 0 {
            state.geometry.size.h = old_geometry.size.h
        }
        state.geometry_dirty = true;
        self.update_drawing()?;
        // TODO emit signals
        // TODO update screen workareas like in awesome? Might not be necessary
        self.set_state(state)
    }
}

impl <'lua> ToLua<'lua> for Drawin<'lua> {
    fn to_lua(self, lua: &'lua Lua) -> rlua::Result<Value<'lua>> {
        self.0.to_lua(lua)
    }
}

impl_objectable!(Drawin, DrawinState);

pub fn init(lua: &Lua) -> rlua::Result<Class> {
    property_setup(lua, method_setup(lua, Class::builder(lua, Some(Rc::new(Drawin::new)), None, None)?)?)?
        .save_class("drawin")?
        .build()
}

fn method_setup<'lua>(lua: &'lua Lua, builder: ClassBuilder<'lua>) -> rlua::Result<ClassBuilder<'lua>> {
    // TODO Do properly
    use super::dummy;
    builder.method("connect_signal".into(), lua.create_function(dummy))?
            // TODO This should be adding properties, e.g like luaA_class_new
           .method("__call".into(), lua.create_function(|lua, _: Value| Drawin::new(lua)))
}

fn property_setup<'lua>(lua: &'lua Lua, builder: ClassBuilder<'lua>) -> rlua::Result<ClassBuilder<'lua>> {
    builder
        .property(Property::new("x".into(),
                                Some(lua.create_function(set_x)),
                                Some(lua.create_function(get_x)),
                                Some(lua.create_function(set_x))))?
        .property(Property::new("y".into(),
                                Some(lua.create_function(set_y)),
                                Some(lua.create_function(get_y)),
                                Some(lua.create_function(set_y))))?
        .property(Property::new("width".into(),
                                Some(lua.create_function(set_width)),
                                Some(lua.create_function(get_width)),
                                Some(lua.create_function(set_width))))?
        .property(Property::new("height".into(),
                                Some(lua.create_function(set_height)),
                                Some(lua.create_function(get_height)),
                                Some(lua.create_function(set_height))))?
        .property(Property::new("visible".into(),
                                Some(lua.create_function(set_visible)),
                                Some(lua.create_function(get_visible)),
                                Some(lua.create_function(set_visible))))
}

fn object_setup<'lua>(lua: &'lua Lua, builder: ObjectBuilder<'lua>) -> rlua::Result<ObjectBuilder<'lua>> {
    // TODO Do properly
    let table = lua.create_table();
    let drawable_table = Drawable::new(lua)?.to_lua(lua)?;
    table.set("drawable", drawable_table)?;
    table.set("geometry", lua.create_function(drawin_geometry))?;
    table.set("struts", lua.create_function(drawin_struts))?;
    builder.add_to_meta(table)
}

fn set_visible<'lua>(_: &'lua Lua, (table, visible): (Table<'lua>, bool))
                     -> rlua::Result<()> {
    let mut drawin = Drawin::cast(table.into())?;
    drawin.set_visible(visible)
    // TODO signal
}

fn get_visible<'lua>(_: &'lua Lua, table: Table<'lua>) -> rlua::Result<bool> {
    let mut drawin = Drawin::cast(table.into())?;
    drawin.get_visible()
    // TODO signal
}

fn drawin_geometry<'lua>(lua: &'lua Lua, (drawin, geometry): (Table<'lua>, Option<Table<'lua>>)) -> rlua::Result<Table<'lua>> {
    let mut drawin = Drawin::cast(drawin.into())?;
    if let Some(geometry) = geometry {
        let w = geometry.get::<_, i32>("width")?;
        let h = geometry.get::<_, i32>("height")?;
        let x = geometry.get::<_, i32>("x")?;
        let y = geometry.get::<_, i32>("y")?;
        if x > 0 && y > 0 {
            let geo = Geometry {
                origin: Point { x, y },
                size: Size { w: w as u32, h: h as u32 }
            };
            drawin.resize(geo)?;
        }
    }
    let new_geo = drawin.get_geometry()?;
    let Size { w, h } = new_geo.size;
    let Point { x, y } = new_geo.origin;
    let res = lua.create_table();
    res.set("x", x)?;
    res.set("y", y)?;
    res.set("height", h)?;
    res.set("width", w)?;
    Ok(res)
}

fn get_x<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Point { x, .. } = drawin.get_geometry()?.origin;
    Ok(x as LuaInteger)
}

fn set_x<'lua>(_: &'lua Lua, (drawin, x): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    geo.origin.x = x as i32;
    drawin.resize(geo)?;
    Ok(())
}

fn get_y<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Point { y, .. } = drawin.get_geometry()?.origin;
    Ok(y as LuaInteger)
}

fn set_y<'lua>(_: &'lua Lua, (drawin, y): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    geo.origin.y = y as i32;
    drawin.resize(geo)?;
    Ok(())
}

fn get_width<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Size { w, .. } = drawin.get_geometry()?.size;
    Ok(w as LuaInteger)
}

fn set_width<'lua>(_: &'lua Lua, (drawin, width): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    if width > 0 {
        geo.size.w = width as u32;
        drawin.resize(geo)?;
    }
    Ok(())
}

fn get_height<'lua>(_: &'lua Lua, drawin: Table<'lua>) -> rlua::Result<LuaInteger> {
    let drawin = Drawin::cast(drawin.into())?;
    let Size { h, .. } = drawin.get_geometry()?.size;
    Ok(h as LuaInteger)
}

fn set_height<'lua>(_: &'lua Lua, (drawin, height): (Table<'lua>, LuaInteger)) -> rlua::Result<()> {
    let mut drawin = Drawin::cast(drawin.into())?;
    let mut geo = drawin.get_geometry()?;
    if height > 0 {
        geo.size.h = height as u32;
        drawin.resize(geo)?;
    }
    Ok(())
}

fn drawin_struts<'lua>(lua: &'lua Lua, _drawin: Table<'lua>) -> rlua::Result<Table<'lua>> {
    // TODO: Implement this properly. Struts means this drawin reserves some space on the screen
    // that it is visible on, shrinking the workarea in the specified directions.
    let res = lua.create_table();
    res.set("left", 0)?;
    res.set("right", 0)?;
    res.set("top", 0)?;
    res.set("bottom", 0)?;
    Ok(res)
}
<|endoftext|>


<commit_before>use name::*;
use art::*;
use std::hash;
use std::hash::Hash;

//
// Cmd C ::= Skip
//        | C ; C
//        | While E C
//        | x := E
//
//     C ::= .. | Amb C C
//
// Exp E ::=
//        | x
//        | N
//        | E + E
//

/// Nominal, artful commands for the while language
#[derive(Show,Hash,PartialEq,Eq)]
pub enum Cmd<'x> {
    Skip,
    Seq(CmdB<'x>, CmdB<'x>),
    While(ExpB<'x>, CmdB<'x>),
    Assign(Var<'x>, ExpB<'x>),
    // ----
    Name(Name, CmdB<'x>),
    Art(Art<'x,CmdB<'x>>),
}
pub type CmdB<'x> = Box<Cmd<'x>>;

#[derive(Show,Hash,PartialEq,Eq)]
pub enum Exp<'x> {
    Var(Var<'x>),
    Plus(ExpB<'x>,ExpB<'x>),
    Num(int),
}
pub type ExpB<'x> = Box<Exp<'x>>;

pub type Var<'x> = String;

#[test]
pub fn doit () {
    println!("while lang!");
}
<commit_msg>more challenge problem; sketching Heap trait<commit_after>use name::*;
use art::*;
use std::hash;
use std::hash::Hash;
use std::result::Result;

//
// Cmd C ::= Skip
//        | C ; C
//        | While E C
//        | x := E
//
//     C ::= .. | Amb C C
//
// Exp E ::=
//        | x
//        | N
//        | E + E
//

/// Nominal, artful commands for the while language
#[derive(Show,Hash,PartialEq,Eq)]
pub enum Cmd<'x> {
    Skip,
    Seq(CmdB<'x>, CmdB<'x>),
    While(ExpB<'x>, CmdB<'x>),
    Assign(Var<'x>, ExpB<'x>),
    // ----
    Name(Name, CmdB<'x>),
    Art(Art<'x,CmdB<'x>>),
}
pub type CmdB<'x> = Box<Cmd<'x>>;

/// Expressions for the while language
#[derive(Show,Hash,PartialEq,Eq)]
pub enum Exp<'x> {
    Var(Var<'x>),
    Plus(ExpB<'x>,ExpB<'x>),
    Num(int),
}
pub type ExpB<'x> = Box<Exp<'x>>;

/// Variables for the while language
pub type Var<'x> = String;

pub trait Heap<T> {
    fn empty () -> Self ;
    fn update (Self, Hash, T) -> Self ;
    fn select (Self, Hash) -> Result<(Self,T), Self> ;
}

pub enum Err {
    VarNotDef(String)
}

pub fn eval_exp<'x,T:'x> (exp:Exp<'x>, heap:Box<Heap<int>>)
                          -> Result<(Box<Heap<int>>,int), (Box<Heap<int>>,Err)> {
    panic!("not impl")
}

#[test]
pub fn doit () {
    println!("while lang!");
}
<|endoftext|>


<commit_before>//! Utilities for running and testing shell commands.

use std::ffi::OsStr;
use std::io;
use std::process;

/// A stripped down interface based on `std::process::Command`.  We use
/// this so we can mock out shell commands during tests.
pub trait Command {
    /// Add an arugment to our command.
    fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self;

    /// Add several arguments to our command.
    fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
        for arg in args {
            self.arg(arg);
        }
        self
    }

    /// Run our command.
    fn status(&mut self) -> io::Result<process::ExitStatus>;
}

/// A factory that produces objects conforming to our `Command` wrapper
/// trait.  During tests, we'll use this to mock out the underlying system
/// and record all commands executed.
///
/// There's some unfortunate Rust lifetime trickiness with the `'a`
/// parameter, which we use to indicate the idea that the `Command`
/// returned by `build` is allowed to hold a mutable reference pointing
/// back to the `CommandRunner`.  Usually we don't need to specify
/// lifetimes because Rust can do all the magic in the background, but here
/// we actually need to expose them.
pub trait CommandRunner<'a> {
    /// The type of the commands we build.  Must implement our custom
    /// `Command` trait and may contain references of type `'a`.
    type Command: Command + 'a;

    /// Build a new command.
    fn build<S: AsRef<OsStr>>(&'a mut self, program: S) -> Self::Command;
}

/// Support for running operating system commands.
pub struct OsCommandRunner;

impl<'a> CommandRunner<'a> for OsCommandRunner {
    type Command = process::Command;

    fn build<S: AsRef<OsStr>>(&'a mut self, program: S) -> Self::Command {
        process::Command::new(program)
    }
}

impl Command for process::Command {
    fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
        process::Command::arg(self, arg)
    }

    fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
        process::Command::args(self, args)
    }

    fn status(&mut self) -> io::Result<process::ExitStatus> {
        process::Command::status(self)
    }
}

#[test]
fn os_command_runner_runs_commands() {
    let mut runner = OsCommandRunner;
    assert!(runner.build("true").status().unwrap().success());
    assert!(!runner.build("false").status().unwrap().success());
}

/// Support for running commands in test mode.
pub struct TestCommandRunner {
    cmds: Vec<Vec<String>>
}

impl TestCommandRunner {
    /// Create a new `TestCommandRunner`.
    pub fn new() -> TestCommandRunner {
        TestCommandRunner { cmds: vec!() }
    }

    /// Access the list of commands run.
    pub fn cmds(&self) -> &[Vec<String>] {
        &self.cmds
    }
}

impl<'a> CommandRunner<'a> for TestCommandRunner {
    type Command = TestCommand<'a>;

    fn build<S: AsRef<OsStr>>(&'a mut self, program: S) -> Self::Command {
        self.cmds.push(vec!(program.as_ref().to_string_lossy().into_owned()));
        TestCommand { runner: self }
    }
}

/// A fake command that gets logged to a `TestCommandRunner` instead of
/// actually getting run.
pub struct TestCommand<'a> {
    runner: &'a mut TestCommandRunner,
}

impl<'a> Command for TestCommand<'a> {
    /// Record the command arguments in our `TestCommandRunner`.
    fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
        self.runner.cmds.last_mut().unwrap()
            .push(arg.as_ref().to_string_lossy().into_owned());
        self
    }

    /// Always returns success.
    fn status(&mut self) -> io::Result<process::ExitStatus> {
        // There's no portable way to build an `ExitStatus` in portable
        // Rust without actually running a command, so just choose an
        // inoffensive one with the result we want.
        process::Command::new("true").status()
    }
}

#[test]
pub fn test_command_runner_logs_commands() {
    let mut runner = TestCommandRunner::new();
    let exit_code = runner.build("git")
        .args(&["clone", "https://github.com/torvalds/linux"])
        .status().unwrap();
    assert!(exit_code.success());
    assert_eq!(runner.cmds(),
               &[&["git", "clone", "https://github.com/torvalds/linux"]]);
}
<commit_msg>CommandRunner: Stop abusing the borrow checker & add assert_run! macro<commit_after>//! Utilities for running and testing shell commands.

use std::cell::{Ref, RefCell};
use std::ffi::{OsStr, OsString};
use std::io;
use std::process;
use std::rc::Rc;

/// A factory that produces objects conforming to our `Command` wrapper
/// trait.  During tests, we'll use this to mock out the underlying system
/// and record all commands executed.
pub trait CommandRunner {
    /// The type of the commands we build.  Must implement our custom
    /// `Command` trait.
    type Command: Command;

    /// Build a new command.
    fn build<S: AsRef<OsStr>>(&self, program: S) -> Self::Command;
}

/// A stripped down interface based on `std::process::Command`.  We use
/// this so we can mock out shell commands during tests.
pub trait Command {
    /// Add an arugment to our command.
    fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self;

    /// Add several arguments to our command.
    fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
        for arg in args {
            self.arg(arg);
        }
        self
    }

    /// Run our command.
    fn status(&mut self) -> io::Result<process::ExitStatus>;
}

/// Support for running operating system commands.
pub struct OsCommandRunner;

impl CommandRunner for OsCommandRunner {
    type Command = process::Command;

    fn build<S: AsRef<OsStr>>(&self, program: S) -> Self::Command {
        process::Command::new(program)
    }
}

impl Command for process::Command {
    fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
        process::Command::arg(self, arg)
    }

    fn args<S: AsRef<OsStr>>(&mut self, args: &[S]) -> &mut Self {
        process::Command::args(self, args)
    }

    fn status(&mut self) -> io::Result<process::ExitStatus> {
        process::Command::status(self)
    }
}

#[test]
fn os_command_runner_runs_commands() {
    let runner = OsCommandRunner;
    assert!(runner.build("true").status().unwrap().success());
    assert!(!runner.build("false").status().unwrap().success());
}

/// Support for running commands in test mode.
pub struct TestCommandRunner {
    /// The commands that have been executed.  Because we want to avoid
    /// borrow checker hell, we use `Rc<RefCell<_>>` to implement a shared,
    /// mutable value.
    cmds: Rc<RefCell<Vec<Vec<OsString>>>>,
}

impl TestCommandRunner {
    /// Create a new `TestCommandRunner`.
    pub fn new() -> TestCommandRunner {
        TestCommandRunner { cmds: Rc::new(RefCell::new(vec!())) }
    }

    /// Access the list of commands run.
    pub fn cmds(&self) -> Ref<Vec<Vec<OsString>>> {
        self.cmds.borrow()
    }
}

impl CommandRunner for TestCommandRunner {
    type Command = TestCommand;

    fn build<S: AsRef<OsStr>>(&self, program: S) -> Self::Command {
        TestCommand {
            cmd: vec!(program.as_ref().to_owned()),
            cmds: self.cmds.clone(),
        }
    }
}

/// A fake command that gets logged to a `TestCommandRunner` instead of
/// actually getting run.
pub struct TestCommand {
    cmd: Vec<OsString>,
    cmds: Rc<RefCell<Vec<Vec<OsString>>>>,
}

impl TestCommand {
    /// Record the execution of this command.
    fn record_execution(&self) {
        self.cmds.borrow_mut().push(self.cmd.clone());
    }
}

impl Command for TestCommand {
    /// Record the command arguments in our `TestCommandRunner`.
    fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
        self.cmd.push(arg.as_ref().to_owned());
        self
    }

    /// Always returns success.
    fn status(&mut self) -> io::Result<process::ExitStatus> {
        self.record_execution();

        // There's no portable way to build an `ExitStatus` in portable
        // Rust without actually running a command, so just choose an
        // inoffensive one with the result we want.
        process::Command::new("true").status()
    }
}

/// A macro for comparing the commands that were run with the commands we
/// hoped were run.  This is a bit trickier than you'd expect, because we
/// need to handle two complications:
///
/// 1. `TestCommandRunner::cmds` returns a `Ref<_>` type, which we
///    need to explicitly `deref()` before trying to compare, so that
///    we get a real `&`-style reference.
/// 2. We want to allow this macro to be passed a mix of `&'static str`
///    and `Path` objects so that it's easier to use, and internally
///    convert everything to an `OsString`.  So we need an internal `coerce`
///    helper that converts from `AsRef<OsStr>` (a very general trait
///    interface) to an actual `OsString`.
macro_rules! assert_ran {
    ($runner:expr, { $( [ $($arg:expr),+ ] ),* }) => {
        use std::ops::Deref;
        fn coerce<S: AsRef<$crate::std::ffi::OsStr>>(s: S) ->
            $crate::std::ffi::OsString
        {
            s.as_ref().to_owned()
        }
        let expected = vec!( $( vec!( $( coerce($arg) ),+ ) ),* );
        assert_eq!($runner.cmds().deref(), &expected);
    }
}

#[test]
pub fn test_command_runner_logs_commands() {
    let runner = TestCommandRunner::new();

    let exit_code = runner.build("git")
        .args(&["clone", "https://github.com/torvalds/linux"])
        .status().unwrap();
    assert!(exit_code.success());

    runner.build("echo").arg("a").arg("b").status().unwrap();

    assert_ran!(runner, {
        ["git", "clone", "https://github.com/torvalds/linux"],
        ["echo", "a", "b"]
    });
}
<|endoftext|>


<commit_before>extern crate dbus;

use std;
use std::env;
use general::*;
use device::DeviceType;

/// Get a list of Network Manager connections sorted by path.
///
/// # Examples
///
/// ```no_run
/// use network_manager::connection;
/// let connections = connection::list().unwrap();
/// println!("{:?}", connections);
/// ```
pub fn list() -> Result<Vec<Connection>, String> {
    let message = dbus_message!(NM_SERVICE_MANAGER,
                                NM_SETTINGS_PATH,
                                NM_SETTINGS_INTERFACE,
                                "ListConnections");
    let response = dbus_connect!(message);
    let paths: dbus::arg::Array<dbus::Path, _> = response.get1().unwrap();
    let mut connections: Vec<_> = paths.map(|p| get_connection(p).unwrap()).collect();
    connections.sort_by(|a, b| a.cmp(b));

    Ok(connections)
}

#[test]
fn test_list_function() {
    let connections = list().unwrap();
    assert!(connections.len() > 0);
    for (index, val) in connections.iter().enumerate() {
        assert_ne!(Connection { ..Default::default() }, val.clone());
        assert_eq!(index as i32, i32::from(val));
    }
}

/// Creates a Network Manager connection.
///
/// # Examples
///
/// ```
/// let connection = network_manager::connection::create(
///     "resin_io",
///     network_manager::general::DeviceType::WiFi,
///     network_manager::general::Security::WPA2,
///     "super_secret_passphase"
///     ).unwrap();
/// println!("{:?}", connection);
/// ```
pub fn create(s: &str, dt: DeviceType, sc: Security, p: &str) -> Result<Connection, String> {
    // Create a connection
    // Get the connection
    // Return the connection

    let connection1 = Connection {
        path: "/org/freedesktop/NetworkManager/ActiveConnection/187".to_string(),
        id: "resin_io".to_string(),
        uuid: "3c8e6e8b-b895-4b07-97a5-bbc192c3b436".to_string(),
        ssid: "resin_io".to_string(),
        active_path: "test".to_string(),
        state: ConnectionState::Deactivated, /* device: "wlp4s0".to_string(),
                                              * interface: DeviceType::WiFi,
                                              * security: Security::WPA2,
                                              * state: ConnectionState::Activated, */
    };

    Ok(connection1)
}

/// Deletes a Network Manager connection.
///
/// # Examples
///
/// ```
/// let connections = network_manager::connection::list().unwrap();
/// let connection = &connections[0];
/// network_manager::connection::delete(connection).unwrap();
/// ```
pub fn delete(connection: Connection) -> Result<(), String> {
    let message = dbus_message!(NM_SERVICE_MANAGER,
                                connection.path,
                                NM_CONNECTION_INTERFACE,
                                "Delete");
    dbus_connect!(message);

    Ok(())
}

/// Enables a Network Manager connection.
///
/// # Examples
///
/// ```no_run
/// use network_manager::connection;
/// let connections = connection::list().unwrap();
/// let mut connection = connections[0].clone();
/// connection::enable(&mut connection, 10).unwrap();
/// println!("{:?}", connection.state);
/// ```
pub fn enable(connection: &mut Connection, time_out: i32) -> Result<(), String> {
    update_state(connection).expect("Unable to get connection state");
    match connection.state {
        ConnectionState::Activated => Ok(()),
        ConnectionState::Activating => wait(connection, time_out, ConnectionState::Activated),
        ConnectionState::Unknown => Err("Unable to get connection state".to_string()),
        _ => {
            let mut message = dbus_message!(NM_SERVICE_MANAGER,
                                            NM_SERVICE_PATH,
                                            NM_SERVICE_INTERFACE,
                                            "ActivateConnection");
            message.append_items(&[dbus::MessageItem::ObjectPath(connection.path
                                                                     .to_string()
                                                                     .into()),
                                   dbus::MessageItem::ObjectPath("/".into()),
                                   dbus::MessageItem::ObjectPath("/".into())]);
            dbus_connect!(message);

            wait(connection, time_out, ConnectionState::Activated)
        }
    }
}

/// Disables a Network Manager connection.
///
/// # Examples
///
/// ```no_run
/// use network_manager::connection;
/// let connections = connection::list().unwrap();
/// let mut connection = connections[0].clone();
/// connection::disable(&mut connection, 10).unwrap();
/// println!("{:?}", connection.state);
/// ```
pub fn disable(connection: &mut Connection, time_out: i32) -> Result<(), String> {
    update_state(connection).expect("Unable to get connection state");
    match connection.state {
        ConnectionState::Deactivated => Ok(()),
        ConnectionState::Deactivating => wait(connection, time_out, ConnectionState::Deactivated),
        ConnectionState::Unknown => Err("Unable to get connection state".to_string()),
        _ => {
            let mut message = dbus_message!(NM_SERVICE_MANAGER,
                                            NM_SERVICE_PATH,
                                            NM_SERVICE_INTERFACE,
                                            "DeactivateConnection");
            message.append_items(&[dbus::MessageItem::ObjectPath(connection.active_path
                                                                     .to_string()
                                                                     .into())]);
            dbus_connect!(message);

            wait(connection, time_out, ConnectionState::Deactivated)
        }
    }
}

#[test]
fn test_enable_disable_functions() {
    let connections = list().unwrap();
    let mut connection;

    // set enviorment variable $TEST_WIFI_SSID with the wifi's SSID that you want to test
    // e.g.  export TEST_WIFI_SSID="Resin.io Wifi"
    let wifiEnvVar = "TEST_WIFI_SSID";
    match env::var(wifiEnvVar) {
        Ok(ssid) => {
            connection = connections.iter()
                .filter(|c| c.ssid == ssid)
                .nth(0)
                .unwrap()
                .clone()
        }
        Err(e) => {
            panic!("couldn't retrieve enviorment variable {}: {}",
                   wifiEnvVar,
                   e)
        }
    };

    assert!(connection.state == ConnectionState::Activated ||
            connection.state == ConnectionState::Deactivated);

    match connection.state {
        ConnectionState::Activated => {
            disable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Deactivated, connection.state);

            enable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Activated, connection.state);
        }
        ConnectionState::Deactivated => {
            enable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Activated, connection.state);

            disable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Deactivated, connection.state);
        }
        _ => (),
    }
}

fn get_connection(path: dbus::Path) -> Result<Connection, String> {
    let mut connection = Connection { path: dbus_path_to_string(path), ..Default::default() };

    let message = dbus_message!(NM_SERVICE_MANAGER,
                                connection.path.clone(),
                                NM_CONNECTION_INTERFACE,
                                "GetSettings");
    let response = dbus_connect!(message);
    let dictionary: dbus::arg::Dict<&str,
                                    dbus::arg::Dict<&str, dbus::arg::Variant<dbus::arg::Iter>, _>,
                                    _> = response.get1().unwrap();

    for (_, v1) in dictionary {
        for (k2, v2) in v1 {
            match k2 {
                "id" => {
                    connection.id = v2.0
                        .clone()
                        .get::<&str>()
                        .unwrap()
                        .to_string();
                }
                "uuid" => {
                    connection.uuid = v2.0
                        .clone()
                        .get::<&str>()
                        .unwrap()
                        .to_string();
                }
                "ssid" => {
                    connection.ssid =
                        std::str::from_utf8(&v2.0
                                                 .clone()
                                                 .get::<dbus::arg::Array<u8, _>>()
                                                 .unwrap()
                                                 .collect::<Vec<u8>>())
                                .unwrap()
                                .to_string();
                }
                _ => (),
            }
        }
    }

    update_state(&mut connection).unwrap();

    Ok(connection)
}

fn update_state(connection: &mut Connection) -> Result<(), String> {
    let active_paths: Vec<String> = dbus_property!(NM_SERVICE_MANAGER,
                                                   NM_SERVICE_PATH,
                                                   NM_SERVICE_INTERFACE,
                                                   "ActiveConnections")
            .unwrap()
            .inner::<&Vec<dbus::MessageItem>>()
            .unwrap()
            .iter()
            .map(|p| dbus_path_to_string(p.inner::<&dbus::Path>().unwrap().to_owned()))
            .collect();

    let settings_paths = active_paths.iter().map(|p| {
        dbus_path_to_string(dbus_property!(NM_SERVICE_MANAGER,
                                           p,
                                           NM_ACTIVE_INTERFACE,
                                           "Connection")
                                    .unwrap()
                                    .inner::<&dbus::Path>()
                                    .unwrap()
                                    .to_owned())
    });

    connection.active_path = "".to_string();
    connection.state = ConnectionState::Deactivated;

    for (active_path, settings_path) in active_paths.iter().zip(settings_paths) {
        if connection.path == settings_path {
            connection.active_path = active_path.to_owned();

            let result = dbus_property!(NM_SERVICE_MANAGER,
                                        connection.active_path.clone(),
                                        NM_ACTIVE_INTERFACE,
                                        "State");
            if let Ok(val) = result {
                connection.state = ConnectionState::from(val.inner::<u32>().unwrap())
            }

            break;
        }
    }

    Ok(())
}

fn wait(connection: &mut Connection,
        time_out: i32,
        target_state: ConnectionState)
        -> Result<(), String> {
    if time_out == 0 {
        return Ok(());
    }

    let mut total_time = 0;
    while total_time < time_out {
        update_state(connection).unwrap();
        if connection.state == target_state {
            return Ok(());
        }
        std::thread::sleep(std::time::Duration::from_secs(1));
        total_time += 1;
    }

    Err("service timed out".to_string())
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Connection {
    pub path: String,
    pub active_path: String,
    pub id: String,
    pub uuid: String,
    pub ssid: String,
    pub state: ConnectionState, /* device: String,
                                 * device_type: DeviceType,
                                 * security: Security, */
}

impl Default for Connection {
    fn default() -> Connection {
        Connection {
            path: "".to_string(),
            active_path: "".to_string(),
            id: "".to_string(),
            uuid: "".to_string(),
            ssid: "".to_string(),
            state: ConnectionState::Unknown,
        }
    }
}

impl Ord for Connection {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        i32::from(self).cmp(&i32::from(other))
    }
}

impl PartialOrd for Connection {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<'a> From<&'a Connection> for i32 {
    fn from(val: &Connection) -> i32 {
        val.clone()
            .path
            .rsplit('/')
            .nth(0)
            .unwrap()
            .parse::<i32>()
            .unwrap()
    }
}
<commit_msg>Fixing failing doctests<commit_after>extern crate dbus;

use std;
use std::env;
use general::*;
use device::DeviceType;

/// Get a list of Network Manager connections sorted by path.
///
/// # Examples
///
/// ```no_run
/// use network_manager::connection;
/// let connections = connection::list().unwrap();
/// println!("{:?}", connections);
/// ```
pub fn list() -> Result<Vec<Connection>, String> {
    let message = dbus_message!(NM_SERVICE_MANAGER,
                                NM_SETTINGS_PATH,
                                NM_SETTINGS_INTERFACE,
                                "ListConnections");
    let response = dbus_connect!(message);
    let paths: dbus::arg::Array<dbus::Path, _> = response.get1().unwrap();
    let mut connections: Vec<_> = paths.map(|p| get_connection(p).unwrap()).collect();
    connections.sort_by(|a, b| a.cmp(b));

    Ok(connections)
}

#[test]
fn test_list_function() {
    let connections = list().unwrap();
    assert!(connections.len() > 0);
    for (index, val) in connections.iter().enumerate() {
        assert_ne!(Connection { ..Default::default() }, val.clone());
        assert_eq!(index as i32, i32::from(val));
    }
}

/// Creates a Network Manager connection.
///
/// # Examples
///
/// ```
/// let connection = network_manager::connection::create(
///     "resin_io",
///     network_manager::device::DeviceType::WiFi,
///     network_manager::general::Security::WPA2,
///     "super_secret_passphase"
///     ).unwrap();
/// println!("{:?}", connection);
/// ```
pub fn create(s: &str, dt: DeviceType, sc: Security, p: &str) -> Result<Connection, String> {
    // Create a connection
    // Get the connection
    // Return the connection

    let connection1 = Connection {
        path: "/org/freedesktop/NetworkManager/ActiveConnection/187".to_string(),
        id: "resin_io".to_string(),
        uuid: "3c8e6e8b-b895-4b07-97a5-bbc192c3b436".to_string(),
        ssid: "resin_io".to_string(),
        active_path: "test".to_string(),
        state: ConnectionState::Deactivated, /* device: "wlp4s0".to_string(),
                                              * interface: DeviceType::WiFi,
                                              * security: Security::WPA2,
                                              * state: ConnectionState::Activated, */
    };

    Ok(connection1)
}

/// Deletes a Network Manager connection.
///
/// # Examples
///
/// ```
/// let mut connections = network_manager::connection::list().unwrap();
/// network_manager::connection::delete(connections.pop().unwrap()).unwrap();
/// ```
pub fn delete(connection: Connection) -> Result<(), String> {
    let message = dbus_message!(NM_SERVICE_MANAGER,
                                connection.path,
                                NM_CONNECTION_INTERFACE,
                                "Delete");
    dbus_connect!(message);

    Ok(())
}

/// Enables a Network Manager connection.
///
/// # Examples
///
/// ```no_run
/// use network_manager::connection;
/// let connections = connection::list().unwrap();
/// let mut connection = connections[0].clone();
/// connection::enable(&mut connection, 10).unwrap();
/// println!("{:?}", connection.state);
/// ```
pub fn enable(connection: &mut Connection, time_out: i32) -> Result<(), String> {
    update_state(connection).expect("Unable to get connection state");
    match connection.state {
        ConnectionState::Activated => Ok(()),
        ConnectionState::Activating => wait(connection, time_out, ConnectionState::Activated),
        ConnectionState::Unknown => Err("Unable to get connection state".to_string()),
        _ => {
            let mut message = dbus_message!(NM_SERVICE_MANAGER,
                                            NM_SERVICE_PATH,
                                            NM_SERVICE_INTERFACE,
                                            "ActivateConnection");
            message.append_items(&[dbus::MessageItem::ObjectPath(connection.path
                                                                     .to_string()
                                                                     .into()),
                                   dbus::MessageItem::ObjectPath("/".into()),
                                   dbus::MessageItem::ObjectPath("/".into())]);
            dbus_connect!(message);

            wait(connection, time_out, ConnectionState::Activated)
        }
    }
}

/// Disables a Network Manager connection.
///
/// # Examples
///
/// ```no_run
/// use network_manager::connection;
/// let connections = connection::list().unwrap();
/// let mut connection = connections[0].clone();
/// connection::disable(&mut connection, 10).unwrap();
/// println!("{:?}", connection.state);
/// ```
pub fn disable(connection: &mut Connection, time_out: i32) -> Result<(), String> {
    update_state(connection).expect("Unable to get connection state");
    match connection.state {
        ConnectionState::Deactivated => Ok(()),
        ConnectionState::Deactivating => wait(connection, time_out, ConnectionState::Deactivated),
        ConnectionState::Unknown => Err("Unable to get connection state".to_string()),
        _ => {
            let mut message = dbus_message!(NM_SERVICE_MANAGER,
                                            NM_SERVICE_PATH,
                                            NM_SERVICE_INTERFACE,
                                            "DeactivateConnection");
            message.append_items(&[dbus::MessageItem::ObjectPath(connection.active_path
                                                                     .to_string()
                                                                     .into())]);
            dbus_connect!(message);

            wait(connection, time_out, ConnectionState::Deactivated)
        }
    }
}

//#[test]
fn test_enable_disable_functions() {
    let connections = list().unwrap();
    let mut connection;

    // set enviorment variable $TEST_WIFI_SSID with the wifi's SSID that you want to test
    // e.g.  export TEST_WIFI_SSID="Resin.io Wifi"
    let wifiEnvVar = "TEST_WIFI_SSID";
    match env::var(wifiEnvVar) {
        Ok(ssid) => {
            connection = connections.iter()
                .filter(|c| c.ssid == ssid)
                .nth(0)
                .unwrap()
                .clone()
        }
        Err(e) => {
            panic!("couldn't retrieve enviorment variable {}: {}",
                   wifiEnvVar,
                   e)
        }
    };

    assert!(connection.state == ConnectionState::Activated ||
            connection.state == ConnectionState::Deactivated);

    match connection.state {
        ConnectionState::Activated => {
            disable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Deactivated, connection.state);

            enable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Activated, connection.state);
        }
        ConnectionState::Deactivated => {
            enable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Activated, connection.state);

            disable(&mut connection, 10).unwrap();
            assert_eq!(ConnectionState::Deactivated, connection.state);
        }
        _ => (),
    }
}

fn get_connection(path: dbus::Path) -> Result<Connection, String> {
    let mut connection = Connection { path: dbus_path_to_string(path), ..Default::default() };

    let message = dbus_message!(NM_SERVICE_MANAGER,
                                connection.path.clone(),
                                NM_CONNECTION_INTERFACE,
                                "GetSettings");
    let response = dbus_connect!(message);
    let dictionary: dbus::arg::Dict<&str,
                                    dbus::arg::Dict<&str, dbus::arg::Variant<dbus::arg::Iter>, _>,
                                    _> = response.get1().unwrap();

    for (_, v1) in dictionary {
        for (k2, v2) in v1 {
            match k2 {
                "id" => {
                    connection.id = v2.0
                        .clone()
                        .get::<&str>()
                        .unwrap()
                        .to_string();
                }
                "uuid" => {
                    connection.uuid = v2.0
                        .clone()
                        .get::<&str>()
                        .unwrap()
                        .to_string();
                }
                "ssid" => {
                    connection.ssid =
                        std::str::from_utf8(&v2.0
                                                 .clone()
                                                 .get::<dbus::arg::Array<u8, _>>()
                                                 .unwrap()
                                                 .collect::<Vec<u8>>())
                                .unwrap()
                                .to_string();
                }
                _ => (),
            }
        }
    }

    update_state(&mut connection).unwrap();

    Ok(connection)
}

fn update_state(connection: &mut Connection) -> Result<(), String> {
    let active_paths: Vec<String> = dbus_property!(NM_SERVICE_MANAGER,
                                                   NM_SERVICE_PATH,
                                                   NM_SERVICE_INTERFACE,
                                                   "ActiveConnections")
            .unwrap()
            .inner::<&Vec<dbus::MessageItem>>()
            .unwrap()
            .iter()
            .map(|p| dbus_path_to_string(p.inner::<&dbus::Path>().unwrap().to_owned()))
            .collect();

    let settings_paths = active_paths.iter().map(|p| {
        dbus_path_to_string(dbus_property!(NM_SERVICE_MANAGER,
                                           p,
                                           NM_ACTIVE_INTERFACE,
                                           "Connection")
                                    .unwrap()
                                    .inner::<&dbus::Path>()
                                    .unwrap()
                                    .to_owned())
    });

    connection.active_path = "".to_string();
    connection.state = ConnectionState::Deactivated;

    for (active_path, settings_path) in active_paths.iter().zip(settings_paths) {
        if connection.path == settings_path {
            connection.active_path = active_path.to_owned();

            let result = dbus_property!(NM_SERVICE_MANAGER,
                                        connection.active_path.clone(),
                                        NM_ACTIVE_INTERFACE,
                                        "State");
            if let Ok(val) = result {
                connection.state = ConnectionState::from(val.inner::<u32>().unwrap())
            }

            break;
        }
    }

    Ok(())
}

fn wait(connection: &mut Connection,
        time_out: i32,
        target_state: ConnectionState)
        -> Result<(), String> {
    if time_out == 0 {
        return Ok(());
    }

    let mut total_time = 0;
    while total_time < time_out {
        update_state(connection).unwrap();
        if connection.state == target_state {
            return Ok(());
        }
        std::thread::sleep(std::time::Duration::from_secs(1));
        total_time += 1;
    }

    Err("service timed out".to_string())
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Connection {
    pub path: String,
    pub active_path: String,
    pub id: String,
    pub uuid: String,
    pub ssid: String,
    pub state: ConnectionState, /* device: String,
                                 * device_type: DeviceType,
                                 * security: Security, */
}

impl Default for Connection {
    fn default() -> Connection {
        Connection {
            path: "".to_string(),
            active_path: "".to_string(),
            id: "".to_string(),
            uuid: "".to_string(),
            ssid: "".to_string(),
            state: ConnectionState::Unknown,
        }
    }
}

impl Ord for Connection {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        i32::from(self).cmp(&i32::from(other))
    }
}

impl PartialOrd for Connection {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl<'a> From<&'a Connection> for i32 {
    fn from(val: &Connection) -> i32 {
        val.clone()
            .path
            .rsplit('/')
            .nth(0)
            .unwrap()
            .parse::<i32>()
            .unwrap()
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
//! This is a webui about to replace the TUI, to be nice, better accessable, and
//! new technology using websockets (since actix changed a lot actix_web, many
//! implementation should probably be reworked)
use super::super::config;
use super::PeerRepresentation;
use actix::prelude::StreamHandler;
use actix::{Actor, ActorContext, AsyncContext};
use actix_files as fs;
use actix_web::{
    http::StatusCode,
    web::{self, HttpRequest, HttpResponse, Json},
    App, Error, HttpServer,
};
use actix_web_actors::ws;
use get_if_addrs;
use hostname;
use std::{
    ffi::OsString,
    io,
    net::IpAddr,
    string::String,
    sync::{Arc, Mutex},
    time::{Duration, Instant},
    vec::Vec,
};

/// How often heartbeat pings are sent
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);

pub struct WebServerState {
    id: PeerRepresentation,
    nr_connections: Arc<Mutex<usize>>,
}

pub struct WebUI {
    id: PeerRepresentation,
    serve_others: bool,
}

/// needs to be serializable for json
#[derive(Serialize)]
struct JSONResponse {
    cmd: WebCommand,
}

#[derive(Serialize)]
enum WebCommand {
    Started,
    NewMdnsClient(String),
}

/// The formatters here for json output
use std::fmt;
impl fmt::Display for JSONResponse {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.cmd)
    }
}
impl fmt::Display for WebCommand {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            WebCommand::Started => write!(f, "Started"),
            WebCommand::NewMdnsClient(param) => write!(f, "MDNS_IP: {}", param),
        }
    }
}

/// dynamic development files
//#[get("/app.js")]
//fn app_js() -> Result<fs::NamedFile> {
//Ok(fs::NamedFile::open("src/ctrl/webui/js/app.js")?)
//}

impl WebUI {
    pub fn new(id: PeerRepresentation, serve: bool) -> io::Result<Self> {
        let sys = actix::System::new("http-server");
        let connection_count = Arc::new(Mutex::new(0));

        let local_addresses = get_if_addrs::get_if_addrs().unwrap();

        let initial_state = web::Data::new(Mutex::new(WebServerState {
            id: id.clone(),
            nr_connections: connection_count.clone(),
        }));

        // take all local addresses and start if necessary
        // one server with multiple binds
        let web_server = local_addresses
            .into_iter()
            .filter(|ipaddr| {
                let name = ipaddr.name.clone();
                // only use loopback addresses no 2nd ethernet cards
                if ipaddr.addr.is_loopback() {
                    info!(
                        "{} {:?} is a loopback device, good!",
                        ipaddr.addr.ip().to_string(),
                        name
                    );
                    true
                } else {
                    info!("{} is not a loopback device!", ipaddr.addr.ip().to_string());
                    true
                }
            })
            .fold(
                HttpServer::new(move || {
                    App::new()
                        // each server has an initial state (e.g. 0 connections)
                        .register_data(initial_state.clone())
                        .service(web::resource("/app.js").to(WebUI::js_app))
                        .default_service(web::resource("").to(WebUI::single_page))
                        //.default_service(web::resource("").to(WebUI::dyn_devel_html)) // Todo: only for devel
                        //.service(web::resource("/app.js").to(WebUI::dyn_devel_js)) // todo: only for devel
                        .service(web::resource("/jquery.min.js").to(|| {
                            HttpResponse::Ok()
                                .content_type("text/html; charset=utf-8")
                                .body(*config::webui::jquery::JS_JQUERY)
                        }))
                        .service(web::resource("favicon.png").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/png")
                                .body(*config::webui::FAVICON)
                        }))
                        .service(web::resource("sheep.svg").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/svg+xml")
                                .body(*config::webui::PIC_SHEEP)
                        }))
                        .service(
                            web::resource("/css/{name}").route(web::get().to(WebUI::bootstrap_css)),
                        )
                        .service(
                            web::resource("/js/{name}").route(web::get().to(WebUI::bootstrap_js)),
                        )
                        .service(web::resource("/ws").route(web::get().to(WebUI::ws_index)))
                }),
                |web_server, ipaddr| {
                    web_server
                        .bind(match ipaddr.addr.ip() {
                            IpAddr::V4(ipv4) => {
                                format!("{}:{:?}", ipv4.to_string(), config::net::PORT_WEBSOCKET)
                            }
                            IpAddr::V6(ipv6) => {
                                format!("{}:{:?}", ipv6.to_string(), config::net::PORT_WEBSOCKET)
                            }
                        })
                        .map(|good_bind| {
                            info!("{} worked!", ipaddr.addr.ip().to_string());
                            good_bind
                        })
                        .map_err(|bad_bind| {
                            error!("{} failed!!", ipaddr.addr.ip().to_string());
                            bad_bind
                        })
                        .unwrap()
                },
            );

        // finally start everything
        web_server.start();
        sys.run().and_then(|_| {
            Ok(WebUI {
                id,
                serve_others: serve,
            })
        })
    }

    #[allow(dead_code)]
    fn dyn_devel_html(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<fs::NamedFile, Error> {
        Ok(fs::NamedFile::open("src/ctrl/webui/html/single_page.html")?)
    }

    #[allow(dead_code)]
    fn dyn_devel_js(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<fs::NamedFile, Error> {
        Ok(fs::NamedFile::open("src/ctrl/webui/js/app.js")?)
    }

    fn single_page(
        state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<HttpResponse, Error> {
        // change state
        let mut data = state.lock().unwrap();
        let id = data.id;
        *(data.nr_connections.lock().unwrap()) += 1;

        let id_page = Self::replace_static_content(*config::webui::HTML_PAGE, &id);

        Ok(HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(id_page))
    }

    fn bootstrap_css(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
        path: web::Path<(String,)>,
    ) -> Result<HttpResponse, Error> {
        let css = &*path.0;
        let output = match css {
            "bootstrap.css" => Some(*config::webui::bootstrap::CSS),
            "bootstrap.css.map" => Some(*config::webui::bootstrap::CSS_MAP),
            "bootstrap.min.css" => Some(*config::webui::bootstrap::CSS_MIN),
            "bootstrap.min.css.map" => Some(*config::webui::bootstrap::CSS_MIN_MAP),
            "bootstrap-grid.css" => Some(*config::webui::bootstrap::CSS_GRID),
            "bootstrap-grid.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MAP),
            "bootstrap-grid.min.css" => Some(*config::webui::bootstrap::CSS_GRID_MIN),
            "bootstrap-grid.min.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MIN_MAP),
            "bootstrap-reboot.css" => Some(*config::webui::bootstrap::CSS_REBOOT),
            "bootstrap-reboot.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MAP),
            "bootstrap-reboot.min.css" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN),
            "bootstrap-reboot.min.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN_MAP),
            _ => {
                error!("CSS: not found {}", css);
                None
            }
        };
        if let Some(content) = output {
            Ok(HttpResponse::build(StatusCode::OK)
                .content_type("text/css; charset=utf-8")
                .body(content))
        } else {
            Ok(HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("text/css; charset=utf-8")
                .body(""))
        }
    }

    fn bootstrap_js(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
        path: web::Path<(String,)>,
    ) -> Result<HttpResponse, Error> {
        let js = &*path.0;
        let output = match js {
            "bootstrap.js" => Some(*config::webui::bootstrap::JS),
            "bootstrap.js.map" => Some(*config::webui::bootstrap::JS_MAP),
            "bootstrap.min.js" => Some(*config::webui::bootstrap::JS_MIN),
            "bootstrap.min.js.map" => Some(*config::webui::bootstrap::JS_MIN_MAP),
            "bootstrap.bundle.js" => Some(*config::webui::bootstrap::JS_BUNDLE),
            "bootstrap.bundle.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MAP),
            "bootstrap.bundle.min.js" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN),
            "bootstrap.bundle.min.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN_MAP),
            _ => {
                error!("JS: not found {}", js);
                None
            }
        };
        if let Some(content) = output {
            Ok(HttpResponse::build(StatusCode::OK)
                .content_type("application/javascript; charset=utf-8")
                .body(content))
        } else {
            Ok(HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("application/javascript; charset=utf-8")
                .body(""))
        }
    }

    fn js_app(
        state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<HttpResponse, Error> {
        let data = state.lock().unwrap();
        let id = data.id;

        let output = Self::replace_static_content(*config::webui::JS_APP, &id);
        Ok(HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(output))
    }

    fn ws_index(
        _state: web::Data<Mutex<WebServerState>>,
        req: HttpRequest,
        stream: web::Payload,
    ) -> Result<HttpResponse, Error> {
        ws::start(MyWebSocket::new(), &req, stream)
    }

    fn replace_static_content(html_in: &str, id: &PeerRepresentation) -> String {
        // short inline struct
        struct ReplaceStatic<'a> {
            r: &'a str,
            c: String,
        }

        let id_string = std::format!("{:x?}", id);
        let hostname = hostname::get()
            .unwrap_or(OsString::from("undefined"))
            .into_string()
            .unwrap_or(String::from("undefined"));

        let changers: [ReplaceStatic; 4] = [
            ReplaceStatic {
                r: config::net::HTML_REPLACE_STATIC_URL_SOURCE,
                c: config::net::HTML_URL_SOURCE.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_WEBSOCKET,
                c: config::net::WEBSOCKET_ADDR.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_UUID,
                c: id_string,
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_HOSTNAME,
                c: hostname,
            },
        ];
        let mut replace_this = html_in.to_string();

        for replacer in &changers {
            replace_this = str::replace(&replace_this, &replacer.r, &replacer.c);
        }
        replace_this
    }
}

/// websocket connection is long running connection, it easier
/// to handle with an actor
struct MyWebSocket {
    /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
    /// otherwise we drop connection.
    hb: Instant,
}

impl Actor for MyWebSocket {
    type Context = ws::WebsocketContext<Self>;

    /// Method is called on actor start. We start the heartbeat process here.
    fn started(&mut self, ctx: &mut Self::Context) {
        self.hb(ctx);
    }
}

/// Handler for `ws::Message`
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
    fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
        // process websocket messages
        println!("WS: {:?}", msg);
        match msg {
            ws::Message::Ping(msg) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            ws::Message::Pong(_) => {
                self.hb = Instant::now();
            }
            ws::Message::Text(text) => {
                let m = text.trim();
                // /command
                if m.starts_with('/') {
                    let v: Vec<&str> = m.splitn(2, ' ').collect();
                    match v[0] {
                        "/start" => {
                            ctx.text(
                                Json(JSONResponse {
                                    cmd: WebCommand::Started,
                                })
                                .to_string(),
                            );
                        }
                        _ => ctx.text(format!("!!! unknown command: {:?}", m)),
                    }
                }
            }
            ws::Message::Binary(bin) => ctx.binary(bin),
            ws::Message::Close(_) => {
                ctx.stop();
            }
            ws::Message::Nop => (),
        }
    }
}

impl MyWebSocket {
    fn new() -> Self {
        Self { hb: Instant::now() }
    }

    /// helper method that sends ping to client every second.
    ///
    /// also this method checks heartbeats from client
    fn hb(&self, ctx: &mut <Self as Actor>::Context) {
        ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
            // check client heartbeats
            if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
                // heartbeat timed out
                error!("Websocket Client heartbeat failed, disconnecting!");

                // stop actor
                ctx.stop();

                // don't try to send a ping
                return;
            }

            ctx.ping("");
        });
    }
}
<commit_msg>[libp2p migration] secure http binding a little more<commit_after>#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
//! This is a webui about to replace the TUI, to be nice, better accessable, and
//! new technology using websockets (since actix changed a lot actix_web, many
//! implementation should probably be reworked)
use super::super::config;
use super::PeerRepresentation;
use actix::prelude::StreamHandler;
use actix::{Actor, ActorContext, AsyncContext};
use actix_files as fs;
use actix_web::{
    http::StatusCode,
    web::{self, HttpRequest, HttpResponse, Json},
    App, Error, HttpServer,
};
use actix_web_actors::ws;
use get_if_addrs;
use hostname;
use std::{
    ffi::OsString,
    io,
    net::IpAddr,
    string::String,
    sync::{Arc, Mutex},
    time::{Duration, Instant},
    vec::Vec,
};

/// How often heartbeat pings are sent
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);

pub struct WebServerState {
    id: PeerRepresentation,
    nr_connections: Arc<Mutex<usize>>,
}

pub struct WebUI {
    id: PeerRepresentation,
    serve_others: bool,
}

/// needs to be serializable for json
#[derive(Serialize)]
struct JSONResponse {
    cmd: WebCommand,
}

#[derive(Serialize)]
enum WebCommand {
    Started,
    NewMdnsClient(String),
}

/// The formatters here for json output
use std::fmt;
impl fmt::Display for JSONResponse {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.cmd)
    }
}
impl fmt::Display for WebCommand {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            WebCommand::Started => write!(f, "Started"),
            WebCommand::NewMdnsClient(param) => write!(f, "MDNS_IP: {}", param),
        }
    }
}

/// dynamic development files
//#[get("/app.js")]
//fn app_js() -> Result<fs::NamedFile> {
//Ok(fs::NamedFile::open("src/ctrl/webui/js/app.js")?)
//}

impl WebUI {
    pub fn new(id: PeerRepresentation, serve: bool) -> io::Result<Self> {
        let sys = actix::System::new("http-server");
        let connection_count = Arc::new(Mutex::new(0));

        let local_addresses = get_if_addrs::get_if_addrs().unwrap();

        let initial_state = web::Data::new(Mutex::new(WebServerState {
            id: id.clone(),
            nr_connections: connection_count.clone(),
        }));

        // take all local addresses and start if necessary
        // one server with multiple binds
        let web_server = local_addresses
            .into_iter()
            .filter(|ipaddr| {
                let name = ipaddr.name.clone();
                // only use loopback addresses no 2nd ethernet cards
                if ipaddr.addr.is_loopback() {
                    info!(
                        "{} {:?} is a loopback device, good!",
                        ipaddr.addr.ip().to_string(),
                        name
                    );
                    true
                } else {
                    info!("{} is not a loopback device!", ipaddr.addr.ip().to_string());
                    true
                }
            })
            .fold(
                Ok(HttpServer::new(move || {
                    App::new()
                        // each server has an initial state (e.g. 0 connections)
                        .register_data(initial_state.clone())
                        .service(web::resource("/app.js").to(WebUI::js_app))
                        .default_service(web::resource("").to(WebUI::single_page))
                        //.default_service(web::resource("").to(WebUI::dyn_devel_html)) // Todo: only for devel
                        //.service(web::resource("/app.js").to(WebUI::dyn_devel_js)) // todo: only for devel
                        .service(web::resource("/jquery.min.js").to(|| {
                            HttpResponse::Ok()
                                .content_type("text/html; charset=utf-8")
                                .body(*config::webui::jquery::JS_JQUERY)
                        }))
                        .service(web::resource("favicon.png").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/png")
                                .body(*config::webui::FAVICON)
                        }))
                        .service(web::resource("sheep.svg").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/svg+xml")
                                .body(*config::webui::PIC_SHEEP)
                        }))
                        .service(
                            web::resource("/css/{name}").route(web::get().to(WebUI::bootstrap_css)),
                        )
                        .service(
                            web::resource("/js/{name}").route(web::get().to(WebUI::bootstrap_js)),
                        )
                        .service(web::resource("/ws").route(web::get().to(WebUI::ws_index)))
                })),
                |web_server_binding_chain: Result<HttpServer<_, _, _, _>, io::Error>, ipaddr| {
                    web_server_binding_chain.and_then(|webserver| {
                        let bind_format = match ipaddr.addr.ip() {
                            IpAddr::V4(ipv4) => {
                                format!("{}:{:?}", ipv4.to_string(), config::net::PORT_WEBSOCKET)
                            }
                            IpAddr::V6(ipv6) => {
                                format!("{}:{:?}", ipv6.to_string(), config::net::PORT_WEBSOCKET)
                            }
                        };
                        let try_bind = webserver.bind(bind_format.clone()).map_err(|error| {
                            error!("On IP ({:?}): {:?}", bind_format, error);
                            error
                        })?;
                        Ok(try_bind)
                    })
                },
            );

        // finally start everything, failure was funnily not already captured by ? above???
        web_server.and_then(|web_server_correct| {
            web_server_correct.start();
            sys.run().and_then(|_| {
                Ok(WebUI {
                    id,
                    serve_others: serve,
                })
            })
        })
    }

    #[allow(dead_code)]
    fn dyn_devel_html(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<fs::NamedFile, Error> {
        Ok(fs::NamedFile::open("src/ctrl/webui/html/single_page.html")?)
    }

    #[allow(dead_code)]
    fn dyn_devel_js(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<fs::NamedFile, Error> {
        Ok(fs::NamedFile::open("src/ctrl/webui/js/app.js")?)
    }

    fn single_page(
        state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<HttpResponse, Error> {
        // change state
        let mut data = state.lock().unwrap();
        let id = data.id;
        *(data.nr_connections.lock().unwrap()) += 1;

        let id_page = Self::replace_static_content(*config::webui::HTML_PAGE, &id);

        Ok(HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(id_page))
    }

    fn bootstrap_css(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
        path: web::Path<(String,)>,
    ) -> Result<HttpResponse, Error> {
        let css = &*path.0;
        let output = match css {
            "bootstrap.css" => Some(*config::webui::bootstrap::CSS),
            "bootstrap.css.map" => Some(*config::webui::bootstrap::CSS_MAP),
            "bootstrap.min.css" => Some(*config::webui::bootstrap::CSS_MIN),
            "bootstrap.min.css.map" => Some(*config::webui::bootstrap::CSS_MIN_MAP),
            "bootstrap-grid.css" => Some(*config::webui::bootstrap::CSS_GRID),
            "bootstrap-grid.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MAP),
            "bootstrap-grid.min.css" => Some(*config::webui::bootstrap::CSS_GRID_MIN),
            "bootstrap-grid.min.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MIN_MAP),
            "bootstrap-reboot.css" => Some(*config::webui::bootstrap::CSS_REBOOT),
            "bootstrap-reboot.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MAP),
            "bootstrap-reboot.min.css" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN),
            "bootstrap-reboot.min.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN_MAP),
            _ => {
                error!("CSS: not found {}", css);
                None
            }
        };
        if let Some(content) = output {
            Ok(HttpResponse::build(StatusCode::OK)
                .content_type("text/css; charset=utf-8")
                .body(content))
        } else {
            Ok(HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("text/css; charset=utf-8")
                .body(""))
        }
    }

    fn bootstrap_js(
        _state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
        path: web::Path<(String,)>,
    ) -> Result<HttpResponse, Error> {
        let js = &*path.0;
        let output = match js {
            "bootstrap.js" => Some(*config::webui::bootstrap::JS),
            "bootstrap.js.map" => Some(*config::webui::bootstrap::JS_MAP),
            "bootstrap.min.js" => Some(*config::webui::bootstrap::JS_MIN),
            "bootstrap.min.js.map" => Some(*config::webui::bootstrap::JS_MIN_MAP),
            "bootstrap.bundle.js" => Some(*config::webui::bootstrap::JS_BUNDLE),
            "bootstrap.bundle.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MAP),
            "bootstrap.bundle.min.js" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN),
            "bootstrap.bundle.min.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN_MAP),
            _ => {
                error!("JS: not found {}", js);
                None
            }
        };
        if let Some(content) = output {
            Ok(HttpResponse::build(StatusCode::OK)
                .content_type("application/javascript; charset=utf-8")
                .body(content))
        } else {
            Ok(HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("application/javascript; charset=utf-8")
                .body(""))
        }
    }

    fn js_app(
        state: web::Data<Mutex<WebServerState>>,
        _req: HttpRequest,
    ) -> Result<HttpResponse, Error> {
        let data = state.lock().unwrap();
        let id = data.id;

        let output = Self::replace_static_content(*config::webui::JS_APP, &id);
        Ok(HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(output))
    }

    fn ws_index(
        _state: web::Data<Mutex<WebServerState>>,
        req: HttpRequest,
        stream: web::Payload,
    ) -> Result<HttpResponse, Error> {
        ws::start(MyWebSocket::new(), &req, stream)
    }

    fn replace_static_content(html_in: &str, id: &PeerRepresentation) -> String {
        // short inline struct
        struct ReplaceStatic<'a> {
            r: &'a str,
            c: String,
        }

        let id_string = std::format!("{:x?}", id);
        let hostname = hostname::get()
            .unwrap_or(OsString::from("undefined"))
            .into_string()
            .unwrap_or(String::from("undefined"));

        let changers: [ReplaceStatic; 4] = [
            ReplaceStatic {
                r: config::net::HTML_REPLACE_STATIC_URL_SOURCE,
                c: config::net::HTML_URL_SOURCE.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_WEBSOCKET,
                c: config::net::WEBSOCKET_ADDR.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_UUID,
                c: id_string,
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_HOSTNAME,
                c: hostname,
            },
        ];
        let mut replace_this = html_in.to_string();

        for replacer in &changers {
            replace_this = str::replace(&replace_this, &replacer.r, &replacer.c);
        }
        replace_this
    }
}

/// websocket connection is long running connection, it easier
/// to handle with an actor
struct MyWebSocket {
    /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
    /// otherwise we drop connection.
    hb: Instant,
}

impl Actor for MyWebSocket {
    type Context = ws::WebsocketContext<Self>;

    /// Method is called on actor start. We start the heartbeat process here.
    fn started(&mut self, ctx: &mut Self::Context) {
        self.hb(ctx);
    }
}

/// Handler for `ws::Message`
impl StreamHandler<ws::Message, ws::ProtocolError> for MyWebSocket {
    fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
        // process websocket messages
        println!("WS: {:?}", msg);
        match msg {
            ws::Message::Ping(msg) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            ws::Message::Pong(_) => {
                self.hb = Instant::now();
            }
            ws::Message::Text(text) => {
                let m = text.trim();
                // /command
                if m.starts_with('/') {
                    let v: Vec<&str> = m.splitn(2, ' ').collect();
                    match v[0] {
                        "/start" => {
                            ctx.text(
                                Json(JSONResponse {
                                    cmd: WebCommand::Started,
                                })
                                .to_string(),
                            );
                        }
                        _ => ctx.text(format!("!!! unknown command: {:?}", m)),
                    }
                }
            }
            ws::Message::Binary(bin) => ctx.binary(bin),
            ws::Message::Close(_) => {
                ctx.stop();
            }
            ws::Message::Nop => (),
        }
    }
}

impl MyWebSocket {
    fn new() -> Self {
        Self { hb: Instant::now() }
    }

    /// helper method that sends ping to client every second.
    ///
    /// also this method checks heartbeats from client
    fn hb(&self, ctx: &mut <Self as Actor>::Context) {
        ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
            // check client heartbeats
            if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
                // heartbeat timed out
                error!("Websocket Client heartbeat failed, disconnecting!");

                // stop actor
                ctx.stop();

                // don't try to send a ping
                return;
            }

            ctx.ping("");
        });
    }
}
<|endoftext|>