<commit_before>/// Rosten track Bring packages in Rust
/// For testing use "TESTPACKAGE-AT-PICKUPPOINT" as tracking number
/// https://www.mybring.com/tracking/api

/// Request parameters
/// Query param     Type        Description
/// q               string      Reference, package number, shipment num to query
/// callback        string      Optional JSONP callback function name
///
/// Endpoints for Base URL
///
/// Method      Endpoint                        Usage
/// GET         /tracking{mediaTypeExtension}   Get information about a shipment
///
/// Request string for JSON would look like tracking.bring.com/tracking.json?q=trackingnumber

extern crate curl;
extern crate clap;
extern crate serde_json;

use std::env;
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::error::Error;
use curl::easy::Easy;
use std::fmt::Display;
use serde_json::Value;

// TODO Define contents of JSON. Would an enum be better here?
// Perhaps put structs in different file?

struct Data {
    Firstname: String,
    Lastname: String,
    Age: u32,
    Address: Address,
    PhoneNumber: Vec<String>
}
// Do we need to define everything in tracking.json?
struct Address {
    Street: String,
    City: String,
    Country: String
}

pub fn main() {

    let path = Path::new("/home/stian/projects/rosten/tracking.json");
    let display = path.display();
    let mut file = match File::open(&path) {
        Err(e) => panic!("unable to open {}: {}", display, e.description()),
        Ok(file) => file,
    };

    let mut buffer = String::new();
    file.read_to_string(&mut buffer).unwrap();
    // TODO Better names
    let data: Value = serde_json::from_str(&buffer).unwrap();
    // TODO Iterate over consignmentSet[array]? How?
    let values = data.as_object()
        .and_then(|object| object.get("consignmentSet"))
        .and_then(|values| values.as_object())
        .unwrap_or_else(|| {
            panic!("Failed to get object value from json"); // currently fails here
        });

    for (key, value) in values.iter() {
        let results = value.find("consignmentId")
            .and_then(|value| value.as_object())
            .unwrap_or_else(|| {
                panic!("Failed to get value from within values");
            });
        println!("{} -> {:?}", key, results);
    }
}

// TODO Handle external GET
//    let mut easy = Easy::new();
//    easy.url(&json_file).unwrap();
//    easy.get("TESTPACKAGE-AT-PICKUPPOINT");

// get_http_body from url
//    let http_body = std::str::from_utf8(http_response.get_http_body()).unwrap_or_else(|err| {
//    panic!("Failed to get response {}; error is {}", url, err); // TODO Error handling
//    });

// import io, os, filecmp // assert_eq!()
// import json, requests
// import argparse

// parser = argparse.ArgumentParser()
// parser.add_argument("sid")
// args = parser.parse_args()

// sid = args.sid // tracking ID

// params = dict(
//             q=sid
//         )
//
// resp = requests.get(url=url, params=params)
// data = json.loads(resp.text)
//
// path = '/home/ix/src/posten/data'
// first = True
// f_i = 0
// f_new = f_old = path + '/' + 'posten-{0}-{1}'.format(sid, f_i) + '.json'
// if os.path.exists(f_new):
//     first = False
//     while os.path.exists(f_new):
//             f_old = path + '/' + 'posten-{0}-{1}'.format(sid, f_i) + '.json'
//             f_i += 1
//             f_new = path + '/' + 'posten-{0}-{1}'.format(sid, f_i) + '.json'
// #print 'f_old: ' + f_old
// #print 'f_new: ' + f_new
//
// # print status for i3
// if not os.path.exists('/tmp/posten'):
//     os.mkdir('/tmp/posten')
//
// e_i = 0
// events = []
// for i in range(0, len(data['consignmentSet'][0]['packageSet'][0]['eventSet'])):
//     events.append(data['consignmentSet'][0]['packageSet'][0]['eventSet'][i]['status'])
// e_i = 0
// while events[e_i] == u'NOTIFICATION_SENT':
//     e_i = e_i + 1
//
// with open('/tmp/posten/' + sid, 'w') as status_file:
//     status_file.write(events[e_i])
//
// with open(f_new, 'w') as outfile:
//      json.dump(data, outfile, sort_keys = True, indent = 4, ensure_ascii=False)
//
// if not first:
//     if filecmp.cmp(f_old, f_new):
//         print 'no change'
//         os.remove(f_new)
//     else:
//         print 'new entry'
<commit_msg>cleanup and add more documentation<commit_after>/// Rosten
/// Track Bring packages in Rust
/// For testing use "TESTPACKAGE-AT-PICKUPPOINT" as tracking number
/// https://www.mybring.com/tracking/api

extern crate curl;
extern crate clap;
extern crate serde_json;

use std::env;
use std::io::prelude::*;
use std::fs::File;
use std::path::Path;
use std::error::Error;
use curl::easy::Easy;
use std::fmt::Display;
use serde_json::Value;

// TODO Define values corresponding to json file
// Would an enum be better here?

// TODO Depending on size, perhaps move structs to different file?

struct Data {
    Firstname: String,
    Lastname: String,
    Age: u32,
    Address: Address,
    PhoneNumber: Vec<String>
}
// Do we need to define everything in tracking.json?
// Is it enough to assign whatever matches to needed values?
struct Address {
    Street: String,
    City: String,
    Country: String
}

pub fn main() {

    // TODO Figure out ~/ path so compiler can find the file easily
    let path = Path::new("/home/stian/projects/rosten/tracking.json");
    let display = path.display();
    let mut file = match File::open(&path) {
        Err(e) => panic!("unable to open {}: {}", display, e.description()),
        Ok(file) => file,
    };

    let mut buffer = String::new();
    file.read_to_string(&mut buffer).unwrap();

    // TODO Better names
    let data: Value = serde_json::from_str(&buffer).unwrap();

    // TODO Iterate over consignmentSet
    let values = data.as_object()
        .and_then(|object| object.get("consignmentSet"))
        .and_then(|values| values.as_object())
        .unwrap_or_else(|| {
            panic!("Failed to get object value from json"); // fails at runtime here
        });

    for (key, value) in values.iter() {
        let results = value.find("consignmentId")
            .and_then(|value| value.as_object())
            .unwrap_or_else(|| {
                panic!("Failed to get value from within values");
            });
        println!("{} -> {:?}", key, results);
    }
}
<|endoftext|>


<commit_before>//! The main entry point for the SpaceAPi server.
//!
//! Running this code starts a HTTP server instance. The default port is 3000, but you can set your
//! own favorite port by exporting the `PORT` environment variable.

extern crate rustc_serialize;
extern crate hyper;
extern crate redis;

mod utils;
mod spaceapi;
mod datastore;
mod redis_store;

use std::io::Write;
use std::net::Ipv4Addr;

use rustc_serialize::json::ToJson;
use hyper::Server;
use hyper::server::Request;
use hyper::server::Response;
use hyper::net::Fresh;
use hyper::header;

use datastore::DataStore;
use spaceapi::Optional::{Value, Absent};

fn build_response_json(people_present: Option<u32>, raspi_temperature: Option<f32>) -> String {
    let people_present_sensor = match people_present {
        Some(count) => Value(vec![
            spaceapi::PeopleNowPresentSensor {
                value: count,
                location: Value("Hackerspace".to_string()),
                name: Absent,
                description: Absent,
                names: Absent,
            }
        ]),
        None => Absent,
    };

    let temperature_sensor = match raspi_temperature {
        Some(degrees) => Value(vec![
            spaceapi::TemperatureSensor {
                value: degrees,
                unit: "°C".to_string(),
                location: "Hackerspace".to_string(),
                name: Value("Raspberry CPU".to_string()),
                description: Absent,
            }
        ]),
        None => Absent,
    };

    let status = spaceapi::Status {

        // Hackerspace properties
        api: "0.13".to_string(),
        space: "coredump".to_string(),
        logo: "https://www.coredump.ch/logo.png".to_string(),
        url: "https://www.coredump.ch/".to_string(),
        location: spaceapi::Location {
            address: Value("Spinnereistrasse 2, 8640 Rapperswil, Switzerland".to_string()),
            lat: 47.22936,
            lon: 8.82949,
        },
        contact: spaceapi::Contact {
            irc: Value("irc://freenode.net/#coredump".to_string()),
            twitter: Value("@coredump_ch".to_string()),
            foursquare: Value("525c20e5498e875d8231b1e5".to_string()),
            email: Value("danilo@coredump.ch".to_string()),
        },

        // Hackerspace features / projects
        spacefed: Value(spaceapi::Spacefed {
            spacenet: false,
            spacesaml: false,
            spacephone: false,
        }),
        projects: Value(vec![
            "https://www.coredump.ch/projekte/".to_string(),
            "https://discourse.coredump.ch/c/projects".to_string(),
            "https://github.com/coredump-ch/".to_string(),
        ]),
        cam: Absent,
        feeds: Value(spaceapi::Feeds {
            blog: Value(spaceapi::Feed {
                _type: Value("rss".to_string()),
                url: "https://www.coredump.ch/feed/".to_string(),
            }),
            wiki: Absent,
            calendar: Absent,
            flickr: Absent,
        }),
        events: Absent,
        radio_show: Absent,

        // SpaceAPI internal usage
        cache: Value(spaceapi::Cache {
            schedule: "m.02".to_string(),
        }),
        issue_report_channels: vec![
            "email".to_string(),
            "twitter".to_string(),
        ],

        // Mutable data
        state: spaceapi::State {
            open: Some(false),
            message: Value("Open every Monday from 20:00".to_string()),
            lastchange: Absent,
            trigger_person: Absent,
            icon: Absent,
        },
        sensors: Value(spaceapi::Sensors {
            people_now_present: people_present_sensor,
            temperature: temperature_sensor,
        }),

    };
    status.to_json().to_string()
}

fn status_endpoint(_: Request, mut res: Response<Fresh>) {
    // Fetch data from datastore
    let datastore = redis_store::RedisStore::new().unwrap();
    let people_present: Option<u32> = match datastore.retrieve("people_present") {
        Ok(v) => match v.parse::<u32>() {
            Ok(i) => Some(i),
            Err(_) => None,
        },
        Err(_) => None,
    };
    let raspi_temperature: Option<f32> = match datastore.retrieve("raspi_temperature") {
        Ok(v) => match v.parse::<f32>() {
            Ok(i) => Some(i),
            Err(_) => None,
        },
        Err(_) => None,
    };

    // Get response body
    let body = build_response_json(people_present, raspi_temperature);
    let body_bytes = body.as_bytes();

    // Set headers
    // A new scope is used here because of the mutable borrow.
    {
        let mut headers = res.headers_mut();
        headers.set(header::ContentLength(body_bytes.len() as u64));
        headers.set(header::ContentType("application/json; charset=utf-8".parse().unwrap()));
        headers.set(header::CacheControl(vec![header::CacheDirective::NoCache]));
        headers.set(header::AccessControlAllowOrigin::Any);
    }

    // Write response body
    let mut stream = res.start().unwrap();
    stream.write_all(body_bytes).unwrap();

    stream.end().unwrap();
}

