<commit_before>use std::collections::VecDeque;
use std::mem;
use std::sync::{Arc, Mutex};
use std::thread;

struct Actor {
    // TODO(gamazeps): Need to add Arc<Mutex<>> for this to be Send.
    ptr: *mut ActorInner,
}

impl Actor {
    fn new(queue: Arc<Mutex<VecDeque<Actor>>>) -> Actor {
        let ptr = Box::new(ActorInner::new(queue));
        Actor {
            ptr: unsafe {mem::transmute(ptr)
            }
        }
    }

    fn inner(&self) -> &ActorInner {
        unsafe {&*self.ptr}
    }

    fn inner_mut(&mut self) -> &mut ActorInner {
        unsafe {&mut *self.ptr}
    }

    fn transmute_inner_mut(&self) -> &mut ActorInner {
        unsafe {&mut *self.ptr}
    }

    fn receive(&self, message: String) {
        self.transmute_inner_mut().receive(message);
    }

    fn treat(&self) {
        self.transmute_inner_mut().treat();
    }

    fn treat_all(&self) {
        self.transmute_inner_mut().treat_all();
    }
}

struct ActorInner {
    messages: VecDeque<String>,
    actor_queue: Arc<Mutex<VecDeque<Actor>>>,
}

impl ActorInner {
    fn new(queue: Arc<Mutex<VecDeque<Actor>>>) -> ActorInner {
        ActorInner {
            messages: VecDeque::new(),
            actor_queue: queue,
        }
    }

    fn receive(&mut self, message: String) {
        self.messages.push_back(message);
        {
            let mut queue = self.actor_queue.lock().unwrap();
            queue.push_back(self.actor());
        }
    }

    fn treat(&mut self) {
        if let Some(s) = self.messages.pop_front() {
            println!("{}", s);
        }
    }

    fn treat_all(&mut self) {
        while let Some(s) = self.messages.pop_front() {
            println!("{}", s);
        }
    }

    fn actor(&self) -> Actor {
        Actor{
            ptr: unsafe {
                mem::transmute(self)
            }
        }
    }
}

fn spawn_consumer(actor_queue: Arc<Mutex<VecDeque<Actor>>>) -> thread::JoinHandle<usize> {
    thread::spawn(move || {
        loop {
            let mut actor = None;
            {
                let mut queue = actor_queue.lock().unwrap();
                actor = queue.pop_front();
            }
            if let Some(actor) = actor {
                actor.treat();
            }
        }
    })
}

fn main() {
    let actor_queue = Arc::new(Mutex::new(VecDeque::new()));
    let actor = Actor::new(actor_queue);

    actor.receive("message 1".to_string());
    actor.receive("message 2".to_string());
    actor.receive("message 3".to_string());

    let handle = spawn_consumer(actor_queue.clone());

    handle.join();

    println!("Hello World");
}
<commit_msg>Make the Actor Send so that it can be shared accross thread, create a barebone Message struct.<commit_after>use std::collections::VecDeque;
use std::mem;
use std::sync::{Arc, Mutex};
use std::thread;

enum Message {
    Data(String),
    Stop,
}

struct Actor {
    // TODO(gamazeps): Need to add Arc<Mutex<>> for this to be Send.
    // Use Unique instead (or maybe not...)
    ptr: *mut ActorInner,
}

impl Actor {
    fn new(queue: Arc<Mutex<VecDeque<Actor>>>) -> Actor {
        // Memory is allocated for an ActorInner.
        let ptr = Box::new(ActorInner::new(queue));
        // Ownership is passed to the created object, it will be deallocated when the object is
        // dropped.
        Actor {
            ptr: unsafe {
                mem::transmute(ptr)
            }
        }
    }

    fn inner(&self) -> &ActorInner {
        unsafe {&*self.ptr}
    }

    fn inner_mut(&mut self) -> &mut ActorInner {
        unsafe {&mut *self.ptr}
    }

    fn transmute_inner_mut(&self) -> &mut ActorInner {
        unsafe {&mut *self.ptr}
    }

    fn receive(&self, message: Message) {
        self.transmute_inner_mut().receive(message);
    }

    fn treat(&self) {
        self.transmute_inner_mut().treat();
    }

    fn treat_all(&self) {
        self.transmute_inner_mut().treat_all();
    }
}

unsafe impl Send for Actor {}

// Note that this struct should not be instantied by hand.
// TODO(gamazeps): create a module to have enforce this.
// An InnerActor  is dropped when the Actor that created it (with new) is dropped.
// This means that if the Actor goes out of scope while it still has a message to treat,
// there will be a segfault (or at least UB).
// This is a design choice for now (otherwise we just need to take inspiration from the code in Arc).
struct ActorInner {
    messages: VecDeque<Message>,
    actor_queue: Arc<Mutex<VecDeque<Actor>>>,
}

impl ActorInner {
    fn new(queue: Arc<Mutex<VecDeque<Actor>>>) -> ActorInner {
        ActorInner {
            messages: VecDeque::new(),
            actor_queue: queue,
        }
    }

    fn receive(&mut self, message: Message) {
        self.messages.push_back(message);
        {
            let mut queue = self.actor_queue.lock().unwrap();
            queue.push_back(self.actor());
        }
    }

    // TODO(gamazeps): There might be a useless copy of data, deal with it later.
    fn treat_message(&self, message: Message) {
        match message {
            Message::Data(s) => println!("{}", s),
            Message::Stop => println!("Stop received"),
        }
    }

    fn treat(&mut self) {
        if let Some(message) = self.messages.pop_front() {
            self.treat_message(message);
        }
    }

    fn treat_all(&mut self) {
        while let Some(message) = self.messages.pop_front() {
            self.treat_message(message);
        }
    }

    fn actor(&self) -> Actor {
        Actor{
            ptr: unsafe {
                mem::transmute(self)
            }
        }
    }
}

fn spawn_consumer(actor_queue: Arc<Mutex<VecDeque<Actor>>>) -> thread::JoinHandle<usize> {
    thread::spawn(move || {
        loop {
            let actor;
            {
                let mut queue = actor_queue.lock().unwrap();
                actor = queue.pop_front();
            }
            if let Some(actor) = actor {
                actor.treat();
            }
        }
    })
}

fn main() {
    let actor_queue = Arc::new(Mutex::new(VecDeque::new()));
    let actor_1 = Actor::new(actor_queue.clone());
    let actor_2 = Actor::new(actor_queue.clone());

    actor_1.receive(Message::Data("message 1".to_string()));
    actor_2.receive(Message::Data("message 2".to_string()));
    actor_1.receive(Message::Data("message 3".to_string()));
    actor_2.receive(Message::Data("message 4".to_string()));

    let handle = spawn_consumer(actor_queue.clone());

    handle.join();

    println!("Hello World");
}
<|endoftext|>


<commit_before>extern crate clap;
extern crate walkdir;
extern crate twox_hash;

use std::collections::HashMap;
use std::collections::BTreeSet;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::os::linux::fs::MetadataExt;

use clap::{App, Arg, ArgMatches};
use walkdir::WalkDir;
use twox_hash::XxHash;
use std::hash::Hasher;

#[derive(Default)]
struct Stats {
    file_count: u64,
    total_size: u64,
    duplicate_count: u64,
}

fn parse_args<'a>() -> ArgMatches<'a> {
    App::new("dupr")
        .version("0.1.0")
        .author("Mike Sampson")
        .about("Duplicate file finder")
        .arg(
            Arg::with_name("DIR")
                .help("Directory to process")
                .required(true),
        )
        .arg(
            Arg::with_name("sameline")
                .short("1")
                .long("sameline")
                .help("List each set of matches on a single line"),
        )
        .arg(Arg::with_name("noempty").short("n").long("noempty").help(
            "Exclude zero length files",
        ))
        .arg(Arg::with_name("summary").short("s").long("summary").help(
            "Print out summary information after duplicates",
        ))
        .arg(Arg::with_name("size").short("S").long("size").help(
            "Show size of duplicate files",
        ))
        .arg(Arg::with_name("quiet").short("q").long("quiet").help(
            "Hide progress indicator",
        ))
        .get_matches()
}

fn collect_paths(matches: &ArgMatches, stats: &mut Stats) -> HashMap<u64, Vec<PathBuf>> {

    let mut length_paths = HashMap::new();
    let spinner = ["|", "/", "-", "\\"];

    let dir = matches.value_of("DIR").unwrap();

    for (idx, entry) in WalkDir::new(dir).into_iter().enumerate() {
        if !matches.is_present("quiet") {
            eprint!("\rBuilding file list {} ", spinner[idx % spinner.len()]);
        }
        let entry = match entry {
            Ok(e) => e,
            Err(err) => {
                eprintln!("dupr: {}", err);
                continue;
            }
        };

        if !entry.file_type().is_file() {
            continue;
        }

        let metadata = match entry.metadata() {
            Ok(e) => e,
            Err(err) => {
                eprintln!("dupr: {}", err);
                continue;
            }
        };

        if matches.is_present("noempty") && metadata.len() == 0 {
            continue;
        }

        stats.file_count += 1;
        stats.total_size += metadata.len();

        let path = entry.path().to_path_buf();

        length_paths
            .entry(metadata.len())
            .or_insert(Vec::new())
            .push(path);
    }
    eprint!("\r{:40}", " ");

    length_paths
}

fn main() {
    let matches = parse_args();

    let mut stats: Stats = Default::default();

    let files = collect_paths(&matches, &mut stats);

    let mut len_hash_path = HashMap::new();
    let mut paths_completed = 0;

    for (len, paths) in &files {
        if !matches.is_present("quiet") {
            eprint!(
                "\rProgress [{}/{}] {:.0}%",
                paths_completed,
                stats.file_count,
                100.0 * paths_completed as f64 / stats.file_count as f64
            );
        }

        if paths.len() < 2 {
            continue;
        }

        paths_completed += paths.len();

        let mut inode_paths = HashMap::new();

        for path in paths {
            let metadata = match path.metadata() {
                Ok(m) => m,
                Err(err) => {
                    eprintln!("dupr: {}", err);
                    continue;
                }
            };

            let dev_inode = (metadata.st_dev(), metadata.st_ino());

            inode_paths.entry(dev_inode).or_insert(Vec::new()).push(
                path,
            );
        }

        if inode_paths.len() > 1 {
            for paths in inode_paths.values() {
                len_hash_path
                    .entry((len, hash_file(paths[0])))
                    .or_insert(BTreeSet::new())
                    .insert(paths[0]);
            }
        }
    }
    eprint!("\r{:40}\r", " ");

    let mut keys: Vec<_> = len_hash_path.iter().filter(|&(_, v)| v.len() > 1).collect();
    keys.sort();
    stats.duplicate_count = keys.len() as u64;

    for (&(len, _), paths) in keys {
        if matches.is_present("size") {
            println!("{} bytes each:", len);
        }

        if matches.is_present("sameline") {
            let p: Vec<_> = paths.iter().map(|x| x.to_string_lossy()).collect();
            println!("{}", p.join("-"));
        } else {
            for path in paths {
                println!("{}", path.display());
            }
            println!();
        }
    }

    if matches.is_present("summary") {
        println!(
            "Processed {} files with a total size of {} bytes. {} duplicates found.",
            stats.file_count,
            stats.total_size,
            stats.duplicate_count
        );
    }
}

