<commit_before>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[license = "MIT/ASL2"];
#[crate_id = "github.com/DaGenix/rust-crypto#rust-crypto-util:0.1"];

extern mod extra;
extern mod rust_crypto = "rust-crypto";

use std::io;
use std::os;
use std::vec;

use extra::getopts::{optopt, optflag, getopts, Matches};

use rust_crypto::scrypt;

fn print_usage() {
    println!("Usage: rust-crypto-util <algorithm> [options]");
    println!("-h, --help\tUsage");
    println!("");
    println!("Algorithms:");
    println!(" * Scrypt (scrypt)");
    println!("");
    println!("Scrypt options:");
    println!("--logn\t\tThe Log N parameter");
    println!("-r\t\tThe R parameter");
    println!("-p\t\tThe P parameter");
    println!("--dklen\t\tThe DkLen parameter");
    println!("--rawsalt\tThe salt parameter is supplied on STDIN");
    println!("--rawpassword\tThe password parameter is supplied on STDIN");
    println!("--rawoutput\tThe resulting value should be output directly to STDOUT");
}

fn run_scrypt(matches: &Matches) {
    if !matches.opt_present("logn") || !matches.opt_present("r") || !matches.opt_present("p") ||
       !matches.opt_present("dklen") {
        print_usage();
        return;
    }
    let logn = from_str::<u8>(matches.opt_str("logn").unwrap()).unwrap();
    let r = from_str::<u32>(matches.opt_str("r").unwrap()).unwrap();
    let p = from_str::<u32>(matches.opt_str("p").unwrap()).unwrap();
    let dklen = from_str::<uint>(matches.opt_str("dklen").unwrap()).unwrap();

    if !matches.opt_present("rawsalt") || !matches.opt_present("rawpassword") ||
       !matches.opt_present("rawoutput") {
        println!("Required options missing.");
        return;
    }

    let salt_len = io::stdin().read_be_u32();
    let salt = io::stdin().read_bytes(salt_len as uint);
    let pass_len = io::stdin().read_be_u32();
    let pass = io::stdin().read_bytes(pass_len as uint);

    let params = scrypt::ScryptParams::new(logn, r, p);
    let mut output = vec::from_elem(dklen, 0u8);
    scrypt::scrypt(pass, salt, &params, output);

    io::stdout().write(output);
}

fn main() {
    let args = os::args();

    let opts = ~[
        // General parameters:
        optflag("h"),
        optflag("help"),

        // Scrypt parameters:
        optopt("logn"),
        optopt("r"),
        optopt("p"),
        optopt("dklen"),
        optflag("rawsalt"),
        optflag("rawpassword"),
        optflag("rawoutput"),
    ];

    let matches = match getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => { fail!(f.to_err_msg()) }
    };

    if matches.opt_present("h") || matches.opt_present("help") {
        print_usage();
        return;
    }

    if matches.free.is_empty() {
        print_usage();
        return;
    }
    let algorithm_name: &str = matches.free[0];

    match algorithm_name {
        "scrypt" => run_scrypt(&matches),
        _ => {
            print_usage();
            return;
        }
    }
}
<commit_msg>Update rust-crypto-tool to handle getopts being in its own crate and the IO refactoring to use the IoResult return type everywhere<commit_after>// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#[license = "MIT/ASL2"];
#[crate_id = "github.com/DaGenix/rust-crypto#rust-crypto-util:0.1"];

extern mod extra;
extern mod getopts;
extern mod rust_crypto = "rust-crypto";

use std::io;
use std::os;
use std::vec;

use getopts::{optopt, optflag, getopts, Matches};

use rust_crypto::scrypt;

fn print_usage() {
    println!("Usage: rust-crypto-util <algorithm> [options]");
    println!("-h, --help\tUsage");
    println!("");
    println!("Algorithms:");
    println!(" * Scrypt (scrypt)");
    println!("");
    println!("Scrypt options:");
    println!("--logn\t\tThe Log N parameter");
    println!("-r\t\tThe R parameter");
    println!("-p\t\tThe P parameter");
    println!("--dklen\t\tThe DkLen parameter");
    println!("--rawsalt\tThe salt parameter is supplied on STDIN");
    println!("--rawpassword\tThe password parameter is supplied on STDIN");
    println!("--rawoutput\tThe resulting value should be output directly to STDOUT");
}

fn run_scrypt(matches: &Matches) {
    if !matches.opt_present("logn") || !matches.opt_present("r") || !matches.opt_present("p") ||
       !matches.opt_present("dklen") {
        print_usage();
        return;
    }
    let logn = from_str::<u8>(matches.opt_str("logn").unwrap()).unwrap();
    let r = from_str::<u32>(matches.opt_str("r").unwrap()).unwrap();
    let p = from_str::<u32>(matches.opt_str("p").unwrap()).unwrap();
    let dklen = from_str::<uint>(matches.opt_str("dklen").unwrap()).unwrap();

    if !matches.opt_present("rawsalt") || !matches.opt_present("rawpassword") ||
       !matches.opt_present("rawoutput") {
        println!("Required options missing.");
        return;
    }

    let salt_len = io::stdin().read_be_u32().unwrap();
    let salt = io::stdin().read_bytes(salt_len as uint).unwrap();
    let pass_len = io::stdin().read_be_u32().unwrap();
    let pass = io::stdin().read_bytes(pass_len as uint).unwrap();

    let params = scrypt::ScryptParams::new(logn, r, p);
    let mut output = vec::from_elem(dklen, 0u8);
    scrypt::scrypt(pass, salt, &params, output);

    match io::stdout().write(output) {
        Ok(_) => { },
        Err(_) => fail!("Error writing result")
    }
}

fn main() {
    let args = os::args();

    let opts = ~[
        // General parameters:
        optflag("h", "help", "Print help"),

        // Scrypt parameters:
        optopt("", "logn", "Log-N parameter for Scrypt", ""),
        optopt("r", "", "R parameter for Scrypt", ""),
        optopt("p", "", "P parameter for Scrypt", ""),
        optopt("", "dklen", "Length of the derived key", ""),
        optflag("", "rawsalt", "Use a raw salt value"),
        optflag("", "rawpassword", "Use a raw password value"),
        optflag("", "rawoutput", "Use raw output mode"),
    ];

    let matches = match getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => { fail!(f.to_err_msg()) }
    };

    if matches.opt_present("h") || matches.opt_present("help") {
        print_usage();
        return;
    }

    if matches.free.is_empty() {
        print_usage();
        return;
    }
    let algorithm_name: &str = matches.free[0];

    match algorithm_name {
        "scrypt" => run_scrypt(&matches),
        _ => {
            print_usage();
            return;
        }
    }
}
<|endoftext|>


<commit_before>/// Serial port management module supporting one
/// writer and multiple readers
///
/// Clients can lock a port for writing, but
/// subscribe to data from multiple ports for reads

use std::collections::HashSet;
use std::sync::mpsc::{Receiver, TryRecvError};
use std::thread;
use std::time::Duration;

use base64;

use serial_support::errors::*;
use serial_support::messages::*;
use serial_support::sub_manager::*;
use serial_support::port_manager::*;
use serial_support::writelock_manager::*;
use serial_support::common::*;

/// Manager manages connection state.
/// TODO Needs a better name
pub struct Manager {
  /// Manage write lock status
  writelock_manager: WriteLockManager,
  /// Manage ports
  port_manager: PortManager,
  /// Manage subscriptions
  sub_manager: SubscriptionManager,
  /// Receiver for serial requests
  receiver: Receiver<(String, SerialRequest)>,
  /// Receiver for response subscription requests
  subsc_receiver: SubscReceiver,
}

impl Manager {
  ///Constructor
  pub fn new(
    receiver: Receiver<(String, SerialRequest)>,
    subsc_receiver: SubscReceiver,
  ) -> Manager {
    Manager {
      writelock_manager: WriteLockManager::new(),
      port_manager: PortManager::new(),
      sub_manager: SubscriptionManager::new(),
      receiver: receiver,
      subsc_receiver: subsc_receiver,
    }
  }

  ///Spawn an instance in a new thread.
  pub fn spawn(
    receiver: Receiver<(String, SerialRequest)>,
    subsc_receiver: SubscReceiver,
  ) -> thread::JoinHandle<()> {
    thread::spawn(move || { Manager::new(receiver, subsc_receiver).run(); })
  }

  /// Main loop
  fn run(&mut self) {

    // Bad ports we couldn't read from
    // A set of SerialResponse::Errors built from
    // from the serial port read/write error responses
    let mut bad_ports = HashSet::<String>::new();
    // Shutdown flag
    let mut shutdown = false;
    // Check about 30 times a second
    let sleep_dur = Duration::from_millis(33);

    while !shutdown {
      // Sleep for a little bit to avoid pegging cpu
      // TODO Dynamic timing loop
      thread::sleep(sleep_dur);

      // Handle serial operation requests
      match self.receiver.try_recv() {
        Err(e) => {
          match e {
            TryRecvError::Empty => {
              // nothing to do
            }
            TryRecvError::Disconnected => {
              // Remote end hung up, time to shutdown
              info!("Shutting down SerialPortManager");
              shutdown = true;
            }
          }
        }
        Ok(req) => self.handle_serial_request(&req.0, req.1),
      }

      // Check for new data on each port
      for (port_name, result) in self.port_manager.read_all_ports() {
        match result {
          Ok(data) => {
            let response = match String::from_utf8(data) {
              // We need to send as binary
              Err(e) => {
                SerialResponse::Read {
                  port: port_name.to_string(),
                  data: base64::encode(&e.into_bytes()),
                  base64: Some(true),
                }
              }
              Ok(s) => {
                SerialResponse::Read {
                  port: port_name.to_string(),
                  data: s,
                  base64: Some(false),
                }
              }
            };
            self.broadcast_message_for_port(&port_name, response);
          }
          // Send data reads
          Err(e) => {
            warn!("Error reading port!");
            warn!("{}", e);
            bad_ports.insert(port_name);
          }
        }
      }

      //Handle write requests
      let mut recv_count = 0;
      let mut do_recv = true;
      while do_recv {
        recv_count += 1;
        match self.subsc_receiver.try_recv() {
          Ok(sub_request) => self.sub_manager.add_subscription(sub_request),
          Err(e) => {
            match e {
              TryRecvError::Disconnected => {
                // Does this mean all senders have disconnected?
                // Or just one?
                debug!("Got disconnected when trying to get serial request");
              }
              TryRecvError::Empty => do_recv = false,
            }
          }
        }

        // Don't let this loop get stuck here forever
        // starving the outer loop
        if recv_count == 50 {
          do_recv = false;
        }
      }

      // Cleanup bad serial ports that failed read or write
      // We remove them from everything before
      self.cleanup_bad_ports(&bad_ports);
      bad_ports.clear();
    }
  }