fn main() {
    let ip = Ipv4Addr::new(127, 0, 0, 1);
    let port = utils::get_port();

    println!("Starting HTTP server on {}:{}...", ip, port);
    Server::http(status_endpoint).listen((ip, port)).unwrap();
}

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

    #[test]
    /// Verify that the response JSON looks OK.
    fn verify_response_json() {
        let people_present = Some(23);
        let temperature = Some(42.1337);
        let json = build_response_json(people_present, temperature);
        assert_eq!(json, "{\
            \"api\":\"0.13\",\
            \"cache\":{\"schedule\":\"m.02\"},\
            \"contact\":{\
                \"email\":\"danilo@coredump.ch\",\
                \"foursquare\":\"525c20e5498e875d8231b1e5\",\
                \"irc\":\"irc://freenode.net/#coredump\",\
                \"twitter\":\"@coredump_ch\"\
            },\
            \"feeds\":{\
                \"blog\":{\"type\":\"rss\",\"url\":\"https://www.coredump.ch/feed/\"}\
            },\
            \"issue_report_channels\":[\"email\",\"twitter\"],\
            \"location\":{\
                \"address\":\"Spinnereistrasse 2, 8640 Rapperswil, Switzerland\",\
                \"lat\":47.22936,\"lon\":8.82949\
            },\
            \"logo\":\"https://www.coredump.ch/logo.png\",\
            \"projects\":[\
                \"https://www.coredump.ch/projekte/\",\
                \"https://discourse.coredump.ch/c/projects\",\
                \"https://github.com/coredump-ch/\"\
            ],\
            \"sensors\":{\
                \"people_now_present\":[{\
                    \"location\":\"Hackerspace\",\"value\":23\
                }],\
                \"temperature\":[{\
                    \"location\":\"Hackerspace\",\"name\":\"Raspberry CPU\",\
                    \"unit\":\"°C\",\"value\":42.133701\
                }]\
            },\
            \"space\":\"coredump\",\
            \"spacefed\":{\"spacenet\":false,\"spacephone\":false,\"spacesaml\":false},\
            \"state\":{\"message\":\"Open every Monday from 20:00\",\"open\":false},\
            \"url\":\"https://www.coredump.ch/\"\
        }");
    }
}
<commit_msg>Fixed unreliable test<commit_after>//! The main entry point for the SpaceAPi server.
//!
//! Running this code starts a HTTP server instance. The default port is 3000, but you can set your
//! own favorite port by exporting the `PORT` environment variable.

extern crate rustc_serialize;
extern crate hyper;
extern crate redis;

mod utils;
mod spaceapi;
mod datastore;
mod redis_store;

use std::io::Write;
use std::net::Ipv4Addr;

use rustc_serialize::json::ToJson;
use hyper::Server;
use hyper::server::Request;
use hyper::server::Response;
use hyper::net::Fresh;
use hyper::header;

use datastore::DataStore;
use spaceapi::Optional::{Value, Absent};

fn build_response_json(people_present: Option<u32>, raspi_temperature: Option<f32>) -> String {
    let people_present_sensor = match people_present {
        Some(count) => Value(vec![
            spaceapi::PeopleNowPresentSensor {
                value: count,
                location: Value("Hackerspace".to_string()),
                name: Absent,
                description: Absent,
                names: Absent,
            }
        ]),
        None => Absent,
    };

    let temperature_sensor = match raspi_temperature {
        Some(degrees) => Value(vec![
            spaceapi::TemperatureSensor {
                value: degrees,
                unit: "°C".to_string(),
                location: "Hackerspace".to_string(),
                name: Value("Raspberry CPU".to_string()),
                description: Absent,
            }
        ]),
        None => Absent,
    };

    let status = spaceapi::Status {

        // Hackerspace properties
        api: "0.13".to_string(),
        space: "coredump".to_string(),
        logo: "https://www.coredump.ch/logo.png".to_string(),
        url: "https://www.coredump.ch/".to_string(),
        location: spaceapi::Location {
            address: Value("Spinnereistrasse 2, 8640 Rapperswil, Switzerland".to_string()),
            lat: 47.22936,
            lon: 8.82949,
        },
        contact: spaceapi::Contact {
            irc: Value("irc://freenode.net/#coredump".to_string()),
            twitter: Value("@coredump_ch".to_string()),
            foursquare: Value("525c20e5498e875d8231b1e5".to_string()),
            email: Value("danilo@coredump.ch".to_string()),
        },

        // Hackerspace features / projects
        spacefed: Value(spaceapi::Spacefed {
            spacenet: false,
            spacesaml: false,
            spacephone: false,
        }),
        projects: Value(vec![
            "https://www.coredump.ch/projekte/".to_string(),
            "https://discourse.coredump.ch/c/projects".to_string(),
            "https://github.com/coredump-ch/".to_string(),
        ]),
        cam: Absent,
        feeds: Value(spaceapi::Feeds {
            blog: Value(spaceapi::Feed {
                _type: Value("rss".to_string()),
                url: "https://www.coredump.ch/feed/".to_string(),
            }),
            wiki: Absent,
            calendar: Absent,
            flickr: Absent,
        }),
        events: Absent,
        radio_show: Absent,

        // SpaceAPI internal usage
        cache: Value(spaceapi::Cache {
            schedule: "m.02".to_string(),
        }),
        issue_report_channels: vec![
            "email".to_string(),
            "twitter".to_string(),
        ],

        // Mutable data
        state: spaceapi::State {
            open: Some(false),
            message: Value("Open every Monday from 20:00".to_string()),
            lastchange: Absent,
            trigger_person: Absent,
            icon: Absent,
        },
        sensors: Value(spaceapi::Sensors {
            people_now_present: people_present_sensor,
            temperature: temperature_sensor,
        }),

    };
    status.to_json().to_string()
}

fn status_endpoint(_: Request, mut res: Response<Fresh>) {
    // Fetch data from datastore
    let datastore = redis_store::RedisStore::new().unwrap();
    let people_present: Option<u32> = match datastore.retrieve("people_present") {
        Ok(v) => match v.parse::<u32>() {
            Ok(i) => Some(i),
            Err(_) => None,
        },
        Err(_) => None,
    };
    let raspi_temperature: Option<f32> = match datastore.retrieve("raspi_temperature") {
        Ok(v) => match v.parse::<f32>() {
            Ok(i) => Some(i),
            Err(_) => None,
        },
        Err(_) => None,
    };

    // Get response body
    let body = build_response_json(people_present, raspi_temperature);
    let body_bytes = body.as_bytes();

    // Set headers
    // A new scope is used here because of the mutable borrow.
    {
        let mut headers = res.headers_mut();
        headers.set(header::ContentLength(body_bytes.len() as u64));
        headers.set(header::ContentType("application/json; charset=utf-8".parse().unwrap()));
        headers.set(header::CacheControl(vec![header::CacheDirective::NoCache]));
        headers.set(header::AccessControlAllowOrigin::Any);
    }

    // Write response body
    let mut stream = res.start().unwrap();
    stream.write_all(body_bytes).unwrap();

    stream.end().unwrap();
}

fn main() {
    let ip = Ipv4Addr::new(127, 0, 0, 1);
    let port = utils::get_port();

    println!("Starting HTTP server on {}:{}...", ip, port);
    Server::http(status_endpoint).listen((ip, port)).unwrap();
}

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

    #[test]
    /// Verify that the response JSON looks OK.
    fn verify_response_json() {
        let people_present = Some(23);
        let temperature = Some(42.5);
        let json = build_response_json(people_present, temperature);
        assert_eq!(json, "{\
            \"api\":\"0.13\",\
            \"cache\":{\"schedule\":\"m.02\"},\
            \"contact\":{\
                \"email\":\"danilo@coredump.ch\",\
                \"foursquare\":\"525c20e5498e875d8231b1e5\",\
                \"irc\":\"irc://freenode.net/#coredump\",\
                \"twitter\":\"@coredump_ch\"\
            },\
            \"feeds\":{\
                \"blog\":{\"type\":\"rss\",\"url\":\"https://www.coredump.ch/feed/\"}\
            },\
            \"issue_report_channels\":[\"email\",\"twitter\"],\
            \"location\":{\
                \"address\":\"Spinnereistrasse 2, 8640 Rapperswil, Switzerland\",\
                \"lat\":47.22936,\"lon\":8.82949\
            },\
            \"logo\":\"https://www.coredump.ch/logo.png\",\
            \"projects\":[\
                \"https://www.coredump.ch/projekte/\",\
                \"https://discourse.coredump.ch/c/projects\",\
                \"https://github.com/coredump-ch/\"\
            ],\
            \"sensors\":{\
                \"people_now_present\":[{\
                    \"location\":\"Hackerspace\",\"value\":23\
                }],\
                \"temperature\":[{\
                    \"location\":\"Hackerspace\",\"name\":\"Raspberry CPU\",\
                    \"unit\":\"°C\",\"value\":42.5\
                }]\
            },\
            \"space\":\"coredump\",\
            \"spacefed\":{\"spacenet\":false,\"spacephone\":false,\"spacesaml\":false},\
            \"state\":{\"message\":\"Open every Monday from 20:00\",\"open\":false},\
            \"url\":\"https://www.coredump.ch/\"\
        }");
    }
}
<|endoftext|>


<commit_before>#![feature(backtrace)]
#![recursion_limit = "256"]

use std::convert::TryInto;
use std::env;
use std::panic;
use std::path::PathBuf;
use std::process;
use std::sync::{Arc, Mutex}; // TODO: async Mutex

use anyhow::Context;
use once_cell::sync::OnceCell;
use structopt::StructOpt;
use tbot::{
    self,
    proxy::{Intercept, Proxy},
};
use tokio;

mod client;
mod data;
mod feed;
mod fetcher;
mod gardener;
mod handlers;
mod messages;

use crate::data::Database;

static BOT_NAME: OnceCell<String> = OnceCell::new();
static BOT_ID: OnceCell<tbot::types::user::Id> = OnceCell::new();

#[derive(Debug, StructOpt)]
#[structopt(about = "A simple Telegram RSS bot.")]
struct Opt {
    /// Telegram bot token
    token: String,
    /// Path to database
    #[structopt(short = "d", long, default_value = "./rssbot.json")]
    database: PathBuf,
    /// Minimum fetch interval in seconds
    #[structopt(long, default_value = "300", parse(try_from_str = parse_interval))]
    // default is 5 minutes
    min_interval: u32,
    /// Maximum fetch interval in seconds
    #[structopt(long, default_value = "43200", parse(try_from_str = parse_interval))]
    // default is 12 hours
    max_interval: u32,
    /// Maximum feed size in bytes, 0 is unlimited
    #[structopt(long, default_value = "2097152")]
    // default is 2MiB
    max_feed_size: u64,
    /// DANGER: Insecure mode, accept invalid TLS certificates
    #[structopt(long)]
    insecure: bool,
}