fn hash_file<P: AsRef<Path>>(path: P) -> u64 {
    let mut file = File::open(path).unwrap();

    let mut hash = XxHash::with_seed(0);

    let mut buffer = [0; 32 * 1024];

    while let Ok(n) = file.read(&mut buffer) {
        if n == 0 {
            break;
        }
        hash.write(&buffer[0..n]);
    }

    hash.finish()
}
<commit_msg>Add recurse option<commit_after>extern crate clap;
extern crate walkdir;
extern crate twox_hash;

use std::collections::HashMap;
use std::collections::BTreeSet;
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::os::linux::fs::MetadataExt;

use clap::{App, Arg, ArgMatches};
use walkdir::WalkDir;
use twox_hash::XxHash;
use std::hash::Hasher;

#[derive(Default)]
struct Stats {
    file_count: u64,
    total_size: u64,
    duplicate_count: u64,
}

fn parse_args<'a>() -> ArgMatches<'a> {
    App::new("dupr")
        .version("0.1.0")
        .author("Mike Sampson")
        .about("Duplicate file finder")
        .arg(
            Arg::with_name("DIR")
                .help("Directory to process")
                .required(true),
        )
        .arg(
            Arg::with_name("sameline")
                .short("1")
                .long("sameline")
                .help("List each set of matches on a single line"),
        )
        .arg(Arg::with_name("noempty").short("n").long("noempty").help(
            "Exclude zero length files",
        ))
        .arg(Arg::with_name("summary").short("s").long("summary").help(
            "Print out summary information after duplicates",
        ))
        .arg(Arg::with_name("size").short("S").long("size").help(
            "Show size of duplicate files",
        ))
        .arg(Arg::with_name("quiet").short("q").long("quiet").help(
            "Hide progress indicator",
        ))
        .arg(Arg::with_name("recurse").short("r").long("recurse").help(
            "Recurse into subdirectories"
        ))
        .get_matches()
}

fn collect_paths(matches: &ArgMatches, stats: &mut Stats) -> HashMap<u64, Vec<PathBuf>> {

    let mut length_paths = HashMap::new();
    let spinner = ["|", "/", "-", "\\"];

    let dir = matches.value_of("DIR").unwrap();

    let walkdir = match matches.is_present("recurse") {
        true  => WalkDir::new(dir).into_iter().enumerate(),
        false => WalkDir::new(dir).max_depth(1).into_iter().enumerate(),
    };

    for (idx, entry) in walkdir {
        if !matches.is_present("quiet") {
            eprint!("\rBuilding file list {} ", spinner[idx % spinner.len()]);
        }
        let entry = match entry {
            Ok(e) => e,
            Err(err) => {
                eprintln!("dupr: {}", err);
                continue;
            }
        };

        if !entry.file_type().is_file() {
            continue;
        }

        let metadata = match entry.metadata() {
            Ok(e) => e,
            Err(err) => {
                eprintln!("dupr: {}", err);
                continue;
            }
        };

        if matches.is_present("noempty") && metadata.len() == 0 {
            continue;
        }

        stats.file_count += 1;
        stats.total_size += metadata.len();

        let path = entry.path().to_path_buf();

        length_paths
            .entry(metadata.len())
            .or_insert(Vec::new())
            .push(path);
    }
    eprint!("\r{:40}", " ");

    length_paths
}

fn main() {
    let matches = parse_args();

    let mut stats: Stats = Default::default();

    let files = collect_paths(&matches, &mut stats);

    let mut len_hash_path = HashMap::new();
    let mut paths_completed = 0;

    for (len, paths) in &files {
        if !matches.is_present("quiet") {
            eprint!(
                "\rProgress [{}/{}] {:.0}%",
                paths_completed,
                stats.file_count,
                100.0 * paths_completed as f64 / stats.file_count as f64
            );
        }

        if paths.len() < 2 {
            continue;
        }

        paths_completed += paths.len();

        let mut inode_paths = HashMap::new();

        for path in paths {
            let metadata = match path.metadata() {
                Ok(m) => m,
                Err(err) => {
                    eprintln!("dupr: {}", err);
                    continue;
                }
            };

            let dev_inode = (metadata.st_dev(), metadata.st_ino());

            inode_paths.entry(dev_inode).or_insert(Vec::new()).push(
                path,
            );
        }

        if inode_paths.len() > 1 {
            for paths in inode_paths.values() {
                len_hash_path
                    .entry((len, hash_file(paths[0])))
                    .or_insert(BTreeSet::new())
                    .insert(paths[0]);
            }
        }
    }
    eprint!("\r{:40}\r", " ");

    let mut keys: Vec<_> = len_hash_path.iter().filter(|&(_, v)| v.len() > 1).collect();
    keys.sort();
    stats.duplicate_count = keys.len() as u64;

    for (&(len, _), paths) in keys {
        if matches.is_present("size") {
            println!("{} bytes each:", len);
        }

        if matches.is_present("sameline") {
            let p: Vec<_> = paths.iter().map(|x| x.to_string_lossy()).collect();
            println!("{}", p.join("-"));
        } else {
            for path in paths {
                println!("{}", path.display());
            }
            println!();
        }
    }

    if matches.is_present("summary") {
        println!(
            "Processed {} files with a total size of {} bytes. {} duplicates found.",
            stats.file_count,
            stats.total_size,
            stats.duplicate_count
        );
    }
}

fn hash_file<P: AsRef<Path>>(path: P) -> u64 {
    let mut file = File::open(path).unwrap();

    let mut hash = XxHash::with_seed(0);

    let mut buffer = [0; 32 * 1024];

    while let Ok(n) = file.read(&mut buffer) {
        if n == 0 {
            break;
        }
        hash.write(&buffer[0..n]);
    }

    hash.finish()
}
<|endoftext|>


<commit_before>extern crate rustless;
extern crate hyper;
extern crate iron;
extern crate valico;
extern crate crypto;

#[macro_use(bson, doc)]
extern crate bson;
extern crate mongodb;

use mongodb::{Client, ThreadedClient};
use mongodb::db::ThreadedDatabase;
use bson::
{
    Bson,
    Document
};

use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use std::path::Path;
use std::process::Command;
use std::thread;
use std::time::
{
    SystemTime,
    UNIX_EPOCH
};
use crypto::digest::Digest;
use crypto::sha3::Sha3;
use valico::json_dsl;
use rustless::server::status::StatusCode;
use rustless::json::ToJson;
use rustless::
{
    Application,
    Api,
    Nesting,
    Versioning
};
use hyper::header::
{
    ContentDisposition,
    DispositionType,
    DispositionParam,
    Charset
};

const GBS_DIR:&str = "/var/lib/gbs";

fn check(path:&str) -> Option<&str>
{
    let p = Path::new(path).file_name()?.to_str()?;
    if p == path
    {
        return Some(path);
    }
    else
    {
        return None;
    }
}

fn is_atom(repository:&str, category:&str, package:&str, version:&str) -> Option<()>
{

    let r = check(repository)?;
    let c = check(category)?;
    let p = check(package)?;
    let v = check(version)?;

    if Path::new(&format!("{}/repos/{}/{}/{}/{}-{}.ebuild", GBS_DIR, r, c, p, p, v)).is_file()
    {
        return Some(());
    }
    else
    {
        return None;
    }
}

fn is_build_request(repository:&str, category:&str, package:&str, version:&str, id:&str) -> Option<()>
{

    let r = check(repository)?;
    let c = check(category)?;
    let p = check(package)?;
    let v = check(version)?;
    let i = check(id)?;

    if Path::new(&format!("{}/packages/{}/{}/{}/{}/{}", GBS_DIR, r, c, p, v, i)).is_dir()
    {
        return Some(());
    }
    else
    {
        return None;
    }
}

#[test]
fn test_is_atom()
{
    assert_eq!(false, is_atom("test","test","test","test").is_some());
    assert_eq!(false, is_atom("test/.","test/../.","test","test").is_some());
    assert_eq!(false, is_atom("gentoo/.","app-editors","vim","8.0.1298").is_some());
    assert_eq!(false, is_atom("gentoo","app-editors","vim","8").is_some());
    assert_eq!(true, is_atom("gentoo","app-editors","vim","8.0.1298").is_some());
}