  /// Handles and dispatches SerialRequest sent by
  /// the channel
  fn handle_serial_request(&mut self, sub_id: &String, msg: SerialRequest) {
    let response = match msg {
      SerialRequest::Open { port } => self.handle_open_port(sub_id, port),
      SerialRequest::WriteLock { port } => self.handle_write_lock(sub_id, port),
      SerialRequest::ReleaseWriteLock { port } => self.handle_release_write_lock(sub_id, port),
      SerialRequest::Write { port, data, base64 } => {
        self.handle_write_port(sub_id, port, data, base64.unwrap_or(false))
      }
      SerialRequest::Close { port } => self.handle_close_port(sub_id, port),
      SerialRequest::List {} => self.handle_list_ports(sub_id),
    };
    match response {
      Ok(response) => {
        // Send acknowledge?
      }
      Err(e) => {
        warn!("Error '{}' occured handling serial request message", e);
        // Send error?
        self.send_message(&sub_id, to_serial_response_error(e));
      }
    }
  }

  /// Handle write port requests
  fn handle_write_port(
    &mut self,
    sub_id: &String,
    port_name: String,
    data: String,
    base_64: bool,
  ) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    self.check_owns_writelock(&port_name, &sub_id)?;
    match base_64 {
      true => {
        base64::decode(&data)
          .map_err(|e| ErrorKind::Base64(e).into())
          .and_then(|d| self.port_manager.write_port(&port_name, &d))
          .map(|_| self.send_message(&sub_id, SerialResponse::Wrote { port: port_name }),)
      }
      false => {
        self
          .port_manager
          .write_port(&port_name, data.as_bytes())
          .map(|_| self.send_message(&sub_id, SerialResponse::Wrote { port: port_name }),)
      }
    }
  }

  /// Handle write lock requests
  fn handle_write_lock(&mut self, sub_id: &String, port_name: String) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    self
      .writelock_manager
      .lock_port(&port_name, &sub_id)
      .map(|_| self.send_message(&sub_id, SerialResponse::WriteLocked { port: port_name }),)
  }

  /// Handle write requests
  fn handle_release_write_lock(
    &mut self,
    sub_id: &String,
    port_name: Option<String>,
  ) -> Result<()> {
    self.check_sub_id(sub_id)?;
    match port_name {
      None => {
        self.writelock_manager.unlock_all_ports_for_sub(&sub_id);
        Ok(
          self.send_message(
            &sub_id,
            SerialResponse::WriteLockReleased { port: port_name },
          ),
        )
      }
      Some(port_name) => {
        self
          .writelock_manager
          .unlock_port(&port_name, &sub_id)
          .map(
            |_| {
              self.send_message(
                &sub_id,
                SerialResponse::WriteLockReleased { port: Some(port_name) },
              )
            },
          )
      }
    }
  }

  /// Handle open port requests
  fn handle_open_port(&mut self, sub_id: &String, port_name: String) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    self.port_manager.open_port(&port_name)?;
    self
      .sub_manager
      .add_port(&sub_id, &port_name)
      .map(|_| self.send_message(&sub_id, SerialResponse::Opened { port: port_name }),)
  }

  /// Handle list ports request
  fn handle_list_ports(&mut self, sub_id: &String) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    let port_names: Result<Vec<String>> =
      self
        .port_manager
        .list_ports()
        .map(|v| v.iter().map(|v| v.port_name.clone()).collect());
    let resp = port_names.map(|pns| SerialResponse::List { ports: pns });
    self.send_message(
      &sub_id,
      resp.unwrap_or(
        SerialResponse::Error {
          display: "".to_string(),
          description: "".to_string(),
        },
      ),
    );
    Ok(())
  }

  /// Handle close port requests
  fn handle_close_port(&mut self, sub_id: &String, port_name: Option<String>) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    // Unsubscribe from ports
    match port_name {
      None => self.sub_manager.clear_ports(Some(&sub_id)),
      Some(pn) => self.sub_manager.remove_port(&sub_id, &pn),
    }?;
    // Close ports with no subscribers
    let open_ports = self.port_manager.open_ports();
    let subscribed_ports = self.sub_manager.subscribed_ports();
    let ports_with_no_subs = open_ports.difference(&subscribed_ports);

    // For each open port that isn't subscribed,
    for port_to_close in ports_with_no_subs {
      // close it
      self.port_manager.close_port(&port_to_close);
      // remove the write lock
      self.writelock_manager.clear_lock(&port_to_close);
      // Let them know its closed
      let close_resp = SerialResponse::Closed { port: port_to_close.clone() };
      self.send_message(&sub_id, close_resp);
    }
    Ok(())
  }

  /// Cleanup any bad ports
  /// Clears out the passed in hash set
  fn cleanup_bad_ports(&mut self, bad_ports: &HashSet<String>) {
    for port_name in bad_ports.iter() {
      // Tell everyone port is sick
      let err_resp = SerialResponse::Error {
        display: format!("Error reading from port '{}', closing!", port_name),
        description: format!(
          "An error occured while trying to read data from '{}'",
          port_name
        ),
      };
      self.broadcast_message_for_port(port_name, err_resp);
      // Tesll everyone the sick ports were closed
      let close_resp = SerialResponse::Closed { port: port_name.clone() };
      self.broadcast_message_for_port(port_name, close_resp);
      // Close bad ports
      self.port_manager.close_port(port_name);
      // Remove write locks on bad ports
      self.writelock_manager.clear_lock(port_name);
      // Remove bad ports from subscriptions
      self.sub_manager.remove_port_from_all(port_name);
    }
    // Clear set of bad_ports
  }

  pub fn send_message(&mut self, sub_id: &String, msg: SerialResponse) {
    match self.sub_manager.send_message(sub_id, msg) {
      Err(e) => {
        warn!("Error sending serial response to sub_id '{}'", sub_id);
        let mut bad_subs = Vec::new();
        bad_subs.push(e);
        self.cleanup_bad_subs(bad_subs);
      }
      _ => {}
    }
  }

  /// Broadcast a message to all subscribers and
  /// then cleanup any subs that errored
  pub fn broadcast_message(&mut self, msg: SerialResponse) {
    let bad_subs = self.sub_manager.broadcast_message(msg);
    self.cleanup_bad_subs(bad_subs);
  }

  /// Broadcast a message to all subscribers interested in the
  /// given port and then cleanup any subs that errored
  pub fn broadcast_message_for_port(&mut self, port_name: &String, msg: SerialResponse) {
    let bad_subs = self
      .sub_manager
      .broadcast_message_for_port(port_name, msg);
    self.cleanup_bad_subs(bad_subs);
  }

  /// Cleanup any bad subs where a message send failed
  fn cleanup_bad_subs(&mut self, bad_subs: Vec<Error>) {
    for e in bad_subs {
      match e {
        Error(ErrorKind::SubscriberSendError(sub_id), _) => {
          self.sub_manager.end_subscription(&sub_id);
          self.writelock_manager.unlock_all_ports_for_sub(&sub_id);
        }
        _ => { /*nop*/ }
      }
    }
  }

  fn check_sub_id(&self, sub_id: &String) -> Result<()> {
    self.sub_manager.check_subscription_exists(sub_id)
  }

  fn check_owns_writelock(&self, port_name: &String, sub_id: &String) -> Result<()> {
    self
      .writelock_manager
      .check_owns_write_lock(port_name, sub_id)
  }
}
<commit_msg>When client closes a port, ensure write locks are removed too<commit_after>/// Serial port management module supporting one
/// writer and multiple readers
///
/// Clients can lock a port for writing, but
/// subscribe to data from multiple ports for reads

use std::collections::HashSet;
use std::sync::mpsc::{Receiver, TryRecvError};
use std::thread;
use std::time::Duration;

use base64;

use serial_support::errors::*;
use serial_support::messages::*;
use serial_support::sub_manager::*;
use serial_support::port_manager::*;
use serial_support::writelock_manager::*;
use serial_support::common::*;

/// Manager manages connection state.
/// TODO Needs a better name
pub struct Manager {
  /// Manage write lock status
  writelock_manager: WriteLockManager,
  /// Manage ports
  port_manager: PortManager,
  /// Manage subscriptions
  sub_manager: SubscriptionManager,
  /// Receiver for serial requests
  receiver: Receiver<(String, SerialRequest)>,
  /// Receiver for response subscription requests
  subsc_receiver: SubscReceiver,
}

impl Manager {
  ///Constructor
  pub fn new(
    receiver: Receiver<(String, SerialRequest)>,
    subsc_receiver: SubscReceiver,
  ) -> Manager {
    Manager {
      writelock_manager: WriteLockManager::new(),
      port_manager: PortManager::new(),
      sub_manager: SubscriptionManager::new(),
      receiver: receiver,
      subsc_receiver: subsc_receiver,
    }
  }

  ///Spawn an instance in a new thread.
  pub fn spawn(
    receiver: Receiver<(String, SerialRequest)>,
    subsc_receiver: SubscReceiver,
  ) -> thread::JoinHandle<()> {
    thread::spawn(move || { Manager::new(receiver, subsc_receiver).run(); })
  }