fn parse_interval(s: &str) -> Result<u32, String> {
    s.parse::<u32>().map_err(|e| e.to_string()).and_then(|r| {
        if r < 1 {
            Err("must >= 1".into())
        } else {
            Ok(r)
        }
    })
}

macro_rules! handle {
    ($env: expr, $f: expr) => {{
        let env = $env.clone();
        let f = $f;
        move |cmd| {
            let future = f(env.clone(), cmd);
            async {
                if let Err(e) = future.await {
                    print_error(e);
                }
            }
        }
    }};
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    enable_fail_fast();

    let opt = Opt::from_args();
    let db = Arc::new(Mutex::new(Database::open(opt.database)?));
    let bot = if let Some(proxy) = init_proxy() {
        tbot::Bot::with_proxy(opt.token, proxy)
    } else {
        tbot::Bot::new(opt.token)
    };
    let me = bot
        .get_me()
        .call()
        .await
        .context("Initialization failed, check your network and Telegram token")?;

    let bot_name = me.user.username.clone().unwrap();
    crate::client::init_client(&bot_name, opt.insecure);

    BOT_NAME.set(bot_name).unwrap();
    BOT_ID.set(me.user.id).unwrap();

    gardener::start_pruning(bot.clone(), db.clone());
    fetcher::start(bot.clone(), db.clone(), opt.min_interval, opt.max_interval);
    let mut event_loop = bot.event_loop();
    event_loop.username(me.user.username.unwrap());
    event_loop.start(handle!(db, handlers::start));
    event_loop.command("rss", handle!(db, handlers::rss));
    event_loop.command("sub", handle!(db, handlers::sub));
    event_loop.command("unsub", handle!(db, handlers::unsub));
    event_loop.command("export", handle!(db, handlers::export));

    event_loop.polling().start().await.unwrap();
    Ok(())
}

// Exit the process when any worker thread panicked
fn enable_fail_fast() {
    let default_panic_hook = panic::take_hook();
    panic::set_hook(Box::new(move |e| {
        default_panic_hook(e);
        process::exit(101);
    }));
}

fn init_proxy() -> Option<Proxy> {
    // Telegram Bot API only uses https, no need to check http_proxy
    env::var("HTTPS_PROXY")
        .or_else(|_| env::var("https_proxy"))
        .map(|uri| {
            let uri = uri
                .try_into()
                .unwrap_or_else(|e| panic!("Illegal HTTPS_PROXY: {}", e));
            Proxy::new(Intercept::All, uri)
        })
        .ok()
}

fn print_error<E: std::error::Error>(err: E) {
    eprintln!("Error: {}", err);
    let mut deepest_backtrace = err.backtrace();

    let mut err: &dyn std::error::Error = &err;
    if let Some(e) = err.source() {
        eprintln!("\nCaused by:");
        let multiple = e.source().is_some();
        let mut line_counter = 0..;
        while let (Some(e), Some(line)) = (err.source(), line_counter.next()) {
            if multiple {
                eprint!("{: >4}: ", line)
            } else {
                eprint!("    ")
            };
            eprintln!("{}", e);
            if let Some(backtrace) = e.backtrace() {
                deepest_backtrace = Some(backtrace);
            }
            err = e;
        }
    }

    if let Some(backtrace) = deepest_backtrace {
        eprintln!("\nBacktrace:\n{}", backtrace);
    }
}
<commit_msg>CUI: Minor adjusts, set the value_name<commit_after>#![feature(backtrace)]
#![recursion_limit = "256"]

use std::convert::TryInto;
use std::env;
use std::panic;
use std::path::PathBuf;
use std::process;
use std::sync::{Arc, Mutex}; // TODO: async Mutex

use anyhow::Context;
use once_cell::sync::OnceCell;
use structopt::StructOpt;
use tbot::{
    self,
    proxy::{Intercept, Proxy},
};
use tokio;

mod client;
mod data;
mod feed;
mod fetcher;
mod gardener;
mod handlers;
mod messages;

use crate::data::Database;

static BOT_NAME: OnceCell<String> = OnceCell::new();
static BOT_ID: OnceCell<tbot::types::user::Id> = OnceCell::new();

#[derive(Debug, StructOpt)]
#[structopt(about = "A simple Telegram RSS bot.")]
struct Opt {
    /// Telegram bot token
    token: String,
    /// Path to database
    #[structopt(short = "d", long, value_name = "path", default_value = "./rssbot.json")]
    database: PathBuf,
    /// Minimum fetch interval
    #[structopt(long, value_name = "seconds", default_value = "300", validator(check_interval))]
    // default is 5 minutes
    min_interval: u32,
    /// Maximum fetch interval
    #[structopt(long, value_name = "seconds", default_value = "43200", validator(check_interval))]
    // default is 12 hours
    max_interval: u32,
    /// Maximum feed size, 0 is unlimited
    #[structopt(long, value_name = "bytes", default_value = "2097152")]
    // default is 2MiB
    max_feed_size: u64,
    /// DANGER: Insecure mode, accept invalid TLS certificates
    #[structopt(long)]
    insecure: bool,
}

fn check_interval(s: String) -> Result<(), String> {
    s.parse::<u32>().map_err(|e| e.to_string()).and_then(|r| {
        if r < 1 {
            Err("must >= 1".into())
        } else {
            Ok(())
        }
    })
}

macro_rules! handle {
    ($env: expr, $f: expr) => {{
        let env = $env.clone();
        let f = $f;
        move |cmd| {
            let future = f(env.clone(), cmd);
            async {
                if let Err(e) = future.await {
                    print_error(e);
                }
            }
        }
    }};
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    enable_fail_fast();

    let opt = Opt::from_args();
    let db = Arc::new(Mutex::new(Database::open(opt.database)?));
    let bot = if let Some(proxy) = init_proxy() {
        tbot::Bot::with_proxy(opt.token, proxy)
    } else {
        tbot::Bot::new(opt.token)
    };
    let me = bot
        .get_me()
        .call()
        .await
        .context("Initialization failed, check your network and Telegram token")?;

    let bot_name = me.user.username.clone().unwrap();
    crate::client::init_client(&bot_name, opt.insecure);

    BOT_NAME.set(bot_name).unwrap();
    BOT_ID.set(me.user.id).unwrap();

    gardener::start_pruning(bot.clone(), db.clone());
    fetcher::start(bot.clone(), db.clone(), opt.min_interval, opt.max_interval);
    let mut event_loop = bot.event_loop();
    event_loop.username(me.user.username.unwrap());
    event_loop.start(handle!(db, handlers::start));
    event_loop.command("rss", handle!(db, handlers::rss));
    event_loop.command("sub", handle!(db, handlers::sub));
    event_loop.command("unsub", handle!(db, handlers::unsub));
    event_loop.command("export", handle!(db, handlers::export));

    event_loop.polling().start().await.unwrap();
    Ok(())
}

// Exit the process when any worker thread panicked
fn enable_fail_fast() {
    let default_panic_hook = panic::take_hook();
    panic::set_hook(Box::new(move |e| {
        default_panic_hook(e);
        process::exit(101);
    }));
}

fn init_proxy() -> Option<Proxy> {
    // Telegram Bot API only uses https, no need to check http_proxy
    env::var("HTTPS_PROXY")
        .or_else(|_| env::var("https_proxy"))
        .map(|uri| {
            let uri = uri
                .try_into()
                .unwrap_or_else(|e| panic!("Illegal HTTPS_PROXY: {}", e));
            Proxy::new(Intercept::All, uri)
        })
        .ok()
}