fn main()
{
    let client = Client::connect("localhost", 27017)
        .expect("Failed to initialize standalone client.");
    let coll = client.db("gbs").collection("builds");

    let api = Api::build(|api|
    {
        api.prefix("api");
        api.version("1", Versioning::Path);

        api.namespace("atoms/:repositories/:categories/:packages/:versions", |atoms_ns|
        {
            atoms_ns.params(|params|
            {
                params.req_typed("repositories", json_dsl::string());
                params.req_typed("categories", json_dsl::string());
                params.req_typed("packages", json_dsl::string());
                params.req_typed("versions", json_dsl::string());
            });
            atoms_ns.get("builds", |endpoint|
            {
                endpoint.handle(|client, params|
                {
                    return client.json(&params.to_json());
                })
            });
            atoms_ns.get("builds/:id/status", |endpoint|
            {
                endpoint.params(|params|
                {
                    params.req_typed("id", json_dsl::string());
                });
                endpoint.handle(|mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();
                    let id = params.find("id").unwrap().to_string().trim_matches('"').to_string();

                    if is_build_request(&repository, &category, &package, &version, &id).is_some()
                    {
                        let s = format!("{}/packages/{}/{}/{}/{}/{}/status", GBS_DIR, repository, category, package, version, id);
                        let path = Path::new(&s);
                        if path.is_file()
                        {
                            let f = File::open(&s).unwrap();
                            let mut reader = BufReader::new(f);
                            let mut buf = String::new();
                            reader.read_to_string(&mut buf);
                            return client.text(buf);
                        }
                    }
                    client.set_status(StatusCode::NotFound);
                    return client.empty();
                })
            });
            atoms_ns.get("builds/:id/log", |endpoint|
            {
                endpoint.params(|params|
                {
                    params.req_typed("id", json_dsl::string());
                });
                endpoint.handle(|mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();
                    let id = params.find("id").unwrap().to_string().trim_matches('"').to_string();

                    if is_build_request(&repository, &category, &package, &version, &id).is_some()
                    {
                        let s = format!("{}/packages/{}/{}/{}/{}/{}/log", GBS_DIR, repository, category, package, version, id);
                        let path = Path::new(&s);
                        if path.is_file()
                        {
                            let f = File::open(&s).unwrap();
                            let mut reader = BufReader::new(f);
                            let mut buf = String::new();
                            reader.read_to_string(&mut buf);
                            return client.text(buf);
                        }
                    }
                    client.set_status(StatusCode::NotFound);
                    return client.empty();
                })
            });
            atoms_ns.get("builds/:id", |endpoint|
            {
                endpoint.params(|params|
                {
                    params.req_typed("id", json_dsl::string());
                });
                endpoint.handle(|mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();
                    let id = params.find("id").unwrap().to_string().trim_matches('"').to_string();

                    if is_build_request(&repository, &category, &package, &version, &id).is_some()
                    {
                        let s = format!("{}/packages/{}/{}/{}/{}/{}/{3}-{4}.tbz2", GBS_DIR, repository, category, package, version, id);
                        let path = Path::new(&s);
                        if path.is_file()
                        {
                            client.set_header(ContentDisposition
                            {
                              disposition: DispositionType::Attachment,
                              parameters: vec![DispositionParam::Filename(
                                Charset::Us_Ascii,
                                None,
                                format!("{}-{}.tbz2", package, version).into_bytes()
                            )]});
                            return client.file(path);
                        }
                    }
                    client.set_status(StatusCode::NotFound);
                    return client.empty();
                })
            });
            atoms_ns.post("builds", |endpoint|
            {
                endpoint.handle(move |mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();

                    let mut uses = String::new();
                    let use_flag = params.find("use").unwrap();
                    for (key, value) in use_flag.as_object().unwrap().iter()
                    {
                        uses.push_str(&format!("{}{} ", match value.as_bool().unwrap(){true => "", false => "-"}, key));
                    }
                    println!("{}", params);
                    println!("{}", uses);

                    let mut hasher = Sha3::sha3_256();
                    hasher.input_str(&format!("{}{}{}{}{}", repository, category, package, version, uses));
                    let id = hasher.result_str();

                    let url = format!("{}/{}/{}/{}/builds/{}", repository, category, package, version, id);

                    if is_atom(&repository, &category, &package, &version).is_some()
                    {
                        if !is_build_request(&repository, &category, &package, &version, &id).is_some()
                        {
                            {
                                let mut uses = Document::new();
                                for (key, value) in use_flag.as_object().unwrap().iter()
                                {
                                    uses.insert(key.clone(), Bson::Boolean(value.as_bool().unwrap().clone()));
                                }
                                let s = SystemTime::now();
                                let doc = doc!
                                {
                                    "date" => &format!("{}", s.duration_since(UNIX_EPOCH).unwrap().as_secs()),
                                    "id" => id.clone(),
                                    "repository" => repository.clone(),
                                    "category" => category.clone(),
                                    "package" => package.clone(),
                                    "version" => version.clone(),
                                    "use" => uses
                                };
                                coll.insert_one(doc.clone(), None).ok().expect("Failed to insert document.");
                            }
                            thread::spawn(move ||
                            {
                                fs::create_dir_all(format!("{}/packages/{}/{}/{}/{}/{}", GBS_DIR, repository, category, package, version, id)).unwrap();
                                let b = Command::new("docker")
                                .arg("run")
                                .arg("--rm")
                                .arg("-v")
                                .arg(format!("{}/distfiles:/usr/portage/distfiles", GBS_DIR))
                                .arg("-v")
                                .arg(format!("{}/repos:/var/db/repos:ro", GBS_DIR))
                                .arg("-v")
                                .arg(format!("{}/ccache:/mnt/ccache", GBS_DIR))
                                .arg("-v")
                                .arg(format!("{}/packages/{}/{}/{}/{}/{}:/mnt/package", GBS_DIR, repository, category, package, version, id))
                                .arg("--cap-add=SYS_PTRACE")
                                .arg("-idt")
                                .arg("--name")
                                .arg(format!("{}_{}_{}_{}_{}", repository, category, package, version, id))
                                .arg("gentoo:gbs")
                                .arg("/usr/bin/build_script.sh")
                                .arg(repository)
                                .arg(category)
                                .arg(package)
                                .arg(version)
                                .arg(uses)
                                .output().unwrap();
                                println!("{}\n", String::from_utf8_lossy(&b.stdout));
                                println!("{}\n", String::from_utf8_lossy(&b.stderr));
                            });
                        }
                        return client.text(url);
                    }
                    else
                    {
                        client.set_status(StatusCode::NotFound);
                        return client.empty();
                    }
                })
            });
        });
    });

    let app = Application::new(api);

    iron::Iron::new(app).http("0.0.0.0:4000").unwrap();
}
<commit_msg>add build_id<commit_after>extern crate rustless;
extern crate hyper;
extern crate iron;
extern crate valico;
extern crate crypto;

#[macro_use(bson, doc)]
extern crate bson;
extern crate mongodb;

use mongodb::{Client, ThreadedClient};
use mongodb::db::ThreadedDatabase;
use bson::
{
    Bson,
    Document
};

use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::io::Read;
use std::path::Path;
use std::process::Command;
use std::thread;
use std::time::
{
    SystemTime,
    UNIX_EPOCH
};
use crypto::digest::Digest;
use crypto::sha3::Sha3;
use valico::json_dsl;
use rustless::server::status::StatusCode;
use rustless::json::ToJson;
use rustless::
{
    Application,
    Api,
    Nesting,
    Versioning
};
use hyper::header::
{
    ContentDisposition,
    DispositionType,
    DispositionParam,
    Charset
};

const GBS_DIR:&str = "/var/lib/gbs";

fn check(path:&str) -> Option<&str>
{
    let p = Path::new(path).file_name()?.to_str()?;
    if p == path
    {
        return Some(path);
    }
    else
    {
        return None;
    }
}

fn is_atom(repository:&str, category:&str, package:&str, version:&str) -> Option<()>
{

    let r = check(repository)?;
    let c = check(category)?;
    let p = check(package)?;
    let v = check(version)?;

    if Path::new(&format!("{}/repos/{}/{}/{}/{}-{}.ebuild", GBS_DIR, r, c, p, p, v)).is_file()
    {
        return Some(());
    }
    else
    {
        return None;
    }
}

fn is_build_request(repository:&str, category:&str, package:&str, version:&str, id:&str) -> Option<()>
{

    let r = check(repository)?;
    let c = check(category)?;
    let p = check(package)?;
    let v = check(version)?;
    let i = check(id)?;

    if Path::new(&format!("{}/packages/{}/{}/{}/{}/{}", GBS_DIR, r, c, p, v, i)).is_dir()
    {
        return Some(());
    }
    else
    {
        return None;
    }
}

fn build_id(repository:&str, category:&str, package:&str, version:&str, uses:&[(String, bool)]) -> String
{
    let mut hasher = Sha3::sha3_256();
    hasher.input_str(&format!("{}{}{}{}", repository, category, package, version));

    for &(ref key, ref value) in uses.iter()
    {
        hasher.input_str(&format!("{}{} ", match *value {true => "", false => "-"}, key));
    }
    return hasher.result_str();
}

#[test]
fn test_is_atom()
{
    assert_eq!(false, is_atom("test","test","test","test").is_some());
    assert_eq!(false, is_atom("test/.","test/../.","test","test").is_some());
    assert_eq!(false, is_atom("gentoo/.","app-editors","vim","8.0.1298").is_some());
    assert_eq!(false, is_atom("gentoo","app-editors","vim","8").is_some());
    assert_eq!(true, is_atom("gentoo","app-editors","vim","8.0.1298").is_some());
}