  /// Main loop
  fn run(&mut self) {

    // Bad ports we couldn't read from
    // A set of SerialResponse::Errors built from
    // from the serial port read/write error responses
    let mut bad_ports = HashSet::<String>::new();
    // Shutdown flag
    let mut shutdown = false;
    // Check about 30 times a second
    let sleep_dur = Duration::from_millis(33);

    while !shutdown {
      // Sleep for a little bit to avoid pegging cpu
      // TODO Dynamic timing loop
      thread::sleep(sleep_dur);

      // Handle serial operation requests
      match self.receiver.try_recv() {
        Err(e) => {
          match e {
            TryRecvError::Empty => {
              // nothing to do
            }
            TryRecvError::Disconnected => {
              // Remote end hung up, time to shutdown
              info!("Shutting down SerialPortManager");
              shutdown = true;
            }
          }
        }
        Ok(req) => self.handle_serial_request(&req.0, req.1),
      }

      // Check for new data on each port
      for (port_name, result) in self.port_manager.read_all_ports() {
        match result {
          Ok(data) => {
            let response = match String::from_utf8(data) {
              // We need to send as binary
              Err(e) => {
                SerialResponse::Read {
                  port: port_name.to_string(),
                  data: base64::encode(&e.into_bytes()),
                  base64: Some(true),
                }
              }
              Ok(s) => {
                SerialResponse::Read {
                  port: port_name.to_string(),
                  data: s,
                  base64: Some(false),
                }
              }
            };
            self.broadcast_message_for_port(&port_name, response);
          }
          // Send data reads
          Err(e) => {
            warn!("Error reading port!");
            warn!("{}", e);
            bad_ports.insert(port_name);
          }
        }
      }

      //Handle write requests
      let mut recv_count = 0;
      let mut do_recv = true;
      while do_recv {
        recv_count += 1;
        match self.subsc_receiver.try_recv() {
          Ok(sub_request) => self.sub_manager.add_subscription(sub_request),
          Err(e) => {
            match e {
              TryRecvError::Disconnected => {
                // Does this mean all senders have disconnected?
                // Or just one?
                debug!("Got disconnected when trying to get serial request");
              }
              TryRecvError::Empty => do_recv = false,
            }
          }
        }

        // Don't let this loop get stuck here forever
        // starving the outer loop
        if recv_count == 50 {
          do_recv = false;
        }
      }

      // Cleanup bad serial ports that failed read or write
      // We remove them from everything before
      self.cleanup_bad_ports(&bad_ports);
      bad_ports.clear();
    }
  }

  /// Handles and dispatches SerialRequest sent by
  /// the channel
  fn handle_serial_request(&mut self, sub_id: &String, msg: SerialRequest) {
    let response = match msg {
      SerialRequest::Open { port } => self.handle_open_port(sub_id, port),
      SerialRequest::WriteLock { port } => self.handle_write_lock(sub_id, port),
      SerialRequest::ReleaseWriteLock { port } => self.handle_release_write_lock(sub_id, port),
      SerialRequest::Write { port, data, base64 } => {
        self.handle_write_port(sub_id, port, data, base64.unwrap_or(false))
      }
      SerialRequest::Close { port } => self.handle_close_port(sub_id, port),
      SerialRequest::List {} => self.handle_list_ports(sub_id),
    };
    match response {
      Ok(response) => {
        // Send acknowledge?
      }
      Err(e) => {
        warn!("Error '{}' occured handling serial request message", e);
        // Send error?
        self.send_message(&sub_id, to_serial_response_error(e));
      }
    }
  }

  /// Handle write port requests
  fn handle_write_port(
    &mut self,
    sub_id: &String,
    port_name: String,
    data: String,
    base_64: bool,
  ) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    self.check_owns_writelock(&port_name, &sub_id)?;
    match base_64 {
      true => {
        base64::decode(&data)
          .map_err(|e| ErrorKind::Base64(e).into())
          .and_then(|d| self.port_manager.write_port(&port_name, &d))
          .map(|_| self.send_message(&sub_id, SerialResponse::Wrote { port: port_name }),)
      }
      false => {
        self
          .port_manager
          .write_port(&port_name, data.as_bytes())
          .map(|_| self.send_message(&sub_id, SerialResponse::Wrote { port: port_name }),)
      }
    }
  }

  /// Handle write lock requests
  fn handle_write_lock(&mut self, sub_id: &String, port_name: String) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    self
      .writelock_manager
      .lock_port(&port_name, &sub_id)
      .map(|_| self.send_message(&sub_id, SerialResponse::WriteLocked { port: port_name }),)
  }

  /// Handle write requests
  fn handle_release_write_lock(
    &mut self,
    sub_id: &String,
    port_name: Option<String>,
  ) -> Result<()> {
    self.check_sub_id(sub_id)?;
    match port_name {
      None => {
        self.writelock_manager.unlock_all_ports_for_sub(&sub_id);
        Ok(
          self.send_message(
            &sub_id,
            SerialResponse::WriteLockReleased { port: port_name },
          ),
        )
      }
      Some(port_name) => {
        self
          .writelock_manager
          .unlock_port(&port_name, &sub_id)
          .map(
            |_| {
              self.send_message(
                &sub_id,
                SerialResponse::WriteLockReleased { port: Some(port_name) },
              )
            },
          )
      }
    }
  }

  /// Handle open port requests
  fn handle_open_port(&mut self, sub_id: &String, port_name: String) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    self.port_manager.open_port(&port_name)?;
    self
      .sub_manager
      .add_port(&sub_id, &port_name)
      .map(|_| self.send_message(&sub_id, SerialResponse::Opened { port: port_name }),)
  }

  /// Handle list ports request
  fn handle_list_ports(&mut self, sub_id: &String) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    let port_names: Result<Vec<String>> =
      self
        .port_manager
        .list_ports()
        .map(|v| v.iter().map(|v| v.port_name.clone()).collect());
    let resp = port_names.map(|pns| SerialResponse::List { ports: pns });
    self.send_message(
      &sub_id,
      resp.unwrap_or(
        SerialResponse::Error {
          display: "".to_string(),
          description: "".to_string(),
        },
      ),
    );
    Ok(())
  }

  /// Handle close port requests
  fn handle_close_port(&mut self, sub_id: &String, port_name: Option<String>) -> Result<()> {
    self.check_sub_id(&sub_id)?;
    // Unsubscribe from ports
    match port_name {
      None => {
        self.sub_manager.clear_ports(Some(&sub_id));
        Ok(())
        },
      Some(ref pn) => self.sub_manager.remove_port(&sub_id, &pn)
    }?;
    // Remove write locks
    match port_name {
      None => {
        self.writelock_manager.unlock_all_ports_for_sub(sub_id);
        Ok(())
      },
      Some(ref pn) => self.writelock_manager.unlock_port(&pn, sub_id)
    }?;
    // TODO: Ensure close messages are sent, etc

    // Close ports with no subscribers
    let open_ports = self.port_manager.open_ports();
    let subscribed_ports = self.sub_manager.subscribed_ports();
    let ports_with_no_subs = open_ports.difference(&subscribed_ports);

    // For each open port that isn't subscribed,
    for port_to_close in ports_with_no_subs {
      // close it
      self.port_manager.close_port(&port_to_close);
      // remove the write lock
      self.writelock_manager.clear_lock(&port_to_close);
      // Let them know its closed
      let close_resp = SerialResponse::Closed { port: port_to_close.clone() };
      self.send_message(&sub_id, close_resp);
    }
    Ok(())
  }

  /// Cleanup any bad ports
  /// Clears out the passed in hash set
  fn cleanup_bad_ports(&mut self, bad_ports: &HashSet<String>) {
    for port_name in bad_ports.iter() {
      // Tell everyone port is sick
      let err_resp = SerialResponse::Error {
        display: format!("Error reading from port '{}', closing!", port_name),
        description: format!(
          "An error occured while trying to read data from '{}'",
          port_name
        ),
      };
      self.broadcast_message_for_port(port_name, err_resp);
      // Tesll everyone the sick ports were closed
      let close_resp = SerialResponse::Closed { port: port_name.clone() };
      self.broadcast_message_for_port(port_name, close_resp);
      // Close bad ports
      self.port_manager.close_port(port_name);
      // Remove write locks on bad ports
      self.writelock_manager.clear_lock(port_name);
      // Remove bad ports from subscriptions
      self.sub_manager.remove_port_from_all(port_name);
    }
    // Clear set of bad_ports
  }

  pub fn send_message(&mut self, sub_id: &String, msg: SerialResponse) {
    match self.sub_manager.send_message(sub_id, msg) {
      Err(e) => {
        warn!("Error sending serial response to sub_id '{}'", sub_id);
        let mut bad_subs = Vec::new();
        bad_subs.push(e);
        self.cleanup_bad_subs(bad_subs);
      }
      _ => {}
    }
  }

  /// Broadcast a message to all subscribers and
  /// then cleanup any subs that errored
  pub fn broadcast_message(&mut self, msg: SerialResponse) {
    let bad_subs = self.sub_manager.broadcast_message(msg);
    self.cleanup_bad_subs(bad_subs);
  }

  /// Broadcast a message to all subscribers interested in the
  /// given port and then cleanup any subs that errored
  pub fn broadcast_message_for_port(&mut self, port_name: &String, msg: SerialResponse) {
    let bad_subs = self
      .sub_manager
      .broadcast_message_for_port(port_name, msg);
    self.cleanup_bad_subs(bad_subs);
  }

  /// Cleanup any bad subs where a message send failed
  fn cleanup_bad_subs(&mut self, bad_subs: Vec<Error>) {
    for e in bad_subs {
      match e {
        Error(ErrorKind::SubscriberSendError(sub_id), _) => {
          self.sub_manager.end_subscription(&sub_id);
          self.writelock_manager.unlock_all_ports_for_sub(&sub_id);
        }
        _ => { /*nop*/ }
      }
    }
  }

  fn check_sub_id(&self, sub_id: &String) -> Result<()> {
    self.sub_manager.check_subscription_exists(sub_id)
  }

  fn check_owns_writelock(&self, port_name: &String, sub_id: &String) -> Result<()> {
    self
      .writelock_manager
      .check_owns_write_lock(port_name, sub_id)
  }
}
<|endoftext|>