fn print_error<E: std::error::Error>(err: E) {
    eprintln!("Error: {}", err);
    let mut deepest_backtrace = err.backtrace();

    let mut err: &dyn std::error::Error = &err;
    if let Some(e) = err.source() {
        eprintln!("\nCaused by:");
        let multiple = e.source().is_some();
        let mut line_counter = 0..;
        while let (Some(e), Some(line)) = (err.source(), line_counter.next()) {
            if multiple {
                eprint!("{: >4}: ", line)
            } else {
                eprint!("    ")
            };
            eprintln!("{}", e);
            if let Some(backtrace) = e.backtrace() {
                deepest_backtrace = Some(backtrace);
            }
            err = e;
        }
    }

    if let Some(backtrace) = deepest_backtrace {
        eprintln!("\nBacktrace:\n{}", backtrace);
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate bmp;
extern crate num;

use num::complex::*;
use bmp::{Image, Pixel};

const SIZE: u32 = 256;
const ESCAPE_LIMIT: f64 = 1000000.0;
const ITER_LIMIT: u32 = 200;
const WIDTH: f64 = 0.005;
const HEIGHT: f64 = 0.010;
const X_CENTER: f64 = 1.941;
const Y_CENTER: f64 = 0.004;

// Burning ship
#[inline]
fn burning_ship() -> Box<Fn(Complex64, Complex64) -> Complex64>
{
    return Box::new(move |z: Complex64, c: Complex64| (Complex{re: z.re.abs(), im: z.im.abs()}).powf(2.0) + c);
}

// Mendelbrot
fn mandelbrot() -> Box<Fn(Complex64, Complex64) -> Complex64>
{
    return Box::new(move |z: Complex64, c: Complex64| z.powf(2.0) + c);
}

fn run_until_escape(gen_fn: Box<Fn(Complex64, Complex64) -> Complex64>,
    c: Complex64, escape_limit: f64, iter_limit: u32) -> u32
{
    let mut z: Complex64 = Complex64{ re: 0.0, im: 0.0};
    let mut n: u32 = 0;
    while z.norm_sqr() < escape_limit && n < iter_limit {
        n += 1;
        z = (*gen_fn)(z, c);
    }

    return n;
}

fn main() {
    let mut pixels = vec![0; (SIZE*SIZE) as usize];

    let coordinates: Vec<(u32, u32)> = (0..SIZE).flat_map(move |x| (0..SIZE).map(move |y| (x,y))).collect();
    for &(x, y) in &coordinates {
        let float_x: f64 = WIDTH*((x as f64)/(SIZE as f64) - 0.5) - X_CENTER;
        let float_y: f64 = HEIGHT*((y as f64)/(SIZE as f64) - 0.5) - Y_CENTER;
        let iterations = run_until_escape(burning_ship(), Complex{re: float_x, im: float_y},
                                          ESCAPE_LIMIT, ITER_LIMIT);
        pixels[(SIZE*x + y) as usize] = iterations;
    }

    let max = pixels.iter().cloned().filter(|&x| x < ITER_LIMIT).max().unwrap();
    println!("max pixel: {}", max);

    for &(x, y) in &coordinates {
            let current = pixels[(SIZE*x + y) as usize];
            if current < ITER_LIMIT {
                pixels[(SIZE*x + y) as usize] *= 256;
                pixels[(SIZE*x + y) as usize] /= max;
            }
            else {
                pixels[(SIZE*x + y) as usize] = 256;
            }
    }

    let mut img = Image::new(SIZE, SIZE);
    for (x,y) in img.coordinates()
    {
        let val = pixels[(SIZE*x + y) as usize];
        img.set_pixel(x, y, px!(val, val, val));
    }

    let _ = img.save("test.bmp");
}
<commit_msg>Removed expensive powf calls<commit_after>#[macro_use]
extern crate bmp;
extern crate num;

use num::complex::*;
use bmp::{Image, Pixel};

const SIZE: u32 = 256;
const ESCAPE_LIMIT: f64 = 1000000.0;
const ITER_LIMIT: u32 = 200;
const WIDTH: f64 = 0.005;
const HEIGHT: f64 = 0.010;
const X_CENTER: f64 = 1.941;
const Y_CENTER: f64 = 0.004;

// Burning ship
#[inline]
fn burning_ship() -> Box<Fn(Complex64, Complex64) -> Complex64>
{
    return Box::new(move |z: Complex64, c: Complex64| {
        let temp = Complex{re: z.re.abs(), im: z.im.abs()};
        return (temp * temp) + c;
    });
}

// Mendelbrot
fn mandelbrot() -> Box<Fn(Complex64, Complex64) -> Complex64>
{
    return Box::new(move |z: Complex64, c: Complex64| z*z + c);
}

fn run_until_escape(gen_fn: Box<Fn(Complex64, Complex64) -> Complex64>,
    c: Complex64, escape_limit: f64, iter_limit: u32) -> u32
{
    let mut z: Complex64 = Complex64{ re: 0.0, im: 0.0};
    let mut n: u32 = 0;
    while z.norm_sqr() < escape_limit && n < iter_limit {
        n += 1;
        z = (*gen_fn)(z, c);
    }

    return n;
}

fn main() {
    let mut pixels = vec![0; (SIZE*SIZE) as usize];

    let coordinates: Vec<(u32, u32)> = (0..SIZE).flat_map(move |x| (0..SIZE).map(move |y| (x,y))).collect();
    for &(x, y) in &coordinates {
        let float_x: f64 = WIDTH*((x as f64)/(SIZE as f64) - 0.5) - X_CENTER;
        let float_y: f64 = HEIGHT*((y as f64)/(SIZE as f64) - 0.5) - Y_CENTER;
        let iterations = run_until_escape(burning_ship(), Complex{re: float_x, im: float_y},
                                          ESCAPE_LIMIT, ITER_LIMIT);
        pixels[(SIZE*x + y) as usize] = iterations;
    }

    let max = pixels.iter().cloned().filter(|&x| x < ITER_LIMIT).max().unwrap();
    println!("max pixel: {}", max);

    for &(x, y) in &coordinates {
            let current = pixels[(SIZE*x + y) as usize];
            if current < ITER_LIMIT {
                pixels[(SIZE*x + y) as usize] *= 256;
                pixels[(SIZE*x + y) as usize] /= max;
            }
            else {
                pixels[(SIZE*x + y) as usize] = 256;
            }
    }

    let mut img = Image::new(SIZE, SIZE);
    for (x,y) in img.coordinates()
    {
        let val = pixels[(SIZE*x + y) as usize];
        img.set_pixel(x, y, px!(val, val, val));
    }

    let _ = img.save("test.bmp");
}
<|endoftext|>


<commit_before>#![feature(io, path, core, rustdoc, collections)]

extern crate docopt;
extern crate rustdoc;
extern crate "rustc-serialize" as rustc_serialize;
extern crate handlebars;

use docopt::Docopt;
use std::old_io::fs;
use std::old_io::fs::PathExtensions;
use std::old_io::{ FileType, File, USER_DIR, USER_FILE };
use rustdoc::html::markdown::Markdown;
use handlebars::Handlebars;
use rustc_serialize::json::{ ToJson, Json };
use std::collections::BTreeMap;

// Docopt usage string
static USAGE: &'static str = "
Usage: rust-notes <source> <dest>
";

#[derive(Show, RustcDecodable)]
struct Args {
    arg_source: String,
    arg_dest: String
}

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

    //Generator(args).start();
    match Generator::new(args) {
        Ok(generator) => {
            generator.begin();
        },
        Err(message) => panic!(message)
    }
}

struct Link {
    name: String,
    url: String
}

impl ToJson for Link {
    fn to_json(&self) -> Json {
        let mut m = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("url".to_string(), self.url.to_json());
        Json::Object(m)
    }
}

struct DirModel {
    name: String,
    parents: Vec<Link>,
    dirs: Vec<Link>,
    notes: Vec<Link>
}

impl ToJson for DirModel {
    fn to_json(&self) -> Json {
        let mut m = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("parents".to_string(), self.parents.to_json());
        m.insert("dirs".to_string(), self.dirs.to_json());
        m.insert("notes".to_string(), self.notes.to_json());
        Json::Object(m)
    }
}

struct NoteModel {
    name: String,
    parents: Vec<Link>,
    content: String,
}

impl ToJson for NoteModel {
    fn to_json(&self) -> Json {
        let mut m = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("parents".to_string(), self.parents.to_json());
        m.insert("content".to_string(), self.content.to_json());
        Json::Object(m)
    }
}

struct Generator {
    root_source_path: Path,
    root_dest_path: Path,
    notes_source_path: Path,
    handlebars: Handlebars,
    dir_template_name: &'static str,
    note_template_name: &'static str
}

impl Generator {

    pub fn new(args: Args) -> Result<Generator, &'static str> {
        let source_path = Path::new(args.arg_source.as_slice());
        let dest_path = Path::new(args.arg_dest.as_slice());

        if !source_path.is_dir() {
            return Err("Invalid source path");
        }

        if !dest_path.is_dir() {
            match fs::mkdir_recursive(&dest_path, USER_DIR) {
                Err(_) => return Err("Cannot create destination directory"),
                _ => ()
            }
        }

        // Validate source
        let notes_source_path = source_path.clone().join("notes");
        if !notes_source_path.is_dir() {
            return Err("Source directory missing required files");
        }

        let dir_hbs_path = source_path.clone().join("dir.hbs");
        if !dir_hbs_path.exists() {
            return Err("Source directory missing dir.hbs");
        }

        let note_hbs_path = source_path.clone().join("note.hbs");
        if !note_hbs_path.exists() {
            return Err("Source directory missing note.hbs");
        }

        // Good to go! Let's return something good

        let dir_template_name = "dir_template";
        let note_template_name = "note_template";

        let dir_hbs_contents = File::open(&dir_hbs_path).read_to_string().unwrap();
        let note_hbs_contents = File::open(&note_hbs_path).read_to_string().unwrap();

        let mut handlebars = Handlebars::new();
        handlebars.register_template_string(dir_template_name, dir_hbs_contents.to_string())
            .ok().expect("Error registering dir template");
        handlebars.register_template_string(note_template_name, note_hbs_contents.to_string())
            .ok().expect("Error registering note template");

        Ok(Generator{
            root_source_path: source_path,
            root_dest_path: dest_path,
            notes_source_path: notes_source_path,
            handlebars: handlebars,
            dir_template_name: dir_template_name,
            note_template_name: note_template_name
        })
    }

    pub fn begin(&self) {
        self.clean_dest();
        self.generate_dir(&Path::new(""));
    }

    fn generate_dir(&self, relative_path: &Path) {
        let full_dest_path = self.root_dest_path.clone()
            .join(relative_path.as_str().unwrap());
        let full_source_path = self.notes_source_path.clone()
            .join(relative_path.as_str().unwrap());

        self.create_dir_index(&full_dest_path, relative_path);

        match fs::readdir(&full_source_path) {
            Ok(items) => {
                for item in items.iter() {
                    if item.is_file() {
                        self.convert_file(item, &full_dest_path, relative_path);
                    } else {
                        self.convert_dir(item, &full_dest_path, relative_path);
                    }
                }
            },
            Err(_) => ()
        }
    }

    fn convert_file(&self, source_file_path: &Path, dest_parent_dir_path: &Path, relative_path: &Path) {
        if Generator::is_markdown_file(source_file_path) {
            let source_contents = File::open(source_file_path).read_to_string().unwrap();
            let file_name = source_file_path.filestem_str().unwrap();

            // Create Model
            let content = Markdown(source_contents.as_slice());
            let parents = Generator::get_parent_links(relative_path, true);

            let model = NoteModel {
                name: String::from_str(file_name),
                parents : parents,
                content : format!("{}", content)
            };

            match self.handlebars.render(self.note_template_name, &model) {
                Ok(rendered) => {
                    // Create File
                    let new_rendered = String::from_str(rendered.as_slice()).replace("\\n", "");
                    let new_file_path = dest_parent_dir_path.join(format!("{}.html", file_name));
                    let mut file = File::create(&new_file_path).ok().expect("Could not note html file");
                    fs::chmod(&new_file_path, USER_FILE).ok().expect("Couldn't chmod new file");
                    file.write_str(new_rendered.as_slice())
                        .ok().expect("Could not write html to file");
                },
                Err(why) => panic!("Error rendering note: {:?}", why)
            }
        }
    }

    fn dir_contains_note(path: &Path) -> bool {
        let mut contains_note: bool = false;
        let mut dirs = fs::walk_dir(path)
            .ok().expect("Could not walk through directories recursively");
        for item2 in dirs {
            contains_note |= Generator::is_markdown_file(&item2);
        }
        contains_note
    }

    fn convert_dir(&self, source_dir_path: &Path, dest_parent_dir_path: &Path, relative_path: &Path) {
        if Generator::dir_contains_note(source_dir_path) {
            let dirname = source_dir_path.filename_str().unwrap();
            let new_dir_path = dest_parent_dir_path.clone().join(dirname);
            //println!("creating directory: {:?}", new_dir_path);
            fs::mkdir(&new_dir_path, USER_DIR).ok().expect("Cannot create destination subdir");
            self.generate_dir(&relative_path.clone().join(dirname));
        }
    }

    fn is_markdown_file(file_path: &Path) -> bool {
        let name = file_path.as_str().unwrap();
        let stat = fs::stat(file_path).ok().expect("Could not get stat for file");
        if stat.kind != FileType::RegularFile {
            return false;
        }
        if !name.ends_with(".md") && !name.ends_with(".markdown") {
            return false;
        }
        true
    }

    fn get_parent_links(path: &Path, is_note: bool) -> Vec<Link> {
        if !is_note && path.filename().is_none() {
            Vec::new()
        } else {
            let mut result: Vec<Link> = vec![Link {
                name: String::from_str("root"),
                url: String::from_str("/")
            }];
            let mut temp = path.clone().dir_path();
            while temp.filename().is_some() {
                result.insert(1, Link {
                    name: String::from_str(temp.filename_str().unwrap()),
                    url: format!("/{}", temp.as_str().unwrap())
                });
                temp.pop();
            }
            result
        }
    }

    fn create_dir_index(&self, dir_path: &Path, relative_path: &Path) {
        let new_file_path = dir_path.join("index.html");
        let dirname = dir_path.filename_str().unwrap();
        let mut file = File::create(&new_file_path).ok().expect("Could not create html file");
        fs::chmod(&new_file_path, USER_FILE).ok().expect("Couldn't chmod new file");

        // Name
        let name = if relative_path.as_str().unwrap() == "." {
            "root"
        } else {
            dirname
        };

        let mut dirs: Vec<Link> = Vec::new();
        let mut notes: Vec<Link> = Vec::new();

        let source_path = self.notes_source_path.clone().join(relative_path);
        match fs::readdir(&source_path) {
            Ok(items) => {
                for item in items.iter() {
                    if item.is_file() && Generator::is_markdown_file(item) {
                        let name = item.filestem_str().unwrap();
                        let url = relative_path.clone().join(format!("{}.html", name).as_slice());
                        notes.push(Link{
                            name: String::from_str(name),
                            url: String::from_str(format!("/{}", url.as_str().unwrap()).as_slice())
                        });
                    } else if Generator::dir_contains_note(item) {
                        let name = item.filename_str().unwrap();
                        let url = relative_path.clone().join(name);
                        dirs.push(Link{
                            name: String::from_str(name),
                            url: String::from_str(format!("/{}", url.as_str().unwrap()).as_slice())
                        });
                    }
                }
            },
            Err(_) => ()
        }

        let model = DirModel {
            name: String::from_str(name),
            parents: Generator::get_parent_links(relative_path, false),
            dirs: dirs,
            notes: notes
        };

        match self.handlebars.render(self.dir_template_name, &model) {
            Ok(rendered) => {
                file.write_str(rendered.as_slice())
                    .ok().expect("Could not write html to file");
            },
            Err(why) => panic!("Error rendering: {:?}", why)
        }
    }

    fn clean_dest(&self) {
        match fs::readdir(&self.root_dest_path) {
            Ok(items) => {
                for item in items.iter() {
                    if item.is_file() {
                        //println!("removing file: {:?}", item);
                        fs::unlink(item).ok().expect("Could not remove file");
                    } else {
                        //println!("removing directory: {:?}", item);
                        fs::rmdir_recursive(item).ok().expect("Could not remove directory");
                    }
                }
            },
            Err(_) => ()
        }
    }
}
<commit_msg>adding assets, and probs other stuff<commit_after>#![feature(io, path, core, rustdoc, collections)]

