<commit_before>#[macro_use]
extern crate log;
extern crate pretty_env_logger;
extern crate clap;
extern crate libmodbus_rs;
extern crate iron;
extern crate router;
extern crate staticfile;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate ctrlc;
extern crate retry;
extern crate hex_slice;
#[macro_use]
extern crate enum_primitive;
#[macro_use]
extern crate bitflags;

use std::fs;
use std::path::Path;
use std::os::unix::fs::FileTypeExt;
use std::sync::{Arc, Mutex};
use std::convert::From;
use std::sync::atomic::{AtomicBool, Ordering};

use clap::{App, Arg};

use iron::Iron;
use iron::prelude::*;
use iron::status;
use iron::middleware::Handler;
use router::Router;
use staticfile::Static;

mod sunsaver_connection;
use sunsaver_connection::{SunSaverConnection, FileSunSaverConnection, ModbusSunSaverConnection};
mod sunsaver;
use sunsaver::{SunSaverResponse, ChargeState, ArrayFault, LoggedResponse, LoggedResponseDay};

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponse {
    generation: ApiStatusResponseGeneration,
    storage: ApiStatusResponseStorage,
    load: ApiStatusResponseLoad,
    temperature: ApiStatusResponseTemperature,
    faults: ApiStatusResponseFaults,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseGeneration {
    solar_input_voltage_filtered: f32,
    calculated_generation_power: f32,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseStorage {
    battery_voltage_filtered: f32,
    battery_charge_current_filtered: f32,
    battery_charge_power_calculated: f32,
    charge_state: ChargeState,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseLoad {
    load_voltage_filtered: f32,
    load_current_filtered: f32,
    load_power_calculated: f32,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseTemperature {
    heatsink_temperature: i8,
    battery_temperature: i8,
    ambient_temperature: i8,
    remote_temperature: i8,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseFaults {
    array_fault: ArrayFault,
}

impl From<SunSaverResponse> for ApiStatusResponse {
    fn from(response: SunSaverResponse) -> Self {
        let battery_voltage_filtered = response.battery_voltage_filtered();
        let battery_charge_current_filtered = response.battery_charge_current_filtered();
        let load_voltage_filtered = response.load_voltage_filtered();
        let load_current_filtered = response.load_current_filtered();
        let solar_input_voltage_filtered = response.solar_input_voltage_filtered();

        let generation = ApiStatusResponseGeneration {
            solar_input_voltage_filtered: solar_input_voltage_filtered,
            calculated_generation_power: (load_current_filtered + battery_charge_current_filtered) * solar_input_voltage_filtered,
        };
        let storage = ApiStatusResponseStorage {
            battery_voltage_filtered: battery_voltage_filtered,
            battery_charge_current_filtered: battery_charge_current_filtered,
            battery_charge_power_calculated: battery_voltage_filtered * battery_charge_current_filtered,
            charge_state: response.charge_state(),
        };
        let load = ApiStatusResponseLoad {
            load_voltage_filtered: load_voltage_filtered,
            load_current_filtered: load_current_filtered,
            load_power_calculated: load_voltage_filtered * load_current_filtered,
        };
        let temperature = ApiStatusResponseTemperature {
            heatsink_temperature: response.heatsink_temperature(),
            battery_temperature: response.battery_temperature(),
            ambient_temperature: response.ambient_temperature(),
            remote_temperature: response.remote_temperature(),
        };
        let faults = ApiStatusResponseFaults {
            array_fault: response.array_fault(),
        };
        ApiStatusResponse {
            generation: generation,
            storage: storage,
            load: load,
            temperature: temperature,
            faults: faults,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
struct ApiLoggedResponse {
    days: Vec<ApiLoggedDayResponse>,
}

#[derive(Debug, Clone, Serialize)]
struct ApiLoggedDayResponse {
    hourmeter: u32,
    battery_voltage_min: f32,
    battery_voltage_max: f32,
}

impl From<LoggedResponse> for ApiLoggedResponse {
    fn from(response: LoggedResponse) -> Self {
        let days = response.days.into_iter().map(ApiLoggedDayResponse::from).collect();
        ApiLoggedResponse {
            days: days,
        }
    }
}

impl From<LoggedResponseDay> for ApiLoggedDayResponse {
    fn from(response: LoggedResponseDay) -> Self {
        ApiLoggedDayResponse {
            hourmeter: response.hourmeter,
            battery_voltage_min: response.battery_voltage_min(),
            battery_voltage_max: response.battery_voltage_max(),
        }
    }
}

#[derive(Clone)]
struct ApiHandler {
    connection: Arc<Mutex<Box<SunSaverConnection>>>,
}

impl ApiHandler {
    fn new(connection: Box<SunSaverConnection>) -> ApiHandler {
        ApiHandler {
            connection: Arc::new(Mutex::new(connection)),
        }
    }
}

unsafe impl Send for ApiHandler {}
unsafe impl Sync for ApiHandler {}

impl Handler for ApiHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        debug!("{:?}", req.url);

        let path = req.url.path();
        let last_path = path.clone().pop().unwrap();
        match last_path {
            "status" => {
                let connection = self.connection.clone();
                let mut unlocked_connection = connection.lock().unwrap();
                let a = ApiStatusResponse::from(unlocked_connection.read_status());
                let b = serde_json::to_string_pretty(&a).unwrap();
                return Ok(Response::with((status::Ok, b)))
            },
            "logger" => {
                let connection = self.connection.clone();
                let mut unlocked_connection = connection.lock().unwrap();
                let a = ApiLoggedResponse::from(unlocked_connection.read_logged());
                let b = serde_json::to_string_pretty(&a).unwrap();
                return Ok(Response::with((status::Ok, b)))
            },
            _ => panic!("Unknown action: {:?}", path),
        }
    }
}

fn is_socket(path: &str) -> bool {
    let metadata = fs::metadata(path).unwrap();
    let file_type = metadata.file_type();
    debug!("is_socket for {} had metadata {:?}", path, metadata);
    debug!("is_socket for {} is_block_device: {}, is_char_device: {}, is_fifo: {}, is_socket: {}", path, file_type.is_block_device(), file_type.is_char_device(), file_type.is_fifo(), file_type.is_socket());
    metadata.file_type().is_char_device()
}

fn main() {
    assert!(pretty_env_logger::init().is_ok());

    let matches = App::new("simple-client")
        .version(env!("CARGO_PKG_VERSION"))
        .about("HTTP RESTful server for SunSaver MPPT ModBus data")
        .author("Guy Taylor <thebiggerguy.co.uk@gmail.com>")
        .arg(Arg::with_name("device")
            .help("Serial device e.g. /dev/ttyUSB0")
            .long("device")
            .short("d")
            .takes_value(true)
            .required(true))
        .get_matches();

    let serial_interface = matches.value_of("device").unwrap();

    let connection: Box<SunSaverConnection> = if is_socket(serial_interface) {
        info!("Device is a socket. Using Modbus");
        Box::new(ModbusSunSaverConnection::open(serial_interface))
    } else {
        info!("Device is not a socket. Using File");
        Box::new(FileSunSaverConnection::open(serial_interface))
    };

    let api_handler = ApiHandler::new(connection);

    let mut router = Router::new();
    router.get("/", Static::new(Path::new("web")), "index");
    router.get("/api/v1/status", api_handler.clone(), "api_v1_status");
    router.get("/api/v1/logger", api_handler.clone(), "api_v2_logger");

    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();
    ctrlc::set_handler(move || {
        r.store(false, Ordering::SeqCst);
        info!("Cought Ctrl-C");
    }).expect("Error setting Ctrl-C handler");

    info!("Starting server ...");
    let bind_address = format!("0.0.0.0:{}", option_env!("PORT").unwrap_or("8080"));
    let mut listening = Iron::new(router).http(&bind_address).unwrap();
    info!("Started server {}", bind_address);

    info!("Use Ctrl-C to stop");
    while running.load(Ordering::SeqCst) {}
    listening.close().unwrap();
}
<commit_msg>Add CORS headers<commit_after>#[macro_use]
extern crate log;
extern crate pretty_env_logger;
extern crate clap;
extern crate libmodbus_rs;
extern crate iron;
extern crate router;
extern crate staticfile;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
extern crate ctrlc;
extern crate retry;
extern crate hex_slice;
#[macro_use]
extern crate enum_primitive;
#[macro_use]
extern crate bitflags;

use std::fs;
use std::path::Path;
use std::os::unix::fs::FileTypeExt;
use std::sync::{Arc, Mutex};
use std::convert::From;
use std::sync::atomic::{AtomicBool, Ordering};

use clap::{App, Arg};

use iron::Iron;
use iron::prelude::*;
use iron::status;
use iron::middleware::Handler;
use iron::headers::{AccessControlAllowMethods, AccessControlAllowOrigin};
use iron::method::Method;
use router::Router;
use staticfile::Static;

mod sunsaver_connection;
use sunsaver_connection::{SunSaverConnection, FileSunSaverConnection, ModbusSunSaverConnection};
mod sunsaver;
use sunsaver::{SunSaverResponse, ChargeState, ArrayFault, LoggedResponse, LoggedResponseDay};

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponse {
    generation: ApiStatusResponseGeneration,
    storage: ApiStatusResponseStorage,
    load: ApiStatusResponseLoad,
    temperature: ApiStatusResponseTemperature,
    faults: ApiStatusResponseFaults,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseGeneration {
    solar_input_voltage_filtered: f32,
    calculated_generation_power: f32,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseStorage {
    battery_voltage_filtered: f32,
    battery_charge_current_filtered: f32,
    battery_charge_power_calculated: f32,
    charge_state: ChargeState,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseLoad {
    load_voltage_filtered: f32,
    load_current_filtered: f32,
    load_power_calculated: f32,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseTemperature {
    heatsink_temperature: i8,
    battery_temperature: i8,
    ambient_temperature: i8,
    remote_temperature: i8,
}

#[derive(Debug, Clone, Serialize)]
struct ApiStatusResponseFaults {
    array_fault: ArrayFault,
}

impl From<SunSaverResponse> for ApiStatusResponse {
    fn from(response: SunSaverResponse) -> Self {
        let battery_voltage_filtered = response.battery_voltage_filtered();
        let battery_charge_current_filtered = response.battery_charge_current_filtered();
        let load_voltage_filtered = response.load_voltage_filtered();
        let load_current_filtered = response.load_current_filtered();
        let solar_input_voltage_filtered = response.solar_input_voltage_filtered();

        let generation = ApiStatusResponseGeneration {
            solar_input_voltage_filtered: solar_input_voltage_filtered,
            calculated_generation_power: (load_current_filtered + battery_charge_current_filtered) * solar_input_voltage_filtered,
        };
        let storage = ApiStatusResponseStorage {
            battery_voltage_filtered: battery_voltage_filtered,
            battery_charge_current_filtered: battery_charge_current_filtered,
            battery_charge_power_calculated: battery_voltage_filtered * battery_charge_current_filtered,
            charge_state: response.charge_state(),
        };
        let load = ApiStatusResponseLoad {
            load_voltage_filtered: load_voltage_filtered,
            load_current_filtered: load_current_filtered,
            load_power_calculated: load_voltage_filtered * load_current_filtered,
        };
        let temperature = ApiStatusResponseTemperature {
            heatsink_temperature: response.heatsink_temperature(),
            battery_temperature: response.battery_temperature(),
            ambient_temperature: response.ambient_temperature(),
            remote_temperature: response.remote_temperature(),
        };
        let faults = ApiStatusResponseFaults {
            array_fault: response.array_fault(),
        };
        ApiStatusResponse {
            generation: generation,
            storage: storage,
            load: load,
            temperature: temperature,
            faults: faults,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
struct ApiLoggedResponse {
    days: Vec<ApiLoggedDayResponse>,
}

#[derive(Debug, Clone, Serialize)]
struct ApiLoggedDayResponse {
    hourmeter: u32,
    battery_voltage_min: f32,
    battery_voltage_max: f32,
}

impl From<LoggedResponse> for ApiLoggedResponse {
    fn from(response: LoggedResponse) -> Self {
        let days = response.days.into_iter().map(ApiLoggedDayResponse::from).collect();
        ApiLoggedResponse {
            days: days,
        }
    }
}

impl From<LoggedResponseDay> for ApiLoggedDayResponse {
    fn from(response: LoggedResponseDay) -> Self {
        ApiLoggedDayResponse {
            hourmeter: response.hourmeter,
            battery_voltage_min: response.battery_voltage_min(),
            battery_voltage_max: response.battery_voltage_max(),
        }
    }
}

#[derive(Clone)]
struct ApiHandler {
    connection: Arc<Mutex<Box<SunSaverConnection>>>,
}

impl ApiHandler {
    fn new(connection: Box<SunSaverConnection>) -> ApiHandler {
        ApiHandler {
            connection: Arc::new(Mutex::new(connection)),
        }
    }
}

unsafe impl Send for ApiHandler {}
unsafe impl Sync for ApiHandler {}

impl Handler for ApiHandler {
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        debug!("{:?}", req.url);

        let mut response = Response::new();
        response.headers.set(AccessControlAllowMethods(vec![Method::Get]));
        response.headers.set(AccessControlAllowOrigin::Any);

        let path = req.url.path();
        let last_path = path.clone().pop().unwrap();
        match last_path {
            "status" => {
                let connection = self.connection.clone();
                let mut unlocked_connection = connection.lock().unwrap();
                let a = ApiStatusResponse::from(unlocked_connection.read_status());
                let b = serde_json::to_string_pretty(&a).unwrap();
                response = response.set((status::Ok, b));
            },
            "logger" => {
                let connection = self.connection.clone();
                let mut unlocked_connection = connection.lock().unwrap();
                let a = ApiLoggedResponse::from(unlocked_connection.read_logged());
                let b = serde_json::to_string_pretty(&a).unwrap();
                response = response.set((status::Ok, b));
            },
            _ => {
                response = response.set((status::NotFound, String::new()));
            },
        };

        Ok(response)
    }
}

fn is_socket(path: &str) -> bool {
    let metadata = fs::metadata(path).unwrap();
    let file_type = metadata.file_type();
    debug!("is_socket for {} had metadata {:?}", path, metadata);
    debug!("is_socket for {} is_block_device: {}, is_char_device: {}, is_fifo: {}, is_socket: {}", path, file_type.is_block_device(), file_type.is_char_device(), file_type.is_fifo(), file_type.is_socket());
    metadata.file_type().is_char_device()
}

fn main() {
    assert!(pretty_env_logger::init().is_ok());

    let matches = App::new("simple-client")
        .version(env!("CARGO_PKG_VERSION"))
        .about("HTTP RESTful server for SunSaver MPPT ModBus data")
        .author("Guy Taylor <thebiggerguy.co.uk@gmail.com>")
        .arg(Arg::with_name("device")
            .help("Serial device e.g. /dev/ttyUSB0")
            .long("device")
            .short("d")
            .takes_value(true)
            .required(true))
        .get_matches();

    let serial_interface = matches.value_of("device").unwrap();

    let connection: Box<SunSaverConnection> = if is_socket(serial_interface) {
        info!("Device is a socket. Using Modbus");
        Box::new(ModbusSunSaverConnection::open(serial_interface))
    } else {
        info!("Device is not a socket. Using File");
        Box::new(FileSunSaverConnection::open(serial_interface))
    };

    let api_handler = ApiHandler::new(connection);

    let mut router = Router::new();
    router.get("/", Static::new(Path::new("web")), "index");
    router.get("/api/v1/status", api_handler.clone(), "api_v1_status");
    router.get("/api/v1/logger", api_handler.clone(), "api_v2_logger");

    let running = Arc::new(AtomicBool::new(true));
    let r = running.clone();
    ctrlc::set_handler(move || {
        r.store(false, Ordering::SeqCst);
        info!("Cought Ctrl-C");
    }).expect("Error setting Ctrl-C handler");

    info!("Starting server ...");
    let bind_address = format!("0.0.0.0:{}", option_env!("PORT").unwrap_or("8080"));
    let mut listening = Iron::new(router).http(&bind_address).unwrap();
    info!("Started server {}", bind_address);

    info!("Use Ctrl-C to stop");
    while running.load(Ordering::SeqCst) {}
    listening.close().unwrap();
}
<|endoftext|>


<commit_before>extern crate chrono;

pub mod location;
pub mod temperature;
pub mod model;

pub use location::GpsCoordinates;
use model::ModelParams;
use chrono::*;
use std::io::{self, Write, BufRead};

fn flush() {
    io::stdout().flush().unwrap();
}

fn main() {
    let stdin = io::stdin();
    let mut lines = stdin.lock().lines();

    // User input.

    println!("==== PARAMÈTRES D'ENTRÉE ====");
    print!("latitude (en degrés) : ");
    flush();
    let lat: f64 = lines.next().unwrap().unwrap().parse().unwrap();

    print!("longitude (en degrés) : ");
    flush();
    let long: f64 = lines.next().unwrap().unwrap().parse().unwrap();

    print!("date et heure locale (e.g. \"28/11/2016 21:00:09 +09:00\"): ");
    flush();

    let local_time_string = lines.next().unwrap().unwrap();
    println!("");

    // Model.

    let local_time = DateTime::parse_from_str(&local_time_string,
                                              "%d/%m/%Y %H:%M:%S %z")
                         .unwrap();

    let coords = GpsCoordinates {
        lat: lat,
        long: long,
    };

    let input = ModelParams {
        coords: coords,
        date_time: local_time,
    };

    let start = Local::now();
    let output = model::run(input);
    let duration = Local::now() - start;

    println!("==== VARIABLES INTERMÉDIAIRES ====");
    println!("jour de l'année : {}", output.day_of_year);
    println!("équation du temps : {:.2} minutes", output.eot);
    println!("longitude du méridien local : {}°",
             output.local_meridian_long);
    println!("facteur de correction de l'heure : {:.2} minutes",
             output.time_correction_factor);
    println!("heure solaire : {:.2}h", output.solar_time);
    println!("angle horaire : {:.4}°", output.hour_angle);
    println!("angle de déclinaison de la Terre : {:.4}°",
             output.declination_angle);
    println!("angle d'élévation : {:.4}°", output.elevation_angle);
    println!("angle zénithal : {:.4}°", output.zenith_angle);
    println!("masse d'air : {:.4}", output.air_mass);
    print!("\n");

    println!("==== RÉSULTAT ====");

    println!("l'irradiance est de {:.2} watts par mètre carré (calculée en \
              {} µs)",
             output.irradiance,
             duration.num_microseconds().unwrap());
}
<commit_msg>éclairement énergétique<commit_after>extern crate chrono;

pub mod location;
pub mod temperature;
pub mod model;

pub use location::GpsCoordinates;
use model::ModelParams;
use chrono::*;
use std::io::{self, Write, BufRead};

fn flush() {
    io::stdout().flush().unwrap();
}

fn main() {
    let stdin = io::stdin();
    let mut lines = stdin.lock().lines();

    // User input.

    println!("==== PARAMÈTRES D'ENTRÉE ====");
    print!("latitude (en degrés) : ");
    flush();
    let lat: f64 = lines.next().unwrap().unwrap().parse().unwrap();

    print!("longitude (en degrés) : ");
    flush();
    let long: f64 = lines.next().unwrap().unwrap().parse().unwrap();

    print!("date et heure locale (e.g. \"28/11/2016 21:00:09 +09:00\"): ");
    flush();

    let local_time_string = lines.next().unwrap().unwrap();
    println!("");

    // Model.

    let local_time = DateTime::parse_from_str(&local_time_string,
                                              "%d/%m/%Y %H:%M:%S %z")
                         .unwrap();

    let coords = GpsCoordinates {
        lat: lat,
        long: long,
    };

    let input = ModelParams {
        coords: coords,
        date_time: local_time,
    };

    let start = Local::now();
    let output = model::run(input);
    let duration = Local::now() - start;

    println!("==== VARIABLES INTERMÉDIAIRES ====");
    println!("jour de l'année : {}", output.day_of_year);
    println!("équation du temps : {:.2} minutes", output.eot);
    println!("longitude du méridien local : {}°",
             output.local_meridian_long);
    println!("facteur de correction de l'heure : {:.2} minutes",
             output.time_correction_factor);
    println!("heure solaire : {:.2}h", output.solar_time);
    println!("angle horaire : {:.4}°", output.hour_angle);
    println!("angle de déclinaison de la Terre : {:.4}°",
             output.declination_angle);
    println!("angle d'élévation : {:.4}°", output.elevation_angle);
    println!("angle zénithal : {:.4}°", output.zenith_angle);
    println!("masse d'air : {:.4}", output.air_mass);
    print!("\n");

    println!("==== RÉSULTAT ====");

    println!("l'éclairement énergétique solaire est de {:.2} watts par mètre \
              carré (calculé en {} µs)",
             output.irradiance,
             duration.num_microseconds().unwrap());
}
<|endoftext|>


<commit_before>
extern crate crypto;
extern crate iron;
extern crate router;
extern crate params;
extern crate hyper;
extern crate logger;

use iron::prelude::*;
use router::{Router};
use iron::status;
use params::Params;
use logger::Logger;
use iron::{BeforeMiddleware};

/// generate the md5sum of a given file (io::Read)
pub fn md5sum<T : std::io::Read> (f: &mut T) -> String  {
  use crypto::digest::Digest;

  let mut digest = crypto::md5::Md5::new();
  let mut data: Vec<u8> = Vec::new();

  match f.read_to_end(&mut data) {
    Err(why) => { println!("Error reading the passed file to calculate the md5sum: {}", why); String::new() }
    Ok(size) => {
      if size > 0 {
        digest.input(&data);
        digest.result_str()
      } else {
        String::new()
      }
    }
  }
}

use params::FromValue;

macro_rules! bad_request {
  // `()` indicates that the macro takes no argument
  ($msg:expr) => (
    Err(IronError::new($msg, status::BadRequest))
  )
}

use std::error::Error;
use std::fmt::{self, Debug};

#[derive(Debug)]
struct StringError(String);

impl fmt::Display for StringError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    Debug::fmt(self, f)
  }
}

impl Error for StringError {
  fn description(&self) -> &str { &*self.0 }
}

struct RequireMd5sumParam;

impl RequireMd5sumParam {
  fn value(params: &params::Map) -> String {
    let s = String::from_value(params.find(&["md5sum"]).unwrap()).unwrap();
    println!("'md5sum': {}", s);
    s
  }
}

impl BeforeMiddleware for RequireMd5sumParam {

  fn before(&self, req: &mut Request) -> IronResult<()> {
    let param_res = req.get_ref::<Params>();
    return match param_res {
      Err(why) => {
        bad_request!(why)
      },
      Ok(ref params) => {
        match params.find(&["md5sum"]) {
          None => bad_request!(StringError("the request doesn't contains the parameter 'md5sum'".to_string())),
          Some(passed_md5sum) => {
            // check the type
            match *passed_md5sum {
              params::Value::String(ref p) => Ok(()),
              _ => bad_request!(StringError("the 'md5sum' parameter is not of type 'string'".to_string()))
            }
          }
        }
      }
    }
  }
}

struct CheckFileParam;
impl BeforeMiddleware for CheckFileParam {

  fn before(&self, req: &mut Request) -> IronResult<()> {
    let param_res = req.get_ref::<Params>();
    return match param_res {
      Err(why) => {
        bad_request!(why)
      },
      Ok(ref params) => {
        match params.find(&["file"]) {
          None => bad_request!(StringError("the request doesn't contains the parameter 'file'".to_string())),
          Some(passed_md5sum) => {
            // check the type
            match *passed_md5sum {
              params::Value::File(ref p) => Ok(()),
              _ => bad_request!(StringError("the 'file' parameter is not of type 'file'".to_string()))
            }
          }
        }
      }
    }
  }
}

macro_rules! check_parameter {
  // `()` indicates that the macro takes no argument
  ($name:ident, $pname:expr, $ptype:pat, $rtype:ty) => (
    struct $name;

    impl BeforeMiddleware for $name {

      fn before(&self, req: &mut Request) -> IronResult<()> {
        let param_res = req.get_ref::<Params>();
        return match param_res {
          Err(why) => {
            bad_request!(why)
          },
          Ok(ref params) => {
            match params.find(&[$pname]) {
              None => bad_request!(StringError(format!("the request doesn't contains the parameter '{}'", $pname))),
              Some(passed_md5sum) => {
                // check the type
                match *passed_md5sum {
                  $ptype => Ok(()),
                  _ => bad_request!(StringError(format!("the '{}' parameter is not of the correct type", $pname)))
                }
              }
            }
          }
        }
      }
    }
  )
}
check_parameter!(RequireMd5sumParam2, "md5sum", params::Value::String(_), String);

fn submit_form_file2(req: &mut Request) -> IronResult<Response> {
  // parameters must exists...
  let param_res = req.get_ref::<Params>();
  let md5sum = RequireMd5sumParam::value(param_res.unwrap());
  Ok(Response::with(status::Ok))
}

// curl -i "http://localhost:3000/openact"  -F "filename=@/home/costa/test.csv" -F "name=jason" -F
// "age=2"
fn submit_form_file(req: &mut Request) -> IronResult<Response> {
//   use params::FromValue;

  let param_res = req.get_ref::<Params>(); // Result<&params::Map, params::ParamsError>
    println!("DEBUG - received request: {:?}", &param_res);

    match param_res {
      Err(why) => {
        let msg = format!("{:?}", why);
        println!("{}", msg);
        Ok(Response::with((status::InternalServerError, msg)))
      },
      Ok(ref params) => match params.find(&["md5sum"]) {
        Some(ref passed_md5sum) => {

          match **passed_md5sum {
            params::Value::String(ref passed_md5sum) => {
              match params.find(&["filename"]) {
                None => {
                  let msg = format!("missing 'filename' field: {:?}", params);
                  println!("{}", msg);
                  Ok(Response::with((status::BadRequest, msg)))
                },
                Some(ref f) => {
                  match **f {
                    params::Value::File(ref file_param) => {
                      let file_path = file_param.path().display();
                      let msg = format!("the path is: {:?}", file_path);
                      println!("{}", msg);
                      match file_param.open() {
                        Err(why) => {
                          let msg = format!("cannot open the uploaded file '{:?}' in path '{}' : '{}'", file_param.filename(), file_path, why);
                          println!("{}", msg);
                          Ok(Response::with((status::InternalServerError, msg)))
                        },
                        Ok(ref mut file) => {
                          let calculated_md5sum = &md5sum(file);
                          if calculated_md5sum != passed_md5sum {
                            let msg = format!(
                              "the md5sum '{}' calculate for the file {} doesn't correspond to the parameter '{}'",
                              calculated_md5sum, file_path, passed_md5sum
                            );
                            return Ok(Response::with((status::PreconditionRequired, msg)))
                          }

                          return Ok(Response::with((status::Ok, "ciao mondo")))
//                           use std::process::Command;

//                           match Command::new("file")
//                             .arg("-b")
//                             .arg("--mime-type")
//                             .arg(file_path)
//                             .output() {
//                               Err(why) =>
//                             }
//                             .unwrap_or_else(|e| { Ok(Response::with((status::PreconditionRequired,
//                               format!("error finding the type of the uploaded file {:?}", file_param.filename()) )) });

                        }
                      }

                    },
                    _ => {
                      let msg = format!("The request parameter 'filename' doesn't correspond to a real file: {:?}", f);
                      println!("{}", msg);
                      Ok(Response::with((status::PreconditionRequired, msg)))
                    }
                  }
                }

              }
            }
            _ => {
              let msg = format!(
                "The request parameter 'md5sum':'{:?}'' in the request {:?} doesn't correspond to a string",
                passed_md5sum, param_res);
              println!("{}", msg);
              Ok(Response::with((status::BadRequest, msg)))
            }
          }

        },
        _ => {
          let msg = format!("The request parameter 'md5sum' is required");
          println!("{}", msg);
          Ok(Response::with((status::BadRequest, msg)))
        }
      },

    }

}

fn start_server() {
   let (logger_before, logger_after) = Logger::new(None);
   let mut router = Router::new();
   let mut chain_form_file = Chain::new(submit_form_file2);
   chain_form_file.link_before(logger_before);
   chain_form_file.link_before(RequireMd5sumParam);
   chain_form_file.link_after(logger_after);
   router.post("/openact", chain_form_file);
   println!("started server at http://localhost:3000/");
   Iron::new(router).http("localhost:3000").unwrap();
}

#[cfg(test)]
mod tests {

    use std::fs::File;
    use super::md5sum;

#[test]
    fn md5sum_of_file() {
        static FP : &'static str = "/home/costa/pkg/lav/regione/attiweb/attività/20150107/definizione procedura di riavvio/Procedure_Riavvio_omnia.odt";

        let mut odt = match File::open(FP) {
            Err(why) => panic!("Cannot open the test file: {}", why),
            Ok(file) => file,
        };

        assert_eq!(md5sum(&mut odt), "a381cbe043c38a94af0983ffa934f338");
    }

    #[test]
    fn simple_get() {
        use hyper::Client;
        use hyper::header::Connection;
        use std::io::Read;
        use super::start_server;

        // start_server();

        // Create a client.
        let client = Client::new();
        //
        // Creating an outgoing request.
        let mut res = client.get("http://localhost:3000/openact")
            // set a header
            .header(Connection::close())
            // let 'er go!
            .send().unwrap();

        // Read the Response.
        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();

        println!("Response: {}", body);
        assert_eq!(body, "Hello World");
    }

}

fn main() {
    println!("Hello, world!");
    start_server();
}
<commit_msg>implemented require_param! and RequiredParam trait<commit_after>
extern crate crypto;
extern crate iron;
extern crate router;
extern crate params;
extern crate hyper;
extern crate logger;

use iron::prelude::*;
use router::{Router};
use iron::status;
use params::Params;
use logger::Logger;
use iron::{BeforeMiddleware};

/// generate the md5sum of a given file (io::Read)
pub fn md5sum<T : std::io::Read> (f: &mut T) -> String  {
  use crypto::digest::Digest;

  let mut digest = crypto::md5::Md5::new();
  let mut data: Vec<u8> = Vec::new();

  match f.read_to_end(&mut data) {
    Err(why) => { println!("Error reading the passed file to calculate the md5sum: {}", why); String::new() }
    Ok(size) => {
      if size > 0 {
        digest.input(&data);
        digest.result_str()
      } else {
        String::new()
      }
    }
  }
}

use params::FromValue;

macro_rules! bad_request {
  // `()` indicates that the macro takes no argument
  ($msg:expr) => (
    Err(IronError::new($msg, status::BadRequest))
  )
}

use std::error::Error;
use std::fmt::{self, Debug};

#[derive(Debug)]
struct StringError(String);

impl fmt::Display for StringError {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    Debug::fmt(self, f)
  }
}

impl Error for StringError {
  fn description(&self) -> &str { &*self.0 }
}

trait RequiredParam<T : params::FromValue = Self> {

  fn get_value(value: &params::Value) -> Option<T> {
    T::from_value(value)
  }

  fn check(req: &mut Request, name: &str) -> IronResult<()> {

    let param_res = req.get_ref::<Params>();
    return match param_res {
      Err(why) => {
        bad_request!(why)
      },
      Ok(ref params) => {
        match params.find(&[name]) {
          None => bad_request!(StringError(format!("the request doesn't contains the parameter {}", name).to_string())),
          Some(p) => if let Some(_) = Self::get_value(p) {
            Ok(())
          } else {
            bad_request!(StringError(format!("the '{}' parameter is not of type 'string'",name).to_string()))
          }

        }
      }
    }
  }

  fn get_param_value(req: &mut Request, name: &str) -> T {
    Self::check(req, name).unwrap();
    Self::get_value(req.get_ref::<Params>().unwrap().find(&[name]).unwrap()).unwrap()
  }
}

macro_rules! require_param {
  ($name:ident, $param_name:expr, $ty:ty) => {
    impl RequiredParam for $ty {}
    struct $name;

    impl BeforeMiddleware for $name {
      fn before(&self, req: &mut Request) -> IronResult<()> {
        <$ty>::check(req, $param_name)
      }
    }
  }
}

require_param!(RequireMd5sumParam3, "md5sum", String);
require_param!(RequireFileParam, "filename", params::File);

fn submit_form_file2(req: &mut Request) -> IronResult<Response> {

  let passed_md5sum = &String::get_param_value(req, "md5sum");
  let file_param = params::File::get_param_value(req, "filename");
  let file_path = file_param.path().display();
  match file_param.open() {
    Err(why) => {
      let msg = format!("cannot open the uploaded file '{:?}' in path '{}' : '{}'", file_param.filename(), file_path, why);
      println!("{}", msg);
      Ok(Response::with((status::InternalServerError, msg)))
    },
    Ok(ref mut file) => {
      let calculated_md5sum = &md5sum(file);
      if calculated_md5sum != passed_md5sum {
        let msg = format!(
          "the md5sum '{}' calculate for the file {} doesn't correspond to the parameter '{}'",
          calculated_md5sum, file_path, passed_md5sum
        );
        return Ok(Response::with((status::PreconditionRequired, msg)))
      }

      return Ok(Response::with((status::Ok, "ciao mondo")))
    }
  }
}

// curl -i "http://localhost:3000/openact"  -F "filename=@/home/costa/test.csv" -F "name=jason" -F
// "age=2"
fn submit_form_file(req: &mut Request) -> IronResult<Response> {
//   use params::FromValue;

  let param_res = req.get_ref::<Params>(); // Result<&params::Map, params::ParamsError>
    println!("DEBUG - received request: {:?}", &param_res);

    match param_res {
      Err(why) => {
        let msg = format!("{:?}", why);
        println!("{}", msg);
        Ok(Response::with((status::InternalServerError, msg)))
      },
      Ok(ref params) => match params.find(&["md5sum"]) {
        Some(ref passed_md5sum) => {

          match **passed_md5sum {
            params::Value::String(ref passed_md5sum) => {
              match params.find(&["filename"]) {
                None => {
                  let msg = format!("missing 'filename' field: {:?}", params);
                  println!("{}", msg);
                  Ok(Response::with((status::BadRequest, msg)))
                },
                Some(ref f) => {
                  match **f {
                    params::Value::File(ref file_param) => {
                      let file_path = file_param.path().display();
                      let msg = format!("the path is: {:?}", file_path);
                      println!("{}", msg);
                      match file_param.open() {
                        Err(why) => {
                          let msg = format!("cannot open the uploaded file '{:?}' in path '{}' : '{}'", file_param.filename(), file_path, why);
                          println!("{}", msg);
                          Ok(Response::with((status::InternalServerError, msg)))
                        },
                        Ok(ref mut file) => {
                          let calculated_md5sum = &md5sum(file);
                          if calculated_md5sum != passed_md5sum {
                            let msg = format!(
                              "the md5sum '{}' calculate for the file {} doesn't correspond to the parameter '{}'",
                              calculated_md5sum, file_path, passed_md5sum
                            );
                            return Ok(Response::with((status::PreconditionRequired, msg)))
                          }

                          return Ok(Response::with((status::Ok, "ciao mondo")))
//                           use std::process::Command;

//                           match Command::new("file")
//                             .arg("-b")
//                             .arg("--mime-type")
//                             .arg(file_path)
//                             .output() {
//                               Err(why) =>
//                             }
//                             .unwrap_or_else(|e| { Ok(Response::with((status::PreconditionRequired,
//                               format!("error finding the type of the uploaded file {:?}", file_param.filename()) )) });

                        }
                      }

                    },
                    _ => {
                      let msg = format!("The request parameter 'filename' doesn't correspond to a real file: {:?}", f);
                      println!("{}", msg);
                      Ok(Response::with((status::PreconditionRequired, msg)))
                    }
                  }
                }

              }
            }
            _ => {
              let msg = format!(
                "The request parameter 'md5sum':'{:?}'' in the request {:?} doesn't correspond to a string",
                passed_md5sum, param_res);
              println!("{}", msg);
              Ok(Response::with((status::BadRequest, msg)))
            }
          }

        },
        _ => {
          let msg = format!("The request parameter 'md5sum' is required");
          println!("{}", msg);
          Ok(Response::with((status::BadRequest, msg)))
        }
      },

    }

}

fn start_server() {
   let (logger_before, logger_after) = Logger::new(None);
   let mut router = Router::new();
   let mut chain_form_file = Chain::new(submit_form_file2);
   chain_form_file.link_before(logger_before);
   chain_form_file.link_before(RequireMd5sumParam3);
   chain_form_file.link_before(RequireFileParam);
   chain_form_file.link_after(logger_after);
   router.post("/openact", chain_form_file);
   println!("started server at http://localhost:3000/");
   Iron::new(router).http("localhost:3000").unwrap();
}

#[cfg(test)]
mod tests {

    use std::fs::File;
    use super::md5sum;

#[test]
    fn md5sum_of_file() {
        static FP : &'static str = "/home/costa/pkg/lav/regione/attiweb/attività/20150107/definizione procedura di riavvio/Procedure_Riavvio_omnia.odt";

        let mut odt = match File::open(FP) {
            Err(why) => panic!("Cannot open the test file: {}", why),
            Ok(file) => file,
        };

        assert_eq!(md5sum(&mut odt), "a381cbe043c38a94af0983ffa934f338");
    }

    #[test]
    fn simple_get() {
        use hyper::Client;
        use hyper::header::Connection;
        use std::io::Read;
        use super::start_server;

        // start_server();

        // Create a client.
        let client = Client::new();
        //
        // Creating an outgoing request.
        let mut res = client.get("http://localhost:3000/openact")
            // set a header
            .header(Connection::close())
            // let 'er go!
            .send().unwrap();

        // Read the Response.
        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();

        println!("Response: {}", body);
        assert_eq!(body, "Hello World");
    }

}

fn main() {
    println!("Hello, world!");
    start_server();
}
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(regex_macros)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

// TODO: I'm not so sure these parameter names aren't terrible.
/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(write_to_temp_file(new2));

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
        for attribute in attributes {
            // TODO: do I seriously have to construct a StrTendril here?
            // There has to be a better way.
            if attribute.name.local.as_slice() == "id" &&
                attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                    return true;
                }
        }
        return false;
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

// TODO: rename?
struct WikipediaWithoutWikipedians {
    wiki: Wiki,
    client: Client
}

impl WikipediaWithoutWikipedians {
    fn new(wiki: Wiki, client: Client) -> WikipediaWithoutWikipedians {
        WikipediaWithoutWikipedians { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut revisions = vec![];
        for revision in try!(self.get_vandalism_reversions(&canonical_title)) {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    info!(concat!("For page \"{}\", restored vandalism ",
                                  "https://{}/w/index.php?title={}&diff=prev&oldid={}"),
                          &title, self.wiki.hostname, &canonical_title, revision.revid);
                    accumulated_contents = merged_contents;
                    revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        // TODO: randomize the placeholder string per-request
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT"));
        Ok(page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &body))
    }
}

impl Handler for WikipediaWithoutWikipedians {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => Response::with(
                        (iron::status::InternalServerError, "ERROR")),
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: not good enough. Needs to include query string. Maybe should be moved to wiki module.
            match self.client.get(
                &format!("https://{}/{}", self.wiki.hostname, request.url.path.join("/")))
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        wikipedia_response.read_to_end(&mut wikipedia_body);

                        let mut response = Response::with(wikipedia_body);
                        response.status = Some(wikipedia_response.status);
                        response.headers = wikipedia_response.headers.clone();
                        info!("Forwarded request for {} to {}", request.url.path.join("/"),
                              self.wiki.hostname);
                        Ok(response)
                    },
                    Err(error) => {
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             format!("ERROR: {}", error)));
                        response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let wikipedia_without_wikipedians =
        WikipediaWithoutWikipedians::new(Wiki::new(wiki.to_string(), Client::new()), Client::new());
    Iron::new(wikipedia_without_wikipedians).http(("localhost", port)).unwrap();
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<commit_msg>Remove a TODO. I can't come up with better names.<commit_after>#![feature(plugin)]
#![plugin(regex_macros)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(write_to_temp_file(new2));

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        // TODO: could also do this with filter() and is_empty(). Not sure if that would be better.
        for attribute in attributes {
            // TODO: do I seriously have to construct a StrTendril here?
            // There has to be a better way.
            if attribute.name.local.as_slice() == "id" &&
                attribute.value == tendril::StrTendril::from_str(id).unwrap() {
                    return true;
                }
        }
        return false;
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => {
            for child in &node.children {
                match find_node_by_id(child, id) {
                    // TODO: this looks weird. Is this an abnormal way to do things?
                    Ok(node) => return Ok(node),
                    _ => continue,
                }
            }
            Err(format!("No node with ID {} found", id))
        },
    }
}

// TODO: rename?
struct WikipediaWithoutWikipedians {
    wiki: Wiki,
    client: Client
}

impl WikipediaWithoutWikipedians {
    fn new(wiki: Wiki, client: Client) -> WikipediaWithoutWikipedians {
        WikipediaWithoutWikipedians { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut revisions = vec![];
        for revision in try!(self.get_vandalism_reversions(&canonical_title)) {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    info!(concat!("For page \"{}\", restored vandalism ",
                                  "https://{}/w/index.php?title={}&diff=prev&oldid={}"),
                          &title, self.wiki.hostname, &canonical_title, revision.revid);
                    accumulated_contents = merged_contents;
                    revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        // TODO: randomize the placeholder string per-request
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", "WMW_PLACEHOLDER_TEXT"));
        Ok(page_contents_with_placeholder.replace("WMW_PLACEHOLDER_TEXT", &body))
    }
}

impl Handler for WikipediaWithoutWikipedians {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => Response::with(
                        (iron::status::InternalServerError, "ERROR")),
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: not good enough. Needs to include query string. Maybe should be moved to wiki module.
            match self.client.get(
                &format!("https://{}/{}", self.wiki.hostname, request.url.path.join("/")))
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        wikipedia_response.read_to_end(&mut wikipedia_body);

                        let mut response = Response::with(wikipedia_body);
                        response.status = Some(wikipedia_response.status);
                        response.headers = wikipedia_response.headers.clone();
                        info!("Forwarded request for {} to {}", request.url.path.join("/"),
                              self.wiki.hostname);
                        Ok(response)
                    },
                    Err(error) => {
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             format!("ERROR: {}", error)));
                        response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let wikipedia_without_wikipedians =
        WikipediaWithoutWikipedians::new(Wiki::new(wiki.to_string(), Client::new()), Client::new());
    Iron::new(wikipedia_without_wikipedians).http(("localhost", port)).unwrap();
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<|endoftext|>


<commit_before>#![feature(slice_patterns)]
#![allow(dead_code)]

extern crate artificial_genome;
extern crate rand;
extern crate fixedbitset;

use artificial_genome::{Genome, ProteinRegulator, GeneNetwork, GeneNetworkState};
use artificial_genome::base4::{Base4, B0, B1};
use std::mem;
use std::io::{self, Write};
use std::collections::{BTreeMap, BTreeSet};
use fixedbitset::FixedBitSet;

#[derive(Debug, Clone)]
struct Edge {
    src_node: usize,
    dst_node: usize,
    length: f32,

    // there is a structural node, and each time it is activated,
    // this count is increased.
    type_count: usize,

    // Every edge has it's own regulatory network state embedded.
    network_state: GeneNetworkState,
}

const EDGE_DIFFERENTIATION: usize = 0;
const EDGE_SPLIT: usize = 1;
const EDGE_DUPLICATE: usize = 2;
const EDGE_SWAP: usize = 3;
const EDGE_GROW: usize = 4;
const EDGE_SHRINK: usize = 5;
const EDGE_TYPE: usize = 6;

const RESIZE_FACTOR: f32 = 0.25;

impl Edge {
    fn transition_state(&mut self, network: &GeneNetwork) {
        // create an empty new state.
        let mut new_state = network.new_state();

        for (i, node) in network.nodes().iter().enumerate() {
            // determine the new state of ```node``` (position i in new_state)
            if node.sum_edges(&self.network_state) > 0 {
                // node is enabled
                new_state.state.insert(i);
            }
        }
        self.network_state = new_state;
    }

    fn develop(&mut self,
               network: &GeneNetwork,
               next_node_id: &mut usize,
               new_edges: &mut Vec<Edge>) {

        // first perform the state transition.
        self.transition_state(network);

        // perform the actions of all active nodes in the gene network.

        // first node is used for differentiation.
        // in a split/duplicate node, this will be set 0 or 1 in the children.
        // starting from second node, the nodes are mapped to graph grammar rules.

        if self.network_state.state.contains(EDGE_SPLIT) {
            let new_node = *next_node_id;
            *next_node_id += 1;

            // differentiate
            let mut child_state = self.network_state.clone();
            child_state.state.set(EDGE_DIFFERENTIATION,
                                  !self.network_state.state.contains(EDGE_DIFFERENTIATION));

            let new_edge = Edge {
                src_node: new_node,
                dst_node: self.dst_node,
                length: 0.5 * self.length,
                network_state: child_state,
                type_count: self.type_count, /* XXX: start with 0 or with the count of the current edge? */
            };
            self.dst_node = new_node;
            self.length /= 2.0;
            new_edges.push(new_edge);
        }

        if self.network_state.state.contains(EDGE_DUPLICATE) {
            // differentiate
            let mut child_state = self.network_state.clone();
            child_state.state.set(EDGE_DIFFERENTIATION,
                                  !self.network_state.state.contains(EDGE_DIFFERENTIATION));

            let new_edge = Edge {
                src_node: self.dst_node,
                dst_node: self.src_node,
                length: self.length,
                network_state: child_state,
                type_count: self.type_count, /* XXX: start with 0 or with the count of the current edge? */
            };
            new_edges.push(new_edge);
        }

        if self.network_state.state.contains(EDGE_SWAP) {
            mem::swap(&mut self.dst_node, &mut self.src_node);
        }

        if self.network_state.state.contains(EDGE_GROW) {
            self.length += RESIZE_FACTOR * self.length;
        }

        if self.network_state.state.contains(EDGE_SHRINK) {
            self.length -= RESIZE_FACTOR * self.length;
        }

        if self.network_state.state.contains(EDGE_TYPE) {
            self.type_count += 1;
        }
    }
}

struct NodeGraph {
    nodes: Vec<NodeInfo>,
    edges: BTreeSet<(usize, usize)>,
}

struct NodeInfo {
    length: f32,
    type_count: usize,
}

#[derive(Debug)]
struct FoundPath {
    target_node: usize,
    length: f32,
}

// Find all paths from current_node to connected nodes.
fn path_finder(current_node: usize,
               current_length: f32,
               neighborhood: &Vec<Vec<usize>>,
               lengths: &Vec<f32>,
               targets: &BTreeSet<usize>,
               visited: &mut FixedBitSet,
               paths: &mut Vec<FoundPath>) {
    for &ni in neighborhood[current_node].iter() {
        if targets.contains(&ni) {
            // we found a target
            paths.push(FoundPath {
                target_node: ni,
                length: current_length,
            });
        } else if !visited.contains(ni) {
            // the node ``ni`` is not a target node and we haven't
            // visited yet.

            // we haven't visited ```ni``` yet. recurse down
            visited.set(ni, true);
            path_finder(ni,
                        current_length + lengths[ni],
                        neighborhood,
                        lengths,
                        targets,
                        visited,
                        paths);
            visited.set(ni, false);
        }
    }
}

#[derive(Debug)]
struct StructuredNode {
    length: f32,
    type_count: usize,
    connections: Vec<FoundPath>,
}

// There are two types of nodes. Processing nodes (e.g. Neuron) or
// connecting nodes (e.g. Synapses). Keep the processing nodes as
// nodes in the graph, while the connecting nodes become edges.
#[derive(Debug)]
struct StructuredGraph {
    nodes: BTreeMap<usize, StructuredNode>,
}

impl NodeGraph {
    fn write_dot<W: Write>(&self, wr: &mut W) -> io::Result<()> {
        try!(writeln!(wr, "digraph artificial {{"));

        // the edges are nodes in this graph.
        for (i, node) in self.nodes.iter().enumerate() {
            try!(writeln!(wr,
                          "{} [label=\"{}:{:.2}:{}\"]",
                          i,
                          i,
                          node.length,
                          node.type_count));
        }

        // now connect them
        for &(src_edge, dst_edge) in self.edges.iter() {
            try!(writeln!(wr, "{} -> {}", src_edge, dst_edge));
        }

        try!(writeln!(wr, "}}"));

        Ok(())
    }

    fn into_structured_graph(&self, type_processing: usize) -> StructuredGraph {
        // determine max type_count.
        // lets say every type_count >= 3 is a neuron for now.
        // everything else is a synapse
        // If two synapses are directly connected to each other,
        // treat them as one synapse with the summation of it's lengths.
        // XXX: remove synapses which are too long.
        // If two neurons are next to each other, what to do?
        // a) Treat them as one big neuron
        // b) Connect them with a minimum synapse?
        // We do b) for now, because it's easier.

        let mut neighbors: BTreeMap<usize, BTreeSet<usize>> = BTreeMap::new();
        for &(src, dst) in self.edges.iter() {
            neighbors.entry(src).or_insert_with(|| BTreeSet::new()).insert(dst);
        }

        let mut processing_nodes: BTreeSet<usize> = BTreeSet::new();

        for (i, node) in self.nodes.iter().enumerate() {
            if node.type_count >= type_processing {
                processing_nodes.insert(i);
            }
        }

        let neighborhood: Vec<Vec<usize>> = self.nodes
                                                .iter()
                                                .enumerate()
                                                .map(|(i, _)| {
                                                    neighbors.get(&i)
                                                             .map(|v| v.iter().cloned().collect())
                                                             .unwrap_or(Vec::new())
                                                })
                                                .collect();

        let lengths: Vec<f32> = self.nodes
                                    .iter()
                                    .map(|n| n.length)
                                    .collect();

        // now follow all possible directed paths from each processing_node, until
        // we arrive at another processing_node.
        // (Or repetetively merge consecutive elements which have same type).

        let mut visited = FixedBitSet::with_capacity(self.nodes.len());

        let mut g = StructuredGraph { nodes: BTreeMap::new() };

        for &pnode in processing_nodes.iter() {
            // find all processing elements connected either directly or through intermediate
            // synapses.
            let mut snode = StructuredNode {
                length: self.nodes[pnode].length,
                type_count: self.nodes[pnode].type_count,
                connections: Vec::new(),
            };

            visited.clear();
            path_finder(pnode,
                        0.0,
                        &neighborhood,
                        &lengths,
                        &processing_nodes,
                        &mut visited,
                        &mut snode.connections);

            g.nodes.insert(pnode, snode);
        }

        g
    }
}

impl StructuredGraph {
    fn write_dot<W: Write>(&self, wr: &mut W) -> io::Result<()> {
        try!(writeln!(wr, "digraph artificial {{"));

        for (&i, node) in self.nodes.iter() {
            if node.connections.is_empty() {
                // XXX
                continue;
            }
            try!(writeln!(wr,
                          "{} [label=\"{} {:.2} {}\"]",
                          i,
                          i,
                          node.length,
                          node.type_count));
        }

        // now connect them
        for (src, node) in self.nodes.iter() {
            for dst in node.connections.iter() {
                if dst.length == 0.0 {
                    // XXX
                    continue;
                }
                try!(writeln!(wr,
                              "{} -> {} [weight={} label=\"{}\"]",
                              src,
                              dst.target_node,
                              dst.length,
                              dst.length));
            }
        }

        try!(writeln!(wr, "}}"));

        Ok(())
    }
}

#[derive(Debug)]
struct GraphBuilder {
    edges: Vec<Edge>,
    next_node_id: usize,
    network: GeneNetwork,
}

impl GraphBuilder {
    fn new(network: GeneNetwork, zygote: GeneNetworkState) -> GraphBuilder {
        let initial_edge = Edge {
            src_node: 0,
            dst_node: 1,
            length: 1.0,
            type_count: 0,
            network_state: zygote,
        };

        GraphBuilder {
            edges: vec![initial_edge],
            next_node_id: 2,
            network: network,
        }
    }

    // During the process, some edges will be added (split), some others will be modified.
    fn next(&mut self) {
        let mut new_edges = Vec::new();
        for edge in self.edges.iter_mut() {
            edge.develop(&self.network, &mut self.next_node_id, &mut new_edges);
        }
        println!("next_node_id: {}", self.next_node_id);
        println!("new edges: {:?}", new_edges);
        self.edges.extend(new_edges);
    }

    // The result of the GraphBuilder is a graph where every edge represents an element (either a
    // Neuron or a Synapse), while the nodes represent connection points (they carry purely
    // structural information). Transform this "edged" graph into a graph where the edges become
    // nodes.
    fn into_node_graph(&self) -> NodeGraph {
        // only keep one directed edge between each pair of nodes.
        // keep the edge with highest type_count.

        let mut e: Vec<Edge> = Vec::new();
        {
            // stores (src_node, dst_node) => edge idx
            let mut selected_edges: BTreeMap<(usize, usize), usize> = BTreeMap::new();
            for (i, edge) in self.edges.iter().enumerate() {
                let idx_ref = selected_edges.entry((edge.src_node, edge.dst_node)).or_insert(i);
                let old = &self.edges[*idx_ref];
                if edge.type_count > old.type_count {
                    *idx_ref = i;
                }
            }
            for (_, &edge_idx) in selected_edges.iter() {
                e.push(self.edges[edge_idx].clone());
            }
        }

        let mut node_graph = NodeGraph {
            nodes: Vec::with_capacity(e.len()),
            edges: BTreeSet::new(),
        };

        let mut node_out_edges: BTreeMap<usize, Vec<usize>> = BTreeMap::new();

        // every edge becomes a node.
        // note that ```i``` matches the indices  we use for ```new_nodes```.
        for (i, edge) in e.iter().enumerate() {
            let j = node_graph.nodes.len();
            node_graph.nodes.push(NodeInfo {
                length: edge.length,
                type_count: edge.type_count,
            });
            assert!(i == j);
            node_out_edges.entry(edge.src_node).or_insert(Vec::new()).push(i);
        }
        assert!(node_graph.nodes.len() == e.len());

        for (i, edge) in e.iter().enumerate() {
            // connect i with all outgoing edges of dst_node

            if let Some(list) = node_out_edges.get(&edge.dst_node) {
                for &out_i in list.iter() {
                    node_graph.edges.insert((i, out_i));
                }
            }
        }

        node_graph
    }

    fn write_dot<W: Write>(&self, wr: &mut W) -> io::Result<()> {
        try!(writeln!(wr, "digraph artificial {{"));

        for edge in self.edges.iter() {
            try!(writeln!(wr,
                          "{} -> {} [weight={} label=\"{} / {}\"]",
                          edge.src_node,
                          edge.dst_node,
                          edge.length,
                          edge.length,
                          edge.type_count));
        }

        try!(writeln!(wr, "}}"));

        Ok(())
    }
}

fn main() {
    use std::fs::File;
    // let genome = Genome::<Base4>::from_str("...11 _0320_23 <0101> T:0311 2...3 _1022_ 133 <0101> \
    // W:3213 121...")
    let mut rng = rand::thread_rng();

    let genome = Genome::<Base4>::random(&mut rng, 10 * 256);

    // let promoter = BaseString::<Base4>::from_str("0101").unwrap();
    let promoter = [B0, B1, B0, B1];

    let genes: Vec<_> = genome.iter_genes(&promoter, 4).collect();
    println!("{:?}", genes);

    let network = genome.construct_network(&promoter,
                                           4,
                                           &|product| {
                                               if product.last() == Some(&B0) {
                                                   // Inhibitor
                                                   ProteinRegulator::inhibit()
                                               } else {
                                                   // Inhibitor
                                                   ProteinRegulator::enhance()
                                               }
                                           });

    println!("{:#?}", network);

    let mut zygote = network.new_state();
    zygote.state.set(0, true);
    // zygote.state.set(1, true);

    let mut gb = GraphBuilder::new(network, zygote);
    println!("{:#?}", gb);

    for _ in 0..5 {
        gb.next();
    }
    println!("{:#?}", gb);

    gb.write_dot(&mut File::create("example1.dot").unwrap()).unwrap();

    let node_graph = gb.into_node_graph();
    node_graph.write_dot(&mut File::create("example1_node.dot").unwrap()).unwrap();
    let g = node_graph.into_structured_graph(1);
    println!("g: {:?}", g);
    g.write_dot(&mut File::create("example1_struct.dot").unwrap()).unwrap();
}
<commit_msg>Refactor<commit_after>#![feature(slice_patterns)]
#![allow(dead_code)]

extern crate artificial_genome;
extern crate rand;
extern crate fixedbitset;

use artificial_genome::{Genome, ProteinRegulator, GeneNetwork, GeneNetworkState};
use artificial_genome::base4::{Base4, B0, B1};
use std::mem;
use std::io::{self, Write};
use std::collections::{BTreeMap, BTreeSet};
use fixedbitset::FixedBitSet;

#[derive(Debug, Clone)]
struct Edge {
    src_node: usize,
    dst_node: usize,
    length: f32,

    // there is a structural node, and each time it is activated,
    // this count is increased.
    type_count: usize,

    // Every edge has it's own regulatory network state embedded.
    network_state: GeneNetworkState,
}

const EDGE_DIFFERENTIATION: usize = 0;
const EDGE_SPLIT: usize = 1;
const EDGE_DUPLICATE: usize = 2;
const EDGE_SWAP: usize = 3;
const EDGE_GROW: usize = 4;
const EDGE_SHRINK: usize = 5;
const EDGE_TYPE: usize = 6;

const RESIZE_FACTOR: f32 = 0.25;

impl Edge {
    fn transition_state(&mut self, network: &GeneNetwork) {
        // create an empty new state.
        let mut new_state = network.new_state();

        for (i, node) in network.nodes().iter().enumerate() {
            // determine the new state of ```node``` (position i in new_state)
            if node.sum_edges(&self.network_state) > 0 {
                // node is enabled
                new_state.state.insert(i);
            }
        }
        self.network_state = new_state;
    }

    fn develop(&mut self,
               network: &GeneNetwork,
               next_node_id: &mut usize,
               new_edges: &mut Vec<Edge>) {

        // first perform the state transition.
        self.transition_state(network);

        // perform the actions of all active nodes in the gene network.

        // first node is used for differentiation.
        // in a split/duplicate node, this will be set 0 or 1 in the children.
        // starting from second node, the nodes are mapped to graph grammar rules.

        if self.network_state.state.contains(EDGE_SPLIT) {
            let new_node = *next_node_id;
            *next_node_id += 1;

            // differentiate
            let mut child_state = self.network_state.clone();
            child_state.state.set(EDGE_DIFFERENTIATION,
                                  !self.network_state.state.contains(EDGE_DIFFERENTIATION));

            let new_edge = Edge {
                src_node: new_node,
                dst_node: self.dst_node,
                length: 0.5 * self.length,
                network_state: child_state,
                type_count: self.type_count, /* XXX: start with 0 or with the count of the current edge? */
            };
            self.dst_node = new_node;
            self.length /= 2.0;
            new_edges.push(new_edge);
        }

        if self.network_state.state.contains(EDGE_DUPLICATE) {
            // differentiate
            let mut child_state = self.network_state.clone();
            child_state.state.set(EDGE_DIFFERENTIATION,
                                  !self.network_state.state.contains(EDGE_DIFFERENTIATION));

            let new_edge = Edge {
                src_node: self.dst_node,
                dst_node: self.src_node,
                length: self.length,
                network_state: child_state,
                type_count: self.type_count, /* XXX: start with 0 or with the count of the current edge? */
            };
            new_edges.push(new_edge);
        }

        if self.network_state.state.contains(EDGE_SWAP) {
            mem::swap(&mut self.dst_node, &mut self.src_node);
        }

        if self.network_state.state.contains(EDGE_GROW) {
            self.length += RESIZE_FACTOR * self.length;
        }

        if self.network_state.state.contains(EDGE_SHRINK) {
            self.length -= RESIZE_FACTOR * self.length;
        }

        if self.network_state.state.contains(EDGE_TYPE) {
            self.type_count += 1;
        }
    }
}

struct NodeGraph {
    nodes: Vec<NodeInfo>,
    edges: BTreeSet<(usize, usize)>,
}

struct NodeInfo {
    length: f32,
    type_count: usize,
}

#[derive(Debug)]
struct FoundPath {
    target_node: usize,
    length: f32,
}

// Find all paths from current_node to connected nodes.
fn path_finder(current_node: usize,
               current_length: f32,
               neighborhood: &Vec<Vec<usize>>,
               lengths: &Vec<f32>,
               targets: &BTreeSet<usize>,
               visited: &mut FixedBitSet,
               paths: &mut Vec<FoundPath>) {
    for &ni in neighborhood[current_node].iter() {
        if targets.contains(&ni) {
            // we found a target
            paths.push(FoundPath {
                target_node: ni,
                length: current_length,
            });
        } else if !visited.contains(ni) {
            // the node ``ni`` is not a target node and we haven't
            // visited yet.

            // we haven't visited ```ni``` yet. recurse down
            visited.set(ni, true);
            path_finder(ni,
                        current_length + lengths[ni],
                        neighborhood,
                        lengths,
                        targets,
                        visited,
                        paths);
            visited.set(ni, false);
        }
    }
}

#[derive(Debug)]
struct StructuredNode {
    length: f32,
    type_count: usize,
    connections: Vec<FoundPath>,
}

// There are two types of nodes. Processing nodes (e.g. Neuron) or
// connecting nodes (e.g. Synapses). Keep the processing nodes as
// nodes in the graph, while the connecting nodes become edges.
#[derive(Debug)]
struct StructuredGraph {
    nodes: BTreeMap<usize, StructuredNode>,
}

impl NodeGraph {
    fn write_dot<W: Write>(&self, wr: &mut W) -> io::Result<()> {
        try!(writeln!(wr, "digraph artificial {{"));

        // the edges are nodes in this graph.
        for (i, node) in self.nodes.iter().enumerate() {
            try!(writeln!(wr,
                          "{} [label=\"{}:{:.2}:{}\"]",
                          i,
                          i,
                          node.length,
                          node.type_count));
        }

        // now connect them
        for &(src_edge, dst_edge) in self.edges.iter() {
            try!(writeln!(wr, "{} -> {}", src_edge, dst_edge));
        }

        try!(writeln!(wr, "}}"));

        Ok(())
    }

    fn into_structured_graph(&self, type_processing: usize) -> StructuredGraph {
        // determine max type_count.
        // lets say every type_count >= 3 is a neuron for now.
        // everything else is a synapse
        // If two synapses are directly connected to each other,
        // treat them as one synapse with the summation of it's lengths.
        // XXX: remove synapses which are too long.
        // If two neurons are next to each other, what to do?
        // a) Treat them as one big neuron
        // b) Connect them with a minimum synapse?
        // We do b) for now, because it's easier.

        let mut neighbors: BTreeMap<usize, BTreeSet<usize>> = BTreeMap::new();
        for &(src, dst) in self.edges.iter() {
            neighbors.entry(src).or_insert_with(|| BTreeSet::new()).insert(dst);
        }

        let mut processing_nodes: BTreeSet<usize> = BTreeSet::new();

        for (i, node) in self.nodes.iter().enumerate() {
            if node.type_count >= type_processing {
                processing_nodes.insert(i);
            }
        }

        let neighborhood: Vec<Vec<usize>> = self.nodes
                                                .iter()
                                                .enumerate()
                                                .map(|(i, _)| {
                                                    neighbors.get(&i)
                                                             .map(|v| v.iter().cloned().collect())
                                                             .unwrap_or(Vec::new())
                                                })
                                                .collect();

        let lengths: Vec<f32> = self.nodes
                                    .iter()
                                    .map(|n| n.length)
                                    .collect();

        // now follow all possible directed paths from each processing_node, until
        // we arrive at another processing_node.
        // (Or repetetively merge consecutive elements which have same type).

        let mut visited = FixedBitSet::with_capacity(self.nodes.len());

        let mut g = StructuredGraph { nodes: BTreeMap::new() };

        for &pnode in processing_nodes.iter() {
            // find all processing elements connected either directly or through intermediate
            // synapses.
            let mut snode = StructuredNode {
                length: self.nodes[pnode].length,
                type_count: self.nodes[pnode].type_count,
                connections: Vec::new(),
            };

            visited.clear();
            path_finder(pnode,
                        0.0,
                        &neighborhood,
                        &lengths,
                        &processing_nodes,
                        &mut visited,
                        &mut snode.connections);

            g.nodes.insert(pnode, snode);
        }

        g
    }
}

impl StructuredGraph {
    fn write_dot<W: Write>(&self, wr: &mut W) -> io::Result<()> {
        try!(writeln!(wr, "digraph artificial {{"));

        for (&i, node) in self.nodes.iter() {
            if node.connections.is_empty() {
                // XXX
                continue;
            }
            try!(writeln!(wr,
                          "{} [label=\"{} {:.2} {}\"]",
                          i,
                          i,
                          node.length,
                          node.type_count));
        }

        // now connect them
        for (src, node) in self.nodes.iter() {
            for dst in node.connections.iter() {
                if dst.length == 0.0 {
                    // XXX
                    continue;
                }
                try!(writeln!(wr,
                              "{} -> {} [weight={} label=\"{}\"]",
                              src,
                              dst.target_node,
                              dst.length,
                              dst.length));
            }
        }

        try!(writeln!(wr, "}}"));

        Ok(())
    }
}

#[derive(Debug)]
struct GraphBuilder {
    edges: Vec<Edge>,
    next_node_id: usize,
    network: GeneNetwork,
}

impl GraphBuilder {
    fn new(network: GeneNetwork, zygote: GeneNetworkState) -> GraphBuilder {
        let initial_edge = Edge {
            src_node: 0,
            dst_node: 1,
            length: 1.0,
            type_count: 0,
            network_state: zygote,
        };

        GraphBuilder {
            edges: vec![initial_edge],
            next_node_id: 2,
            network: network,
        }
    }

    // During the process, some edges will be added (split), some others will be modified.
    fn next(&mut self) {
        let mut new_edges = Vec::new();
        for edge in self.edges.iter_mut() {
            edge.develop(&self.network, &mut self.next_node_id, &mut new_edges);
        }
        println!("next_node_id: {}", self.next_node_id);
        println!("new edges: {:?}", new_edges);
        self.edges.extend(new_edges);
    }

    // The result of the GraphBuilder is a graph where every edge represents an element (either a
    // Neuron or a Synapse), while the nodes represent connection points (they carry purely
    // structural information). Transform this "edged" graph into a graph where the edges become
    // nodes.
    fn into_node_graph(&self) -> NodeGraph {
        // only keep one directed edge between each pair of nodes.
        // keep the edge with highest type_count.

        let mut e: Vec<Edge> = Vec::new();
        {
            // stores (src_node, dst_node) => edge idx
            let mut selected_edges: BTreeMap<(usize, usize), usize> = BTreeMap::new();
            for (i, edge) in self.edges.iter().enumerate() {
                let idx_ref = selected_edges.entry((edge.src_node, edge.dst_node)).or_insert(i);
                let old = &self.edges[*idx_ref];
                if edge.type_count > old.type_count {
                    *idx_ref = i;
                }
            }
            for (_, &edge_idx) in selected_edges.iter() {
                e.push(self.edges[edge_idx].clone());
            }
        }

        let mut node_graph = NodeGraph {
            nodes: Vec::with_capacity(e.len()),
            edges: BTreeSet::new(),
        };

        let mut node_out_edges: BTreeMap<usize, Vec<usize>> = BTreeMap::new();

        // every edge becomes a node.
        // note that ```i``` matches the indices  we use for ```new_nodes```.
        for (i, edge) in e.iter().enumerate() {
            let j = node_graph.nodes.len();
            node_graph.nodes.push(NodeInfo {
                length: edge.length,
                type_count: edge.type_count,
            });
            assert!(i == j);
            node_out_edges.entry(edge.src_node).or_insert(Vec::new()).push(i);
        }
        assert!(node_graph.nodes.len() == e.len());

        for (i, edge) in e.iter().enumerate() {
            // connect i with all outgoing edges of dst_node

            if let Some(list) = node_out_edges.get(&edge.dst_node) {
                for &out_i in list.iter() {
                    node_graph.edges.insert((i, out_i));
                }
            }
        }

        node_graph
    }

    fn write_dot<W: Write>(&self, wr: &mut W) -> io::Result<()> {
        try!(writeln!(wr, "digraph artificial {{"));

        for edge in self.edges.iter() {
            try!(writeln!(wr,
                          "{} -> {} [weight={} label=\"{} / {}\"]",
                          edge.src_node,
                          edge.dst_node,
                          edge.length,
                          edge.length,
                          edge.type_count));
        }

        try!(writeln!(wr, "}}"));

        Ok(())
    }
}

pub fn graph_from_base4_genome(genome: &Genome<Base4>, num_iterations: usize) {
    use std::fs::File;

    // let promoter = BaseString::<Base4>::from_str("0101").unwrap();
    let promoter = [B0, B1, B0, B1];

    let genes: Vec<_> = genome.iter_genes(&promoter, 4).collect();
    println!("{:?}", genes);

    let network = genome.construct_network(&promoter,
                                           4,
                                           &|product| {
                                               if product.last() == Some(&B0) {
                                                   // Inhibitor
                                                   ProteinRegulator::inhibit()
                                               } else {
                                                   // Inhibitor
                                                   ProteinRegulator::enhance()
                                               }
                                           });

    println!("{:#?}", network);

    let mut zygote = network.new_state();
    zygote.state.set(0, true);
    // zygote.state.set(1, true);

    let mut gb = GraphBuilder::new(network, zygote);
    println!("{:#?}", gb);

    for _ in 0..num_iterations {
        gb.next();
    }
    println!("{:#?}", gb);

    gb.write_dot(&mut File::create("example1.dot").unwrap()).unwrap();

    let node_graph = gb.into_node_graph();
    node_graph.write_dot(&mut File::create("example1_node.dot").unwrap()).unwrap();
    let g = node_graph.into_structured_graph(1);
    println!("g: {:?}", g);
    g.write_dot(&mut File::create("example1_struct.dot").unwrap()).unwrap();
}

fn main() {
    let mut rng = rand::thread_rng();

    let genome = Genome::<Base4>::random(&mut rng, 8 * 256);
    graph_from_base4_genome(&genome, 5);
}
<|endoftext|>