<commit_before>use std::sync::Arc;
use std::sync::mpsc::Sender;

use iron::prelude::*;
use iron::status;
use iron::middleware::Handler;
use bodyparser;
use regex::Regex;
use serde_json;

use config::Config;
use github;
use github::CommentLike;
use messenger::{self, Messenger};
use pr_merge::{self, PRMergeMessage};
use slack::SlackAttachmentBuilder;
use util;

pub struct GithubHandler {
    pub config: Arc<Config>,
    pub github_session: Arc<github::api::Session>,
    pr_merge_worker: pr_merge::Worker,
}

struct GithubEventHandler {
    pub messenger: Box<Messenger>,
    pub config: Arc<Config>,
    pub event: String,
    pub data: github::HookBody,
    pub action: String,
    pub github_session: Arc<github::api::Session>,
    pub pr_merge: Sender<PRMergeMessage>,
}

const MAX_CONCURRENT_MERGES: usize = 20;

impl GithubHandler {
    pub fn new(config: Arc<Config>, github_session: github::api::Session) -> GithubHandler {

        let session = Arc::new(github_session);
        GithubHandler {
            config: config.clone(),
            github_session: session.clone(),
            pr_merge_worker: pr_merge::Worker::new(MAX_CONCURRENT_MERGES,
                                                   config.clone(),
                                                   session.clone()),
        }
    }
}

impl Handler for GithubHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        let event: String = match req.headers.get_raw("x-github-event") {
            Some(ref h) if h.len() == 1 => String::from_utf8_lossy(&h[0]).into_owned(),
            None | Some(..) => {
                error!("Expected to find exactly one event header");
                return Ok(Response::with((status::BadRequest,
                                          "Expected to find exactly one event header")));
            }
        };

        let body = match req.get::<bodyparser::Raw>() {
            Ok(Some(j)) => j,
            Err(_) | Ok(None) => {
                error!("Error reading json");
                return Ok(Response::with((status::BadRequest, format!("Error reading json"))));
            }
        };

        let data: github::HookBody = match serde_json::from_str(&body) {
            Ok(h) => h,
            Err(e) => {
                error!("Error parsing json: {}\n---\n{}\n---\n", e, &body);
                return Ok(Response::with((status::BadRequest,
                                          format!("Error parsing JSON: {}", e))));
            }
        };

        let action = match data.action {
            Some(ref a) => a.clone(),
            None => String::new(),
        };

        let handler = GithubEventHandler {
            event: event.clone(),
            data: data,
            action: action,
            config: self.config.clone(),
            messenger: messenger::from_config(self.config.clone()),
            github_session: self.github_session.clone(),
            pr_merge: self.pr_merge_worker.new_sender(),
        };

        match handler.handle_event() {
            Some(r) => Ok(r),
            None => Ok(Response::with((status::Ok, format!("Unhandled event: {}", event)))),
        }
    }
}

impl GithubEventHandler {
    fn handle_event(&self) -> Option<Response> {
        info!("Received event: {}", self.event);
        if self.event == "ping" {
            Some(self.handle_ping())
        } else if self.event == "pull_request" {
            Some(self.handle_pr())
        } else if self.event == "pull_request_review_comment" {
            Some(self.handle_pr_review_comment())
        } else if self.event == "pull_request_review" {
            Some(self.handle_pr_review())
        } else if self.event == "commit_comment" {
            Some(self.handle_commit_comment())
        } else if self.event == "issue_comment" {
            Some(self.handle_issue_comment())
        } else if self.event == "push" {
            Some(self.handle_push())
        } else {
            None
        }
    }

    fn slack_user_name(&self, user: &github::User) -> String {
        self.config.users.slack_user_name(user.login(), &self.data.repository)
    }

    fn handle_ping(&self) -> Response {
        Response::with((status::Ok, "ping"))
    }

    fn handle_pr(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            let verb: Option<String>;
            if self.action == "opened" {
                verb = Some(format!("opened by {}", self.slack_user_name(&pull_request.user)));
            } else if self.action == "closed" {
                if pull_request.merged == Some(true) {
                    verb = Some("merged".to_string());
                } else {
                    verb = Some("closed".to_string());
                }
            } else if self.action == "reopened" {
                verb = Some("reopened".to_string());
            } else if self.action == "assigned" {
                let assignees_str = self.config
                    .users
                    .slack_user_names(&pull_request.assignees, &self.data.repository)
                    .join(", ");
                verb = Some(format!("assigned to {}", assignees_str));
            } else if self.action == "unassigned" {
                verb = Some("unassigned".to_string());
            } else {
                verb = None;
            }

            if let Some(ref verb) = verb {
                let msg = format!("Pull Request {}", verb);
                let attachments = vec![SlackAttachmentBuilder::new("")
                                           .title(format!("Pull Request #{}: \"{}\"",
                                                          pull_request.number,
                                                          pull_request.title.as_str()))
                                           .title_link(pull_request.html_url.as_str())
                                           .build()];

                self.messenger.send_to_all(&msg,
                                           &attachments,
                                           &pull_request.user,
                                           &self.data.sender,
                                           &self.data.repository,
                                           &pull_request.assignees);
            }

            if self.action == "labeled" {
                if let Some(ref label) = self.data.label {
                    self.merge_pull_request(pull_request, label);
                }
            } else if verb == Some("merged".to_string()) {
                self.merge_pull_request_all_labels(pull_request);
            }
        }

        Response::with((status::Ok, "pr"))
    }

    fn handle_pr_review_comment(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    self.do_pull_request_comment(&pull_request, &comment)
                }

            }
        }

        Response::with((status::Ok, "pr_review_comment"))
    }

    fn handle_pr_review(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref review) = self.data.review {
                if self.action == "submitted" {

                    // just a comment. should just be handled by regular comment handler.
                    if review.state == "commented" {
                        self.do_pull_request_comment(&pull_request, &review);
                        return Response::with((status::Ok, "pr_review [comment]"));
                    }

                    let action_msg;
                    let state_msg;
                    let color;
                    if review.state == "changes_requested" {
                        action_msg = "requested changes to";
                        state_msg = "Changes Requested";
                        color = "danger";

                    } else if review.state == "approved" {
                        action_msg = "approved";
                        state_msg = "Approved";
                        color = "good";

                    } else {
                        return Response::with((status::Ok, "pr_review [ignored]"));
                    }

                    let msg = format!("{} {} PR \"{}\"",
                                      self.slack_user_name(&review.user),
                                      action_msg,
                                      util::make_link(pull_request.html_url.as_str(),
                                                      pull_request.title.as_str()));

                    let attachments = vec![SlackAttachmentBuilder::new(review.body())
                                               .title(format!("Review: {}", state_msg))
                                               .title_link(review.html_url.as_str())
                                               .color(color)
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &pull_request.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &pull_request.assignees);

                }
            }
        }

        Response::with((status::Ok, "pr_review"))
    }

    fn do_pull_request_comment(&self, pull_request: &github::PullRequestLike,
                               comment: &github::CommentLike) {
        if comment.body().trim().len() == 0 {
            return;
        }
        if comment.user().login() == self.github_session.user().login() {
            info!("Ignoring message from octobot ({}): {}",
                  self.github_session.user().login(),
                  comment.body());
            return;
        }

        let msg = format!("Comment on \"{}\"",
                          util::make_link(pull_request.html_url(), pull_request.title()));

        let attachments = vec![SlackAttachmentBuilder::new(comment.body().trim())
                                   .title(format!("{} said:",
                                                  self.slack_user_name(comment.user())))
                                   .title_link(comment.html_url())
                                   .build()];

        self.messenger.send_to_all(&msg,
                                   &attachments,
                                   pull_request.user(),
                                   &self.data.sender,
                                   &self.data.repository,
                                   pull_request.assignees());

    }

    fn handle_commit_comment(&self) -> Response {
        if let Some(ref comment) = self.data.comment {
            if self.action == "created" {
                if let Some(ref commit_id) = comment.commit_id {
                    let commit: &str = &commit_id[0..7];
                    let commit_url =
                        format!("{}/commit/{}", self.data.repository.html_url, commit_id);
                    let commit_path: String;
                    if let Some(ref path) = comment.path {
                        commit_path = path.to_string();
                    } else {
                        commit_path = commit.to_string();
                    }

                    let msg = format!("Comment on \"{}\" ({})",
                                      commit_path,
                                      util::make_link(commit_url.as_str(), commit));

                    let attachments = vec![SlackAttachmentBuilder::new(comment.body())
                                               .title(format!("{} said:",
                                                              self.slack_user_name(&comment.user)))
                                               .title_link(comment.html_url.as_str())
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &comment.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &vec![]);
                }
            }
        }

        Response::with((status::Ok, "commit_comment"))
    }

    fn handle_issue_comment(&self) -> Response {
        if let Some(ref issue) = self.data.issue {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    self.do_pull_request_comment(&issue, &comment);
                }
            }
        }
        Response::with((status::Ok, "issue_comment"))
    }

    fn handle_push(&self) -> Response {
        if self.data.deleted() || self.data.created() {
            // ignore
            return Response::with((status::Ok, "push [ignored]"));
        }
        if self.data.ref_name().len() > 0 && self.data.after().len() > 0 &&
           self.data.before().len() > 0 {

            let branch_name = self.data.ref_name().replace("refs/heads/", "");

            let prs = match self.github_session
                .get_pull_requests(&self.data.repository.owner.name(),
                                   &self.data.repository.name,
                                   Some("open"),
                                   Some(self.data.after())) {
                Ok(p) => p,
                Err(e) => {
                    error!("Error looking up PR for '{}' ({}): {}",
                           branch_name,
                           self.data.after(),
                           e);
                    return Response::with((status::Ok, "push [no PR]"));
                }
            };
            if prs.len() == 0 {
                info!("No PRs found for '{}' ({})", branch_name, self.data.after());
            } else {
                let attachments: Vec<_>;
                if let Some(ref commits) = self.data.commits {
                    attachments = commits.iter()
                        .map(|commit| {
                            let msg = commit.message.lines().next().unwrap_or("");
                            let hash: &str = &commit.id[0..7];
                            let attach = format!("{}: {}", util::make_link(&commit.url, hash), msg);
                            SlackAttachmentBuilder::new(&attach).build()
                        })
                        .collect();
                } else {
                    attachments = vec![];
                }

                let message = format!("{} pushed {} commit(s) to branch {}",
                                      self.slack_user_name(&self.data.sender),
                                      attachments.len(),
                                      branch_name);

                for pull_request in &prs {
                    let mut attachments = attachments.clone();
                    attachments.insert(0,
                                       SlackAttachmentBuilder::new("")
                                           .title(format!("Pull Request #{}: \"{}\"",
                                                          pull_request.number,
                                                          pull_request.title.as_str()))
                                           .title_link(pull_request.html_url.as_str())
                                           .build());

                    self.messenger.send_to_all(&message,
                                               &attachments,
                                               &pull_request.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &pull_request.assignees);

                    if self.data.forced() && self.config.repos.notify_force_push(&self.data.repository) {
                        let mut comment = format!("Force-push detected: before: {}, after: {}",
                                                  &self.data.before()[0..7],
                                                  &self.data.after()[0..7]);
                        if let Some(ref url) = self.data.compare {
                            comment += &format!(" ([compare]({}))", url);
                        }
                        if let Err(e) = self.github_session
                            .comment_pull_request(&self.data.repository.owner.name(),
                                                  &self.data.repository.name,
                                                  pull_request.number,
                                                  &comment) {
                            error!("Error sending github PR comment: {}", e);
                        }
                    }
                }
            }
        }

        Response::with((status::Ok, "push"))
    }

    fn merge_pull_request_all_labels(&self, pull_request: &github::PullRequest) {
        if !pull_request.is_merged() {
            return;
        }

        let labels = match self.github_session
            .get_pull_request_labels(&self.data.repository.owner.login(),
                                     &self.data.repository.name,
                                     pull_request.number) {
            Ok(l) => l,
            Err(e) => {
                self.messenger.send_to_owner("Error getting Pull Request labels",
                                             &vec![SlackAttachmentBuilder::new(&e)
                                                       .color("danger")
                                                       .build()],
                                             &pull_request.user,
                                             &self.data.repository);
                return;
            }
        };

        for label in &labels {
            self.merge_pull_request(pull_request, label);
        }
    }

    fn merge_pull_request(&self, pull_request: &github::PullRequest, label: &github::Label) {
        if !pull_request.is_merged() {
            return;
        }

        let re = Regex::new(r"(?i)backport-([\d\.]+)").unwrap();
        let backport = match re.captures(&label.name) {
            Some(c) => c[1].to_string(),
            None => return,
        };
        let target_branch = "release/".to_string() + &backport;

        if let Err(e) = self.pr_merge
            .send(PRMergeMessage::merge(&self.data.repository, pull_request, &target_branch)) {
            error!("Error sending merge request message: {}", e)
        }
    }
}
<commit_msg>Don't add force-push warning for WIP PRs (#30)<commit_after>use std::sync::Arc;
use std::sync::mpsc::Sender;