extern crate docopt;
extern crate rustdoc;
extern crate "rustc-serialize" as rustc_serialize;
extern crate handlebars;

use docopt::Docopt;
use std::old_io::fs;
use std::old_io::fs::PathExtensions;
use std::old_io::{ FileType, File, USER_DIR, USER_FILE };
use rustdoc::html::markdown::Markdown;
use handlebars::Handlebars;
use rustc_serialize::json::{ ToJson, Json };
use std::collections::BTreeMap;

// Docopt usage string
static USAGE: &'static str = "
Usage: rust-notes <source> <dest>
";

#[derive(Show, RustcDecodable)]
struct Args {
    arg_source: String,
    arg_dest: String
}

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

    //Generator(args).start();
    match Generator::new(args) {
        Ok(generator) => {
            generator.begin();
        },
        Err(message) => panic!(message)
    }
}

struct Link {
    name: String,
    url: String
}

impl ToJson for Link {
    fn to_json(&self) -> Json {
        let mut m = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("url".to_string(), self.url.to_json());
        Json::Object(m)
    }
}

struct DirModel {
    name: String,
    parents: Vec<Link>,
    dirs: Vec<Link>,
    notes: Vec<Link>,
    base_url: String
}

impl ToJson for DirModel {
    fn to_json(&self) -> Json {
        let mut m = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("parents".to_string(), self.parents.to_json());
        m.insert("dirs".to_string(), self.dirs.to_json());
        m.insert("notes".to_string(), self.notes.to_json());
        m.insert("baseUrl".to_string(), self.base_url.to_json());
        Json::Object(m)
    }
}

struct NoteModel {
    name: String,
    parents: Vec<Link>,
    content: String,
    base_url: String
}

impl ToJson for NoteModel {
    fn to_json(&self) -> Json {
        let mut m = BTreeMap::new();
        m.insert("name".to_string(), self.name.to_json());
        m.insert("parents".to_string(), self.parents.to_json());
        m.insert("content".to_string(), self.content.to_json());
        m.insert("baseUrl".to_string(), self.base_url.to_json());
        Json::Object(m)
    }
}

fn cp_dir(source: &Path, dest: &Path) {
    fs::mkdir(dest, USER_DIR).ok().expect("Problem copying directory");
    for item in fs::walk_dir(source).ok().expect("Problem copying directory") {
        let relative = item.clone().path_relative_from(source).unwrap();
        let dest_path = dest.clone().join(relative.as_str().unwrap());
        if item.is_file() {
            fs::copy(&item, &dest_path).ok().expect("Problem copying directory");
        } else {
            fs::mkdir(&dest_path, USER_DIR).ok().expect("Problem copying directory");
        }
    }
}

struct Generator {
    root_source_path: Path,
    root_dest_path: Path,
    notes_source_path: Path,
    handlebars: Handlebars,
    dir_template_name: &'static str,
    note_template_name: &'static str,
    base_url: String
}

impl Generator {

    pub fn new(args: Args) -> Result<Generator, &'static str> {
        let source_path = Path::new(args.arg_source.as_slice());
        let dest_path = Path::new(args.arg_dest.as_slice());

        if !source_path.is_dir() {
            return Err("Invalid source path");
        }

        if !dest_path.is_dir() {
            match fs::mkdir_recursive(&dest_path, USER_DIR) {
                Err(_) => return Err("Cannot create destination directory"),
                _ => ()
            }
        }

        // Validate source
        let notes_source_path = source_path.clone().join("notes");
        if !notes_source_path.is_dir() {
            return Err("Source directory missing required files");
        }

        let dir_hbs_path = source_path.clone().join("layouts/dir.hbs");
        if !dir_hbs_path.exists() {
            return Err("Missing /layouts/dir.hbs");
        }

        let note_hbs_path = source_path.clone().join("layouts/note.hbs");
        if !note_hbs_path.exists() {
            return Err("Missing /layouts/note.hbs");
        }

        let header_hbs_path = source_path.clone().join("partials/header.hbs");
        if !header_hbs_path.exists() {
            return Err("Missing partials/header.hbs");
        }

        let footer_hbs_path = source_path.clone().join("partials/footer.hbs");
        if !footer_hbs_path.exists() {
            return Err("Missing partials/footer.hbs");
        }

        // Good to go! Let's return something good

        let dir_template_name = "dir_template";
        let note_template_name = "note_template";

        let dir_hbs_contents = File::open(&dir_hbs_path).read_to_string().unwrap();
        let note_hbs_contents = File::open(&note_hbs_path).read_to_string().unwrap();
        let header_hbs_contents = File::open(&header_hbs_path).read_to_string().unwrap();
        let footer_hbs_contents = File::open(&footer_hbs_path).read_to_string().unwrap();

        let mut handlebars = Handlebars::new();
        handlebars.register_template_string(dir_template_name, format!("{}\n{}\n{}", header_hbs_contents, dir_hbs_contents, footer_hbs_contents))
            .ok().expect("Error registering header|dir|footer template");
        handlebars.register_template_string(note_template_name, format!("{}\n{}\n{}", header_hbs_contents, note_hbs_contents, footer_hbs_contents))
            .ok().expect("Error registering header|note|footer template");