fn main()
{
    let client = Client::connect("localhost", 27017)
        .expect("Failed to initialize standalone client.");
    let coll = client.db("gbs").collection("builds");

    let api = Api::build(|api|
    {
        api.prefix("api");
        api.version("1", Versioning::Path);

        api.namespace("atoms/:repositories/:categories/:packages/:versions", |atoms_ns|
        {
            atoms_ns.params(|params|
            {
                params.req_typed("repositories", json_dsl::string());
                params.req_typed("categories", json_dsl::string());
                params.req_typed("packages", json_dsl::string());
                params.req_typed("versions", json_dsl::string());
            });
            atoms_ns.get("builds", |endpoint|
            {
                endpoint.handle(|client, params|
                {
                    return client.json(&params.to_json());
                })
            });
            atoms_ns.get("builds/:id/status", |endpoint|
            {
                endpoint.params(|params|
                {
                    params.req_typed("id", json_dsl::string());
                });
                endpoint.handle(|mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();
                    let id = params.find("id").unwrap().to_string().trim_matches('"').to_string();

                    if is_build_request(&repository, &category, &package, &version, &id).is_some()
                    {
                        let s = format!("{}/packages/{}/{}/{}/{}/{}/status", GBS_DIR, repository, category, package, version, id);
                        let path = Path::new(&s);
                        if path.is_file()
                        {
                            let f = File::open(&s).unwrap();
                            let mut reader = BufReader::new(f);
                            let mut buf = String::new();
                            reader.read_to_string(&mut buf);
                            return client.text(buf);
                        }
                    }
                    client.set_status(StatusCode::NotFound);
                    return client.empty();
                })
            });
            atoms_ns.get("builds/:id/log", |endpoint|
            {
                endpoint.params(|params|
                {
                    params.req_typed("id", json_dsl::string());
                });
                endpoint.handle(|mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();
                    let id = params.find("id").unwrap().to_string().trim_matches('"').to_string();

                    if is_build_request(&repository, &category, &package, &version, &id).is_some()
                    {
                        let s = format!("{}/packages/{}/{}/{}/{}/{}/log", GBS_DIR, repository, category, package, version, id);
                        let path = Path::new(&s);
                        if path.is_file()
                        {
                            let f = File::open(&s).unwrap();
                            let mut reader = BufReader::new(f);
                            let mut buf = String::new();
                            reader.read_to_string(&mut buf);
                            return client.text(buf);
                        }
                    }
                    client.set_status(StatusCode::NotFound);
                    return client.empty();
                })
            });
            atoms_ns.get("builds/:id", |endpoint|
            {
                endpoint.params(|params|
                {
                    params.req_typed("id", json_dsl::string());
                });
                endpoint.handle(|mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();
                    let id = params.find("id").unwrap().to_string().trim_matches('"').to_string();

                    if is_build_request(&repository, &category, &package, &version, &id).is_some()
                    {
                        let s = format!("{}/packages/{}/{}/{}/{}/{}/{3}-{4}.tbz2", GBS_DIR, repository, category, package, version, id);
                        let path = Path::new(&s);
                        if path.is_file()
                        {
                            client.set_header(ContentDisposition
                            {
                              disposition: DispositionType::Attachment,
                              parameters: vec![DispositionParam::Filename(
                                Charset::Us_Ascii,
                                None,
                                format!("{}-{}.tbz2", package, version).into_bytes()
                            )]});
                            return client.file(path);
                        }
                    }
                    client.set_status(StatusCode::NotFound);
                    return client.empty();
                })
            });
            atoms_ns.post("builds", |endpoint|
            {
                endpoint.handle(move |mut client, params|
                {
                    let repository = params.find("repositories").unwrap().to_string().trim_matches('"').to_string();
                    let category = params.find("categories").unwrap().to_string().trim_matches('"').to_string();
                    let package = params.find("packages").unwrap().to_string().trim_matches('"').to_string();
                    let version = params.find("versions").unwrap().to_string().trim_matches('"').to_string();

                    let mut uses = String::new();
                    let use_flag = params.find("use").unwrap();
                    for (key, value) in use_flag.as_object().unwrap().iter()
                    {
                        uses.push_str(&format!("{}{} ", match value.as_bool().unwrap(){true => "", false => "-"}, key));
                    }
                    let mut uu :Vec<(String, bool)> = Vec::new();
                    for (key, value) in use_flag.as_object().unwrap().iter()
                    {
                        uu.push((key.clone(), value.as_bool().unwrap()));
                    }
                    let id = build_id(&repository, &category, &package, &version, &uu);
                    println!("{}/{}/{}/{}", repository, category, package, version);
                    println!("USE=\"{}\"", uses);

                    let url = format!("{}/{}/{}/{}/builds/{}", repository, category, package, version, id);

                    if is_atom(&repository, &category, &package, &version).is_some()
                    {
                        if !is_build_request(&repository, &category, &package, &version, &id).is_some()
                        {
                            {
                                let mut uses = Document::new();
                                for (key, value) in use_flag.as_object().unwrap().iter()
                                {
                                    uses.insert(key.clone(), Bson::Boolean(value.as_bool().unwrap().clone()));
                                }
                                let s = SystemTime::now();
                                let doc = doc!
                                {
                                    "date" => &format!("{}", s.duration_since(UNIX_EPOCH).unwrap().as_secs()),
                                    "id" => id.clone(),
                                    "repository" => repository.clone(),
                                    "category" => category.clone(),
                                    "package" => package.clone(),
                                    "version" => version.clone(),
                                    "use" => uses
                                };
                                coll.insert_one(doc.clone(), None).ok().expect("Failed to insert document.");
                            }
                            thread::spawn(move ||
                            {
                                fs::create_dir_all(format!("{}/packages/{}/{}/{}/{}/{}", GBS_DIR, repository, category, package, version, id)).unwrap();
                                let b = Command::new("docker")
                                .arg("run")
                                .arg("--rm")
                                .arg("-v")
                                .arg(format!("{}/distfiles:/usr/portage/distfiles", GBS_DIR))
                                .arg("-v")
                                .arg(format!("{}/repos:/var/db/repos:ro", GBS_DIR))
                                .arg("-v")
                                .arg(format!("{}/ccache:/mnt/ccache", GBS_DIR))
                                .arg("-v")
                                .arg(format!("{}/packages/{}/{}/{}/{}/{}:/mnt/package", GBS_DIR, repository, category, package, version, id))
                                .arg("--cap-add=SYS_PTRACE")
                                .arg("-idt")
                                .arg("--name")
                                .arg(format!("{}_{}_{}_{}_{}", repository, category, package, version, id))
                                .arg("gentoo:gbs")
                                .arg("/usr/bin/build_script.sh")
                                .arg(repository)
                                .arg(category)
                                .arg(package)
                                .arg(version)
                                .arg(uses)
                                .output().unwrap();
                                println!("{}\n", String::from_utf8_lossy(&b.stdout));
                                println!("{}\n", String::from_utf8_lossy(&b.stderr));
                            });
                        }
                        return client.text(url);
                    }
                    else
                    {
                        client.set_status(StatusCode::NotFound);
                        return client.empty();
                    }
                })
            });
        });
    });

    let app = Application::new(api);

    iron::Iron::new(app).http("0.0.0.0:4000").unwrap();
}
<|endoftext|>


<commit_before>// logging
#[macro_use]
extern crate log;
use env_logger;

// json
#[macro_use]
extern crate serde_derive;
use serde_json;

// datatypes
#[macro_use]
extern crate enum_primitive;
#[macro_use]
extern crate bitflags;

use std::fs;
use std::os::unix::fs::FileTypeExt;
use std::path::Path;
use std::sync::{Arc, Mutex};

use clap;

use actix_web;
use actix_web::dev::Handler;
use actix_web::{HttpRequest, HttpResponse};

mod sunsaver_connection;
use crate::sunsaver_connection::{FileSunSaverConnection, ModbusSunSaverConnection, SunSaverConnection};
mod sunsaver;
use crate::sunsaver::{ArrayFault, ChargeState, LoggedResponseDay};
mod api;
use crate::api::*;

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

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

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

impl<S> Handler<S> for ApiHandler {
    type Result = HttpResponse;

    fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
        debug!("ApiHandler: {:?}", req.uri());

        let mut response_builder = HttpResponse::Ok();
        response_builder.header(http::header::ACCESS_CONTROL_ALLOW_METHODS, "GET");
        response_builder.header(http::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        response_builder.header(http::header::CONTENT_TYPE, "application/json");

        let path = req.path();
        let last_path = path.rsplitn(2, '/').next().unwrap();
        trace!("ApiHandler: last_path={:?}", last_path);
        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_builder.status(http::StatusCode::OK).body(b)
            }
            "logged" => {
                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_builder.status(http::StatusCode::OK).body(b)
            }
            _ => response_builder.status(http::StatusCode::NOT_FOUND).finish(),
        }
    }
}

fn is_rtu_modbus_device(path: &Path) -> 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()
}

static CLI_ARG_DEVICE: &'static str = "DEVICE";
static CLI_ARG_PORT: &'static str = "PORT";
static CLI_ARG_WEB_ROOT: &'static str = "WEB_ROOT";

fn is_port_number(port_string: String) -> Result<(), String> {
    port_string.parse::<u16>().map(|_| ()).map_err(|_| String::from("Invalid port number"))
}

fn main() {
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("restful_sunsaver=info")).init();

    let matches = clap::App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .about("HTTP RESTful server for SunSaver MPPT ModBus data")
        .author("Guy Taylor <thebiggerguy.co.uk@gmail.com>")
        .arg(
            clap::Arg::with_name(CLI_ARG_DEVICE)
                .help("Serial device e.g. /dev/ttyUSB0")
                .long("device")
                .short("d")
                .takes_value(true)
                .empty_values(false)
                .required(true),
        )
        .arg(
            clap::Arg::with_name(CLI_ARG_PORT)
                .help("HTTP server port")
                .long("port")
                .short("p")
                .takes_value(true)
                .empty_values(false)
                .required(false)
                .default_value("8080")
                .validator(is_port_number),
        )
        .arg(
            clap::Arg::with_name(CLI_ARG_WEB_ROOT)
                .help("HTTP server root folder")
                .long("webroot")
                .takes_value(true)
                .empty_values(false)
                .required(false)
                .default_value("web"),
        )
        .get_matches();

    let serial_interface = Path::new(matches.value_of(CLI_ARG_DEVICE).unwrap());
    let port_number = matches.value_of(CLI_ARG_PORT).unwrap().parse::<u16>().unwrap();
    // TODO: Make static
    //let web_root: &'static Path = Path::new(matches.value_of(CLI_ARG_WEB_ROOT).unwrap());

    if !serial_interface.exists() {
        panic!("Device does not exists: {:?}", serial_interface);
    }

    let connection: Box<dyn SunSaverConnection> = if is_rtu_modbus_device(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);

    info!("Starting server ...");
    let bind_address = format!("0.0.0.0:{}", port_number);
    actix_web::server::new(move || {
        actix_web::App::new()
            .handler("/api/v1/status", api_handler.clone())
            .handler("/api/v1/logged", api_handler.clone())
            .handler("/", actix_web::fs::StaticFiles::new("web").unwrap().index_file("index.html"))
            .finish()
    })
    .bind(bind_address)
    .unwrap()
    .run();
}

#[cfg(test)]
mod test {
    use tempdir;

    use std::fs::OpenOptions;

    use self::tempdir::TempDir;

    use super::*;

    #[test]
    fn is_rtu_modbus_device_test() {
        assert_eq!(is_rtu_modbus_device(Path::new("/dev/zero")), true); // TODO
        assert_eq!(is_rtu_modbus_device(Path::new("/dev/urandom")), true); // TODO
        assert_eq!(is_rtu_modbus_device(Path::new("/dev/tty")), true);
        let tty_usb = Path::new("/dev/ttyUSB0");
        if tty_usb.exists() {
            assert_eq!(is_rtu_modbus_device(tty_usb), true);
        }

        let temp_dir = TempDir::new(concat!(module_path!(), "is_rtu_modbus_device_test")).unwrap();
        let test_file = temp_dir.path().join("test");
        OpenOptions::new().create(true).write(true).open(&test_file).unwrap();
        assert_eq!(is_rtu_modbus_device(test_file.as_path()), false);
    }
}
<commit_msg>Tighten locking scope<commit_after>// logging
#[macro_use]
extern crate log;
use env_logger;

// json
#[macro_use]
extern crate serde_derive;
use serde_json;

// datatypes
#[macro_use]
extern crate enum_primitive;
#[macro_use]
extern crate bitflags;

use std::fs;
use std::os::unix::fs::FileTypeExt;
use std::path::Path;
use std::sync::{Arc, Mutex};

use clap;

use actix_web;
use actix_web::dev::Handler;
use actix_web::{HttpRequest, HttpResponse};

mod sunsaver_connection;
use crate::sunsaver_connection::{FileSunSaverConnection, ModbusSunSaverConnection, SunSaverConnection};
mod sunsaver;
use crate::sunsaver::{ArrayFault, ChargeState, LoggedResponseDay};
mod api;
use crate::api::*;

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

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

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

impl<S> Handler<S> for ApiHandler {
    type Result = HttpResponse;

    fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
        debug!("ApiHandler: {:?}", req.uri());