use iron::prelude::*;
use iron::status;
use iron::middleware::Handler;
use bodyparser;
use regex::Regex;
use serde_json;

use config::Config;
use github;
use github::CommentLike;
use messenger::{self, Messenger};
use pr_merge::{self, PRMergeMessage};
use slack::SlackAttachmentBuilder;
use util;

pub struct GithubHandler {
    pub config: Arc<Config>,
    pub github_session: Arc<github::api::Session>,
    pr_merge_worker: pr_merge::Worker,
}

struct GithubEventHandler {
    pub messenger: Box<Messenger>,
    pub config: Arc<Config>,
    pub event: String,
    pub data: github::HookBody,
    pub action: String,
    pub github_session: Arc<github::api::Session>,
    pub pr_merge: Sender<PRMergeMessage>,
}

const MAX_CONCURRENT_MERGES: usize = 20;

impl GithubHandler {
    pub fn new(config: Arc<Config>, github_session: github::api::Session) -> GithubHandler {

        let session = Arc::new(github_session);
        GithubHandler {
            config: config.clone(),
            github_session: session.clone(),
            pr_merge_worker: pr_merge::Worker::new(MAX_CONCURRENT_MERGES,
                                                   config.clone(),
                                                   session.clone()),
        }
    }
}

impl Handler for GithubHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        let event: String = match req.headers.get_raw("x-github-event") {
            Some(ref h) if h.len() == 1 => String::from_utf8_lossy(&h[0]).into_owned(),
            None | Some(..) => {
                error!("Expected to find exactly one event header");
                return Ok(Response::with((status::BadRequest,
                                          "Expected to find exactly one event header")));
            }
        };

        let body = match req.get::<bodyparser::Raw>() {
            Ok(Some(j)) => j,
            Err(_) | Ok(None) => {
                error!("Error reading json");
                return Ok(Response::with((status::BadRequest, format!("Error reading json"))));
            }
        };

        let data: github::HookBody = match serde_json::from_str(&body) {
            Ok(h) => h,
            Err(e) => {
                error!("Error parsing json: {}\n---\n{}\n---\n", e, &body);
                return Ok(Response::with((status::BadRequest,
                                          format!("Error parsing JSON: {}", e))));
            }
        };

        let action = match data.action {
            Some(ref a) => a.clone(),
            None => String::new(),
        };

        let handler = GithubEventHandler {
            event: event.clone(),
            data: data,
            action: action,
            config: self.config.clone(),
            messenger: messenger::from_config(self.config.clone()),
            github_session: self.github_session.clone(),
            pr_merge: self.pr_merge_worker.new_sender(),
        };

        match handler.handle_event() {
            Some(r) => Ok(r),
            None => Ok(Response::with((status::Ok, format!("Unhandled event: {}", event)))),
        }
    }
}

impl GithubEventHandler {
    fn handle_event(&self) -> Option<Response> {
        info!("Received event: {}", self.event);
        if self.event == "ping" {
            Some(self.handle_ping())
        } else if self.event == "pull_request" {
            Some(self.handle_pr())
        } else if self.event == "pull_request_review_comment" {
            Some(self.handle_pr_review_comment())
        } else if self.event == "pull_request_review" {
            Some(self.handle_pr_review())
        } else if self.event == "commit_comment" {
            Some(self.handle_commit_comment())
        } else if self.event == "issue_comment" {
            Some(self.handle_issue_comment())
        } else if self.event == "push" {
            Some(self.handle_push())
        } else {
            None
        }
    }

    fn slack_user_name(&self, user: &github::User) -> String {
        self.config.users.slack_user_name(user.login(), &self.data.repository)
    }

    fn handle_ping(&self) -> Response {
        Response::with((status::Ok, "ping"))
    }

    fn handle_pr(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            let verb: Option<String>;
            if self.action == "opened" {
                verb = Some(format!("opened by {}", self.slack_user_name(&pull_request.user)));
            } else if self.action == "closed" {
                if pull_request.merged == Some(true) {
                    verb = Some("merged".to_string());
                } else {
                    verb = Some("closed".to_string());
                }
            } else if self.action == "reopened" {
                verb = Some("reopened".to_string());
            } else if self.action == "assigned" {
                let assignees_str = self.config
                    .users
                    .slack_user_names(&pull_request.assignees, &self.data.repository)
                    .join(", ");
                verb = Some(format!("assigned to {}", assignees_str));
            } else if self.action == "unassigned" {
                verb = Some("unassigned".to_string());
            } else {
                verb = None;
            }

            if let Some(ref verb) = verb {
                let msg = format!("Pull Request {}", verb);
                let attachments = vec![SlackAttachmentBuilder::new("")
                                           .title(format!("Pull Request #{}: \"{}\"",
                                                          pull_request.number,
                                                          pull_request.title.as_str()))
                                           .title_link(pull_request.html_url.as_str())
                                           .build()];

                self.messenger.send_to_all(&msg,
                                           &attachments,
                                           &pull_request.user,
                                           &self.data.sender,
                                           &self.data.repository,
                                           &pull_request.assignees);
            }

            if self.action == "labeled" {
                if let Some(ref label) = self.data.label {
                    self.merge_pull_request(pull_request, label);
                }
            } else if verb == Some("merged".to_string()) {
                self.merge_pull_request_all_labels(pull_request);
            }
        }