        Ok(Generator{
            root_source_path: source_path,
            root_dest_path: dest_path,
            notes_source_path: notes_source_path,
            handlebars: handlebars,
            dir_template_name: dir_template_name,
            note_template_name: note_template_name,
            base_url: String::from_str("")
        })
    }

    pub fn begin(&self) {
        self.clean_dest();
        let assets_source_path = self.root_source_path.clone().join("assets");
        if assets_source_path.is_dir() {
            let assets_dest_path = self.root_dest_path.clone().join("assets");
            cp_dir(&assets_source_path, &assets_dest_path);
        }
        self.generate_dir(&Path::new(""));
    }

    fn generate_dir(&self, relative_path: &Path) {
        let full_dest_path = self.root_dest_path.clone()
            .join(relative_path.as_str().unwrap());
        let full_source_path = self.notes_source_path.clone()
            .join(relative_path.as_str().unwrap());

        self.create_dir_index(&full_dest_path, relative_path);

        match fs::readdir(&full_source_path) {
            Ok(items) => {
                for item in items.iter() {
                    if item.is_file() {
                        self.convert_file(item, &full_dest_path, relative_path);
                    } else {
                        self.convert_dir(item, &full_dest_path, relative_path);
                    }
                }
            },
            Err(_) => ()
        }
    }

    fn convert_file(&self, source_file_path: &Path, dest_parent_dir_path: &Path, relative_path: &Path) {
        if Generator::is_markdown_file(source_file_path) {
            let source_contents = File::open(source_file_path).read_to_string().unwrap();
            let file_name = source_file_path.filestem_str().unwrap();

            // Create Model
            let content = Markdown(source_contents.as_slice());
            let parents = self.get_parent_links(relative_path, true);

            let model = NoteModel {
                name: String::from_str(file_name),
                parents : parents,
                content : format!("{}", content),
                base_url: self.base_url.clone()
            };

            match self.handlebars.render(self.note_template_name, &model) {
                Ok(rendered) => {
                    // Create File
                    let new_rendered = String::from_str(rendered.as_slice())
                        .replace("\\n", "")
                        .replace("\\\"", "\"");
                    let new_file_path = dest_parent_dir_path.join(format!("{}.html", file_name));
                    let mut file = File::create(&new_file_path).ok().expect("Could not note html file");
                    fs::chmod(&new_file_path, USER_FILE).ok().expect("Couldn't chmod new file");
                    file.write_str(new_rendered.as_slice())
                        .ok().expect("Could not write html to file");
                },
                Err(why) => panic!("Error rendering note: {:?}", why)
            }
        }
    }

    fn dir_contains_note(path: &Path) -> bool {
        let mut contains_note: bool = false;
        let mut dirs = fs::walk_dir(path)
            .ok().expect("Could not walk through directories recursively");
        for item2 in dirs {
            contains_note |= Generator::is_markdown_file(&item2);
        }
        contains_note
    }

    fn convert_dir(&self, source_dir_path: &Path, dest_parent_dir_path: &Path, relative_path: &Path) {
        if Generator::dir_contains_note(source_dir_path) {
            let dirname = source_dir_path.filename_str().unwrap();
            let new_dir_path = dest_parent_dir_path.clone().join(dirname);
            //println!("creating directory: {:?}", new_dir_path);
            fs::mkdir(&new_dir_path, USER_DIR).ok().expect("Cannot create destination subdir");
            self.generate_dir(&relative_path.clone().join(dirname));
        }
    }

    fn is_markdown_file(file_path: &Path) -> bool {
        let name = file_path.as_str().unwrap();
        let stat = fs::stat(file_path).ok().expect("Could not get stat for file");
        if stat.kind != FileType::RegularFile {
            return false;
        }
        if !name.ends_with(".md") && !name.ends_with(".markdown") {
            return false;
        }
        true
    }

    fn get_parent_links(&self, path: &Path, is_note: bool) -> Vec<Link> {
        if !is_note && path.filename().is_none() {
            Vec::new()
        } else {
            let mut result: Vec<Link> = vec![Link {
                name: String::from_str("root"),
                url: format!("{}/", self.base_url)
            }];
            let mut temp = path.clone().dir_path();
            while temp.filename().is_some() {
                result.insert(1, Link {
                    name: String::from_str(temp.filename_str().unwrap()),
                    url: format!("{}/{}", self.base_url, temp.as_str().unwrap())
                });
                temp.pop();
            }
            result
        }
    }

    fn create_dir_index(&self, dir_path: &Path, relative_path: &Path) {
        let new_file_path = dir_path.join("index.html");
        let dirname = dir_path.filename_str().unwrap();
        let mut file = File::create(&new_file_path).ok().expect("Could not create html file");
        fs::chmod(&new_file_path, USER_FILE).ok().expect("Couldn't chmod new file");

        // Name
        let name = if relative_path.as_str().unwrap() == "." {
            "root"
        } else {
            dirname
        };

        let mut dirs: Vec<Link> = Vec::new();
        let mut notes: Vec<Link> = Vec::new();

        let source_path = self.notes_source_path.clone().join(relative_path);
        match fs::readdir(&source_path) {
            Ok(items) => {
                for item in items.iter() {
                    if item.is_file() && Generator::is_markdown_file(item) {
                        let name = item.filestem_str().unwrap();
                        let url = relative_path.clone().join(format!("{}.html", name).as_slice());
                        notes.push(Link{
                            name: String::from_str(name),
                            url: String::from_str(format!("{}/{}", self.base_url, url.as_str().unwrap()).as_slice())
                        });
                    } else if Generator::dir_contains_note(item) {
                        let name = item.filename_str().unwrap();
                        let url = relative_path.clone().join(name);
                        dirs.push(Link{
                            name: String::from_str(name),
                            url: String::from_str(format!("{}/{}", self.base_url, url.as_str().unwrap()).as_slice())
                        });
                    }
                }
            },
            Err(_) => ()
        }

        let model = DirModel {
            name: String::from_str(name),
            parents: self.get_parent_links(relative_path, false),
            dirs: dirs,
            notes: notes,
            base_url: self.base_url.clone()
        };

        match self.handlebars.render(self.dir_template_name, &model) {
            Ok(rendered) => {
                file.write_str(rendered.as_slice())
                    .ok().expect("Could not write html to file");
            },
            Err(why) => panic!("Error rendering: {:?}", why)
        }
    }

    fn clean_dest(&self) {
        match fs::readdir(&self.root_dest_path) {
            Ok(items) => {
                for item in items.iter() {
                    if item.is_file() {
                        //println!("removing file: {:?}", item);
                        fs::unlink(item).ok().expect("Could not remove file");
                    } else {
                        //println!("removing directory: {:?}", item);
                        fs::rmdir_recursive(item).ok().expect("Could not remove directory");
                    }
                }
            },
            Err(_) => ()
        }
    }
}
<|endoftext|>


<commit_before>#![feature(question_mark)]
#![feature(associated_consts)]
#![feature(const_fn)]
#![feature(conservative_impl_trait)]

extern crate mio;
extern crate slab;
extern crate uuid;
extern crate byteorder;
extern crate graphsearch;
extern crate clap;
#[macro_use]
extern crate protocol as proto;

pub use self::proto_connection::{ProtoConnection, ProtoState};
pub use self::proto_node::ProtoNode;
pub use self::connection::*;
pub use self::network::{Network, Node, Edge};
pub use self::error::Error;
pub use self::protocol::Packet;
pub use self::path::Path;

pub mod proto_connection;
pub mod proto_node;
pub mod connection;
pub mod network;
pub mod error;
pub mod protocol;
pub mod path;

use mio::tcp::*;
use slab::Slab;

use uuid::Uuid;

const SERVER_TOKEN: mio::Token = mio::Token(usize::max_value() - 10);
const NEW_CONNECTION_TOKEN: mio::Token = mio::Token(usize::max_value() - 11);