        let mut response_builder = HttpResponse::Ok();
        response_builder.header(http::header::ACCESS_CONTROL_ALLOW_METHODS, "GET");
        response_builder.header(http::header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
        response_builder.header(http::header::CONTENT_TYPE, "application/json");

        let path = req.path();
        let last_path = path.rsplitn(2, '/').next().unwrap();
        trace!("ApiHandler: last_path={:?}", last_path);
        match last_path {
            "status" => {
                let a = {
                    let connection = self.connection.clone();
                    let mut unlocked_connection = connection.lock().unwrap();
                    ApiStatusResponse::from(unlocked_connection.read_status())
                };
                let b = serde_json::to_string_pretty(&a).unwrap();
                response_builder.status(http::StatusCode::OK).body(b)
            }
            "logged" => {
                let a = {
                    let connection = self.connection.clone();
                    let mut unlocked_connection = connection.lock().unwrap();
                    ApiLoggedResponse::from(unlocked_connection.read_logged())
                };
                let b = serde_json::to_string_pretty(&a).unwrap();
                response_builder.status(http::StatusCode::OK).body(b)
            }
            _ => response_builder.status(http::StatusCode::NOT_FOUND).finish(),
        }
    }
}

fn is_rtu_modbus_device(path: &Path) -> 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()
}

static CLI_ARG_DEVICE: &'static str = "DEVICE";
static CLI_ARG_PORT: &'static str = "PORT";
static CLI_ARG_WEB_ROOT: &'static str = "WEB_ROOT";

fn is_port_number(port_string: String) -> Result<(), String> {
    port_string.parse::<u16>().map(|_| ()).map_err(|_| String::from("Invalid port number"))
}

fn main() {
    env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("restful_sunsaver=info")).init();

    let matches = clap::App::new(env!("CARGO_PKG_NAME"))
        .version(env!("CARGO_PKG_VERSION"))
        .about("HTTP RESTful server for SunSaver MPPT ModBus data")
        .author("Guy Taylor <thebiggerguy.co.uk@gmail.com>")
        .arg(
            clap::Arg::with_name(CLI_ARG_DEVICE)
                .help("Serial device e.g. /dev/ttyUSB0")
                .long("device")
                .short("d")
                .takes_value(true)
                .empty_values(false)
                .required(true),
        )
        .arg(
            clap::Arg::with_name(CLI_ARG_PORT)
                .help("HTTP server port")
                .long("port")
                .short("p")
                .takes_value(true)
                .empty_values(false)
                .required(false)
                .default_value("8080")
                .validator(is_port_number),
        )
        .arg(
            clap::Arg::with_name(CLI_ARG_WEB_ROOT)
                .help("HTTP server root folder")
                .long("webroot")
                .takes_value(true)
                .empty_values(false)
                .required(false)
                .default_value("web"),
        )
        .get_matches();

    let serial_interface = Path::new(matches.value_of(CLI_ARG_DEVICE).unwrap());
    let port_number = matches.value_of(CLI_ARG_PORT).unwrap().parse::<u16>().unwrap();
    // TODO: Make static
    //let web_root: &'static Path = Path::new(matches.value_of(CLI_ARG_WEB_ROOT).unwrap());

    if !serial_interface.exists() {
        panic!("Device does not exists: {:?}", serial_interface);
    }

    let connection: Box<dyn SunSaverConnection> = if is_rtu_modbus_device(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);

    info!("Starting server ...");
    let bind_address = format!("0.0.0.0:{}", port_number);
    actix_web::server::new(move || {
        actix_web::App::new()
            .handler("/api/v1/status", api_handler.clone())
            .handler("/api/v1/logged", api_handler.clone())
            .handler("/", actix_web::fs::StaticFiles::new("web").unwrap().index_file("index.html"))
            .finish()
    })
    .bind(bind_address)
    .unwrap()
    .run();
}

#[cfg(test)]
mod test {
    use tempdir;

    use std::fs::OpenOptions;

    use self::tempdir::TempDir;

    use super::*;

    #[test]
    fn is_rtu_modbus_device_test() {
        assert_eq!(is_rtu_modbus_device(Path::new("/dev/zero")), true); // TODO
        assert_eq!(is_rtu_modbus_device(Path::new("/dev/urandom")), true); // TODO
        assert_eq!(is_rtu_modbus_device(Path::new("/dev/tty")), true);
        let tty_usb = Path::new("/dev/ttyUSB0");
        if tty_usb.exists() {
            assert_eq!(is_rtu_modbus_device(tty_usb), true);
        }

        let temp_dir = TempDir::new(concat!(module_path!(), "is_rtu_modbus_device_test")).unwrap();
        let test_file = temp_dir.path().join("test");
        OpenOptions::new().create(true).write(true).open(&test_file).unwrap();
        assert_eq!(is_rtu_modbus_device(test_file.as_path()), false);
    }
}
<|endoftext|>


<commit_before>extern crate pencil;
extern crate bbt;
extern crate rustc_serialize;
extern crate env_logger;
extern crate rand;
extern crate rusqlite;
extern crate time;

use pencil::Pencil;
use pencil::{Request, PencilResult, Response};
use pencil::method::Post;
use pencil::HTTPError;
use std::collections::BTreeMap;
use rustc_serialize::json::{Json, ToJson};
//use time::Timespec;
use rusqlite::Connection;

const BETA: f64 = 5000.0;

#[derive(Debug)]
struct PlayedGame {
    home:   String,
    away:   String,
    home_score:   i32,
    away_score:  i32,
}

impl ToJson for PlayedGame {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("home".to_string(), self.home.to_json());
        m.insert("away".to_string(), self.away.to_json());
        m.insert("home_score".to_string(), self.home_score.to_json());
        m.insert("away_score".to_string(), self.away_score.to_json());
        m.to_json()
    }
}

use rand::Rng;

fn newgame(request: &mut Request) -> PencilResult {
    request.app.render_template("index.html", &newgame_con())
}

fn newgame_con() -> BTreeMap<String, Json> {
    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT id, name FROM players").unwrap();
    let mut names: Vec<_> = stmt.query_map(&[], |row| {
        let mut n = BTreeMap::new();
        n.insert("id".to_string(), row.get::<_, i32>(0).to_json());
        n.insert("name".to_string(), row.get::<_, String>(1).to_json());
        n.to_json()
    }).unwrap().map(Result::unwrap).collect();

    names.sort_by(|_, _| *rand::thread_rng().choose(&[Greater, Less]).unwrap());

    let mut context = BTreeMap::new();

    context.insert("names".to_string(), names.to_json());
    context.insert("newgame".to_string(), true.to_json());
    context.insert("heading".to_string(), "Ny kamp".to_json());
    context.insert("body".to_string(), "Ny kamp".to_json());
    context
}

fn submit_newgame(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();

    let home: i32 = request.form().get("home").unwrap().parse().unwrap();
    let away: i32 = request.form().get("away").unwrap().parse().unwrap();
    let home_score: i32 = request.form().get("home_score").unwrap().parse().unwrap();
    let away_score: i32 = request.form().get("away_score").unwrap().parse().unwrap();

    if !(home_score == 10 || away_score == 10) || home_score == away_score || home == away {
        let mut context = newgame_con();

        context.insert("nykamp_fejl".to_string(), "Den indtastede kamp er ikke lovlig 😜".to_json());
        context.insert("heading".to_string(), "Fejl: Ny kamp".to_json());
        context.insert("body".to_string(), "Fejl ved registrering af ny kamp".to_json());
        return request.app.render_template("index.html", &context)
    }

    println!("{:?}", conn.execute("INSERT INTO games (home_id, away_id, home_score, away_score) VALUES (?, ?, ?, ?)",
        &[&home, &away, &home_score, &away_score]));

    pencil::redirect("/", 302)
}

fn games(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT (SELECT name FROM players p WHERE p.id = g.home_id) AS home, (SELECT name FROM players p WHERE p.id = g.away_id) AS away, home_score, away_score FROM games g ORDER BY ID DESC").unwrap();
    let games: Vec<_> = stmt.query_map(&[], |row|
        PlayedGame {
            home: row.get(0),
            away: row.get(1),
            home_score: row.get(2),
            away_score: row.get(3),
        }
    ).unwrap().map(Result::unwrap).collect();

    let mut context = BTreeMap::new();

    context.insert("games".to_string(), games.to_json());
    context.insert("heading".to_string(), "Kampe".to_json());
    context.insert("body".to_string(), "Alle kampe".to_json());
    request.app.render_template("index.html", &context)
}

fn page_not_found(_: HTTPError) -> PencilResult {
    let mut response = Response::from("Uh-ohh, 404 :O");
    response.status_code = 404;
    Ok(response)
}

#[derive(Debug, Clone)]
struct Player {
    name: String,
    rating: Rating,
    kampe: u32,
    vundne: u32,
    tabte: u32
}

use std::mem::replace;
use bbt::Outcome::{Win, Loss};

impl Player {
    fn new<S: ToString>(name: S) -> Self{
        Player{
            name: name.to_string(),
            rating: Rating::new(BETA, BETA/3.0),
            kampe: 0,
            vundne: 0,
            tabte: 0
        }
    }
    fn duel(&mut self, rater: &Rater, o: Rating, won: bool) {
        let a = replace(&mut self.rating, Default::default());

        let (a, _) = rater.duel(a, o, if won{Win}else{Loss});
        self.rating = a;

        self.kampe += 1;
        if won{
            self.vundne += 1;
        }else{
            self.tabte += 1;
        }
    }
}

impl ToJson for Player {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("rating".to_string(), format!("{:.1}", self.rating.mu()).to_json());
        m.insert("sigma".to_string(), format!("{:.1}", self.rating.sigma()).to_json());
        m.insert("name".to_string(), self.name.to_json());
        m.insert("kampe".to_string(), self.kampe.to_json());
        m.insert("vundne".to_string(), self.vundne.to_json());
        m.insert("tabte".to_string(), self.tabte.to_json());
        m.to_json()
    }
}

use bbt::{Rater, Rating};

fn get_rating(rating: &Rating) -> f64 {
    rating.mu() - 3. * rating.sigma()
}

struct Game {
    home: i32,
    away: i32,
    home_win: bool
}

use std::cmp::Ordering::{Greater, Less};
use std::collections::HashMap;