        Response::with((status::Ok, "pr"))
    }

    fn handle_pr_review_comment(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    self.do_pull_request_comment(&pull_request, &comment)
                }

            }
        }

        Response::with((status::Ok, "pr_review_comment"))
    }

    fn handle_pr_review(&self) -> Response {
        if let Some(ref pull_request) = self.data.pull_request {
            if let Some(ref review) = self.data.review {
                if self.action == "submitted" {

                    // just a comment. should just be handled by regular comment handler.
                    if review.state == "commented" {
                        self.do_pull_request_comment(&pull_request, &review);
                        return Response::with((status::Ok, "pr_review [comment]"));
                    }

                    let action_msg;
                    let state_msg;
                    let color;
                    if review.state == "changes_requested" {
                        action_msg = "requested changes to";
                        state_msg = "Changes Requested";
                        color = "danger";

                    } else if review.state == "approved" {
                        action_msg = "approved";
                        state_msg = "Approved";
                        color = "good";

                    } else {
                        return Response::with((status::Ok, "pr_review [ignored]"));
                    }

                    let msg = format!("{} {} PR \"{}\"",
                                      self.slack_user_name(&review.user),
                                      action_msg,
                                      util::make_link(pull_request.html_url.as_str(),
                                                      pull_request.title.as_str()));

                    let attachments = vec![SlackAttachmentBuilder::new(review.body())
                                               .title(format!("Review: {}", state_msg))
                                               .title_link(review.html_url.as_str())
                                               .color(color)
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &pull_request.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &pull_request.assignees);

                }
            }
        }

        Response::with((status::Ok, "pr_review"))
    }

    fn do_pull_request_comment(&self, pull_request: &github::PullRequestLike,
                               comment: &github::CommentLike) {
        if comment.body().trim().len() == 0 {
            return;
        }
        if comment.user().login() == self.github_session.user().login() {
            info!("Ignoring message from octobot ({}): {}",
                  self.github_session.user().login(),
                  comment.body());
            return;
        }

        let msg = format!("Comment on \"{}\"",
                          util::make_link(pull_request.html_url(), pull_request.title()));

        let attachments = vec![SlackAttachmentBuilder::new(comment.body().trim())
                                   .title(format!("{} said:",
                                                  self.slack_user_name(comment.user())))
                                   .title_link(comment.html_url())
                                   .build()];

        self.messenger.send_to_all(&msg,
                                   &attachments,
                                   pull_request.user(),
                                   &self.data.sender,
                                   &self.data.repository,
                                   pull_request.assignees());

    }

    fn handle_commit_comment(&self) -> Response {
        if let Some(ref comment) = self.data.comment {
            if self.action == "created" {
                if let Some(ref commit_id) = comment.commit_id {
                    let commit: &str = &commit_id[0..7];
                    let commit_url =
                        format!("{}/commit/{}", self.data.repository.html_url, commit_id);
                    let commit_path: String;
                    if let Some(ref path) = comment.path {
                        commit_path = path.to_string();
                    } else {
                        commit_path = commit.to_string();
                    }

                    let msg = format!("Comment on \"{}\" ({})",
                                      commit_path,
                                      util::make_link(commit_url.as_str(), commit));

                    let attachments = vec![SlackAttachmentBuilder::new(comment.body())
                                               .title(format!("{} said:",
                                                              self.slack_user_name(&comment.user)))
                                               .title_link(comment.html_url.as_str())
                                               .build()];

                    self.messenger.send_to_all(&msg,
                                               &attachments,
                                               &comment.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &vec![]);
                }
            }
        }

        Response::with((status::Ok, "commit_comment"))
    }

    fn handle_issue_comment(&self) -> Response {
        if let Some(ref issue) = self.data.issue {
            if let Some(ref comment) = self.data.comment {
                if self.action == "created" {
                    self.do_pull_request_comment(&issue, &comment);
                }
            }
        }
        Response::with((status::Ok, "issue_comment"))
    }

    fn handle_push(&self) -> Response {
        if self.data.deleted() || self.data.created() {
            // ignore
            return Response::with((status::Ok, "push [ignored]"));
        }
        if self.data.ref_name().len() > 0 && self.data.after().len() > 0 &&
           self.data.before().len() > 0 {

            let branch_name = self.data.ref_name().replace("refs/heads/", "");

            let prs = match self.github_session
                .get_pull_requests(&self.data.repository.owner.name(),
                                   &self.data.repository.name,
                                   Some("open"),
                                   Some(self.data.after())) {
                Ok(p) => p,
                Err(e) => {
                    error!("Error looking up PR for '{}' ({}): {}",
                           branch_name,
                           self.data.after(),
                           e);
                    return Response::with((status::Ok, "push [no PR]"));
                }
            };
            if prs.len() == 0 {
                info!("No PRs found for '{}' ({})", branch_name, self.data.after());
            } else {
                let attachments: Vec<_>;
                if let Some(ref commits) = self.data.commits {
                    attachments = commits.iter()
                        .map(|commit| {
                            let msg = commit.message.lines().next().unwrap_or("");
                            let hash: &str = &commit.id[0..7];
                            let attach = format!("{}: {}", util::make_link(&commit.url, hash), msg);
                            SlackAttachmentBuilder::new(&attach).build()
                        })
                        .collect();
                } else {
                    attachments = vec![];
                }

                let message = format!("{} pushed {} commit(s) to branch {}",
                                      self.slack_user_name(&self.data.sender),
                                      attachments.len(),
                                      branch_name);

                for pull_request in &prs {
                    let mut attachments = attachments.clone();
                    attachments.insert(0,
                                       SlackAttachmentBuilder::new("")
                                           .title(format!("Pull Request #{}: \"{}\"",
                                                          pull_request.number,
                                                          pull_request.title.as_str()))
                                           .title_link(pull_request.html_url.as_str())
                                           .build());

                    self.messenger.send_to_all(&message,
                                               &attachments,
                                               &pull_request.user,
                                               &self.data.sender,
                                               &self.data.repository,
                                               &pull_request.assignees);

                    if self.data.forced() &&
                       self.config.repos.notify_force_push(&self.data.repository) &&
                       !pull_request.title.starts_with("WIP:") {
                        let mut comment = format!("Force-push detected: before: {}, after: {}",
                                                  &self.data.before()[0..7],
                                                  &self.data.after()[0..7]);
                        if let Some(ref url) = self.data.compare {
                            comment += &format!(" ([compare]({}))", url);
                        }
                        if let Err(e) = self.github_session
                            .comment_pull_request(&self.data.repository.owner.name(),
                                                  &self.data.repository.name,
                                                  pull_request.number,
                                                  &comment) {
                            error!("Error sending github PR comment: {}", e);
                        }
                    }
                }
            }
        }

        Response::with((status::Ok, "push"))
    }

    fn merge_pull_request_all_labels(&self, pull_request: &github::PullRequest) {
        if !pull_request.is_merged() {
            return;
        }

        let labels = match self.github_session
            .get_pull_request_labels(&self.data.repository.owner.login(),
                                     &self.data.repository.name,
                                     pull_request.number) {
            Ok(l) => l,
            Err(e) => {
                self.messenger.send_to_owner("Error getting Pull Request labels",
                                             &vec![SlackAttachmentBuilder::new(&e)
                                                       .color("danger")
                                                       .build()],
                                             &pull_request.user,
                                             &self.data.repository);
                return;
            }
        };

        for label in &labels {
            self.merge_pull_request(pull_request, label);
        }
    }

    fn merge_pull_request(&self, pull_request: &github::PullRequest, label: &github::Label) {
        if !pull_request.is_merged() {
            return;
        }

        let re = Regex::new(r"(?i)backport-([\d\.]+)").unwrap();
        let backport = match re.captures(&label.name) {
            Some(c) => c[1].to_string(),
            None => return,
        };
        let target_branch = "release/".to_string() + &backport;

        if let Err(e) = self.pr_merge
            .send(PRMergeMessage::merge(&self.data.repository, pull_request, &target_branch)) {
            error!("Error sending merge request message: {}", e)
        }
    }
}
<|endoftext|>


<commit_before>import std::vec;
import std::vec::plus_option;

import front::ast;
import front::ast::crate;
import front::ast::ann;
import front::ast::arg;
import front::ast::method;
import front::ast::local;
import front::ast::item;
import front::ast::item_fn;
import front::ast::item_obj;
import front::ast::_obj;
import front::ast::obj_def_ids;
import front::ast::_fn;
import front::ast::ty_param;
import front::ast::_mod;
import front::ast::decl;
import front::ast::decl_local;
import front::ast::def_id;
import front::ast::ident;
import middle::fold::span;
import middle::fold::respan;
import middle::fold::new_identity_fold;
import middle::fold::fold_block;
import middle::fold::fold_fn;
import middle::fold::fold_crate;

import aux::fn_info;
import aux::var_info;
import aux::crate_ctxt;

import util::common::new_def_hash;

fn var_is_local(def_id v, fn_info m) -> bool {
  ret (m.vars.contains_key(v));
}

fn collect_local(&@vec[tup(ident, def_id)] vars, &span sp, &@local loc)
    -> @decl {
    log("collect_local: pushing " + loc.ident);
    vec::push[tup(ident, def_id)](*vars, tup(loc.ident, loc.id));
    ret @respan(sp, decl_local(loc));
}

fn find_locals(_fn f) -> @vec[tup(ident,def_id)] {
  auto res = @vec::alloc[tup(ident,def_id)](0u);

  auto fld = new_identity_fold[@vec[tup(ident, def_id)]]();
  fld = @rec(fold_decl_local = bind collect_local(_,_,_) with *fld);
  auto ignore = fold_fn[@vec[tup(ident, def_id)]](res, fld, f);

  ret res;
}

fn add_var(def_id v, ident nm, uint next, fn_info tbl) -> uint {
  log(nm + " |-> " + util::common::uistr(next));
  tbl.vars.insert(v, tup(next,nm));
  ret (next + 1u);
}

/* builds a table mapping each local var defined in f
   to a bit number in the precondition/postcondition vectors */
fn mk_fn_info(_fn f, def_id f_id, ident f_name) -> fn_info {
    auto res = rec(vars=@new_def_hash[var_info](),
                   cf=f.decl.cf);
    let uint next = 0u;
    let vec[arg] f_args = f.decl.inputs;

    /* ignore args, which we know are initialized;
       just collect locally declared vars */

    let @vec[tup(ident,def_id)] locals = find_locals(f);
    // log (uistr(vec::len[tup(ident, def_id)](locals)) + " locals");
    for (tup(ident,def_id) p in *locals) {
        next = add_var(p._1, p._0, next, res);
    }
    /* add a pseudo-entry for the function's return value
       we can safely use the function's name itself for this purpose */
    add_var(f_id, f_name, next, res);

    ret res;
}

/* extends mk_fn_info to a function item, side-effecting the map fi from
   function IDs to fn_info maps */
fn mk_fn_info_item_fn(&crate_ctxt ccx, &span sp, &ident i, &_fn f,
                 &vec[ty_param] ty_params, &def_id id, &ann a) -> @item {
    auto f_inf = mk_fn_info(f, id, i);
    ccx.fm.insert(id, f_inf);
    //  log_err("inserting: " + i);
    ret @respan(sp, item_fn(i, f, ty_params, id, a));
}