const SERVER_PORT: u16 = 53371;
const SERVER_ADDRESS: (&'static str, u16) = ("127.0.0.1", SERVER_PORT);

const CLIENT_NAME: &'static str = "vanilla";
const CLIENT_VERSION: &'static str = env!("CARGO_PKG_VERSION");

const PROTOCOL_MAJOR: u16 = 0;
const PROTOCOL_REVISION: u16 = 0;

const DESCRIPTION: &'static str = "
    If you pass an address, it will connect to an existing node on
    some network, otherwise a new network will be created.
";

fn user_agent() -> protocol::UserAgent {
    protocol::UserAgent {
        client: format!("{} v{}", CLIENT_NAME, CLIENT_VERSION),
        protocol_major: PROTOCOL_MAJOR,
        protocol_revision: PROTOCOL_REVISION,
    }
}

pub struct ConnectedNode
{
    pub uuid: Uuid,
    pub listener: TcpListener,

    /// The network we are apart of.
    pub network: Network,
}

// Flow:
//
// Client sends 'JoinRequest' to some node
// Server responds with 'JoinResponse'
// Client is now ready to act as server.

pub enum State
{
    Unconnected,
    Pending(ProtoConnection),
    /// We are now a connected node in the network.
    Connected {
        node: ConnectedNode,

        pending_connections: Slab<ProtoNode, mio::Token>,
    },
}

pub struct Parapet
{
    pub state: State,
    pub poll: mio::Poll,
}

impl Parapet
{
    /// Create a new network.
    pub fn new<A>(addr: A) -> Result<Self, Error>
        where A: std::net::ToSocketAddrs {
        let mut poll = mio::Poll::new()?;

        let listener = Parapet::bind(&mut poll, addr)?;

        Ok(Parapet {
            state: State::Connected {
                node: ConnectedNode {
                    uuid: Uuid::new_v4(),
                    listener: listener,
                    network: Network::new(),
                },
                pending_connections: Slab::with_capacity(1024),
            },
            poll: poll,
        })
    }

    /// Create a new tcp listener locally.
    fn bind<A>(poll: &mio::Poll, addr: A) -> Result<TcpListener, Error>
        where A: std::net::ToSocketAddrs {
        let mut addresses = addr.to_socket_addrs()?;
        let address = addresses.next().expect("could not resolve address");

        let listener = TcpListener::bind(&address)?;

        poll.register(&listener, SERVER_TOKEN, mio::Ready::readable(),
            mio::PollOpt::edge())?;

        Ok(listener)
    }

    /// Connect to an existing network.
    /// * `addr` - Any node on the network.
    pub fn connect<A>(addr: A) -> Result<Self, std::io::Error>
        where A: std::net::ToSocketAddrs {
        let mut addresses = addr.to_socket_addrs()?;
        let address = addresses.next().expect("could not resolve address");

        let stream = TcpStream::connect(&address)?;

        let poll = mio::Poll::new()?;
        poll.register(&stream, NEW_CONNECTION_TOKEN, mio::Ready::writable(),
            mio::PollOpt::edge())?;

        Ok(Parapet {
            state: State::Pending(ProtoConnection::new(Connection {
                token: NEW_CONNECTION_TOKEN,
                protocol: proto::wire::stream::Connection::new(stream, proto::wire::middleware::pipeline::default()),
            })),
            poll: poll,
        })
    }

    /// Attempts to advance the current state if possible.
    pub fn advance(&mut self) -> Result<(), Error> {
        self.mutate_state(|parapet, state|
            if let State::Pending(mut proto_connection) = state {
                match proto_connection.state.clone() {
                    ProtoState::PendingPing => {
                        let ping = protocol::Ping {
                            user_agent: user_agent(),
                            // TODO: randomise this data
                            data: vec![6, 2, 6, 1, 8, 8],
                        };

                        proto_connection.connection.send_packet(&Packet::Ping(ping.clone()))?;
                        proto_connection.state = ProtoState::PendingPong { original_ping: ping };

                        Ok(State::Pending(proto_connection))
                    },
                    ProtoState::PendingJoinRequest => {
                        proto_connection.connection.send_packet(&Packet::JoinRequest(protocol::JoinRequest))?;

                        proto_connection.state = ProtoState::PendingJoinResponse;
                        Ok(State::Pending(proto_connection))
                    },
                    ProtoState::Complete { join_response } => {
                        let mut network: Network = join_response.network.into();
                        let listener = Parapet::bind(&mut parapet.poll, SERVER_ADDRESS)?;

                        network.set_connection(&join_response.my_uuid, proto_connection.connection);

                        Ok(State::Connected {
                            node: ConnectedNode {
                                uuid: join_response.your_uuid,
                                listener: listener,
                                network: network,
                            },
                            pending_connections: Slab::with_capacity(1024),
                        })
                    },
                    _ => Ok(State::Pending(proto_connection)), // we don't have to do anything.
                }
            } else {
                Ok(state)
            }
        )
    }

    pub fn run(&mut self) -> Result<(), Error> {
        // Create storage for events
        let mut events = mio::Events::with_capacity(1024);

        loop {
            self.poll.poll(&mut events, None).unwrap();

            self.advance()?;

            for event in events.iter() {
                match event.token() {
                    // A pending connection.
                    SERVER_TOKEN => {
                        if let State::Connected { ref mut node, ref mut pending_connections } = self.state {
                            let (socket, addr) = node.listener.accept()?;

                            println!("accepted connection from {:?}", addr);

                            let entry = pending_connections.vacant_entry().expect("ran out of connections");
                            let token = entry.index();

                            self.poll.register(&socket, token, mio::Ready::readable(),
                                mio::PollOpt::edge())?;

                            entry.insert(ProtoNode(ProtoConnection::new(Connection {
                                token: token,
                                protocol: proto::wire::stream::Connection::new(socket, proto::wire::middleware::pipeline::default()),
                            })));
                        } else {
                            // We only start listening after we are successfully connected to the
                            // network.
                            unreachable!();
                        }
                    },
                    token => {
                        match self.state {
                            State::Pending(ref mut proto_connection) => {
                                assert_eq!(token, NEW_CONNECTION_TOKEN);

                                match proto_connection.state.clone() {
                                    ProtoState::PendingPing => (),
                                    ProtoState::PendingPong { original_ping } => {
                                        if let Some(packet) = proto_connection.connection.receive_packet()? {
                                            if let Packet::Pong(pong) = packet {
                                                // Check if the echoed data is correct.
                                                if pong.data != original_ping.data {
                                                    return Err(Error::InvalidPong{
                                                        expected: original_ping.data.clone(),
                                                        received: pong.data,
                                                    });
                                                }

                                                // Ensure the protocol versions are compatible.
                                                if !pong.user_agent.is_compatible(&original_ping.user_agent) {
                                                    // proto_connection.connection.terminate("protocol versions are not compatible")?;

                                                    // FIXME: Remove the connection.
                                                    unimplemented!();
                                                }

                                                proto_connection.state = ProtoState::PendingJoinRequest;
                                            } else {
                                                return Err(Error::UnexpectedPacket { expected: "pong", received: packet })
                                            }
                                        } else {
                                            // we haven't received a full packet yet.
                                        }
                                    },
                                    ProtoState::PendingJoinRequest  => (),
                                    ProtoState::PendingJoinResponse => {
                                        if let Some(packet) = proto_connection.connection.receive_packet()? {
                                            if let Packet::JoinResponse(join_response) = packet {
                                                proto_connection.state = ProtoState::Complete { join_response: join_response };
                                            } else {
                                                return Err(Error::UnexpectedPacket { expected: "join response", received: packet })
                                            }
                                        }
                                    },
                                    ProtoState::Complete { .. } => {
                                        // nothing to do
                                    },
                                }
                            },
                            State::Connected { ref mut node, ref mut pending_connections } => {
                                let mut pending_connection = pending_connections.entry(token).unwrap();
                                pending_connection.get_mut().process_incoming_data(node)?;
                            },
                            State::Unconnected => unreachable!(),
                        }
                    },
                }
            }
        }
    }

    fn mutate_state<F>(&mut self, mut f: F) -> Result<(), Error>
        where F: FnMut(&mut Self, State) -> Result<State, Error> {
        let mut state = State::Unconnected;
        std::mem::swap(&mut state, &mut self.state);

        self.state = f(self, state)?;

        Ok(())
    }
}

fn main() {
    use clap::{App, Arg};

    let matches = App::new("parapet")
                      // .version("1.0")
                      .author("Dylan <dylanmckay34@gmail.com>")
                      .about("Peer-to-peer build system")
                      .after_help(DESCRIPTION)
                      .arg(Arg::with_name("address")
                           .help("The address of an existing node on a network to connect to")
                           .index(1))
                      .get_matches();

    let mut parapet = if let Some(address) = matches.value_of("address") {
        println!("connecting to existing network on {}", address);

        Parapet::connect(address).unwrap()
    } else {
        println!("running new network on {}:{}", SERVER_ADDRESS.0, SERVER_ADDRESS.1);

        // Create a new network.
        Parapet::new(SERVER_ADDRESS).unwrap()
    };

    parapet.run().unwrap();
}

<commit_msg>Fix bug in client<commit_after>#![feature(question_mark)]
#![feature(associated_consts)]
#![feature(const_fn)]
#![feature(conservative_impl_trait)]

extern crate mio;
extern crate slab;
extern crate uuid;
extern crate byteorder;
extern crate graphsearch;
extern crate clap;
#[macro_use]
extern crate protocol as proto;

pub use self::proto_connection::{ProtoConnection, ProtoState};
pub use self::proto_node::ProtoNode;
pub use self::connection::*;
pub use self::network::{Network, Node, Edge};
pub use self::error::Error;
pub use self::protocol::Packet;
pub use self::path::Path;

pub mod proto_connection;
pub mod proto_node;
pub mod connection;
pub mod network;
pub mod error;
pub mod protocol;
pub mod path;

use mio::tcp::*;
use slab::Slab;

use uuid::Uuid;

const SERVER_TOKEN: mio::Token = mio::Token(usize::max_value() - 10);
const NEW_CONNECTION_TOKEN: mio::Token = mio::Token(usize::max_value() - 11);

const SERVER_PORT: u16 = 53371;
const SERVER_ADDRESS: (&'static str, u16) = ("127.0.0.1", SERVER_PORT);

const CLIENT_NAME: &'static str = "vanilla";
const CLIENT_VERSION: &'static str = env!("CARGO_PKG_VERSION");

const PROTOCOL_MAJOR: u16 = 0;
const PROTOCOL_REVISION: u16 = 0;

const DESCRIPTION: &'static str = "
    If you pass an address, it will connect to an existing node on
    some network, otherwise a new network will be created.
";

fn user_agent() -> protocol::UserAgent {
    protocol::UserAgent {
        client: format!("{} v{}", CLIENT_NAME, CLIENT_VERSION),
        protocol_major: PROTOCOL_MAJOR,
        protocol_revision: PROTOCOL_REVISION,
    }
}

pub struct ConnectedNode
{
    pub uuid: Uuid,
    pub listener: TcpListener,

    /// The network we are apart of.
    pub network: Network,
}

// Flow:
//
// Client sends 'JoinRequest' to some node
// Server responds with 'JoinResponse'
// Client is now ready to act as server.

pub enum State
{
    Unconnected,
    Pending(ProtoConnection),
    /// We are now a connected node in the network.
    Connected {
        node: ConnectedNode,

        pending_connections: Slab<ProtoNode, mio::Token>,
    },
}

pub struct Parapet
{
    pub state: State,
    pub poll: mio::Poll,
}

impl Parapet
{
    /// Create a new network.
    pub fn new<A>(addr: A) -> Result<Self, Error>
        where A: std::net::ToSocketAddrs {
        let mut poll = mio::Poll::new()?;

        let listener = Parapet::bind(&mut poll, addr)?;

        Ok(Parapet {
            state: State::Connected {
                node: ConnectedNode {
                    uuid: Uuid::new_v4(),
                    listener: listener,
                    network: Network::new(),
                },
                pending_connections: Slab::with_capacity(1024),
            },
            poll: poll,
        })
    }

    /// Create a new tcp listener locally.
    fn bind<A>(poll: &mio::Poll, addr: A) -> Result<TcpListener, Error>
        where A: std::net::ToSocketAddrs {
        let mut addresses = addr.to_socket_addrs()?;
        let address = addresses.next().expect("could not resolve address");

        let listener = TcpListener::bind(&address)?;

        poll.register(&listener, SERVER_TOKEN, mio::Ready::readable(),
            mio::PollOpt::edge())?;

        Ok(listener)
    }

    /// Connect to an existing network.
    /// * `addr` - Any node on the network.
    pub fn connect<A>(addr: A) -> Result<Self, std::io::Error>
        where A: std::net::ToSocketAddrs {
        let mut addresses = addr.to_socket_addrs()?;
        let address = addresses.next().expect("could not resolve address");

        let stream = TcpStream::connect(&address)?;

        let poll = mio::Poll::new()?;
        poll.register(&stream, NEW_CONNECTION_TOKEN, mio::Ready::writable(),
            mio::PollOpt::edge())?;

        Ok(Parapet {
            state: State::Pending(ProtoConnection::new(Connection {
                token: NEW_CONNECTION_TOKEN,
                protocol: proto::wire::stream::Connection::new(stream, proto::wire::middleware::pipeline::default()),
            })),
            poll: poll,
        })
    }

    /// Attempts to advance the current state if possible.
    pub fn advance_new_connection_state(&mut self) -> Result<(), Error> {
        self.mutate_state(|parapet, state|
            if let State::Pending(mut proto_connection) = state {
                match proto_connection.state.clone() {
                    ProtoState::PendingPing => {
                        let ping = protocol::Ping {
                            user_agent: user_agent(),
                            // TODO: randomise this data
                            data: vec![6, 2, 6, 1, 8, 8],
                        };

                        proto_connection.connection.send_packet(&Packet::Ping(ping.clone()))?;
                        proto_connection.state = ProtoState::PendingPong { original_ping: ping };

                        Ok(State::Pending(proto_connection))
                    },
                    ProtoState::PendingJoinRequest => {
                        proto_connection.connection.send_packet(&Packet::JoinRequest(protocol::JoinRequest))?;

                        proto_connection.state = ProtoState::PendingJoinResponse;
                        Ok(State::Pending(proto_connection))
                    },
                    ProtoState::Complete { join_response } => {
                        let mut network: Network = join_response.network.into();
                        let listener = Parapet::bind(&mut parapet.poll, SERVER_ADDRESS)?;

                        network.set_connection(&join_response.my_uuid, proto_connection.connection);

                        Ok(State::Connected {
                            node: ConnectedNode {
                                uuid: join_response.your_uuid,
                                listener: listener,
                                network: network,
                            },
                            pending_connections: Slab::with_capacity(1024),
                        })
                    },
                    _ => Ok(State::Pending(proto_connection)), // we don't have to do anything.
                }
            } else {
                Ok(state)
            }
        )
    }

    pub fn run(&mut self) -> Result<(), Error> {
        // Create storage for events
        let mut events = mio::Events::with_capacity(1024);

        loop {
            self.poll.poll(&mut events, None).unwrap();

            for event in events.iter() {
                match event.token() {
                    // A pending connection.
                    SERVER_TOKEN => {
                        if let State::Connected { ref mut node, ref mut pending_connections } = self.state {
                            let (socket, addr) = node.listener.accept()?;

                            println!("accepted connection from {:?}", addr);

                            let entry = pending_connections.vacant_entry().expect("ran out of connections");
                            let token = entry.index();

                            self.poll.register(&socket, token, mio::Ready::readable(),
                                mio::PollOpt::edge())?;

                            entry.insert(ProtoNode(ProtoConnection::new(Connection {
                                token: token,
                                protocol: proto::wire::stream::Connection::new(socket, proto::wire::middleware::pipeline::default()),
                            })));
                        } else {
                            // We only start listening after we are successfully connected to the
                            // network.
                            unreachable!();
                        }
                    },
                    token => {
                        println!("event kind: {:?}", event.kind());

                        if event.kind().is_readable() {
                            self.advance_new_connection_state()?;
                        }

                        // TODO: check for `HUP` event.

                        match self.state {
                            State::Pending(ref mut proto_connection) => {
                                assert_eq!(token, NEW_CONNECTION_TOKEN);

                                match proto_connection.state.clone() {
                                    ProtoState::PendingPing => (),
                                    ProtoState::PendingPong { original_ping } => {
                                        if let Some(packet) = proto_connection.connection.receive_packet()? {
                                            if let Packet::Pong(pong) = packet {
                                                // Check if the echoed data is correct.
                                                if pong.data != original_ping.data {
                                                    return Err(Error::InvalidPong{
                                                        expected: original_ping.data.clone(),
                                                        received: pong.data,
                                                    });
                                                }

                                                // Ensure the protocol versions are compatible.
                                                if !pong.user_agent.is_compatible(&original_ping.user_agent) {
                                                    // proto_connection.connection.terminate("protocol versions are not compatible")?;

                                                    // FIXME: Remove the connection.
                                                    unimplemented!();
                                                }

                                                proto_connection.state = ProtoState::PendingJoinRequest;
                                            } else {
                                                return Err(Error::UnexpectedPacket { expected: "pong", received: packet })
                                            }
                                        } else {
                                            // we haven't received a full packet yet.
                                        }
                                    },
                                    ProtoState::PendingJoinRequest  => (),
                                    ProtoState::PendingJoinResponse => {
                                        if let Some(packet) = proto_connection.connection.receive_packet()? {
                                            if let Packet::JoinResponse(join_response) = packet {
                                                proto_connection.state = ProtoState::Complete { join_response: join_response };
                                            } else {
                                                return Err(Error::UnexpectedPacket { expected: "join response", received: packet })
                                            }
                                        }
                                    },
                                    ProtoState::Complete { .. } => {
                                        // nothing to do
                                    },
                                }
                            },
                            State::Connected { ref mut node, ref mut pending_connections } => {
                                let mut pending_connection = pending_connections.entry(token).unwrap();
                                pending_connection.get_mut().process_incoming_data(node)?;
                            },
                            State::Unconnected => unreachable!(),
                        }
                    },
                }
            }
        }
    }

    fn mutate_state<F>(&mut self, mut f: F) -> Result<(), Error>
        where F: FnMut(&mut Self, State) -> Result<State, Error> {
        let mut state = State::Unconnected;
        std::mem::swap(&mut state, &mut self.state);

        self.state = f(self, state)?;

        Ok(())
    }
}

fn main() {
    use clap::{App, Arg};

    let matches = App::new("parapet")
                      // .version("1.0")
                      .author("Dylan <dylanmckay34@gmail.com>")
                      .about("Peer-to-peer build system")
                      .after_help(DESCRIPTION)
                      .arg(Arg::with_name("address")
                           .help("The address of an existing node on a network to connect to")
                           .index(1))
                      .get_matches();

    let mut parapet = if let Some(address) = matches.value_of("address") {
        println!("connecting to existing network on {}", address);

        Parapet::connect(address).unwrap()
    } else {
        println!("running new network on {}:{}", SERVER_ADDRESS.0, SERVER_ADDRESS.1);

        // Create a new network.
        Parapet::new(SERVER_ADDRESS).unwrap()
    };

    parapet.run().unwrap();
}

<|endoftext|>


<commit_before>extern crate piston_window;
extern crate image;
extern crate rand;

mod cpu;
mod display;
mod instruction;
mod keyboard;

use piston_window::*;
use cpu::Cpu;
use keyboard::{ Keyboard, KeyMapping };
use std::fs::File;
use std::io::Read;
use std::env;
use std::process;
use std::time::Instant;

const ENLARGEMENT_FACTOR: u32 = 8;
const WINDOW_WIDTH: u32 = 64;
const WINDOW_HEIGHT: u32 = 32;

// Must be a multiple of 60 for the timers to work properly
const CLOCK_SPEED_HZ: u32 = 360;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        println!("Usage: chip8 [rom] [--debug (optional)] [--step (optional)]");
        process::exit(0);
    }
    let rom = &args[1];
    let debug = args.contains(&String::from("--debug"));
    let step = args.contains(&String::from("--step"));

    let width = WINDOW_WIDTH * ENLARGEMENT_FACTOR;
    let height = WINDOW_HEIGHT * ENLARGEMENT_FACTOR;

    let mut window = create_window(width, height);

    let mut file = File::open(rom)
        .expect("Unable to open the ROM file.");

    let mut game_data = Vec::new();
    file.read_to_end(&mut game_data)
        .expect("Unable to read the ROM file.",);

    let mut cpu = Cpu::new(game_data, CLOCK_SPEED_HZ, debug);
    let keyboard = Keyboard::new(KeyMapping::Improved);

    let cycle_time_millis: u128 = (1000 / CLOCK_SPEED_HZ).into();

    let mut clock = Instant::now();
    while let Some(e) = window.next() {

        let mut step_forward = false;

        if let Some(_) = e.render_args() {
            if cpu.draw_flag {
                draw_screen(&e, &cpu.get_screen(), &mut window);
                cpu.draw_flag = false
            }
        }

        if let Some(button) = e.press_args() {
            if button == Button::Keyboard(Key::Space) && step {
                step_forward = true;
            }

            if let Some(key_val) = keyboard.map_key(button) {
                cpu.set_key(key_val, true);
            }
        }

        if let Some(button) = e.release_args() {
            if let Some(key_val) = keyboard.map_key(button) {
                cpu.set_key(key_val, false);
            }
        }

        // If debugging is enabled, only cycle on space bar presses
        if step {
            if step_forward {
                cpu.cycle();
            }
        } else {
            let elapsed = clock.elapsed().as_millis();
            if elapsed >= cycle_time_millis {
                cpu.cycle();
                clock = Instant::now();
            }
        }
    }
}