fn rating(request: &mut Request) -> PencilResult {
    let rater = Rater::new(BETA/6.0);

    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT id, name from players").unwrap();
    let mut stmt2 = conn.prepare("SELECT home_id, away_id, home_score, away_score from games").unwrap();

    let mut players = HashMap::new();

    for p in stmt.query_map(&[], |row| (row.get::<_, i32>(0), row.get::<_, String>(1))).unwrap() {
        let (id, name) = p.unwrap();
        players.insert(id, Player::new(name));
    }

    for g in stmt2.query_map(&[], |row| Game{
        home: row.get(0),
        away: row.get(1),
        home_win: row.get::<_, i32>(2) > row.get::<_, i32>(3)
    }).unwrap() {
        let g = g.unwrap();
        let away_rating = players[&g.away].rating.clone();
        let home_rating = players[&g.home].rating.clone();
        players.get_mut(&g.home).unwrap().duel(&rater, away_rating, g.home_win);
        players.get_mut(&g.away).unwrap().duel(&rater, home_rating, !g.home_win);
    }

    let mut ps: Vec<_> = players.values().map(Clone::clone).collect();
    ps.sort_by(|a, b| if get_rating(&b.rating) < get_rating(&a.rating){Less}else{Greater});
    ps.insert(0, Player::new("N/A"));

    let mut context = BTreeMap::new();

    context.insert("ps".to_string(), ps.to_json());
    context.insert("heading".to_string(), "Stilling".to_json());
    context.insert("body".to_string(), "Stilling".to_json());
    request.app.render_template("index.html", &context)
}

fn players(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT id, name from players").unwrap();

    let mut players = Vec::new();

    for p in stmt.query_map(&[], |row| (row.get::<_, i32>(0), row.get::<_, String>(1))).unwrap() {
        let (id, name) = p.unwrap();
        let mut player = BTreeMap::new();
        player.insert("id".to_string(), id.to_json());
        player.insert("name".to_string(), name.to_json());
        players.push(player.to_json());
    }

    let mut context = BTreeMap::new();

    context.insert("players".to_string(), players.to_json());
    context.insert("heading".to_string(), "Spillere".to_json());
    context.insert("body".to_string(), "Spillere".to_json());
    request.app.render_template("index.html", &context)
}

fn newplayer(request: &mut Request) -> PencilResult {
    request.app.render_template("index.html", &newplayer_con())
}

fn newplayer_con() -> BTreeMap<String, Json> {
    let mut context = BTreeMap::new();
    context.insert("newplayer".to_string(), true.to_json());
    context.insert("heading".to_string(), "Ny spiller".to_json());
    context.insert("body".to_string(), "Ny spiller".to_json());
    context
}

fn submit_newplayer(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();

    let name = request.form().get("name").unwrap();

    if name.is_empty() {
        let mut context = newplayer_con();

        context.insert("nyspiller_fejl".to_string(), "Den indtastede spiller er ikke lovlig 😜".to_json());
        context.insert("heading".to_string(), "Fejl: Ny spiller".to_json());
        context.insert("body".to_string(), "Fejl ved registrering af ny spiller".to_json());
        return request.app.render_template("index.html", &context)
    }
    println!("{:?}", conn.execute("INSERT INTO players (name) VALUES (?)",
        &[&*name]));

    pencil::redirect("/", 302)
}

fn main() {
    let mut app = Pencil::new("");
    app.register_template("index.html");
    app.get("/", "index_template", rating);
    app.get("/rating", "flest heste", rating);
    app.get("/games", "games", games);
    app.get("/newgame", "new squirtle game", newgame);
    app.get("/players", "players hest", players);
    app.route("/newgame/submit", &[Post], "ny kamp", submit_newgame);
    app.get("/newplayer", "players new new new hest", newplayer);
    app.route("/newplayer/submit", &[Post], "ny hestesandwcih", submit_newplayer);

    app.enable_static_file_handling();
    app.httperrorhandler(404, page_not_found);
    // app.set_debug(true);
    // app.set_log_level();
    // env_logger::init().unwrap();
    app.run("0.0.0.0:5000");
}
<commit_msg>Moved to listen on localhost only.<commit_after>extern crate pencil;
extern crate bbt;
extern crate rustc_serialize;
extern crate env_logger;
extern crate rand;
extern crate rusqlite;
extern crate time;

use pencil::Pencil;
use pencil::{Request, PencilResult, Response};
use pencil::method::Post;
use pencil::HTTPError;
use std::collections::BTreeMap;
use rustc_serialize::json::{Json, ToJson};
//use time::Timespec;
use rusqlite::Connection;

const BETA: f64 = 5000.0;

#[derive(Debug)]
struct PlayedGame {
    home:   String,
    away:   String,
    home_score:   i32,
    away_score:  i32,
}

impl ToJson for PlayedGame {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("home".to_string(), self.home.to_json());
        m.insert("away".to_string(), self.away.to_json());
        m.insert("home_score".to_string(), self.home_score.to_json());
        m.insert("away_score".to_string(), self.away_score.to_json());
        m.to_json()
    }
}

use rand::Rng;

fn newgame(request: &mut Request) -> PencilResult {
    request.app.render_template("index.html", &newgame_con())
}

fn newgame_con() -> BTreeMap<String, Json> {
    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT id, name FROM players").unwrap();
    let mut names: Vec<_> = stmt.query_map(&[], |row| {
        let mut n = BTreeMap::new();
        n.insert("id".to_string(), row.get::<_, i32>(0).to_json());
        n.insert("name".to_string(), row.get::<_, String>(1).to_json());
        n.to_json()
    }).unwrap().map(Result::unwrap).collect();

    names.sort_by(|_, _| *rand::thread_rng().choose(&[Greater, Less]).unwrap());

    let mut context = BTreeMap::new();

    context.insert("names".to_string(), names.to_json());
    context.insert("newgame".to_string(), true.to_json());
    context.insert("heading".to_string(), "Ny kamp".to_json());
    context.insert("body".to_string(), "Ny kamp".to_json());
    context
}

fn submit_newgame(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();

    let home: i32 = request.form().get("home").unwrap().parse().unwrap();
    let away: i32 = request.form().get("away").unwrap().parse().unwrap();
    let home_score: i32 = request.form().get("home_score").unwrap().parse().unwrap();
    let away_score: i32 = request.form().get("away_score").unwrap().parse().unwrap();

    if !(home_score == 10 || away_score == 10) || home_score == away_score || home == away {
        let mut context = newgame_con();

        context.insert("nykamp_fejl".to_string(), "Den indtastede kamp er ikke lovlig 😜".to_json());
        context.insert("heading".to_string(), "Fejl: Ny kamp".to_json());
        context.insert("body".to_string(), "Fejl ved registrering af ny kamp".to_json());
        return request.app.render_template("index.html", &context)
    }

    println!("{:?}", conn.execute("INSERT INTO games (home_id, away_id, home_score, away_score) VALUES (?, ?, ?, ?)",
        &[&home, &away, &home_score, &away_score]));

    pencil::redirect("/", 302)
}

fn games(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT (SELECT name FROM players p WHERE p.id = g.home_id) AS home, (SELECT name FROM players p WHERE p.id = g.away_id) AS away, home_score, away_score FROM games g ORDER BY ID DESC").unwrap();
    let games: Vec<_> = stmt.query_map(&[], |row|
        PlayedGame {
            home: row.get(0),
            away: row.get(1),
            home_score: row.get(2),
            away_score: row.get(3),
        }
    ).unwrap().map(Result::unwrap).collect();

    let mut context = BTreeMap::new();

    context.insert("games".to_string(), games.to_json());
    context.insert("heading".to_string(), "Kampe".to_json());
    context.insert("body".to_string(), "Alle kampe".to_json());
    request.app.render_template("index.html", &context)
}

fn page_not_found(_: HTTPError) -> PencilResult {
    let mut response = Response::from("Uh-ohh, 404 :O");
    response.status_code = 404;
    Ok(response)
}

#[derive(Debug, Clone)]
struct Player {
    name: String,
    rating: Rating,
    kampe: u32,
    vundne: u32,
    tabte: u32
}

use std::mem::replace;
use bbt::Outcome::{Win, Loss};

impl Player {
    fn new<S: ToString>(name: S) -> Self{
        Player{
            name: name.to_string(),
            rating: Rating::new(BETA, BETA/3.0),
            kampe: 0,
            vundne: 0,
            tabte: 0
        }
    }
    fn duel(&mut self, rater: &Rater, o: Rating, won: bool) {
        let a = replace(&mut self.rating, Default::default());

        let (a, _) = rater.duel(a, o, if won{Win}else{Loss});
        self.rating = a;

        self.kampe += 1;
        if won{
            self.vundne += 1;
        }else{
            self.tabte += 1;
        }
    }
}

impl ToJson for Player {
    fn to_json(&self) -> Json {
        let mut m: BTreeMap<String, Json> = BTreeMap::new();
        m.insert("rating".to_string(), format!("{:.1}", self.rating.mu()).to_json());
        m.insert("sigma".to_string(), format!("{:.1}", self.rating.sigma()).to_json());
        m.insert("name".to_string(), self.name.to_json());
        m.insert("kampe".to_string(), self.kampe.to_json());
        m.insert("vundne".to_string(), self.vundne.to_json());
        m.insert("tabte".to_string(), self.tabte.to_json());
        m.to_json()
    }
}

use bbt::{Rater, Rating};

fn get_rating(rating: &Rating) -> f64 {
    rating.mu() - 3. * rating.sigma()
}

struct Game {
    home: i32,
    away: i32,
    home_win: bool
}

use std::cmp::Ordering::{Greater, Less};
use std::collections::HashMap;

fn rating(request: &mut Request) -> PencilResult {
    let rater = Rater::new(BETA/6.0);

    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT id, name from players").unwrap();
    let mut stmt2 = conn.prepare("SELECT home_id, away_id, home_score, away_score from games").unwrap();

    let mut players = HashMap::new();

    for p in stmt.query_map(&[], |row| (row.get::<_, i32>(0), row.get::<_, String>(1))).unwrap() {
        let (id, name) = p.unwrap();
        players.insert(id, Player::new(name));
    }

    for g in stmt2.query_map(&[], |row| Game{
        home: row.get(0),
        away: row.get(1),
        home_win: row.get::<_, i32>(2) > row.get::<_, i32>(3)
    }).unwrap() {
        let g = g.unwrap();
        let away_rating = players[&g.away].rating.clone();
        let home_rating = players[&g.home].rating.clone();
        players.get_mut(&g.home).unwrap().duel(&rater, away_rating, g.home_win);
        players.get_mut(&g.away).unwrap().duel(&rater, home_rating, !g.home_win);
    }

    let mut ps: Vec<_> = players.values().map(Clone::clone).collect();
    ps.sort_by(|a, b| if get_rating(&b.rating) < get_rating(&a.rating){Less}else{Greater});
    ps.insert(0, Player::new("N/A"));

    let mut context = BTreeMap::new();

    context.insert("ps".to_string(), ps.to_json());
    context.insert("heading".to_string(), "Stilling".to_json());
    context.insert("body".to_string(), "Stilling".to_json());
    request.app.render_template("index.html", &context)
}