/* extends mk_fn_info to an obj item, side-effecting the map fi from
   function IDs to fn_info maps */
fn mk_fn_info_item_obj(&crate_ctxt ccx, &span sp, &ident i, &_obj o,
                       &vec[ty_param] ty_params,
                       &obj_def_ids odid, &ann a) -> @item {
    auto all_methods = vec::clone[@method](o.methods);
    plus_option[@method](all_methods, o.dtor);
    auto f_inf;
    for (@method m in all_methods) {
        f_inf = mk_fn_info(m.node.meth, m.node.id, m.node.ident);
        ccx.fm.insert(m.node.id, f_inf);
    }
    ret @respan(sp, item_obj(i, o, ty_params, odid, a));
}

/* initializes the global fn_info_map (mapping each function ID, including
   nested locally defined functions, onto a mapping from local variable name
   to bit number) */
fn mk_f_to_fn_info(&crate_ctxt ccx, @crate c) -> () {

  auto fld = new_identity_fold[crate_ctxt]();
  fld = @rec(fold_item_fn  = bind mk_fn_info_item_fn(_,_,_,_,_,_,_),
             fold_item_obj = bind mk_fn_info_item_obj(_,_,_,_,_,_,_)
               with *fld);
  fold_crate[crate_ctxt](ccx, fld, c);
}
<commit_msg>Beginning of Operation Eliminate Folds<commit_after>import std::vec;
import std::vec::plus_option;

import front::ast;
import front::ast::crate;
import front::ast::ann;
import front::ast::arg;
import front::ast::method;
import front::ast::local;
import front::ast::item;
import front::ast::item_fn;
import front::ast::item_obj;
import front::ast::_obj;
import front::ast::obj_def_ids;
import front::ast::_fn;
import front::ast::ty_param;
import front::ast::_mod;
import front::ast::decl;
import front::ast::decl_local;
import front::ast::def_id;
import front::ast::ident;

import middle::fold::span;
import middle::fold::respan;
import middle::walk::walk_crate;
import middle::walk::walk_fn;
import middle::walk::ast_visitor;

import aux::fn_info;
import aux::var_info;
import aux::crate_ctxt;

import util::common::new_def_hash;

fn var_is_local(def_id v, fn_info m) -> bool {
  ret (m.vars.contains_key(v));
}

fn collect_local(&@vec[tup(ident, def_id)] vars, &@local loc) -> () {
    log("collect_local: pushing " + loc.ident);
    _vec::push[tup(ident, def_id)](*vars, tup(loc.ident, loc.id));
}

fn find_locals(_fn f) -> @vec[tup(ident,def_id)] {
  auto res = @vec::alloc[tup(ident,def_id)](0u);

  auto visitor = walk::default_visitor();
  visitor = rec(visit_decl_pre=bind collect_local(res,_) with visitor);
  walk_fn(visitor, f);

  ret res;
}

fn add_var(def_id v, ident nm, uint next, fn_info tbl) -> uint {
  log(nm + " |-> " + util::common::uistr(next));
  tbl.vars.insert(v, tup(next,nm));
  ret (next + 1u);
}

/* builds a table mapping each local var defined in f
   to a bit number in the precondition/postcondition vectors */
fn mk_fn_info(_fn f, def_id f_id, ident f_name) -> fn_info {
    auto res = rec(vars=@new_def_hash[var_info](),
                   cf=f.decl.cf);
    let uint next = 0u;
    let vec[arg] f_args = f.decl.inputs;

    /* ignore args, which we know are initialized;
       just collect locally declared vars */

    let @vec[tup(ident,def_id)] locals = find_locals(f);
    // log (uistr(vec::len[tup(ident, def_id)](locals)) + " locals");
    for (tup(ident,def_id) p in *locals) {
        next = add_var(p._1, p._0, next, res);
    }
    /* add a pseudo-entry for the function's return value
       we can safely use the function's name itself for this purpose */
    add_var(f_id, f_name, next, res);

    ret res;
}

/* extends mk_fn_info to an item, side-effecting the map fi from
   function IDs to fn_info maps
   only looks at function and object items. */
fn mk_fn_info_item (&crate_ctxt ccx, &@item i) -> () {
  alt (i.node) {
    case (item_fn(?i,?f,?ty_params,?id,?a)) {
      auto f_inf = mk_fn_info(f, id, i);
      ccx.fm.insert(id, f_inf);
    }
    case (item_obj(?i,?o,?ty_params,?odid,?a)) {
      auto all_methods = _vec::clone[@method](o.methods);
      plus_option[@method](all_methods, o.dtor);
      auto f_inf;
      for (@method m in all_methods) {
        f_inf = mk_fn_info(m.node.meth, m.node.id, m.node.ident);
        ccx.fm.insert(m.node.id, f_inf);
      }
    }
    case (_) { ret; }
  }
}

/* initializes the global fn_info_map (mapping each function ID, including
   nested locally defined functions, onto a mapping from local variable name
   to bit number) */
fn mk_f_to_fn_info(&crate_ctxt ccx, @crate c) -> () {
  let ast_visitor vars_visitor = walk::default_visitor();
  vars_visitor = rec(visit_item_post=bind mk_fn_info_item(ccx,_)
		     with vars_visitor);

  walk_crate(vars_visitor, *c);
}

//
// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
// End:
//

<|endoftext|>


<commit_before>// stacks
pub mod linked_stack;
pub mod fixed_capacity_stack;
pub mod resizing_array_stack;

pub trait StackOfStrings {
    /// create an empty stack
    fn new() -> Self;
    /// insert a new string onto stack
    fn push(&mut self, item: String);
    /// remove and return the string most recently added
    fn pop(&mut self) ->  String;
    /// is the stack empty?
    fn is_empty(&self) -> bool;
    /// number of strings on the stack
    fn size(&self) -> usize {
        unimplemented!()
    }
}

/// generic stack
pub trait Stack<T> {
    /// create an empty stack
    fn new() -> Self;
    /// insert a new item onto stack
    fn push(&mut self, item: T);
    /// remove and return the item most recently added
    fn pop(&mut self) ->  T;
    /// is the stack empty?
    fn is_empty(&self) -> bool;
    /// number of strings on the stack
    fn size(&self) -> usize {
        unimplemented!()
    }
}

// queues
pub mod linked_queue;
pub mod resizing_array_queue;

pub trait QueueOfStrings {
    /// create an empty queue
    fn new() -> Self;
    /// insert a new string onto queue
    fn enqueue(&mut self, item: String);
    /// remove and return the string least recently added
    fn dequeue(&mut self) -> String;
    /// is the queue empty?
    fn is_empty(&self) -> bool;
    /// number of strings on the queue
    fn size(&self) -> usize {
        unimplemented!()
    }
}

/// generic queue
pub trait Queue<T> {
    /// create an empty queue
    fn new() -> Self;
    /// insert a new item onto queue
    fn enqueue(&mut self, item: T);
    /// remove and return the item least recently added
    fn dequeue(&mut self) -> T;
    /// is the queue empty?
    fn is_empty(&self) -> bool;
    /// number of items on the queue
    fn size(&self) -> usize {
        unimplemented!()
    }
}
<commit_msg>stacks_and_queues: add Bag trait<commit_after>use std::iter::Iterator;

// stacks
pub mod linked_stack;
pub mod fixed_capacity_stack;
pub mod resizing_array_stack;

pub trait StackOfStrings {
    /// create an empty stack
    fn new() -> Self;
    /// insert a new string onto stack
    fn push(&mut self, item: String);
    /// remove and return the string most recently added
    fn pop(&mut self) ->  String;
    /// is the stack empty?
    fn is_empty(&self) -> bool;
    /// number of strings on the stack
    fn size(&self) -> usize {
        unimplemented!()
    }
}

/// generic stack
pub trait Stack<T> {
    /// create an empty stack
    fn new() -> Self;
    /// insert a new item onto stack
    fn push(&mut self, item: T);
    /// remove and return the item most recently added
    fn pop(&mut self) ->  T;
    /// is the stack empty?
    fn is_empty(&self) -> bool;
    /// number of strings on the stack
    fn size(&self) -> usize {
        unimplemented!()
    }
}

// queues
pub mod linked_queue;
pub mod resizing_array_queue;

pub trait QueueOfStrings {
    /// create an empty queue
    fn new() -> Self;
    /// insert a new string onto queue
    fn enqueue(&mut self, item: String);
    /// remove and return the string least recently added
    fn dequeue(&mut self) -> String;
    /// is the queue empty?
    fn is_empty(&self) -> bool;
    /// number of strings on the queue
    fn size(&self) -> usize {
        unimplemented!()
    }
}

/// generic queue
pub trait Queue<T> {
    /// create an empty queue
    fn new() -> Self;
    /// insert a new item onto queue
    fn enqueue(&mut self, item: T);
    /// remove and return the item least recently added
    fn dequeue(&mut self) -> T;
    /// is the queue empty?
    fn is_empty(&self) -> bool;
    /// number of items on the queue
    fn size(&self) -> usize {
        unimplemented!()
    }
}

// bag
pub trait Bag<T> {
    fn new() -> Self;
    fn add(&mut self, item: T);
    fn iter(&self) -> Iterator<Item=T>;
    fn size(&self) -> usize {
        unimplemented!()
    }
}
<|endoftext|>


<commit_before>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use calculate_svh::SvhCalculate;
use rbml::Error;
use rbml::opaque::Decoder;
use rustc::dep_graph::DepNode;
use rustc::hir::def_id::DefId;
use rustc::hir::svh::Svh;
use rustc::ty::TyCtxt;
use rustc_data_structures::fnv::FnvHashMap;
use rustc_serialize::Decodable;
use std::io::{ErrorKind, Read};
use std::fs::File;
use syntax::ast;

use super::data::*;
use super::util::*;