fn create_window(width: u32, height: u32) -> PistonWindow {
    let opengl = OpenGL::V3_2;

    let mut window: PistonWindow = WindowSettings::new("chip8", (width, height))
        .exit_on_esc(true)
        .opengl(opengl)
        .build()
        .unwrap();

    window.set_max_fps(60);

    window
}

fn draw_screen(event: &Event, screen: &display::Screen, window: &mut PistonWindow) {
    window.draw_2d(event, |context, graphics| {
        piston_window::clear(color::BLACK, graphics);

        for (i, row) in screen.iter().enumerate() {
            for (j, val) in row.iter().enumerate() {
                if *val {
                    let dimensions = [
                        (j * ENLARGEMENT_FACTOR as usize) as f64,
                        (i * ENLARGEMENT_FACTOR as usize) as f64,
                        ENLARGEMENT_FACTOR as f64,
                        ENLARGEMENT_FACTOR as f64,
                    ];
                    Rectangle::new(color::WHITE).draw(
                        dimensions,
                        &context.draw_state,
                        context.transform,
                        graphics,
                    );
                }
            }
        }
    });
}

// TODO:
// 1) Fix display issues, it is currently not working at all
//     a) This might (and probably should) involve rewriting the display logic
// 2) Add command line argument for ROM file
// 3) Fix command line arguments, potentially add a create for arg parsing (docopt?)
// 4) Keyboard scheme?
// 5) The display should really be separate from the cpu....not sure how to do this though<commit_msg>Refactored arg parsing<commit_after>extern crate piston_window;
extern crate image;
extern crate rand;

mod cpu;
mod display;
mod instruction;
mod keyboard;

use piston_window::*;
use cpu::Cpu;
use keyboard::{ Keyboard, KeyMapping };
use std::fs::File;
use std::io::Read;
use std::env;
use std::process;
use std::time::Instant;

const ENLARGEMENT_FACTOR: u32 = 8;
const WINDOW_WIDTH: u32 = 64;
const WINDOW_HEIGHT: u32 = 32;

// Must be a multiple of 60 for the timers to work properly
const CLOCK_SPEED_HZ: u32 = 360;

struct Arguments {
    rom: String,
    step: bool,
    debug: bool
}

fn main() {
    let arguments = match parse_args() {
        Ok(args) => args,
        Err(_) => {
            println!("Usage: chip8 [rom] [--debug (optional)] [--step (optional)]");
            process::exit(0);
        }
    };

    let width = WINDOW_WIDTH * ENLARGEMENT_FACTOR;
    let height = WINDOW_HEIGHT * ENLARGEMENT_FACTOR;

    let mut window = create_window(width, height);

    let mut file = File::open(arguments.rom)
        .expect("Unable to open the ROM file.");

    let mut game_data = Vec::new();
    file.read_to_end(&mut game_data)
        .expect("Unable to read the ROM file.",);

    let mut cpu = Cpu::new(game_data, CLOCK_SPEED_HZ, arguments.debug);
    let keyboard = Keyboard::new(KeyMapping::Improved);

    let cycle_time_millis: u128 = (1000 / CLOCK_SPEED_HZ).into();

    let mut clock = Instant::now();
    while let Some(e) = window.next() {

        let mut step_forward = false;

        if let Some(_) = e.render_args() {
            if cpu.draw_flag {
                draw_screen(&e, &cpu.get_screen(), &mut window);
                cpu.draw_flag = false
            }
        }

        if let Some(button) = e.press_args() {
            if button == Button::Keyboard(Key::Space) && arguments.step {
                step_forward = true;
            }

            if let Some(key_val) = keyboard.map_key(button) {
                cpu.set_key(key_val, true);
            }
        }

        if let Some(button) = e.release_args() {
            if let Some(key_val) = keyboard.map_key(button) {
                cpu.set_key(key_val, false);
            }
        }

        // If debugging is enabled, only cycle on space bar presses
        if arguments.step {
            if step_forward {
                cpu.cycle();
            }
        } else {
            let elapsed = clock.elapsed().as_millis();
            if elapsed >= cycle_time_millis {
                cpu.cycle();
                clock = Instant::now();
            }
        }
    }
}

fn parse_args() -> Result<Arguments, String> {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        return Err(String::from("Too few arguments"))
    }
    let rom = &args[1];
    let debug = args.contains(&String::from("--debug"));
    let step = args.contains(&String::from("--step"));

    let args = Arguments {
        rom: String::from(rom),
        step: step,
        debug: debug
    };
    return Ok(args)
}

fn create_window(width: u32, height: u32) -> PistonWindow {
    let opengl = OpenGL::V3_2;

    let mut window: PistonWindow = WindowSettings::new("chip8", (width, height))
        .exit_on_esc(true)
        .opengl(opengl)
        .build()
        .unwrap();

    window.set_max_fps(60);

    window
}

fn draw_screen(event: &Event, screen: &display::Screen, window: &mut PistonWindow) {
    window.draw_2d(event, |context, graphics| {
        piston_window::clear(color::BLACK, graphics);

        for (i, row) in screen.iter().enumerate() {
            for (j, val) in row.iter().enumerate() {
                if *val {
                    let dimensions = [
                        (j * ENLARGEMENT_FACTOR as usize) as f64,
                        (i * ENLARGEMENT_FACTOR as usize) as f64,
                        ENLARGEMENT_FACTOR as f64,
                        ENLARGEMENT_FACTOR as f64,
                    ];
                    Rectangle::new(color::WHITE).draw(
                        dimensions,
                        &context.draw_state,
                        context.transform,
                        graphics,
                    );
                }
            }
        }
    });
}

// TODO:
// 1) Fix display issues, it is currently not working at all
//     a) This might (and probably should) involve rewriting the display logic
// 2) Add command line argument for ROM file
// 3) Fix command line arguments, potentially add a create for arg parsing (docopt?)
// 4) Keyboard scheme?
// 5) The display should really be separate from the cpu....not sure how to do this though<|endoftext|>