fn players(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();
    let mut stmt = conn.prepare("SELECT id, name from players").unwrap();

    let mut players = Vec::new();

    for p in stmt.query_map(&[], |row| (row.get::<_, i32>(0), row.get::<_, String>(1))).unwrap() {
        let (id, name) = p.unwrap();
        let mut player = BTreeMap::new();
        player.insert("id".to_string(), id.to_json());
        player.insert("name".to_string(), name.to_json());
        players.push(player.to_json());
    }

    let mut context = BTreeMap::new();

    context.insert("players".to_string(), players.to_json());
    context.insert("heading".to_string(), "Spillere".to_json());
    context.insert("body".to_string(), "Spillere".to_json());
    request.app.render_template("index.html", &context)
}

fn newplayer(request: &mut Request) -> PencilResult {
    request.app.render_template("index.html", &newplayer_con())
}

fn newplayer_con() -> BTreeMap<String, Json> {
    let mut context = BTreeMap::new();
    context.insert("newplayer".to_string(), true.to_json());
    context.insert("heading".to_string(), "Ny spiller".to_json());
    context.insert("body".to_string(), "Ny spiller".to_json());
    context
}

fn submit_newplayer(request: &mut Request) -> PencilResult {
    let conn = Connection::open("ratings.db").unwrap();

    let name = request.form().get("name").unwrap();

    if name.is_empty() {
        let mut context = newplayer_con();

        context.insert("nyspiller_fejl".to_string(), "Den indtastede spiller er ikke lovlig 😜".to_json());
        context.insert("heading".to_string(), "Fejl: Ny spiller".to_json());
        context.insert("body".to_string(), "Fejl ved registrering af ny spiller".to_json());
        return request.app.render_template("index.html", &context)
    }
    println!("{:?}", conn.execute("INSERT INTO players (name) VALUES (?)",
        &[&*name]));

    pencil::redirect("/", 302)
}

fn main() {
    let mut app = Pencil::new("");
    app.register_template("index.html");
    app.get("/", "index_template", rating);
    app.get("/rating", "flest heste", rating);
    app.get("/games", "games", games);
    app.get("/newgame", "new squirtle game", newgame);
    app.get("/players", "players hest", players);
    app.route("/newgame/submit", &[Post], "ny kamp", submit_newgame);
    app.get("/newplayer", "players new new new hest", newplayer);
    app.route("/newplayer/submit", &[Post], "ny hestesandwcih", submit_newplayer);

    app.enable_static_file_handling();
    app.httperrorhandler(404, page_not_found);
    // app.set_debug(true);
    // app.set_log_level();
    // env_logger::init().unwrap();
    app.run("127.0.0.1:5000");
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate rustc_serialize;
extern crate chrono;
extern crate regex;
extern crate env_logger;
#[macro_use] extern crate log;
extern crate docopt;

use std::ascii::AsciiExt;
use std::io::Read;
use std::env;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::fmt;
use std::error::Error;
use std::result;

use chrono::naive::datetime::NaiveDateTime;
use chrono::offset::local::Local;
use chrono::offset::fixed::FixedOffset;
use chrono::datetime::DateTime;
use chrono::Datelike;

use rustc_serialize::json;
use rustc_serialize::Decodable;
use hyper::client::Client;

use regex::Regex;

use docopt::Docopt;

static USAGE: &'static str = "
Washing State Ferry Schedules

Usage:
  wsf [options] <from> <to>
  wsf -h

  <from> and <to> are a prefix of the departing terminal and arriving
  terminal, respectively. For example 'wsf sea ba' is equivalent to
  'wsf Seattle \"Bainbridge Island\"'.

Options:
  -h --help     Show this screen.
";

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_from: String,
    arg_to: String,
}

fn run() -> Result<()> {
    try!(env_logger::init());

    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    let from_in: &str = &args.arg_from.to_ascii_lowercase();
    let to_in: &str   = &args.arg_to.to_ascii_lowercase();

    // pull in api key at *build* time from environment
    let mut s = Session::new(env!("WSDOT_API_KEY"));

    let now = Local::now();
    let mut from: Option<i32> = None;
    let mut to: Option<i32> = None;
    for terminal in try!(s.terminals()).iter() {
        if terminal.Description.to_ascii_lowercase().starts_with(&from_in) {
            from = Some(terminal.TerminalID);
        }
        if terminal.Description.to_ascii_lowercase().starts_with(&to_in) {
            to = Some(terminal.TerminalID);
        }
    }
    let from = try!(from.ok_or(CliError::BadInput(format!("'{}' is not a known port!", from_in))));
    let to = try!(to.ok_or(CliError::BadInput(format!("'{}' is not a known port!", to_in))));
    let tc = try!(s.schedule(from, to));
    for time in tc.Times.iter() {
        if time.depart_time() > now {
            println!("{}\t{}\t{}\t{}",
                     time.depart_time().time(),
                     tc.DepartingTerminalName,
                     tc.ArrivingTerminalName,
                     time.VesselName );
        }
    }
    s.save_cache()
}

fn main() {
    match run() {
        Ok(_) => {},
        Err(e) => {
            println!("{}", e);
            std::process::exit(1);
        }
    }
}

struct Session {
    api_key: String,
    client: Client,
    cache: Cache,
    cacheflushdate: String,
    cache_path: String,
    offline: bool,
}

impl Session {

    fn new(api_key: &str) -> Session {
        let mut cache_path: PathBuf = env::home_dir().unwrap();
        cache_path.push(".wsf.cache");
        let cache_path = format!("{}", cache_path.display());

        let mut s = Session {
            api_key: api_key.to_string(),
            client: Client::new(),
            cache: Cache::load(&cache_path),
            cacheflushdate: String::new(),
            cache_path: cache_path,
            offline: false,
        };

        s.offline = match s.get::<String>("/cacheflushdate".to_owned()) {
            Ok(cfd) => {
                    s.cacheflushdate = cfd;
                    false
                },
            Err(_) => true,
        };
        return s;
    }

    fn save_cache(&mut self) -> Result<()> {
        self.cache.cache_flush_date = self.cacheflushdate.clone();

        let mut f = try!(File::create(&self.cache_path));
        let encoded = try!(json::encode(&self.cache));
        Ok(try!(f.write_all(encoded.as_bytes())))
    }

    fn get<T: Decodable>(&self, path: String) -> Result<T> {
        let url = &format!("http://www.wsdot.wa.gov/ferries/api/schedule/rest{}?apiaccesscode={}",
                            path,
                            self.api_key);
        let mut res = try!(self.client.get(url).send());
        assert_eq!(res.status, hyper::Ok);

        let mut buf = String::new();
        try!(res.read_to_string(&mut buf));
        Ok(try!(json::decode::<T>(&buf)))
    }

    fn terminals(&mut self) -> Result<Vec<Terminal>> {
        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            return Ok(self.cache.terminals.clone())
        }
        else {
            let now = Local::today();
            let path = format!("/terminals/{}-{}-{}", now.year(), now.month(), now.day());
            let routes: Vec<Terminal> = try!(self.get(path));
            self.cache.terminals = routes.clone();
            return Ok(routes);
        }
    }

    fn schedule(&mut self, from: i32, to: i32) -> Result<TerminalCombo> {
        let mut cache_is_stale = true;
        let cache_key = format!("{} {}", from, to);

        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            if self.cache.sailings.contains_key(&cache_key) {
                // cache is up to date and has route!
                // unwrap is correct as we checked for enry first
                return Ok(self.cache.sailings.get(&cache_key).unwrap().clone());
            }
            else {
                // cache is up to date, but we don't have this route in it
                cache_is_stale = false;
            }
        }

        if cache_is_stale {
            self.cache.sailings.clear();
        }

        let now = Local::now();
        let path = format!("/schedule/{}-{}-{}/{}/{}",
                            now.year(), now.month(), now.day(), from, to);

        let schedule: Schedule = try!(self.get(path));

        self.cache.sailings.insert(cache_key, schedule.TerminalCombos[0].clone());
        Ok(schedule.TerminalCombos[0].clone())
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Cache {
    terminals: Vec<Terminal>,
    sailings: HashMap<String, TerminalCombo>,
    cache_flush_date: String,
}

impl Cache {
    fn load(path: &String) -> Cache {
        let r = File::open(path);
        match r {
            Ok(mut f) => {
                let mut s = String::new();
                f.read_to_string(&mut s).unwrap();
                let cache = json::decode(&s).unwrap();
                cache
            },
            Err(_) => {
                Cache {
                    terminals: vec![],
                    sailings: HashMap::new(),
                    cache_flush_date: String::new(),
                }
            }
        }
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug, Clone)]
struct Terminal {
    TerminalID: i32,
    Description: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct SailingTime {
    DepartingTime: String,
    ArrivingTime: Option<String>,
    VesselName: String,
}

impl SailingTime {
    // parse date strings of form "/Date(1436318400000-0700)/"
    fn depart_time(&self) -> DateTime<Local> {
        let re = Regex::new(r"^/Date\((\d{10})000-(\d{2})(\d{2})\)/$").unwrap();
        let caps = re.captures(&self.DepartingTime).unwrap();

        let epoch: i64 = caps.at(1).unwrap().parse().unwrap();
        let tz_hours: i32 = caps.at(2).unwrap().parse().unwrap();
        let tz_minutes: i32 = caps.at(3).unwrap().parse().unwrap();

        let nd = NaiveDateTime::from_timestamp(epoch, 0);
        let tz = FixedOffset::west((tz_hours * 3600) + (tz_minutes * 60));
        let fotz: DateTime<FixedOffset> = DateTime::from_utc(nd, tz);
        fotz.with_timezone(&Local)
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct TerminalCombo {
    Times: Vec<SailingTime>,
    DepartingTerminalName: String,
    ArrivingTerminalName: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Schedule {
    TerminalCombos: Vec<TerminalCombo>,
}

type Result<T> = result::Result<T, CliError>;

#[derive(Debug)]
enum CliError {
    Log(log::SetLoggerError),
    Parse(rustc_serialize::json::DecoderError),
    SaveCache(rustc_serialize::json::EncoderError),
    Http(hyper::error::Error),
    Io(std::io::Error),
    BadInput(String),
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CliError::Log(ref err) => format!("Unable to configure logging: {}", err).fmt(f),
            CliError::Parse(ref err) => format!("Unable to parse WSDOT Data: {}", err).fmt(f),
            CliError::SaveCache(ref err) => format!("Unable to save cache: {}", err).fmt(f),
            CliError::Http(ref err) => format!("Unable to communicate with WSDOT: {}", err).fmt(f),
            CliError::Io(ref err) => format!("Unable to read data: {}", err).fmt(f),
            CliError::BadInput(ref desc) => format!("Unable to understand input: {}", desc).fmt(f),
        }
    }
}

impl Error for CliError {
    fn description(&self) -> &str {
        match *self {
            CliError::BadInput(ref err) => err,
            _ => self.description(),
        }
    }
}

impl From<rustc_serialize::json::EncoderError> for CliError {
    fn from(err: rustc_serialize::json::EncoderError) -> CliError {
        CliError::SaveCache(err)
    }
}

impl From<log::SetLoggerError> for CliError {
    fn from(err: log::SetLoggerError) -> CliError {
        CliError::Log(err)
    }
}
impl From<hyper::error::Error> for CliError {
    fn from(err: hyper::error::Error) -> CliError {
        CliError::Http(err)
    }
}

impl From<std::io::Error> for CliError {
    fn from(err: std::io::Error) -> CliError {
        CliError::Io(err)
    }
}

impl From<rustc_serialize::json::DecoderError> for CliError {
    fn from(err: rustc_serialize::json::DecoderError) -> CliError {
        CliError::Parse(err)
    }
}
<commit_msg>cleaning up error handling further<commit_after>extern crate hyper;
extern crate rustc_serialize;
extern crate chrono;
extern crate regex;
extern crate env_logger;
#[macro_use] extern crate log;
extern crate docopt;

use std::ascii::AsciiExt;
use std::io::Read;
use std::env;
use std::collections::HashMap;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::fmt;
use std::error::Error;
use std::result;

use chrono::naive::datetime::NaiveDateTime;
use chrono::offset::local::Local;
use chrono::offset::fixed::FixedOffset;
use chrono::datetime::DateTime;
use chrono::Datelike;

use rustc_serialize::json;
use rustc_serialize::Decodable;
use hyper::client::Client;

use regex::Regex;

use docopt::Docopt;

static USAGE: &'static str = "
Washing State Ferry Schedules

Usage:
  wsf [options] <from> <to>
  wsf -h

