<commit_before>#![recursion_limit = "1024"]
#[macro_use]

extern crate error_chain;

pub mod config;
mod errors;

use std::process::Command;

use config::Config;
use errors::*;

#[derive(Clone, Debug, PartialEq)]
pub struct Author {
  pub name: String,
  pub email: String,
}

pub struct GitTogether<C> {
  pub config: C,
}

impl<C: Config> GitTogether<C> {
  pub fn set_active(&self, inits: &[&str]) -> Result<()> {
    self.get_authors(inits)
      .and_then(|_| self.config.set("active", &inits.join("+")))
  }

  pub fn signoff<'a>(&self, cmd: &'a mut Command) -> Result<&'a mut Command> {
    let active = try!(self.config.get("active"));
    let inits: Vec<_> = active.split('+').collect();
    let authors = try!(self.get_authors(&inits));

    let cmd = match authors.get(0) {
      Some(author) => {
        cmd.env("GIT_AUTHOR_NAME", author.name.clone())
          .env("GIT_AUTHOR_EMAIL", author.email.clone())
      }
      _ => cmd,
    };

    let cmd = match authors.get(1) {
      Some(committer) => {
        cmd.env("GIT_COMMITTER_NAME", committer.name.clone())
          .env("GIT_COMMITTER_EMAIL", committer.email.clone())
          .arg("--signoff")
      }
      _ => cmd,
    };

    Ok(cmd)
  }

  fn get_active(&self) -> Result<Vec<String>> {
    self.config
      .get("active")
      .map(|active| active.split('+').map(|s| s.into()).collect())
  }

  pub fn rotate_active(&self) -> Result<()> {
    self.get_active().and_then(|active| {
      let mut inits: Vec<_> = active.iter().map(String::as_ref).collect();
      if !inits.is_empty() {
        let author = inits.remove(0);
        inits.push(author);
      }
      self.set_active(&inits[..])
    })
  }

  fn get_authors(&self, inits: &[&str]) -> Result<Vec<Author>> {
    let domain = try!(self.config.get("domain"));
    inits.iter()
      .map(|&init| {
        self.config
          .get(&format!("authors.{}", init))
          .chain_err(|| ErrorKind::AuthorNotFound(init.into()))
          .and_then(|raw| {
            if raw.is_empty() {
              return Err(ErrorKind::InvalidAuthor(raw).into());
            }

            Self::author(&domain, &raw)
          })
      })
      .collect()
  }

  fn author(domain: &str, raw: &str) -> Result<Author> {
    let mut split = raw.split(';');
    let name = try!(split.next()
        .ok_or(ErrorKind::InvalidAuthor(raw.into())))
      .trim();
    let username = try!(split.next()
        .ok_or(ErrorKind::InvalidAuthor(raw.into())))
      .trim();
    let email = format!("{}@{}", username, domain);

    Ok(Author {
      name: name.into(),
      email: email,
    })
  }
}

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

  use std::cell::RefCell;
  use std::collections::HashMap;

  use config::Config;
  use errors::*;

  #[test]
  fn get_authors_no_domain() {
    let config = MockConfig::new(&[("authors.jh", "James Holden; jholden")]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
  }

  #[test]
  fn get_authors() {
    let config = MockConfig::new(&[("domain", "rocinante.com"),
                                   ("authors.jh", ""),
                                   ("authors.nn", "Naomi Nagata"),
                                   ("authors.ab", "Amos Burton; aburton"),
                                   ("authors.ak", "Alex Kamal; akamal") /* ("authors.ca", "Chrisjen Avasarala;"),
                                                                         * ("authors.bd", "Bobbie Draper; bdraper@mars.mil"),
                                                                         * ("authors.jm", "Joe Miller; jmiller@starhelix.com"), */]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
    assert!(gt.get_authors(&["nn"]).is_err());
    // assert!(gt.get_authors(&["ca"]).is_err());
    // assert!(gt.get_authors(&["jh", "bd"]).is_err());

    assert_eq!(gt.get_authors(&["ab", "ak"]).unwrap(),
               vec![
               Author { name: "Amos Burton".into(), email: "aburton@rocinante.com".into() },
               Author { name: "Alex Kamal".into(), email: "akamal@rocinante.com".into() },
               ]);
    // assert_eq!(gt.get_authors(&["ab", "bd", "jm"]).unwrap(),
    //            vec![Author { name: "Amos Burton".into(), email: "aburton@rocinante.com".into() },
    // Author { name: "Bobbie Draper".into(), email: "bdraper@mars.mil".into() },
    // Author { name: "Joe Miller".into(), email: "jmiller@starhelix.com".into() },
    // ]);
  }

  #[test]
  fn set_active() {
    let config = MockConfig::new(&[("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let gt = GitTogether { config: config };

    gt.set_active(&["jh"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh"]);

    gt.set_active(&["jh", "nn"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh", "nn"]);
  }

  #[test]
  fn rotate_active() {
    let config = MockConfig::new(&[("active", "jh+nn"),
                                   ("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let gt = GitTogether { config: config };

    gt.rotate_active().unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["nn", "jh"]);
  }

  struct MockConfig {
    data: RefCell<HashMap<String, String>>,
  }

  impl MockConfig {
    fn new(data: &[(&str, &str)]) -> MockConfig {
      let data = data.iter()
        .map(|&(k, v)| (k.into(), v.into()))
        .collect();
      MockConfig { data: RefCell::new(data) }
    }
  }

  impl Config for MockConfig {
    fn get(&self, name: &str) -> Result<String> {
      self.data
        .borrow()
        .get(name.into())
        .cloned()
        .ok_or(format!("name not found: '{}'", name).into())
    }

    fn set(&self, name: &str, value: &str) -> Result<()> {
      self.data.borrow_mut().insert(name.into(), value.into());
      Ok(())
    }
  }
}
<commit_msg>handle different domains<commit_after>#![recursion_limit = "1024"]
#[macro_use]

extern crate error_chain;

pub mod config;
mod errors;

use std::process::Command;

use config::Config;
use errors::*;

#[derive(Clone, Debug, PartialEq)]
pub struct Author {
  pub name: String,
  pub email: String,
}

pub struct GitTogether<C> {
  pub config: C,
}

impl<C: Config> GitTogether<C> {
  pub fn set_active(&self, inits: &[&str]) -> Result<()> {
    self.get_authors(inits)
      .and_then(|_| self.config.set("active", &inits.join("+")))
  }

  pub fn signoff<'a>(&self, cmd: &'a mut Command) -> Result<&'a mut Command> {
    let active = try!(self.config.get("active"));
    let inits: Vec<_> = active.split('+').collect();
    let authors = try!(self.get_authors(&inits));

    let cmd = match authors.get(0) {
      Some(author) => {
        cmd.env("GIT_AUTHOR_NAME", author.name.clone())
          .env("GIT_AUTHOR_EMAIL", author.email.clone())
      }
      _ => cmd,
    };

    let cmd = match authors.get(1) {
      Some(committer) => {
        cmd.env("GIT_COMMITTER_NAME", committer.name.clone())
          .env("GIT_COMMITTER_EMAIL", committer.email.clone())
          .arg("--signoff")
      }
      _ => cmd,
    };

    Ok(cmd)
  }

  fn get_active(&self) -> Result<Vec<String>> {
    self.config
      .get("active")
      .map(|active| active.split('+').map(|s| s.into()).collect())
  }

  pub fn rotate_active(&self) -> Result<()> {
    self.get_active().and_then(|active| {
      let mut inits: Vec<_> = active.iter().map(String::as_ref).collect();
      if !inits.is_empty() {
        let author = inits.remove(0);
        inits.push(author);
      }
      self.set_active(&inits[..])
    })
  }

  fn get_authors(&self, inits: &[&str]) -> Result<Vec<Author>> {
    let domain = try!(self.config.get("domain"));
    inits.iter()
      .map(|&init| {
        self.config
          .get(&format!("authors.{}", init))
          .chain_err(|| ErrorKind::AuthorNotFound(init.into()))
          .and_then(|raw| {
            if raw.is_empty() {
              return Err(ErrorKind::InvalidAuthor(raw).into());
            }

            Self::author(&domain, &raw)
          })
      })
      .collect()
  }

  fn author(domain: &str, raw: &str) -> Result<Author> {
    let split: Vec<_> = raw.split(';').collect();
    if split.len() < 2 {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let name = split[0].trim().to_string();
    if name.is_empty() {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let email_seed = split[1].trim();
    if email_seed.is_empty() {
      return Err(ErrorKind::InvalidAuthor(raw.into()).into());
    }

    let email = if email_seed.contains('@') {
      email_seed.into()
    } else {
      format!("{}@{}", email_seed, domain)
    };

    Ok(Author {
      name: name,
      email: email,
    })
  }
}

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

  use std::cell::RefCell;
  use std::collections::HashMap;

  use config::Config;
  use errors::*;

  #[test]
  fn get_authors_no_domain() {
    let config = MockConfig::new(&[("authors.jh", "James Holden; jholden")]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
  }

  #[test]
  fn get_authors() {
    let config =
      MockConfig::new(&[("domain", "rocinante.com"),
                        ("authors.jh", ""),
                        ("authors.nn", "Naomi Nagata"),
                        ("authors.ab", "Amos Burton; aburton"),
                        ("authors.ak", "Alex Kamal; akamal"),
                        ("authors.ca", "Chrisjen Avasarala;"),
                        ("authors.bd", "Bobbie Draper; bdraper@mars.mil"),
                        ("authors.jm", "Joe Miller; jmiller@starhelix.com")]);
    let gt = GitTogether { config: config };

    assert!(gt.get_authors(&["jh"]).is_err());
    assert!(gt.get_authors(&["nn"]).is_err());
    assert!(gt.get_authors(&["ca"]).is_err());
    assert!(gt.get_authors(&["jh", "bd"]).is_err());

    assert_eq!(gt.get_authors(&["ab", "ak"]).unwrap(),
               vec![Author {
                      name: "Amos Burton".into(),
                      email: "aburton@rocinante.com".into(),
                    },
                    Author {
                      name: "Alex Kamal".into(),
                      email: "akamal@rocinante.com".into(),
                    }]);
    assert_eq!(gt.get_authors(&["ab", "bd", "jm"]).unwrap(),
               vec![Author {
                      name: "Amos Burton".into(),
                      email: "aburton@rocinante.com".into(),
                    },
                    Author {
                      name: "Bobbie Draper".into(),
                      email: "bdraper@mars.mil".into(),
                    },
                    Author {
                      name: "Joe Miller".into(),
                      email: "jmiller@starhelix.com".into(),
                    }]);
  }

  #[test]
  fn set_active() {
    let config = MockConfig::new(&[("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let gt = GitTogether { config: config };

    gt.set_active(&["jh"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh"]);

    gt.set_active(&["jh", "nn"]).unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["jh", "nn"]);
  }

  #[test]
  fn rotate_active() {
    let config = MockConfig::new(&[("active", "jh+nn"),
                                   ("domain", "rocinante.com"),
                                   ("authors.jh", "James Holden; jholden"),
                                   ("authors.nn", "Naomi Nagata; nnagata")]);
    let gt = GitTogether { config: config };

    gt.rotate_active().unwrap();
    assert_eq!(gt.get_active().unwrap(), vec!["nn", "jh"]);
  }

  struct MockConfig {
    data: RefCell<HashMap<String, String>>,
  }

  impl MockConfig {
    fn new(data: &[(&str, &str)]) -> MockConfig {
      let data = data.iter()
        .map(|&(k, v)| (k.into(), v.into()))
        .collect();
      MockConfig { data: RefCell::new(data) }
    }
  }

  impl Config for MockConfig {
    fn get(&self, name: &str) -> Result<String> {
      self.data
        .borrow()
        .get(name.into())
        .cloned()
        .ok_or(format!("name not found: '{}'", name).into())
    }

    fn set(&self, name: &str, value: &str) -> Result<()> {
      self.data.borrow_mut().insert(name.into(), value.into());
      Ok(())
    }
  }
}
<|endoftext|>


<commit_before>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use devices::*;
use requests::*;
use values::Value;

/// An error produced by one of the APIs in this module.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Error {
    /// There is no such node connected to the Foxbox, even indirectly.
    NoSuchNode(NodeId),

    /// There is no such service connected to the Foxbox, even indirectly.
    NoSuchService(ServiceId),

    /// Attempting to set a value with the wrong type
    TypeError,
}

/// An event during watching.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum WatchEvent {
    /// A new value is available.
    Value {
        /// The service that sent the value.
        from: ServiceId,

        /// The actual value.
        value: Value
    },

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// removed. Payload is the id of the device that was removed.
    InputRemoved(ServiceId),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// added. Payload is the id of the device that was added.
    InputAdded(ServiceId),
}

/// A handle to the public API.
pub trait API: Send {
    /// Get the metadata on nodes matching some conditions.
    ///
    /// A call to `API::get_nodes(vec![req1, req2, ...])` will return
    /// the metadata on all nodes matching _either_ `req1` or `req2`
    /// or ...
    ///
    /// # REST API
    ///
    /// `GET /api/v1/nodes`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to a `Vec<NodeRequest>`. See
    /// the implementation of `NodeRequest` for details.
    ///
    /// ### Example
    ///
    /// Request all doors in the entrance (tags `door`, `entrance`)
    /// that support output service `OpenClose`
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door"],
    ///   "inputs": [
    ///     {
    ///       "kind": {
    ///         "Exactly": {
    ///           "OpenClose": []
    ///         }
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    ///
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing an array of `Node`. See the implementation
    /// of `Node` for details.
    ///
    /// ### Example
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door", "somevendor"],
    ///   "id: "some-node-id",
    ///   "inputs": [],
    ///   "outputs": [
    ///     "tags": [...],
    ///     "id": "some-service-id",
    ///     "node": "some-node-id",
    ///     "last_seen": "some-date",
    ///     "mechanism": {
    ///       "Output":  {
    ///         "kind": {
    ///           "OnOff": []
    ///         },
    ///         "push": [5000],
    ///         "updated": "some-date",
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    fn get_nodes(&self, &Vec<NodeRequest>) -> Vec<Node>;

    /// Label a set of nodes with a set of tags.
    ///
    /// A call to `API::put_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the nodes matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON string representing a number.
    fn put_node_tag(&self, set: &Vec<NodeRequest>, tags: &Vec<String>) -> usize;

    /// Remove a set of tags from a set of nodes.
    ///
    /// A call to `API::delete_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the nodes matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_node_tag(&self, set: &Vec<NodeRequest>, tags: String) -> usize;

    /// Get a list of inputs matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services`
    fn get_input_services(&self, &Vec<InputRequest>) -> Vec<Service<Input>>;
    fn get_output_services(&self, &Vec<OutputRequest>) -> Vec<Service<Output>>;

    /// Label a set of services with a set of tags.
    ///
    /// A call to `API::put_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the services matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn put_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn put_output_tag(&self, &Vec<OutputRequest>, &Vec<String>) -> usize;

    /// Remove a set of tags from a set of services.
    ///
    /// A call to `API::delete_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the services matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn delete_output_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;

    /// Read the latest value from a set of services
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services/value`
    fn get_service_value(&self, &Vec<InputRequest>) -> Vec<(ServiceId, Result<Value, Error>)>;

    /// Send one value to a set of services
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/value`
    fn put_service_value(&self, &Vec<OutputRequest>, Value) -> Vec<(ServiceId, Result<(), Error>)>;

    /// Watch for any change
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/services/watch`
    fn register_service_watch(&self, Vec<WatchOptions>, cb: Box<FnMut(WatchEvent) + Send + 'static>) -> Self::WatchGuard;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}

/// Options for watching changes in one or more services.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct WatchOptions {
    /// The set of inputs to watch. Note that the actual inputs in the
    /// set may change over time.
    source: InputRequest,

    /// If `true`, watch as new values become available.
    should_watch_values: bool,

    /// If `true`, watch as nodes are connected/disconnected.
    should_watch_topology: bool,
}

impl WatchOptions {
    pub fn new() -> Self {
        WatchOptions {
            source: InputRequest::new(),
            should_watch_values: false,
            should_watch_topology: false,
        }
    }

    /// Restrict to input services in a given set.
    ///
    /// Also note that the actual input services that are part of the
    /// set may change with time, for instance if devices are added
    /// ore removed.  The request _is live_, i.e. the service watch
    /// will continue watching any input services that match `req`.
    pub fn with_inputs(self, req: InputRequest) -> Self {
        WatchOptions {
            source: self.source.and(req),
            ..self
        }
    }

    pub fn with_watch_values(self, should: bool) -> Self {
        WatchOptions {
            should_watch_values: should,
            ..self
        }
    }

    pub fn with_watch_topology(self, should: bool) -> Self {
        WatchOptions {
            should_watch_topology: should,
            ..self
        }
    }
}
<commit_msg>api::WatchOptions is now semi-public<commit_after>//!
//! The API for communicating with devices.
//!
//! This API is provided as Traits to be implemented:
//!
//! - by the low-level layers of the FoxBox, including the adapters;
//! - by test suites and tools that need to simulate connected devices.
//!
//! In turn, this API is used to implement:
//!
//! - the public-facing REST and WebSocket API;
//! - the rules API (ThinkerBell).
//!
//!

use devices::*;
use requests::*;
use values::Value;

/// An error produced by one of the APIs in this module.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum Error {
    /// There is no such node connected to the Foxbox, even indirectly.
    NoSuchNode(NodeId),

    /// There is no such service connected to the Foxbox, even indirectly.
    NoSuchService(ServiceId),

    /// Attempting to set a value with the wrong type
    TypeError,
}

/// An event during watching.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum WatchEvent {
    /// A new value is available.
    Value {
        /// The service that sent the value.
        from: ServiceId,

        /// The actual value.
        value: Value
    },

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// removed. Payload is the id of the device that was removed.
    InputRemoved(ServiceId),

    /// The set of devices being watched has changed, typically either
    /// because a tag was edited or because a device was
    /// added. Payload is the id of the device that was added.
    InputAdded(ServiceId),
}

/// A handle to the public API.
pub trait API: Send {
    /// Get the metadata on nodes matching some conditions.
    ///
    /// A call to `API::get_nodes(vec![req1, req2, ...])` will return
    /// the metadata on all nodes matching _either_ `req1` or `req2`
    /// or ...
    ///
    /// # REST API
    ///
    /// `GET /api/v1/nodes`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to a `Vec<NodeRequest>`. See
    /// the implementation of `NodeRequest` for details.
    ///
    /// ### Example
    ///
    /// Request all doors in the entrance (tags `door`, `entrance`)
    /// that support output service `OpenClose`
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door"],
    ///   "inputs": [
    ///     {
    ///       "kind": {
    ///         "Exactly": {
    ///           "OpenClose": []
    ///         }
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    ///
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing an array of `Node`. See the implementation
    /// of `Node` for details.
    ///
    /// ### Example
    ///
    /// ```json
    /// [{
    ///   "tags": ["entrance", "door", "somevendor"],
    ///   "id: "some-node-id",
    ///   "inputs": [],
    ///   "outputs": [
    ///     "tags": [...],
    ///     "id": "some-service-id",
    ///     "node": "some-node-id",
    ///     "last_seen": "some-date",
    ///     "mechanism": {
    ///       "Output":  {
    ///         "kind": {
    ///           "OnOff": []
    ///         },
    ///         "push": [5000],
    ///         "updated": "some-date",
    ///       }
    ///     }
    ///   ]
    /// }]
    /// ```
    fn get_nodes(&self, &Vec<NodeRequest>) -> Vec<Node>;

    /// Label a set of nodes with a set of tags.
    ///
    /// A call to `API::put_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the nodes matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON string representing a number.
    fn put_node_tag(&self, set: &Vec<NodeRequest>, tags: &Vec<String>) -> usize;

    /// Remove a set of tags from a set of nodes.
    ///
    /// A call to `API::delete_node_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the nodes matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of nodes
    /// matching any of the requests.
    ///
    /// Some of the nodes may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if nodes
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/nodes/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<NodeRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_node_tag(&self, set: &Vec<NodeRequest>, tags: String) -> usize;

    /// Get a list of inputs matching some conditions
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services`
    fn get_input_services(&self, &Vec<InputRequest>) -> Vec<Service<Input>>;
    fn get_output_services(&self, &Vec<OutputRequest>) -> Vec<Service<Output>>;

    /// Label a set of services with a set of tags.
    ///
    /// A call to `API::put_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will label all the services matching _either_ `req1` or
    /// `req2` or ... with `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may already be labelled with `tag1`, or
    /// `tag2`, ... They will not change state. They are counted in
    /// the resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn put_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn put_output_tag(&self, &Vec<OutputRequest>, &Vec<String>) -> usize;

    /// Remove a set of tags from a set of services.
    ///
    /// A call to `API::delete_{input, output}_tag(vec![req1, req2, ...], vec![tag1,
    /// ...])` will remove from all the services matching _either_ `req1` or
    /// `req2` or ... all of the tags `tag1`, ... and return the number of services
    /// matching any of the requests.
    ///
    /// Some of the services may not be labelled with `tag1`, or `tag2`,
    /// ... They will not change state. They are counted in the
    /// resulting `usize` nevertheless.
    ///
    /// Note that this call is _not live_. In other words, if services
    /// are added after the call, they will not be affected.
    ///
    /// # REST API
    ///
    /// `DELETE /api/v1/services/tag`
    ///
    /// ## Inputs
    ///
    /// Any JSON that can be deserialized to
    ///
    /// ```ignore
    /// {
    ///   set: Vec<InputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    /// or
    /// ```ignore
    /// {
    ///   set: Vec<OutputRequest>,
    ///   tags: Vec<String>,
    /// }
    /// ```
    ///
    /// ## Errors
    ///
    /// In case of syntax error, Error 400, accompanied with a
    /// somewhat human-readable JSON string detailing the error.
    ///
    /// ## Success
    ///
    /// A JSON representing a number.
    fn delete_input_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;
    fn delete_output_tag(&self, &Vec<InputRequest>, &Vec<String>) -> usize;

    /// Read the latest value from a set of services
    ///
    /// # REST API
    ///
    /// `GET /api/v1/services/value`
    fn get_service_value(&self, &Vec<InputRequest>) -> Vec<(ServiceId, Result<Value, Error>)>;

    /// Send one value to a set of services
    ///
    /// # REST API
    ///
    /// `POST /api/v1/services/value`
    fn put_service_value(&self, &Vec<OutputRequest>, Value) -> Vec<(ServiceId, Result<(), Error>)>;

    /// Watch for any change
    ///
    /// # WebSocket API
    ///
    /// `/api/v1/services/watch`
    fn register_service_watch(&self, Vec<WatchOptions>, cb: Box<FnMut(WatchEvent) + Send + 'static>) -> Self::WatchGuard;

    /// A value that causes a disconnection once it is dropped.
    type WatchGuard;
}

/// Options for watching changes in one or more services.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct WatchOptions {
    /// The set of inputs to watch. Note that the actual inputs in the
    /// set may change over time.
    pub source: InputRequest,

    /// If `true`, watch as new values become available.
    pub should_watch_values: bool,

    /// If `true`, watch as nodes are connected/disconnected.
    pub should_watch_topology: bool,

    /// Make sure that we can't instantiate from another crate.
    #[serde(default, skip_serializing)]
    private: (),
}

impl WatchOptions {
    pub fn new() -> Self {
        WatchOptions {
            source: InputRequest::new(),
            should_watch_values: false,
            should_watch_topology: false,
            private: (),
        }
    }

    /// Restrict to input services in a given set.
    ///
    /// Also note that the actual input services that are part of the
    /// set may change with time, for instance if devices are added
    /// ore removed.  The request _is live_, i.e. the service watch
    /// will continue watching any input services that match `req`.
    pub fn with_inputs(self, req: InputRequest) -> Self {
        WatchOptions {
            source: self.source.and(req),
            ..self
        }
    }

    pub fn with_watch_values(self, should: bool) -> Self {
        WatchOptions {
            should_watch_values: should,
            ..self
        }
    }

    pub fn with_watch_topology(self, should: bool) -> Self {
        WatchOptions {
            should_watch_topology: should,
            ..self
        }
    }
}
<|endoftext|>


<commit_before>use std::io::{File, Open, Read, Append, ReadWrite, IoResult,
    SeekSet, SeekCur};

#[deriving(Show)]
struct BMPid {
    magic1: u8,
    magic2: u8
}

impl BMPid {
    pub fn new() -> BMPid {
        BMPid {
            magic1: 0x42 /* 'B' */,
            magic2: 0x4D /* 'M' */
        }
    }
}

#[deriving(Show)]
struct BMPheader {
    fileSize: u32,
    creator1: u16,
    creator2: u16,
    pixelOffset: u32
}

impl BMPheader {
    pub fn new(width: u32, height: u32) -> BMPheader {
        BMPheader {
            fileSize: width * height * 4 /* bytes per pixel */ + 54 /* Header size */,
            creator1: 0 /* Unused */,
            creator2: 0 /* Unused */,
            pixelOffset: 54
        }
    }
}

#[deriving(Show)]
struct BMPDIBheader {
    headerSize: u32,
    width: i32,
    height: i32,
    numPlanes: u16,
    bitPrPixel: u16,
    compressType: u32,
    dataSize: u32,
    hres: i32,
    vres: i32,
    numColors: u32,
    numImpColors: u32,
}

impl BMPDIBheader {
    pub fn new(width: i32, height: i32) -> BMPDIBheader {
        let rowSize = ((24.0 * width as f32 + 31.0) / 32.0).floor() as u32 * 4;
        let pixelArraySize = rowSize * height as u32;

        BMPDIBheader {
            headerSize: 40,
            width: width,
            height: height,
            numPlanes: 1,
            bitPrPixel: 24,
            compressType: 0,
            dataSize: pixelArraySize,
            hres: 0x100,
            vres: 0x100,
            numColors: 0,
            numImpColors: 0
        }
    }
}

#[deriving(Show, Eq)]
pub struct BMPpixel {
    r: u8,
    g: u8,
    b: u8
}

pub static RED: BMPpixel = BMPpixel{ r: 255, g: 0, b: 0 };
pub static GREEN: BMPpixel = BMPpixel{ r: 0, g: 255, b: 0 };
pub static BLUE: BMPpixel = BMPpixel{ r: 0, g: 0, b: 255 };
pub static WHITE: BMPpixel = BMPpixel{ r: 255, g: 255, b: 255 };

pub struct BMPimage {
    magic: BMPid,
    header: BMPheader,
    dibHeader: BMPDIBheader,
    width: i32,
    height: i32,
    padding: i32,
    paddingData: [u8, .. 4],
    data: Option<Vec<BMPpixel>>
}

impl BMPimage {
    pub fn new(width: i32, height: i32) -> BMPimage {
        let mut data = Vec::with_capacity((width * height) as uint);
        for _ in range(0, width * height) {
            data.push(BMPpixel { r: 0, g: 0, b: 0});
        }
        BMPimage {
            magic: BMPid::new(),
            header: BMPheader::new(width as u32, height as u32),
            dibHeader: BMPDIBheader::new(width, height),
            width: width,
            height: height,
            padding: width % 4,
            paddingData: [0, 0, 0, 0],
            data: Some(data)
        }
    }

    pub fn set_pixel(&mut self, x: uint, y: uint, val: BMPpixel) {
        if x < self.width as uint && y < self.height as uint {
            match self.data {
                Some(ref mut data) => data.insert(y * (self.width as uint) + x, val),
                None => fail!("Image has no data")
            }
        } else {
            fail!("Index out of bounds: ({}, {})", x, y);
        }
    }

    pub fn get_pixel<'a>(&'a self, x: uint, y: uint) -> &'a BMPpixel {
        if x < self.width as uint && y < self.height as uint {
            match self.data {
                Some(ref data) => data.get(y * (self.width as uint) + x),
                None => fail!("Image has no data")
            }
        } else {
            fail!("Index out of bounds: ({}, {})", x, y);
        }
    }

    fn write_header(&self, name: &str) {
        let mut f = File::create(&Path::new(name));
        let id = self.magic;
        access(f.write([id.magic1, id.magic2]));

        let header = self.header;
        access(f.write_le_u32(header.fileSize));
        access(f.write_le_u16(header.creator1));
        access(f.write_le_u16(header.creator2));
        access(f.write_le_u32(header.pixelOffset));

        let dib_header = self.dibHeader;
        access(f.write_le_u32(dib_header.headerSize));
        access(f.write_le_i32(dib_header.width));
        access(f.write_le_i32(dib_header.height));
        access(f.write_le_u16(dib_header.numPlanes));
        access(f.write_le_u16(dib_header.bitPrPixel));
        access(f.write_le_u32(dib_header.compressType));
        access(f.write_le_u32(dib_header.dataSize));
        access(f.write_le_i32(dib_header.hres));
        access(f.write_le_i32(dib_header.vres));
        access(f.write_le_u32(dib_header.numColors));
        access(f.write_le_u32(dib_header.numImpColors));
    }

    pub fn save(&self, name: &str) {
        self.write_header(name);

        let mut file = match File::open_mode(&Path::new(name), Append, ReadWrite) {
            Ok(f) => f,
            Err(e) => fail!("File error: {}", e),
        };

        match self.data {
            Some(ref data) => {
                for y in range(0, self.height) {
                    for x in range(0, self.width) {
                        let index: uint = (y * self.width + x) as uint;
                        let p = data.get(index as uint);
                        access(file.write([p.r, p.g, p.b]));
                    }
                    let p = self.paddingData.slice(0, self.padding as uint);
                    access(file.write(p));
                }
            },
            None => fail!("Image has no data")
        }
    }

    fn read_BMP_id(f: &mut File) -> Option<BMPid> {
        match f.eof() {
            false =>
                Some(BMPid {
                    magic1: access(f.read_byte()),
                    magic2: access(f.read_byte())
                }),
            true => None
        }
    }

    fn read_BMP_header(f: &mut File) -> Option<BMPheader> {
        match f.eof() {
            false =>
                Some(BMPheader {
                    fileSize: access(f.read_le_u32()),
                    creator1: access(f.read_le_u16()),
                    creator2: access(f.read_le_u16()),
                    pixelOffset: access(f.read_le_u32())
                }),
            true => None
        }
    }

    fn read_BMP_dib_header(f: &mut File) -> Option<BMPDIBheader> {
        match f.eof() {
            false =>
                Some(BMPDIBheader {
                    headerSize: access(f.read_le_u32()),
                    width: access(f.read_le_i32()),
                    height: access(f.read_le_i32()),
                    numPlanes: access(f.read_le_u16()),
                    bitPrPixel: access(f.read_le_u16()),
                    compressType: access(f.read_le_u32()),
                    dataSize: access(f.read_le_u32()),
                    hres: access(f.read_le_i32()),
                    vres: access(f.read_le_i32()),
                    numColors: access(f.read_le_u32()),
                    numImpColors: access(f.read_le_u32()),
                }),
            true => None
        }
    }

    fn read_pixel(f: &mut File) -> BMPpixel {
        BMPpixel {
            r: access(f.read_byte()),
            g: access(f.read_byte()),
            b: access(f.read_byte()),
        }
    }

    fn read_image_data(f: &mut File, dh: BMPDIBheader, offset: u32, padding: i64) -> Option<Vec<BMPpixel>> {
        let dataSize = ((24.0 * dh.width as f32 + 31.0) / 32.0).floor() as u32
            * 4 * dh.height as u32;

        if dataSize == dh.dataSize {
            let mut data = Vec::new();
            // seek until data
            access(f.seek(offset as i64, SeekSet));
            // read pixels until padding
            for _ in range(0, dh.height) {
                for _ in range(0, dh.width) {
                   data.push(BMPimage::read_pixel(f));
                }
                // seek padding
                access(f.seek(padding, SeekCur));
            }
            Some(data)
        } else {
            None
        }
    }

    pub fn open(name: &str) -> BMPimage {
        let mut f = match File::open_mode(&Path::new(name), Open, Read) {
            Ok(f) => f,
            Err(e) => fail!("File error: {}", e),
        };

        let id = match BMPimage::read_BMP_id(&mut f) {
            Some(id) => id,
            None => fail!("File is not a bitmap")
        };
        assert_eq!(id.magic1, 0x42);
        assert_eq!(id.magic2, 0x4D);

        let header = match BMPimage::read_BMP_header(&mut f) {
            Some(header) => header,
            None => fail!("Header of bitmap is not valid")
        };

        let dibHeader = match BMPimage::read_BMP_dib_header(&mut f) {
            Some(dibHeader) => dibHeader,
            None => fail!("DIB header of bitmap is not valid")
        };

        let padding = dibHeader.width % 4;
        BMPimage {
            magic: id,
            header: header,
            dibHeader: dibHeader,
            width: dibHeader.width,
            height: dibHeader.height,
            padding: padding,
            paddingData: [0, 0, 0, 0],
            data: BMPimage::read_image_data(&mut f, dibHeader, header.pixelOffset, padding as i64)
        }
    }
}

fn access<T>(res: IoResult<T>) -> T {
    match res {
        Err(e) => fail!("File error: {}", e),
        Ok(r) => r
    }
}

#[cfg(test)]
mod tests {
    use std::mem::size_of;
    use std::io::{File, SeekSet};

    use BMPid;
    use BMPheader;
    use BMPDIBheader;
    use BMPimage;
    use BMPpixel;
    use RED;
    use GREEN;
    use BLUE;
    use WHITE;

    #[test]
    fn size_of_bmp_header_is_54_bytes() {
        let bmp_magic_size = size_of::<BMPid>();
        let bmp_header_size = size_of::<BMPheader>();
        let bmp_bip_header_size = size_of::<BMPDIBheader>();

        assert_eq!(2,  bmp_magic_size);
        assert_eq!(12, bmp_header_size);
        assert_eq!(40, bmp_bip_header_size);
    }

    #[test]
    fn size_of_4pixel_bmp_image_is_70_bytes() {
        let path_wd = Path::new("src/test/rgbw.bmp");
        let size = path_wd.stat().unwrap().size as i32;
        assert_eq!(70, size);
    }

    fn verify_test_bmp_image(img: BMPimage) {
        let header = img.header;
        assert_eq!(70, header.fileSize);
        assert_eq!(0,  header.creator1);
        assert_eq!(0,  header.creator2);
        assert_eq!(54, header.pixelOffset);

        let dibHeader = img.dibHeader;
        assert_eq!(40,    dibHeader.headerSize);
        assert_eq!(2,     dibHeader.width);
        assert_eq!(2,     dibHeader.height);
        assert_eq!(1,     dibHeader.numPlanes);
        assert_eq!(24,    dibHeader.bitPrPixel);
        assert_eq!(0,     dibHeader.compressType);
        assert_eq!(16,    dibHeader.dataSize);
        assert_eq!(0x100, dibHeader.hres);
        assert_eq!(0x100, dibHeader.vres);
        assert_eq!(0,     dibHeader.numColors);
        assert_eq!(0,     dibHeader.numImpColors);

        assert_eq!(2, img.padding);
    }

    #[test]
    fn can_read_bmp_image() {
        let bmpImg = BMPimage::open("src/test/rgbw.bmp");
        verify_test_bmp_image(bmpImg);
    }

    #[test]
    fn can_read_image_data() {
        let mut f = match File::open(&Path::new("src/test/rgbw.bmp"), ) {
            Ok(file) => file,
            Err(e) => fail!("File error: {}", e)
        };
        assert_eq!(0x42, f.read_byte().unwrap());
        assert_eq!(0x4D, f.read_byte().unwrap());

        match f.seek(54, SeekSet) {
            Ok(_) => (),
            Err(e) => fail!("Seek error: {}", e)
        }

        let pixel = BMPpixel {
            r: f.read_byte().unwrap(),
            g: f.read_byte().unwrap(),
            b: f.read_byte().unwrap()
        };

        assert_eq!(pixel, RED);
    }

    #[test]
    fn can_create_bmp_file() {
        let mut bmp = BMPimage::new(2, 2);
        bmp.set_pixel(0, 0, RED);
        bmp.set_pixel(1, 0, WHITE);
        bmp.set_pixel(0, 1, BLUE);
        bmp.set_pixel(1, 1, GREEN);
        bmp.save("src/test/rgbw_test.bmp");

        let bmpImg = BMPimage::open("src/test/rgbw_test.bmp");
        verify_test_bmp_image(bmpImg);
    }

    #[test]
    fn can_read_entire_bmp_image() {
        let bmpImg = BMPimage::open("src/test/rgbw.bmp");
        assert!(None != bmpImg.data);

        assert_eq!(bmpImg.get_pixel(0, 0), &RED);
        assert_eq!(bmpImg.get_pixel(1, 0), &WHITE);
        assert_eq!(bmpImg.get_pixel(0, 1), &BLUE);
        assert_eq!(bmpImg.get_pixel(1, 1), &GREEN);
    }
}
<commit_msg>Renamed fields to snake case and used index instead of deprecated get<commit_after>use std::io::{File, Open, Read, Append, ReadWrite, IoResult,
    SeekSet, SeekCur};

#[deriving(Show)]
struct BMPid {
    magic1: u8,
    magic2: u8
}

impl BMPid {
    pub fn new() -> BMPid {
        BMPid {
            magic1: 0x42 /* 'B' */,
            magic2: 0x4D /* 'M' */
        }
    }
}

#[deriving(Show)]
struct BMPheader {
    file_size: u32,
    creator1: u16,
    creator2: u16,
    pixel_offset: u32
}

impl BMPheader {
    pub fn new(width: u32, height: u32) -> BMPheader {
        BMPheader {
            file_size: width * height * 4 /* bytes per pixel */ + 54 /* Header size */,
            creator1: 0 /* Unused */,
            creator2: 0 /* Unused */,
            pixel_offset: 54
        }
    }
}

#[deriving(Show)]
struct BMPDIBheader {
    header_size: u32,
    width: i32,
    height: i32,
    num_planes: u16,
    bits_per_pixel: u16,
    compress_type: u32,
    data_size: u32,
    hres: i32,
    vres: i32,
    num_colors: u32,
    num_imp_colors: u32,
}

impl BMPDIBheader {
    pub fn new(width: i32, height: i32) -> BMPDIBheader {
        let row_size = ((24.0 * width as f32 + 31.0) / 32.0).floor() as u32 * 4;
        let pixel_array_size = row_size * height as u32;

        BMPDIBheader {
            header_size: 40,
            width: width,
            height: height,
            num_planes: 1,
            bits_per_pixel: 24,
            compress_type: 0,
            data_size: pixel_array_size,
            hres: 0x100,
            vres: 0x100,
            num_colors: 0,
            num_imp_colors: 0
        }
    }
}

#[deriving(Show, PartialEq)]
pub struct BMPpixel {
    r: u8,
    g: u8,
    b: u8
}

pub static RED: BMPpixel = BMPpixel{ r: 255, g: 0, b: 0 };
pub static GREEN: BMPpixel = BMPpixel{ r: 0, g: 255, b: 0 };
pub static BLUE: BMPpixel = BMPpixel{ r: 0, g: 0, b: 255 };
pub static WHITE: BMPpixel = BMPpixel{ r: 255, g: 255, b: 255 };

pub struct BMPimage {
    magic: BMPid,
    header: BMPheader,
    dib_header: BMPDIBheader,
    width: i32,
    height: i32,
    padding: i32,
    padding_data: [u8, .. 4],
    data: Option<Vec<BMPpixel>>
}

impl BMPimage {
    pub fn new(width: i32, height: i32) -> BMPimage {
        let mut data = Vec::with_capacity((width * height) as uint);
        for _ in range(0, width * height) {
            data.push(BMPpixel { r: 0, g: 0, b: 0});
        }
        BMPimage {
            magic: BMPid::new(),
            header: BMPheader::new(width as u32, height as u32),
            dib_header: BMPDIBheader::new(width, height),
            width: width,
            height: height,
            padding: width % 4,
            padding_data: [0, 0, 0, 0],
            data: Some(data)
        }
    }

    pub fn set_pixel(&mut self, x: uint, y: uint, val: BMPpixel) {
        if x < self.width as uint && y < self.height as uint {
            match self.data {
                Some(ref mut data) => data.insert(y * (self.width as uint) + x, val),
                None => fail!("Image has no data")
            }
        } else {
            fail!("Index out of bounds: ({}, {})", x, y);
        }
    }

    pub fn get_pixel<'a>(&'a self, x: uint, y: uint) -> &'a BMPpixel {
        if x < self.width as uint && y < self.height as uint {
            match self.data {
                Some(ref data) => data.index(&(y * (self.width as uint) + x)),
                None => fail!("Image has no data")
            }
        } else {
            fail!("Index out of bounds: ({}, {})", x, y);
        }
    }

    fn write_header(&self, name: &str) {
        let mut f = File::create(&Path::new(name));
        let id = self.magic;
        access(f.write([id.magic1, id.magic2]));

        let header = self.header;
        access(f.write_le_u32(header.file_size));
        access(f.write_le_u16(header.creator1));
        access(f.write_le_u16(header.creator2));
        access(f.write_le_u32(header.pixel_offset));

        let dib_header = self.dib_header;
        access(f.write_le_u32(dib_header.header_size));
        access(f.write_le_i32(dib_header.width));
        access(f.write_le_i32(dib_header.height));
        access(f.write_le_u16(dib_header.num_planes));
        access(f.write_le_u16(dib_header.bits_per_pixel));
        access(f.write_le_u32(dib_header.compress_type));
        access(f.write_le_u32(dib_header.data_size));
        access(f.write_le_i32(dib_header.hres));
        access(f.write_le_i32(dib_header.vres));
        access(f.write_le_u32(dib_header.num_colors));
        access(f.write_le_u32(dib_header.num_imp_colors));
    }

    pub fn save(&self, name: &str) {
        self.write_header(name);

        let mut file = match File::open_mode(&Path::new(name), Append, ReadWrite) {
            Ok(f) => f,
            Err(e) => fail!("File error: {}", e),
        };

        match self.data {
            Some(ref data) => {
                for y in range(0, self.height) {
                    for x in range(0, self.width) {
                        let index: uint = (y * self.width + x) as uint;
                        let p = data[index as uint];
                        access(file.write([p.r, p.g, p.b]));
                    }
                    let p = self.padding_data.slice(0, self.padding as uint);
                    access(file.write(p));
                }
            },
            None => fail!("Image has no data")
        }
    }

    fn read_bmp_id(f: &mut File) -> Option<BMPid> {
        match f.eof() {
            false =>
                Some(BMPid {
                    magic1: access(f.read_byte()),
                    magic2: access(f.read_byte())
                }),
            true => None
        }
    }

    fn read_bmp_header(f: &mut File) -> Option<BMPheader> {
        match f.eof() {
            false =>
                Some(BMPheader {
                    file_size: access(f.read_le_u32()),
                    creator1: access(f.read_le_u16()),
                    creator2: access(f.read_le_u16()),
                    pixel_offset: access(f.read_le_u32())
                }),
            true => None
        }
    }

    fn read_bmp_dib_header(f: &mut File) -> Option<BMPDIBheader> {
        match f.eof() {
            false =>
                Some(BMPDIBheader {
                    header_size: access(f.read_le_u32()),
                    width: access(f.read_le_i32()),
                    height: access(f.read_le_i32()),
                    num_planes: access(f.read_le_u16()),
                    bits_per_pixel: access(f.read_le_u16()),
                    compress_type: access(f.read_le_u32()),
                    data_size: access(f.read_le_u32()),
                    hres: access(f.read_le_i32()),
                    vres: access(f.read_le_i32()),
                    num_colors: access(f.read_le_u32()),
                    num_imp_colors: access(f.read_le_u32()),
                }),
            true => None
        }
    }

    fn read_pixel(f: &mut File) -> BMPpixel {
        BMPpixel {
            r: access(f.read_byte()),
            g: access(f.read_byte()),
            b: access(f.read_byte()),
        }
    }

    fn read_image_data(f: &mut File, dh: BMPDIBheader, offset: u32, padding: i64) -> Option<Vec<BMPpixel>> {
        let data_size = ((24.0 * dh.width as f32 + 31.0) / 32.0).floor() as u32
            * 4 * dh.height as u32;

        if data_size == dh.data_size {
            let mut data = Vec::new();
            // seek until data
            access(f.seek(offset as i64, SeekSet));
            // read pixels until padding
            for _ in range(0, dh.height) {
                for _ in range(0, dh.width) {
                   data.push(BMPimage::read_pixel(f));
                }
                // seek padding
                access(f.seek(padding, SeekCur));
            }
            Some(data)
        } else {
            None
        }
    }

    pub fn open(name: &str) -> BMPimage {
        let mut f = match File::open_mode(&Path::new(name), Open, Read) {
            Ok(f) => f,
            Err(e) => fail!("File error: {}", e),
        };

        let id = match BMPimage::read_bmp_id(&mut f) {
            Some(id) => id,
            None => fail!("File is not a bitmap")
        };
        assert_eq!(id.magic1, 0x42);
        assert_eq!(id.magic2, 0x4D);

        let header = match BMPimage::read_bmp_header(&mut f) {
            Some(header) => header,
            None => fail!("Header of bitmap is not valid")
        };

        let dib_header = match BMPimage::read_bmp_dib_header(&mut f) {
            Some(dib_header) => dib_header,
            None => fail!("DIB header of bitmap is not valid")
        };

        let padding = dib_header.width % 4;
        BMPimage {
            magic: id,
            header: header,
            dib_header: dib_header,
            width: dib_header.width,
            height: dib_header.height,
            padding: padding,
            padding_data: [0, 0, 0, 0],
            data: BMPimage::read_image_data(&mut f, dib_header, header.pixel_offset, padding as i64)
        }
    }
}

fn access<T>(res: IoResult<T>) -> T {
    match res {
        Err(e) => fail!("File error: {}", e),
        Ok(r) => r
    }
}

#[cfg(test)]
mod tests {
    use std::mem::size_of;
    use std::io::{File, SeekSet};

    use BMPid;
    use BMPheader;
    use BMPDIBheader;
    use BMPimage;
    use BMPpixel;
    use RED;
    use GREEN;
    use BLUE;
    use WHITE;

    #[test]
    fn size_of_bmp_header_is_54_bytes() {
        let bmp_magic_size = size_of::<BMPid>();
        let bmp_header_size = size_of::<BMPheader>();
        let bmp_bip_header_size = size_of::<BMPDIBheader>();

        assert_eq!(2,  bmp_magic_size);
        assert_eq!(12, bmp_header_size);
        assert_eq!(40, bmp_bip_header_size);
    }

    #[test]
    fn size_of_4pixel_bmp_image_is_70_bytes() {
        let path_wd = Path::new("src/test/rgbw.bmp");
        let size = path_wd.stat().unwrap().size as i32;
        assert_eq!(70, size);
    }

    fn verify_test_bmp_image(img: BMPimage) {
        let header = img.header;
        assert_eq!(70, header.file_size);
        assert_eq!(0,  header.creator1);
        assert_eq!(0,  header.creator2);

        let dib_header = img.dib_header;
        assert_eq!(54, header.pixel_offset);
        assert_eq!(40,    dib_header.header_size);
        assert_eq!(2,     dib_header.width);
        assert_eq!(2,     dib_header.height);
        assert_eq!(1,     dib_header.num_planes);
        assert_eq!(24,    dib_header.bits_per_pixel);
        assert_eq!(0,     dib_header.compress_type);
        assert_eq!(16,    dib_header.data_size);
        assert_eq!(0x100, dib_header.hres);
        assert_eq!(0x100, dib_header.vres);
        assert_eq!(0,     dib_header.num_colors);
        assert_eq!(0,     dib_header.num_imp_colors);

        assert_eq!(2, img.padding);
    }

    #[test]
    fn can_read_bmp_image() {
        let bmpImg = BMPimage::open("src/test/rgbw.bmp");
        verify_test_bmp_image(bmpImg);
    }

    #[test]
    fn can_read_image_data() {
        let mut f = match File::open(&Path::new("src/test/rgbw.bmp"), ) {
            Ok(file) => file,
            Err(e) => fail!("File error: {}", e)
        };
        assert_eq!(0x42, f.read_byte().unwrap());
        assert_eq!(0x4D, f.read_byte().unwrap());

        match f.seek(54, SeekSet) {
            Ok(_) => (),
            Err(e) => fail!("Seek error: {}", e)
        }

        let pixel = BMPpixel {
            r: f.read_byte().unwrap(),
            g: f.read_byte().unwrap(),
            b: f.read_byte().unwrap()
        };

        assert_eq!(pixel, RED);
    }

    #[test]
    fn can_create_bmp_file() {
        let mut bmp = BMPimage::new(2, 2);
        bmp.set_pixel(0, 0, RED);
        bmp.set_pixel(1, 0, WHITE);
        bmp.set_pixel(0, 1, BLUE);
        bmp.set_pixel(1, 1, GREEN);
        bmp.save("src/test/rgbw_test.bmp");

        let bmpImg = BMPimage::open("src/test/rgbw_test.bmp");
        verify_test_bmp_image(bmpImg);
    }

    #[test]
    fn can_read_entire_bmp_image() {
        let bmpImg = BMPimage::open("src/test/rgbw.bmp");
        assert!(None != bmpImg.data);

        assert_eq!(bmpImg.get_pixel(0, 0), &RED);
        assert_eq!(bmpImg.get_pixel(1, 0), &WHITE);
        assert_eq!(bmpImg.get_pixel(0, 1), &BLUE);
        assert_eq!(bmpImg.get_pixel(1, 1), &GREEN);
    }
}
<|endoftext|>


<commit_before>use crate::config::{Config, Solver};
use crate::heuristic;
use crate::patterns::Patterns;
use oppai_field::field::{self, Field};
use oppai_field::player::Player;
use oppai_field::zobrist::Zobrist;
use oppai_minimax::minimax::Minimax;
use oppai_uct::uct::UctRoot;
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use std::{cmp, sync::Arc, time::Duration};

const BOT_STR: &str = "bot";

const MIN_COMPLEXITY: u32 = 0;

const MAX_COMPLEXITY: u32 = 100;

const MIN_UCT_ITERATIONS: usize = 0;

const MAX_UCT_ITERATIONS: usize = 500_000;

const MIN_MINIMAX_DEPTH: u32 = 0;

const MAX_MINIMAX_DEPTH: u32 = 12;

pub struct Bot {
  rng: XorShiftRng,
  patterns: Arc<Patterns>,
  field: Field,
  uct: UctRoot,
  minimax: Minimax,
  config: Config,
}

impl Bot {
  pub fn new(width: u32, height: u32, seed: u64, patterns: Arc<Patterns>, config: Config) -> Bot {
    info!(
      target: BOT_STR,
      "Initialization with width {0}, height {1}, seed {2}.", width, height, seed
    );
    let length = field::length(width, height);
    let seed_array = [
      2,
      (seed & 0xff) as u8,
      3,
      ((seed >> 8) & 0xff) as u8,
      5,
      ((seed >> 16) & 0xff) as u8,
      7,
      ((seed >> 24) & 0xff) as u8,
      11,
      ((seed >> 32) & 0xff) as u8,
      13,
      ((seed >> 40) & 0xff) as u8,
      17,
      ((seed >> 48) & 0xff) as u8,
      19,
      ((seed >> 56) & 0xff) as u8,
    ];
    let mut rng = XorShiftRng::from_seed(seed_array);
    let zobrist = Arc::new(Zobrist::new(length * 2, &mut rng));
    let field_zobrist = Arc::clone(&zobrist);
    Bot {
      rng,
      patterns,
      field: Field::new(width, height, field_zobrist),
      uct: UctRoot::new(config.uct.clone(), length),
      minimax: Minimax::new(config.minimax.clone()),
      config,
    }
  }

  pub fn initial_move(&self) -> Option<(u32, u32)> {
    match self.field.moves_count() {
      0 => Some((self.field.width() / 2, self.field.height() / 2)),
      1 => {
        let width = self.field.width();
        let height = self.field.height();
        let pos = self.field.points_seq()[0];
        let x = self.field.to_x(pos);
        let y = self.field.to_y(pos);
        if x == 0 || x == width - 1 || y == 0 || y == height - 1 {
          Some((width / 2, height / 2))
        } else if cmp::min(x, width - x - 1) < cmp::min(y, height - y - 1) {
          if x < width - x - 1 {
            Some((x + 1, y))
          } else {
            Some((x - 1, y))
          }
        } else if cmp::min(x, width - x - 1) > cmp::min(y, height - y - 1) {
          if y < height - y - 1 {
            Some((x, y + 1))
          } else {
            Some((x, y - 1))
          }
        } else {
          let dx = x as i32 - (width / 2) as i32;
          let dy = y as i32 - (height / 2) as i32;
          if dx.abs() > dy.abs() {
            if dx < 0 {
              Some((x + 1, y))
            } else {
              Some((x - 1, y))
            }
          } else if dy < 0 {
            Some((x, y + 1))
          } else {
            Some((x, y - 1))
          }
        }
      }
      _ => None,
    }
  }

  fn is_field_occupied(field: &Field) -> bool {
    for pos in field.min_pos()..=field.max_pos() {
      if field.cell(pos).is_putting_allowed() {
        return false;
      }
    }
    true
  }

  pub fn best_move(&mut self, player: Player) -> Option<(u32, u32)> {
    self.best_move_with_complexity(player, (MAX_COMPLEXITY - MIN_COMPLEXITY) / 2 + MIN_COMPLEXITY)
  }

  pub fn best_move_with_time(&mut self, player: Player, time: u32) -> Option<(u32, u32)> {
    if self.field.width() < 3 || self.field.height() < 3 || Bot::is_field_occupied(&self.field) {
      return None;
    }
    if let Some(m) = self.initial_move() {
      return Some(m);
    }
    if let Some(pos) = self.patterns.find_rand(&self.field, player, false, &mut self.rng) {
      return Some((self.field.to_x(pos), self.field.to_y(pos)));
    }
    match self.config.bot.solver {
      Solver::Uct => self
        .uct
        .best_move_with_time(&self.field, player, &mut self.rng, Duration::from_millis(u64::from(time - self.config.bot.time_gap)))
        .or_else(|| heuristic::heuristic(&self.field, player))
        .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get()))),
      Solver::Minimax => self
        .minimax
        .minimax_with_time(&mut self.field, player, Duration::from_millis(u64::from(time - self.config.bot.time_gap)))
        .or_else(|| heuristic::heuristic(&self.field, player))
        .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get()))),
      Solver::Heuristic => {
        heuristic::heuristic(&self.field, player).map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
    }
  }

  pub fn best_move_with_full_time(
    &mut self,
    player: Player,
    remaining_time: u32,
    time_per_move: u32,
  ) -> Option<(u32, u32)> {
    self.best_move_with_time(player, time_per_move + remaining_time / 25)
  }

  pub fn best_move_with_complexity(&mut self, player: Player, complexity: u32) -> Option<(u32, u32)> {
    if self.field.width() < 3 || self.field.height() < 3 || Bot::is_field_occupied(&self.field) {
      return None;
    }
    if let Some(m) = self.initial_move() {
      return Some(m);
    }
    if let Some(pos) = self.patterns.find_rand(&self.field, player, false, &mut self.rng) {
      return Some((self.field.to_x(pos), self.field.to_y(pos)));
    }
    match self.config.bot.solver {
      Solver::Uct => {
        let iterations_count = (complexity - MIN_COMPLEXITY) as usize * (MAX_UCT_ITERATIONS - MIN_UCT_ITERATIONS)
          / (MAX_COMPLEXITY - MIN_COMPLEXITY) as usize
          + MIN_UCT_ITERATIONS;
        self
          .uct
          .best_move_with_iterations_count(&self.field, player, &mut self.rng, iterations_count)
          .or_else(|| heuristic::heuristic(&self.field, player))
          .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
      Solver::Minimax => {
        let depth = (complexity - MIN_COMPLEXITY) * (MAX_MINIMAX_DEPTH - MIN_MINIMAX_DEPTH)
          / (MAX_COMPLEXITY - MIN_COMPLEXITY)
          + MIN_MINIMAX_DEPTH;
        self
          .minimax
          .minimax(&mut self.field, player, depth)
          .or_else(|| heuristic::heuristic(&self.field, player))
          .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
      Solver::Heuristic => {
        heuristic::heuristic(&self.field, player).map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
    }
  }

  pub fn put_point(&mut self, x: u32, y: u32, player: Player) -> bool {
    let pos = self.field.to_pos(x, y);
    self.field.put_point(pos, player)
  }

  pub fn undo(&mut self) -> bool {
    self.field.undo()
  }
}
<commit_msg>Apply rustfmt.<commit_after>use crate::config::{Config, Solver};
use crate::heuristic;
use crate::patterns::Patterns;
use oppai_field::field::{self, Field};
use oppai_field::player::Player;
use oppai_field::zobrist::Zobrist;
use oppai_minimax::minimax::Minimax;
use oppai_uct::uct::UctRoot;
use rand::SeedableRng;
use rand_xorshift::XorShiftRng;
use std::{cmp, sync::Arc, time::Duration};

const BOT_STR: &str = "bot";

const MIN_COMPLEXITY: u32 = 0;

const MAX_COMPLEXITY: u32 = 100;

const MIN_UCT_ITERATIONS: usize = 0;

const MAX_UCT_ITERATIONS: usize = 500_000;

const MIN_MINIMAX_DEPTH: u32 = 0;

const MAX_MINIMAX_DEPTH: u32 = 12;

pub struct Bot {
  rng: XorShiftRng,
  patterns: Arc<Patterns>,
  field: Field,
  uct: UctRoot,
  minimax: Minimax,
  config: Config,
}

impl Bot {
  pub fn new(width: u32, height: u32, seed: u64, patterns: Arc<Patterns>, config: Config) -> Bot {
    info!(
      target: BOT_STR,
      "Initialization with width {0}, height {1}, seed {2}.", width, height, seed
    );
    let length = field::length(width, height);
    let seed_array = [
      2,
      (seed & 0xff) as u8,
      3,
      ((seed >> 8) & 0xff) as u8,
      5,
      ((seed >> 16) & 0xff) as u8,
      7,
      ((seed >> 24) & 0xff) as u8,
      11,
      ((seed >> 32) & 0xff) as u8,
      13,
      ((seed >> 40) & 0xff) as u8,
      17,
      ((seed >> 48) & 0xff) as u8,
      19,
      ((seed >> 56) & 0xff) as u8,
    ];
    let mut rng = XorShiftRng::from_seed(seed_array);
    let zobrist = Arc::new(Zobrist::new(length * 2, &mut rng));
    let field_zobrist = Arc::clone(&zobrist);
    Bot {
      rng,
      patterns,
      field: Field::new(width, height, field_zobrist),
      uct: UctRoot::new(config.uct.clone(), length),
      minimax: Minimax::new(config.minimax.clone()),
      config,
    }
  }

  pub fn initial_move(&self) -> Option<(u32, u32)> {
    match self.field.moves_count() {
      0 => Some((self.field.width() / 2, self.field.height() / 2)),
      1 => {
        let width = self.field.width();
        let height = self.field.height();
        let pos = self.field.points_seq()[0];
        let x = self.field.to_x(pos);
        let y = self.field.to_y(pos);
        if x == 0 || x == width - 1 || y == 0 || y == height - 1 {
          Some((width / 2, height / 2))
        } else if cmp::min(x, width - x - 1) < cmp::min(y, height - y - 1) {
          if x < width - x - 1 {
            Some((x + 1, y))
          } else {
            Some((x - 1, y))
          }
        } else if cmp::min(x, width - x - 1) > cmp::min(y, height - y - 1) {
          if y < height - y - 1 {
            Some((x, y + 1))
          } else {
            Some((x, y - 1))
          }
        } else {
          let dx = x as i32 - (width / 2) as i32;
          let dy = y as i32 - (height / 2) as i32;
          if dx.abs() > dy.abs() {
            if dx < 0 {
              Some((x + 1, y))
            } else {
              Some((x - 1, y))
            }
          } else if dy < 0 {
            Some((x, y + 1))
          } else {
            Some((x, y - 1))
          }
        }
      }
      _ => None,
    }
  }

  fn is_field_occupied(field: &Field) -> bool {
    for pos in field.min_pos()..=field.max_pos() {
      if field.cell(pos).is_putting_allowed() {
        return false;
      }
    }
    true
  }

  pub fn best_move(&mut self, player: Player) -> Option<(u32, u32)> {
    self.best_move_with_complexity(player, (MAX_COMPLEXITY - MIN_COMPLEXITY) / 2 + MIN_COMPLEXITY)
  }

  pub fn best_move_with_time(&mut self, player: Player, time: u32) -> Option<(u32, u32)> {
    if self.field.width() < 3 || self.field.height() < 3 || Bot::is_field_occupied(&self.field) {
      return None;
    }
    if let Some(m) = self.initial_move() {
      return Some(m);
    }
    if let Some(pos) = self.patterns.find_rand(&self.field, player, false, &mut self.rng) {
      return Some((self.field.to_x(pos), self.field.to_y(pos)));
    }
    match self.config.bot.solver {
      Solver::Uct => self
        .uct
        .best_move_with_time(
          &self.field,
          player,
          &mut self.rng,
          Duration::from_millis(u64::from(time - self.config.bot.time_gap)),
        )
        .or_else(|| heuristic::heuristic(&self.field, player))
        .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get()))),
      Solver::Minimax => self
        .minimax
        .minimax_with_time(
          &mut self.field,
          player,
          Duration::from_millis(u64::from(time - self.config.bot.time_gap)),
        )
        .or_else(|| heuristic::heuristic(&self.field, player))
        .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get()))),
      Solver::Heuristic => {
        heuristic::heuristic(&self.field, player).map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
    }
  }

  pub fn best_move_with_full_time(
    &mut self,
    player: Player,
    remaining_time: u32,
    time_per_move: u32,
  ) -> Option<(u32, u32)> {
    self.best_move_with_time(player, time_per_move + remaining_time / 25)
  }

  pub fn best_move_with_complexity(&mut self, player: Player, complexity: u32) -> Option<(u32, u32)> {
    if self.field.width() < 3 || self.field.height() < 3 || Bot::is_field_occupied(&self.field) {
      return None;
    }
    if let Some(m) = self.initial_move() {
      return Some(m);
    }
    if let Some(pos) = self.patterns.find_rand(&self.field, player, false, &mut self.rng) {
      return Some((self.field.to_x(pos), self.field.to_y(pos)));
    }
    match self.config.bot.solver {
      Solver::Uct => {
        let iterations_count = (complexity - MIN_COMPLEXITY) as usize * (MAX_UCT_ITERATIONS - MIN_UCT_ITERATIONS)
          / (MAX_COMPLEXITY - MIN_COMPLEXITY) as usize
          + MIN_UCT_ITERATIONS;
        self
          .uct
          .best_move_with_iterations_count(&self.field, player, &mut self.rng, iterations_count)
          .or_else(|| heuristic::heuristic(&self.field, player))
          .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
      Solver::Minimax => {
        let depth = (complexity - MIN_COMPLEXITY) * (MAX_MINIMAX_DEPTH - MIN_MINIMAX_DEPTH)
          / (MAX_COMPLEXITY - MIN_COMPLEXITY)
          + MIN_MINIMAX_DEPTH;
        self
          .minimax
          .minimax(&mut self.field, player, depth)
          .or_else(|| heuristic::heuristic(&self.field, player))
          .map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
      Solver::Heuristic => {
        heuristic::heuristic(&self.field, player).map(|pos| (self.field.to_x(pos.get()), self.field.to_y(pos.get())))
      }
    }
  }

  pub fn put_point(&mut self, x: u32, y: u32, player: Player) -> bool {
    let pos = self.field.to_pos(x, y);
    self.field.put_point(pos, player)
  }

  pub fn undo(&mut self) -> bool {
    self.field.undo()
  }
}
<|endoftext|>


<commit_before>//
// Copyright 2018 The Project Oak Authors
//
// Licensed 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.
//

//! Functionality to help Oak Nodes interact with gRPC.

pub use crate::proto::code::Code;
use crate::{proto, OakError};
use log::{error, warn};
use proto::grpc_encap::{GrpcRequest, GrpcResponse};
use protobuf::ProtobufEnum;

mod invocation;
pub use invocation::Invocation;

/// Result type that uses a [`proto::status::Status`] type for error values.
pub type Result<T> = std::result::Result<T, proto::status::Status>;

/// Helper to create a gRPC status object.
pub fn build_status(code: Code, msg: &str) -> proto::status::Status {
    let mut status = proto::status::Status::new();
    status.set_code(code.value());
    status.set_message(msg.to_string());
    status
}

/// Channel-holding object that encapsulates response messages into
/// `GrpcResponse` wrapper messages and writes serialized versions to a send
/// channel.
pub struct ChannelResponseWriter {
    sender: crate::io::Sender<GrpcResponse>,
}

/// Indicate whether a write method should leave the current gRPC method
/// invocation open or close it.
#[derive(PartialEq, Clone, Debug)]
pub enum WriteMode {
    KeepOpen,
    Close,
}

impl ChannelResponseWriter {
    pub fn new(sender: crate::io::Sender<GrpcResponse>) -> Self {
        ChannelResponseWriter { sender }
    }

    /// Retrieve the Oak handle underlying the writer object.
    pub fn handle(self) -> crate::WriteHandle {
        self.sender.handle
    }

    /// Write out a gRPC response and optionally close out the method
    /// invocation.  Any errors from the channel are silently dropped.
    pub fn write<T: protobuf::Message>(
        &mut self,
        rsp: &T,
        mode: WriteMode,
    ) -> std::result::Result<(), OakError> {
        // Put the serialized response into a GrpcResponse message wrapper and
        // serialize it into the channel.
        let mut grpc_rsp = GrpcResponse::new();
        let mut any = protobuf::well_known_types::Any::new();
        rsp.write_to_writer(&mut any.value)?;
        grpc_rsp.set_rsp_msg(any);
        grpc_rsp.set_last(match mode {
            WriteMode::KeepOpen => false,
            WriteMode::Close => true,
        });
        self.sender.send(&grpc_rsp)?;
        if mode == WriteMode::Close {
            self.sender.close()?;
        }
        Ok(())
    }

    /// Write an empty gRPC response and optionally close out the method
    /// invocation. Any errors from the channel are silently dropped.
    pub fn write_empty(&mut self, mode: WriteMode) -> std::result::Result<(), OakError> {
        let mut grpc_rsp = GrpcResponse::new();
        grpc_rsp.set_rsp_msg(protobuf::well_known_types::Any::new());
        grpc_rsp.set_last(match mode {
            WriteMode::KeepOpen => false,
            WriteMode::Close => true,
        });
        self.sender.send(&grpc_rsp)?;
        if mode == WriteMode::Close {
            self.sender.close()?;
        }
        Ok(())
    }

    /// Close out the gRPC method invocation with the given final result.
    pub fn close(&mut self, result: Result<()>) -> std::result::Result<(), OakError> {
        // Build a final GrpcResponse message wrapper and serialize it into the
        // channel.
        let mut grpc_rsp = GrpcResponse::new();
        grpc_rsp.set_last(true);
        if let Err(status) = result {
            grpc_rsp.set_status(status);
        }
        self.sender.send(&grpc_rsp)?;
        self.sender.close()?;
        Ok(())
    }
}

/// Trait for Oak Nodes that act as a gRPC services.
///
/// An `OakNode` instance is normally passed to [`event_loop`], to allow
/// repeated invocation of its `invoke()` method.
pub trait OakNode {
    /// Construct the (single) instance of the node.
    ///
    /// This method may choose to initialize logging by invoking
    /// [`oak_log::init()`].
    ///
    /// [`oak_log::init()`]: ../../oak_log/fn.init.html
    fn new() -> Self
    where
        Self: Sized;

    /// Process a single gRPC method invocation.
    ///
    /// The method name is provided by `method` and the incoming serialized gRPC
    /// request is held in `req`.  Response messages should be written using
    /// `writer.write`, followed by `writer.close`.
    fn invoke(&mut self, method: &str, req: &[u8], writer: ChannelResponseWriter);
}

impl<T: OakNode> crate::Node<Invocation> for T {
    fn new() -> Self {
        T::new()
    }
    /// Handle incoming gRPC events for an [`OakNode`].
    ///
    /// Invoking the given `node`'s [`invoke`] method for each incoming request that
    /// arrives on the inbound channel as a serialized [`Invocation`] object,
    /// giving the [`invoke`] method the outbound channel for encapsulated responses
    /// to be written to.
    ///
    /// [`invoke`]: OakNode::invoke
    fn handle_command(
        &mut self,
        invocation: Invocation,
    ) -> std::result::Result<(), crate::OakError> {
        // Read a single encapsulated request message from the read half.
        let req: GrpcRequest = invocation.request_receiver.receive()?;
        // Since we are expecting a single message, close the channel immediately.
        // This will change when we implement client streaming (#97).
        invocation.request_receiver.close()?;
        if !req.last {
            // TODO(#97): Implement client streaming.
            panic!("Support for streaming requests not yet implemented");
        }
        self.invoke(
            &req.method_name,
            req.get_req_msg().value.as_slice(),
            ChannelResponseWriter::new(invocation.response_sender),
        );
        Ok(())
    }
}

/// Encapsulate a protocol buffer message in a GrpcRequest wrapper using the
/// given method name.
pub fn encap_request<T: protobuf::Message>(req: T, method_name: &str) -> Option<GrpcRequest> {
    // Put the request in a GrpcRequest wrapper and serialize it.
    let mut grpc_req = GrpcRequest::new();
    grpc_req.set_method_name(method_name.to_string());
    let mut any = protobuf::well_known_types::Any::new();
    if let Err(e) = req.write_to_writer(&mut any.value) {
        warn!("failed to serialize gRPC request: {}", e);
        return None;
    };
    grpc_req.set_req_msg(any);
    grpc_req.set_last(true);
    Some(grpc_req)
}

/// Helper to inject a (single) gRPC request message via a notification channel,
/// in the same manner as the gRPC pseudo-Node does, and collect a (single)
/// response.
pub fn invoke_grpc_method<R, Q>(
    method_name: &str,
    req: R,
    invocation_channel: &crate::io::Sender<Invocation>,
) -> Result<Q>
where
    R: protobuf::Message,
    Q: protobuf::Message,
{
    // Create a new channel for request message delivery.
    let (req_sender, req_receiver) =
        crate::io::channel_create::<GrpcRequest>().expect("failed to create channel");

    // Put the request in a GrpcRequest wrapper and send it into the request
    // message channel.
    let req = encap_request(req, method_name).expect("failed to serialize GrpcRequest");
    req_sender.send(&req).expect("failed to write to channel");
    req_sender.close().expect("failed to close channel");

    // Create a new channel for responses to arrive on.
    let (rsp_sender, rsp_receiver) =
        crate::io::channel_create::<GrpcResponse>().expect("failed to create channel");

    // Build an Invocation holding the two channels and send it down the
    // specified channel.
    let invocation = Invocation {
        request_receiver: req_receiver.clone(),
        response_sender: rsp_sender.clone(),
    };
    invocation_channel
        .send(&invocation)
        .expect("failed to write invocation to channel");
    req_receiver.close().expect("failed to close channel");
    rsp_sender.close().expect("failed to close channel");

    // Read a single encapsulated response.
    let mut rsp = rsp_receiver.receive().map_err(|status| {
        error!("failed to receive response: {:?}", status);
        build_status(
            Code::INTERNAL,
            &format!("failed to receive gRPC response: {:?}", status),
        )
    })?;
    rsp_receiver.close().expect("failed to close channel");
    if !rsp.last {
        panic!("Expected single final response");
    }

    if rsp.get_status().code != Code::OK.value() {
        return Err(rsp.take_status());
    }
    Ok(protobuf::parse_from_bytes(&rsp.get_rsp_msg().value)
        .expect("Failed to parse response protobuf message"))
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// request/response pair.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_req_rsp<C, R, Q>(mut node_fn: C, req: &[u8], mut writer: ChannelResponseWriter)
where
    C: FnMut(R) -> Result<Q>,
    R: protobuf::Message,
    Q: protobuf::Message,
{
    let r: R = protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message");
    let result = match node_fn(r) {
        Ok(rsp) => writer.write(&rsp, WriteMode::Close),
        Err(status) => writer.close(Err(status)),
    };
    if let Err(e) = result {
        panic!("Failed to process response: {:?}", e)
    }
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// request and streams responses.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_req_stream<C, R>(mut node_fn: C, req: &[u8], writer: ChannelResponseWriter)
where
    C: FnMut(R, ChannelResponseWriter),
    R: protobuf::Message,
{
    let r: R = protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message");
    node_fn(r, writer)
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// stream of requests to produce a response.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_stream_rsp<C, R, Q>(mut node_fn: C, req: &[u8], mut writer: ChannelResponseWriter)
where
    C: FnMut(Vec<R>) -> Result<Q>,
    R: protobuf::Message,
    Q: protobuf::Message,
{
    // TODO(#97): better client-side streaming
    let rr: Vec<R> =
        vec![protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message")];
    let result = match node_fn(rr) {
        Ok(rsp) => writer.write(&rsp, WriteMode::Close),
        Err(status) => writer.close(Err(status)),
    };
    if let Err(e) = result {
        panic!("Failed to process response: {:?}", e)
    }
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// stream of requests to produce a stream of responses.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_stream_stream<C, R>(mut node_fn: C, req: &[u8], writer: ChannelResponseWriter)
where
    C: FnMut(Vec<R>, ChannelResponseWriter),
    R: protobuf::Message,
{
    // TODO(#97): better client-side streaming
    let rr: Vec<R> =
        vec![protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message")];
    node_fn(rr, writer)
}
<commit_msg>sdk: fix missing doc link<commit_after>//
// Copyright 2018 The Project Oak Authors
//
// Licensed 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.
//

//! Functionality to help Oak Nodes interact with gRPC.

pub use crate::proto::code::Code;
use crate::{proto, OakError};
use log::{error, warn};
use proto::grpc_encap::{GrpcRequest, GrpcResponse};
use protobuf::ProtobufEnum;

mod invocation;
pub use invocation::Invocation;

/// Result type that uses a [`proto::status::Status`] type for error values.
pub type Result<T> = std::result::Result<T, proto::status::Status>;

/// Helper to create a gRPC status object.
pub fn build_status(code: Code, msg: &str) -> proto::status::Status {
    let mut status = proto::status::Status::new();
    status.set_code(code.value());
    status.set_message(msg.to_string());
    status
}

/// Channel-holding object that encapsulates response messages into
/// `GrpcResponse` wrapper messages and writes serialized versions to a send
/// channel.
pub struct ChannelResponseWriter {
    sender: crate::io::Sender<GrpcResponse>,
}

/// Indicate whether a write method should leave the current gRPC method
/// invocation open or close it.
#[derive(PartialEq, Clone, Debug)]
pub enum WriteMode {
    KeepOpen,
    Close,
}

impl ChannelResponseWriter {
    pub fn new(sender: crate::io::Sender<GrpcResponse>) -> Self {
        ChannelResponseWriter { sender }
    }

    /// Retrieve the Oak handle underlying the writer object.
    pub fn handle(self) -> crate::WriteHandle {
        self.sender.handle
    }

    /// Write out a gRPC response and optionally close out the method
    /// invocation.  Any errors from the channel are silently dropped.
    pub fn write<T: protobuf::Message>(
        &mut self,
        rsp: &T,
        mode: WriteMode,
    ) -> std::result::Result<(), OakError> {
        // Put the serialized response into a GrpcResponse message wrapper and
        // serialize it into the channel.
        let mut grpc_rsp = GrpcResponse::new();
        let mut any = protobuf::well_known_types::Any::new();
        rsp.write_to_writer(&mut any.value)?;
        grpc_rsp.set_rsp_msg(any);
        grpc_rsp.set_last(match mode {
            WriteMode::KeepOpen => false,
            WriteMode::Close => true,
        });
        self.sender.send(&grpc_rsp)?;
        if mode == WriteMode::Close {
            self.sender.close()?;
        }
        Ok(())
    }

    /// Write an empty gRPC response and optionally close out the method
    /// invocation. Any errors from the channel are silently dropped.
    pub fn write_empty(&mut self, mode: WriteMode) -> std::result::Result<(), OakError> {
        let mut grpc_rsp = GrpcResponse::new();
        grpc_rsp.set_rsp_msg(protobuf::well_known_types::Any::new());
        grpc_rsp.set_last(match mode {
            WriteMode::KeepOpen => false,
            WriteMode::Close => true,
        });
        self.sender.send(&grpc_rsp)?;
        if mode == WriteMode::Close {
            self.sender.close()?;
        }
        Ok(())
    }

    /// Close out the gRPC method invocation with the given final result.
    pub fn close(&mut self, result: Result<()>) -> std::result::Result<(), OakError> {
        // Build a final GrpcResponse message wrapper and serialize it into the
        // channel.
        let mut grpc_rsp = GrpcResponse::new();
        grpc_rsp.set_last(true);
        if let Err(status) = result {
            grpc_rsp.set_status(status);
        }
        self.sender.send(&grpc_rsp)?;
        self.sender.close()?;
        Ok(())
    }
}

/// Trait for Oak Nodes that act as a gRPC services.
///
/// An `OakNode` instance is normally passed to [`oak::run_event_loop`], to
/// allow repeated invocation of its `invoke()` method.
pub trait OakNode {
    /// Construct the (single) instance of the node.
    ///
    /// This method may choose to initialize logging by invoking
    /// [`oak_log::init()`].
    ///
    /// [`oak_log::init()`]: ../../oak_log/fn.init.html
    fn new() -> Self
    where
        Self: Sized;

    /// Process a single gRPC method invocation.
    ///
    /// The method name is provided by `method` and the incoming serialized gRPC
    /// request is held in `req`.  Response messages should be written using
    /// `writer.write`, followed by `writer.close`.
    fn invoke(&mut self, method: &str, req: &[u8], writer: ChannelResponseWriter);
}

impl<T: OakNode> crate::Node<Invocation> for T {
    fn new() -> Self {
        T::new()
    }
    /// Handle incoming gRPC events for an [`OakNode`].
    ///
    /// Invoking the given `node`'s [`invoke`] method for each incoming request that
    /// arrives on the inbound channel as a serialized [`Invocation`] object,
    /// giving the [`invoke`] method the outbound channel for encapsulated responses
    /// to be written to.
    ///
    /// [`invoke`]: OakNode::invoke
    fn handle_command(
        &mut self,
        invocation: Invocation,
    ) -> std::result::Result<(), crate::OakError> {
        // Read a single encapsulated request message from the read half.
        let req: GrpcRequest = invocation.request_receiver.receive()?;
        // Since we are expecting a single message, close the channel immediately.
        // This will change when we implement client streaming (#97).
        invocation.request_receiver.close()?;
        if !req.last {
            // TODO(#97): Implement client streaming.
            panic!("Support for streaming requests not yet implemented");
        }
        self.invoke(
            &req.method_name,
            req.get_req_msg().value.as_slice(),
            ChannelResponseWriter::new(invocation.response_sender),
        );
        Ok(())
    }
}

/// Encapsulate a protocol buffer message in a GrpcRequest wrapper using the
/// given method name.
pub fn encap_request<T: protobuf::Message>(req: T, method_name: &str) -> Option<GrpcRequest> {
    // Put the request in a GrpcRequest wrapper and serialize it.
    let mut grpc_req = GrpcRequest::new();
    grpc_req.set_method_name(method_name.to_string());
    let mut any = protobuf::well_known_types::Any::new();
    if let Err(e) = req.write_to_writer(&mut any.value) {
        warn!("failed to serialize gRPC request: {}", e);
        return None;
    };
    grpc_req.set_req_msg(any);
    grpc_req.set_last(true);
    Some(grpc_req)
}

/// Helper to inject a (single) gRPC request message via a notification channel,
/// in the same manner as the gRPC pseudo-Node does, and collect a (single)
/// response.
pub fn invoke_grpc_method<R, Q>(
    method_name: &str,
    req: R,
    invocation_channel: &crate::io::Sender<Invocation>,
) -> Result<Q>
where
    R: protobuf::Message,
    Q: protobuf::Message,
{
    // Create a new channel for request message delivery.
    let (req_sender, req_receiver) =
        crate::io::channel_create::<GrpcRequest>().expect("failed to create channel");

    // Put the request in a GrpcRequest wrapper and send it into the request
    // message channel.
    let req = encap_request(req, method_name).expect("failed to serialize GrpcRequest");
    req_sender.send(&req).expect("failed to write to channel");
    req_sender.close().expect("failed to close channel");

    // Create a new channel for responses to arrive on.
    let (rsp_sender, rsp_receiver) =
        crate::io::channel_create::<GrpcResponse>().expect("failed to create channel");

    // Build an Invocation holding the two channels and send it down the
    // specified channel.
    let invocation = Invocation {
        request_receiver: req_receiver.clone(),
        response_sender: rsp_sender.clone(),
    };
    invocation_channel
        .send(&invocation)
        .expect("failed to write invocation to channel");
    req_receiver.close().expect("failed to close channel");
    rsp_sender.close().expect("failed to close channel");

    // Read a single encapsulated response.
    let mut rsp = rsp_receiver.receive().map_err(|status| {
        error!("failed to receive response: {:?}", status);
        build_status(
            Code::INTERNAL,
            &format!("failed to receive gRPC response: {:?}", status),
        )
    })?;
    rsp_receiver.close().expect("failed to close channel");
    if !rsp.last {
        panic!("Expected single final response");
    }

    if rsp.get_status().code != Code::OK.value() {
        return Err(rsp.take_status());
    }
    Ok(protobuf::parse_from_bytes(&rsp.get_rsp_msg().value)
        .expect("Failed to parse response protobuf message"))
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// request/response pair.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_req_rsp<C, R, Q>(mut node_fn: C, req: &[u8], mut writer: ChannelResponseWriter)
where
    C: FnMut(R) -> Result<Q>,
    R: protobuf::Message,
    Q: protobuf::Message,
{
    let r: R = protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message");
    let result = match node_fn(r) {
        Ok(rsp) => writer.write(&rsp, WriteMode::Close),
        Err(status) => writer.close(Err(status)),
    };
    if let Err(e) = result {
        panic!("Failed to process response: {:?}", e)
    }
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// request and streams responses.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_req_stream<C, R>(mut node_fn: C, req: &[u8], writer: ChannelResponseWriter)
where
    C: FnMut(R, ChannelResponseWriter),
    R: protobuf::Message,
{
    let r: R = protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message");
    node_fn(r, writer)
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// stream of requests to produce a response.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_stream_rsp<C, R, Q>(mut node_fn: C, req: &[u8], mut writer: ChannelResponseWriter)
where
    C: FnMut(Vec<R>) -> Result<Q>,
    R: protobuf::Message,
    Q: protobuf::Message,
{
    // TODO(#97): better client-side streaming
    let rr: Vec<R> =
        vec![protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message")];
    let result = match node_fn(rr) {
        Ok(rsp) => writer.write(&rsp, WriteMode::Close),
        Err(status) => writer.close(Err(status)),
    };
    if let Err(e) = result {
        panic!("Failed to process response: {:?}", e)
    }
}

/// Generic function that handles request deserialization and response
/// serialization (and sending down a channel) for a function that handles a
/// stream of requests to produce a stream of responses.
///
/// Panics if [de-]serialization or channel operations fail.
pub fn handle_stream_stream<C, R>(mut node_fn: C, req: &[u8], writer: ChannelResponseWriter)
where
    C: FnMut(Vec<R>, ChannelResponseWriter),
    R: protobuf::Message,
{
    // TODO(#97): better client-side streaming
    let rr: Vec<R> =
        vec![protobuf::parse_from_bytes(&req).expect("Failed to parse request protobuf message")];
    node_fn(rr, writer)
}
<|endoftext|>