pub struct HashContext<'a, 'tcx: 'a> {
    pub tcx: TyCtxt<'a, 'tcx, 'tcx>,
    item_metadata_hashes: FnvHashMap<DefId, u64>,
    crate_hashes: FnvHashMap<ast::CrateNum, Svh>,
}

impl<'a, 'tcx> HashContext<'a, 'tcx> {
    pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Self {
        HashContext {
            tcx: tcx,
            item_metadata_hashes: FnvHashMap(),
            crate_hashes: FnvHashMap(),
        }
    }

    pub fn hash(&mut self, dep_node: DepNode<DefId>) -> Option<u64> {
        match dep_node {
            // HIR nodes (which always come from our crate) are an input:
            DepNode::Hir(def_id) => {
                assert!(def_id.is_local());
                Some(self.hir_hash(def_id))
            }

            // MetaData from other crates is an *input* to us.
            // MetaData nodes from *our* crates are an *output*; we
            // don't hash them, but we do compute a hash for them and
            // save it for others to use.
            DepNode::MetaData(def_id) if !def_id.is_local() => {
                Some(self.metadata_hash(def_id))
            }

            _ => {
                // Other kinds of nodes represent computed by-products
                // that we don't hash directly; instead, they should
                // have some transitive dependency on a Hir or
                // MetaData node, so we'll just hash that
                None
            }
        }
    }

    fn hir_hash(&mut self, def_id: DefId) -> u64 {
        assert!(def_id.is_local());
        // FIXME(#32753) -- should we use a distinct hash here
        self.tcx.calculate_item_hash(def_id)
    }

    fn metadata_hash(&mut self, def_id: DefId) -> u64 {
        debug!("metadata_hash(def_id={:?})", def_id);

        assert!(!def_id.is_local());
        loop {
            // check whether we have a result cached for this def-id
            if let Some(&hash) = self.item_metadata_hashes.get(&def_id) {
                debug!("metadata_hash: def_id={:?} hash={:?}", def_id, hash);
                return hash;
            }

            // check whether we did not find detailed metadata for this
            // krate; in that case, we just use the krate's overall hash
            if let Some(&hash) = self.crate_hashes.get(&def_id.krate) {
                debug!("metadata_hash: def_id={:?} crate_hash={:?}", def_id, hash);
                return hash.as_u64();
            }

            // otherwise, load the data and repeat.
            self.load_data(def_id.krate);
            assert!(self.crate_hashes.contains_key(&def_id.krate));
        }
    }

    fn load_data(&mut self, cnum: ast::CrateNum) {
        debug!("load_data(cnum={})", cnum);

        let svh = self.tcx.sess.cstore.crate_hash(cnum);
        let old = self.crate_hashes.insert(cnum, svh);
        debug!("load_data: svh={}", svh);
        assert!(old.is_none(), "loaded data for crate {:?} twice", cnum);

        if let Some(path) = metadata_hash_path(self.tcx, cnum) {
            debug!("load_data: path={:?}", path);
            let mut data = vec![];
            match
                File::open(&path)
                .and_then(|mut file| file.read_to_end(&mut data))
            {
                Ok(_) => {
                    match self.load_from_data(cnum, &data) {
                        Ok(()) => { }
                        Err(err) => {
                            bug!("decoding error in dep-graph from `{}`: {}",
                                 path.display(), err);
                        }
                    }
                }
                Err(err) => {
                    match err.kind() {
                        ErrorKind::NotFound => {
                            // If the file is not found, that's ok.
                        }
                        _ => {
                            self.tcx.sess.err(
                                &format!("could not load dep information from `{}`: {}",
                                         path.display(), err));
                            return;
                        }
                    }
                }
            }
        }
    }

    fn load_from_data(&mut self, cnum: ast::CrateNum, data: &[u8]) -> Result<(), Error> {
        debug!("load_from_data(cnum={})", cnum);

        // Load up the hashes for the def-ids from this crate.
        let mut decoder = Decoder::new(data, 0);
        let serialized_hashes = try!(SerializedMetadataHashes::decode(&mut decoder));
        for serialized_hash in serialized_hashes.hashes {
            // the hashes are stored with just a def-index, which is
            // always relative to the old crate; convert that to use
            // our internal crate number
            let def_id = DefId { krate: cnum, index: serialized_hash.def_index };

            // record the hash for this dep-node
            let old = self.item_metadata_hashes.insert(def_id, serialized_hash.hash);
            debug!("load_from_data: def_id={:?} hash={}", def_id, serialized_hash.hash);
            assert!(old.is_none(), "already have hash for {:?}", def_id);
        }
        Ok(())
    }
}
<commit_msg>nit: cache crate-hash for next time<commit_after>// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use calculate_svh::SvhCalculate;
use rbml::Error;
use rbml::opaque::Decoder;
use rustc::dep_graph::DepNode;
use rustc::hir::def_id::DefId;
use rustc::hir::svh::Svh;
use rustc::ty::TyCtxt;
use rustc_data_structures::fnv::FnvHashMap;
use rustc_serialize::Decodable;
use std::io::{ErrorKind, Read};
use std::fs::File;
use syntax::ast;

use super::data::*;
use super::util::*;

pub struct HashContext<'a, 'tcx: 'a> {
    pub tcx: TyCtxt<'a, 'tcx, 'tcx>,
    item_metadata_hashes: FnvHashMap<DefId, u64>,
    crate_hashes: FnvHashMap<ast::CrateNum, Svh>,
}

impl<'a, 'tcx> HashContext<'a, 'tcx> {
    pub fn new(tcx: TyCtxt<'a, 'tcx, 'tcx>) -> Self {
        HashContext {
            tcx: tcx,
            item_metadata_hashes: FnvHashMap(),
            crate_hashes: FnvHashMap(),
        }
    }

    pub fn hash(&mut self, dep_node: DepNode<DefId>) -> Option<u64> {
        match dep_node {
            // HIR nodes (which always come from our crate) are an input:
            DepNode::Hir(def_id) => {
                assert!(def_id.is_local());
                Some(self.hir_hash(def_id))
            }

            // MetaData from other crates is an *input* to us.
            // MetaData nodes from *our* crates are an *output*; we
            // don't hash them, but we do compute a hash for them and
            // save it for others to use.
            DepNode::MetaData(def_id) if !def_id.is_local() => {
                Some(self.metadata_hash(def_id))
            }

            _ => {
                // Other kinds of nodes represent computed by-products
                // that we don't hash directly; instead, they should
                // have some transitive dependency on a Hir or
                // MetaData node, so we'll just hash that
                None
            }
        }
    }

    fn hir_hash(&mut self, def_id: DefId) -> u64 {
        assert!(def_id.is_local());
        // FIXME(#32753) -- should we use a distinct hash here
        self.tcx.calculate_item_hash(def_id)
    }

    fn metadata_hash(&mut self, def_id: DefId) -> u64 {
        debug!("metadata_hash(def_id={:?})", def_id);

        assert!(!def_id.is_local());
        loop {
            // check whether we have a result cached for this def-id
            if let Some(&hash) = self.item_metadata_hashes.get(&def_id) {
                debug!("metadata_hash: def_id={:?} hash={:?}", def_id, hash);
                return hash;
            }

            // check whether we did not find detailed metadata for this
            // krate; in that case, we just use the krate's overall hash
            if let Some(&hash) = self.crate_hashes.get(&def_id.krate) {
                debug!("metadata_hash: def_id={:?} crate_hash={:?}", def_id, hash);

                // micro-"optimization": avoid a cache miss if we ask
                // for metadata from this particular def-id again.
                self.item_metadata_hashes.insert(def_id, hash.as_u64());

                return hash.as_u64();
            }

            // otherwise, load the data and repeat.
            self.load_data(def_id.krate);
            assert!(self.crate_hashes.contains_key(&def_id.krate));
        }
    }

    fn load_data(&mut self, cnum: ast::CrateNum) {
        debug!("load_data(cnum={})", cnum);

        let svh = self.tcx.sess.cstore.crate_hash(cnum);
        let old = self.crate_hashes.insert(cnum, svh);
        debug!("load_data: svh={}", svh);
        assert!(old.is_none(), "loaded data for crate {:?} twice", cnum);

        if let Some(path) = metadata_hash_path(self.tcx, cnum) {
            debug!("load_data: path={:?}", path);
            let mut data = vec![];
            match
                File::open(&path)
                .and_then(|mut file| file.read_to_end(&mut data))
            {
                Ok(_) => {
                    match self.load_from_data(cnum, &data) {
                        Ok(()) => { }
                        Err(err) => {
                            bug!("decoding error in dep-graph from `{}`: {}",
                                 path.display(), err);
                        }
                    }
                }
                Err(err) => {
                    match err.kind() {
                        ErrorKind::NotFound => {
                            // If the file is not found, that's ok.
                        }
                        _ => {
                            self.tcx.sess.err(
                                &format!("could not load dep information from `{}`: {}",
                                         path.display(), err));
                            return;
                        }
                    }
                }
            }
        }
    }

    fn load_from_data(&mut self, cnum: ast::CrateNum, data: &[u8]) -> Result<(), Error> {
        debug!("load_from_data(cnum={})", cnum);

        // Load up the hashes for the def-ids from this crate.
        let mut decoder = Decoder::new(data, 0);
        let serialized_hashes = try!(SerializedMetadataHashes::decode(&mut decoder));
        for serialized_hash in serialized_hashes.hashes {
            // the hashes are stored with just a def-index, which is
            // always relative to the old crate; convert that to use
            // our internal crate number
            let def_id = DefId { krate: cnum, index: serialized_hash.def_index };

            // record the hash for this dep-node
            let old = self.item_metadata_hashes.insert(def_id, serialized_hash.hash);
            debug!("load_from_data: def_id={:?} hash={}", def_id, serialized_hash.hash);
            assert!(old.is_none(), "already have hash for {:?}", def_id);
        }
        Ok(())
    }
}
<|endoftext|>