  <from> and <to> are a prefix of the departing terminal and arriving
  terminal, respectively. For example 'wsf sea ba' is equivalent to
  'wsf Seattle \"Bainbridge Island\"'.

Options:
  -h --help     Show this screen.
";

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_from: String,
    arg_to: String,
}

fn run() -> Result<()> {
    try!(env_logger::init());

    let args: Args = Docopt::new(USAGE)
                            .and_then(|d| d.decode())
                            .unwrap_or_else(|e| e.exit());

    let from_in: &str = &args.arg_from.to_ascii_lowercase();
    let to_in: &str   = &args.arg_to.to_ascii_lowercase();

    // pull in api key at *build* time from environment
    let mut s = Session::new(env!("WSDOT_API_KEY"));

    let now = Local::now();
    let mut from: Option<i32> = None;
    let mut to: Option<i32> = None;
    for terminal in try!(s.terminals()).iter() {
        if terminal.Description.to_ascii_lowercase().starts_with(&from_in) {
            from = Some(terminal.TerminalID);
        }
        if terminal.Description.to_ascii_lowercase().starts_with(&to_in) {
            to = Some(terminal.TerminalID);
        }
    }
    let from = try!(from.ok_or(CliError::BadInput(format!("'{}' is not a known port!", from_in))));
    let to = try!(to.ok_or(CliError::BadInput(format!("'{}' is not a known port!", to_in))));
    let tc = try!(s.schedule(from, to));
    for time in tc.Times.iter() {
        if time.depart_time() > now {
            println!("{}\t{}\t{}\t{}",
                     time.depart_time().time(),
                     tc.DepartingTerminalName,
                     tc.ArrivingTerminalName,
                     time.VesselName );
        }
    }
    s.save_cache()
}

fn main() {
    match run() {
        Ok(_) => {},
        Err(e) => {
            println!("{}", e);
            std::process::exit(1);
        }
    }
}

struct Session {
    api_key: String,
    client: Client,
    cache: Cache,
    cacheflushdate: String,
    cache_path: String,
    offline: bool,
}

impl Session {

    fn new(api_key: &str) -> Session {
        let mut cache_path: PathBuf = env::home_dir().unwrap();
        cache_path.push(".wsf.cache");
        let cache_path = format!("{}", cache_path.display());

        let mut s = Session {
            api_key: api_key.to_string(),
            client: Client::new(),
            cache: Cache::load(&cache_path).unwrap_or(Cache::empty()),
            cacheflushdate: String::new(),
            cache_path: cache_path,
            offline: false,
        };

        s.offline = match s.get::<String>("/cacheflushdate".to_owned()) {
            Ok(cfd) => {
                    s.cacheflushdate = cfd;
                    false
                },
            Err(_) => true,
        };
        return s;
    }

    fn save_cache(&mut self) -> Result<()> {
        self.cache.cache_flush_date = self.cacheflushdate.clone();

        let mut f = try!(File::create(&self.cache_path));
        let encoded = try!(json::encode(&self.cache));
        Ok(try!(f.write_all(encoded.as_bytes())))
    }

    fn get<T: Decodable>(&self, path: String) -> Result<T> {
        let url = &format!("http://www.wsdot.wa.gov/ferries/api/schedule/rest{}?apiaccesscode={}",
                            path,
                            self.api_key);
        let mut res = try!(self.client.get(url).send());
        assert_eq!(res.status, hyper::Ok);

        let mut buf = String::new();
        try!(res.read_to_string(&mut buf));
        Ok(try!(json::decode::<T>(&buf)))
    }

    fn terminals(&mut self) -> Result<Vec<Terminal>> {
        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            return Ok(self.cache.terminals.clone())
        }
        else {
            let now = Local::today();
            let path = format!("/terminals/{}-{}-{}", now.year(), now.month(), now.day());
            let routes: Vec<Terminal> = try!(self.get(path));
            self.cache.terminals = routes.clone();
            return Ok(routes);
        }
    }

    fn schedule(&mut self, from: i32, to: i32) -> Result<TerminalCombo> {
        let mut cache_is_stale = true;
        let cache_key = format!("{} {}", from, to);

        if self.offline || (self.cache.cache_flush_date == self.cacheflushdate) {
            if self.cache.sailings.contains_key(&cache_key) {
                // cache is up to date and has route!
                return Ok(self.cache.sailings.get(&cache_key)
                                              .expect("checked for key in cache then not found")
                                              .clone());
            }
            else {
                // cache is up to date, but we don't have this route in it
                cache_is_stale = false;
            }
        }

        if cache_is_stale {
            self.cache.sailings.clear();
        }

        let now = Local::now();
        let path = format!("/schedule/{}-{}-{}/{}/{}",
                            now.year(), now.month(), now.day(), from, to);

        let schedule: Schedule = try!(self.get(path));

        self.cache.sailings.insert(cache_key, schedule.TerminalCombos[0].clone());
        Ok(schedule.TerminalCombos[0].clone())
    }
}

#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Cache {
    terminals: Vec<Terminal>,
    sailings: HashMap<String, TerminalCombo>,
    cache_flush_date: String,
}

impl Cache {

    fn empty() -> Cache {
        Cache {
            terminals: vec![],
            sailings: HashMap::new(),
            cache_flush_date: String::new(),
        }
    }

    fn load(path: &String) -> Result<Cache> {
        let mut f = try!(File::open(path));
        let mut s = String::new();
        try!(f.read_to_string(&mut s));
        let cache: Cache = try!(json::decode(&s));
        Ok(cache)
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug, Clone)]
struct Terminal {
    TerminalID: i32,
    Description: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct SailingTime {
    DepartingTime: String,
    ArrivingTime: Option<String>,
    VesselName: String,
}

impl SailingTime {
    // parse date strings of form "/Date(1436318400000-0700)/"
    fn depart_time(&self) -> DateTime<Local> {
        let re = Regex::new(r"^/Date\((\d{10})000-(\d{2})(\d{2})\)/$").unwrap();
        let caps = re.captures(&self.DepartingTime).unwrap();

        let epoch: i64 = caps.at(1).unwrap().parse().unwrap();
        let tz_hours: i32 = caps.at(2).unwrap().parse().unwrap();
        let tz_minutes: i32 = caps.at(3).unwrap().parse().unwrap();

        let nd = NaiveDateTime::from_timestamp(epoch, 0);
        let tz = FixedOffset::west((tz_hours * 3600) + (tz_minutes * 60));
        let fotz: DateTime<FixedOffset> = DateTime::from_utc(nd, tz);
        fotz.with_timezone(&Local)
    }
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Clone, Debug)]
struct TerminalCombo {
    Times: Vec<SailingTime>,
    DepartingTerminalName: String,
    ArrivingTerminalName: String,
}

#[allow(non_snake_case)]
#[derive(RustcDecodable, RustcEncodable, Debug)]
struct Schedule {
    TerminalCombos: Vec<TerminalCombo>,
}

type Result<T> = result::Result<T, CliError>;

#[derive(Debug)]
enum CliError {
    Log(log::SetLoggerError),
    Parse(rustc_serialize::json::DecoderError),
    SaveCache(rustc_serialize::json::EncoderError),
    Http(hyper::error::Error),
    Io(std::io::Error),
    BadInput(String),
}

impl fmt::Display for CliError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            CliError::Log(ref err) => format!("Unable to configure logging: {}", err).fmt(f),
            CliError::Parse(ref err) => format!("Unable to parse WSDOT Data: {}", err).fmt(f),
            CliError::SaveCache(ref err) => format!("Unable to save cache: {}", err).fmt(f),
            CliError::Http(ref err) => format!("Unable to communicate with WSDOT: {}", err).fmt(f),
            CliError::Io(ref err) => format!("Unable to read data: {}", err).fmt(f),
            CliError::BadInput(ref desc) => format!("Unable to understand input: {}", desc).fmt(f),
        }
    }
}

impl Error for CliError {
    fn description(&self) -> &str {
        match *self {
            CliError::BadInput(ref err) => err,
            _ => self.description(),
        }
    }
}

impl From<rustc_serialize::json::EncoderError> for CliError {
    fn from(err: rustc_serialize::json::EncoderError) -> CliError {
        CliError::SaveCache(err)
    }
}

impl From<log::SetLoggerError> for CliError {
    fn from(err: log::SetLoggerError) -> CliError {
        CliError::Log(err)
    }
}
impl From<hyper::error::Error> for CliError {
    fn from(err: hyper::error::Error) -> CliError {
        CliError::Http(err)
    }
}

impl From<std::io::Error> for CliError {
    fn from(err: std::io::Error) -> CliError {
        CliError::Io(err)
    }
}

impl From<rustc_serialize::json::DecoderError> for CliError {
    fn from(err: rustc_serialize::json::DecoderError) -> CliError {
        CliError::Parse(err)
    }
}
<|endoftext|>