<commit_before>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate hyper;
extern crate hyper_native_tls;

mod gitignore;

use std::fs::{File, OpenOptions, create_dir};
use std::io::{BufReader, Result, Error, ErrorKind};
use std::io::prelude::*;
use std::process::exit;

use clap::{App, SubCommand};

#[derive(Deserialize)]
struct Config {
    mkdir: Option<Vec<String>>,
    touch: Option<Vec<String>>,
    exec: Option<Vec<String>>,
    gitignore: Option<Vec<String>>,
}

fn main() {
    let matches = App::new("test")
        .version("0.1")
        .author("Valentin B. <mail@mail.mail>")
        .about("Project manager")
        .args_from_usage("-l, --lang=[LANG]  'Set language configuration'")
        .subcommand(SubCommand::with_name("new")
                        .about("create new project")
                        .arg_from_usage("<NAME>   'The project name'"))
        .subcommand(SubCommand::with_name("init").about("initialize existing project"))
        .get_matches();
    let lang = matches.value_of("lang").unwrap().to_string();

    if let Some(matches) = matches.subcommand_matches("new") {
        println!("doing new {}", matches.value_of("NAME").unwrap());
    }

    let config: Config = match parse_config(lang) {
        Ok(c) => c,
        Err(err) => {
            println!("Error while parsing config! {:?}", err);
            exit(1);
        }
    };
    // println!("mkdir: {}", (config.mkdir.unwrap())[0]);
    for dir in config.mkdir.unwrap() {
        create_dir(dir);
    }

    match config.gitignore {
        Some(gi) => {
            let ign = gitignore::get_gitignore(gi).unwrap();
            let mut file = OpenOptions::new()
                .write(true)
                .append(true)
                .create(true)
                .open("gitignore")
                .unwrap();
            writeln!(file, "{}", ign);
        }
        None => {}
    }
    println!("touch: {}", (config.touch.unwrap())[0]);
    println!("exec: {}", (config.exec.unwrap())[0]);
}

fn parse_config(file_name: String) -> Result<Config> {
    let file = File::open(file_name)?;
    let mut reader = BufReader::new(file);
    let mut contents = String::new();

    reader.read_to_string(&mut contents)?;

    match toml::from_str(&contents) {
        Ok(c) => Ok(c),
        Err(err) => Err(Error::new(ErrorKind::InvalidInput, err)),
    }
}
<commit_msg>Add some tests and methods to create files<commit_after>#[macro_use]
extern crate serde_derive;
extern crate toml;
extern crate clap;
extern crate hyper;
extern crate hyper_native_tls;

mod gitignore;

use std::fs::{File, OpenOptions, create_dir};
use std::io::{BufReader, Result, Error, ErrorKind};
use std::io::prelude::*;
use std::path::Path;
use std::process::exit;

use clap::{App, SubCommand};

#[derive(Deserialize)]
struct Config {
    mkdir: Option<Vec<String>>,
    touch: Option<Vec<String>>,
    exec: Option<Vec<String>>,
    gitignore: Option<Vec<String>>,
}

fn main() {
    let matches = App::new("test")
        .version("0.1")
        .author("Valentin B. <mail@mail.mail>")
        .about("Project manager")
        .args_from_usage("-l, --lang=<LANG>  'Set language configuration'")
        .subcommand(SubCommand::with_name("new")
                        .about("create new project")
                        .arg_from_usage("<NAME>   'The project name'"))
        .subcommand(SubCommand::with_name("init").about("initialize existing project"))
        .get_matches();
    let lang = matches.value_of("lang").unwrap().to_string();

    if let Some(matches) = matches.subcommand_matches("new") {
        println!("doing new {}", matches.value_of("NAME").unwrap());
    }

    let config: Config = match parse_config(lang) {
        Ok(c) => c,
        Err(err) => {
            println!("Error while parsing config! {:?}", err);
            exit(1);
        }
    };

    if let Some(dirs) = config.mkdir {
        for dir in dirs {
            if !file_exists(&dir) {
                match create_dir(dir.clone()) {
                    Ok(_) => println!("Created directory '{}'", dir),
                    Err(e) => println!("Could not create directory '{}'. {:?}", dir, e),
                }
            } else {
                println!("Directory '{}' already exists", dir);
            }
        }
    }

    if let Some(gi) = config.gitignore {
        let ign = gitignore::get_gitignore(gi).unwrap();
        let file = OpenOptions::new()
            .create_new(true)
            .append(true)
            .open("gitignore");
        if let Ok(mut file) = file {
            writeln!(file, "{}", ign);
        } else {
            println!("Could not write .gitignore");
        }
    }

    if let Some(fnames) = config.touch {
        for file in fnames {
            if !create_file(&file) {
                println!("Could not create file '{}'", file);
            }
        }
    }

    if let Some(cmds) = config.exec {
        for cmd in cmds {
            exec(&cmd);
        }
    }
}

fn file_exists(name: &String) -> bool {
    Path::new(&name).exists()
}

fn exec(cmd: &String) {
    println!("exec: {}", cmd);
}

fn create_file(file_name: &String) -> bool {
    if !file_exists(&file_name) {
        match OpenOptions::new()
                  .append(true)
                  .create(true)
                  .open(file_name) {
            Ok(_) => true,
            Err(_) => false,
        }
    } else {
        false
    }
}

fn parse_config(file_name: String) -> Result<Config> {
    let file = File::open(file_name)?;
    let mut reader = BufReader::new(file);
    let mut contents = String::new();

    reader.read_to_string(&mut contents)?;

    match toml::from_str(&contents) {
        Ok(c) => Ok(c),
        Err(err) => Err(Error::new(ErrorKind::InvalidInput, err)),
    }
}

#[test]
fn test_file_exists() {
    assert!(file_exists(&"test/foo.toml".to_string()));
    assert!(!file_exists(&"test/none".to_string()));
}

#[test]
fn test_parse_config() {
    let conf = parse_config("test/foo.toml".to_string()).unwrap();
    let gi = conf.gitignore.unwrap();
    assert_eq!(gi, vec!["rust".to_string(), "vim".to_string()]);

    let mkd = conf.mkdir.unwrap();
    assert_eq!(mkd, vec!["b".to_string()]);

    let touch = conf.touch.unwrap();
    assert_eq!(touch, vec!["a".to_string()]);

    let exec = conf.exec.unwrap();
    assert_eq!(exec, vec!["c".to_string()]);
}
<|endoftext|>


<commit_before>extern crate walkdir;
extern crate regex;
extern crate getopts;
extern crate ansi_term;

use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::io::Write;
use std::path::Path;
use std::process;

use walkdir::{WalkDir, DirEntry, WalkDirIterator};
use regex::{Regex, RegexBuilder};
use getopts::Options;
use ansi_term::Colour;

struct FdOptions {
    case_sensitive: bool,
    search_full_path: bool,
    search_hidden: bool,
    follow_links: bool,
    colored: bool
}

/// Print a search result to the console.
fn print_entry(entry: &DirEntry, path_str: &str, config: &FdOptions) {
    if config.colored {
        let style = match entry {
            e if e.path_is_symbolic_link() => Colour::Purple,
            e if e.path().is_dir()         => Colour::Cyan,
            _                              => Colour::White
        };
        println!("{}", style.paint(path_str));
    } else {
        println!("{}", path_str);
    }
}

/// Check if filename of entry starts with a dot.
fn is_hidden(entry: &DirEntry) -> bool {
    entry.file_name()
         .to_str()
         .map(|s| s.starts_with("."))
         .unwrap_or(false)
}

/// Recursively scan the given root path and search for files / pathnames
/// matching the pattern.
fn scan(root: &Path, pattern: &Regex, config: &FdOptions) {
    let walker = WalkDir::new(root)
                     .follow_links(config.follow_links)
                     .into_iter()
                     .filter_entry(|e| config.search_hidden || !is_hidden(e))
                     .filter_map(|e| e.ok())
                     .filter(|e| e.path() != root);

    for entry in walker {
        let path_rel = match entry.path().strip_prefix(root) {
            Ok(p) => p,
            Err(_) => continue
        };

        if let Some(path_str) = path_rel.to_str() {
            let res =
                if config.search_full_path {
                    pattern.find(path_str)
                } else {
                    if !path_rel.is_file() { continue }

                    path_rel.file_name()
                            .and_then(OsStr::to_str)
                            .and_then(|s| pattern.find(s))
                };

            res.map(|_| print_entry(&entry, path_str, &config));
        }
    }
}

/// Print error message to stderr and exit with status `1`.
fn error(message: &str) -> ! {
    writeln!(&mut std::io::stderr(), "{}", message)
        .expect("Failed writing to stderr");
    process::exit(1);
}

fn main() {
    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help message");
    opts.optflag("s", "sensitive",
                      "case-sensitive search (default: smart case)");
    opts.optflag("f", "filename",
                      "search filenames only (default: full path)");
    opts.optflag("", "hidden",
                      "search hidden files/directories (default: off)");
    opts.optflag("F", "follow", "follow symlinks (default: off)");
    opts.optflag("n", "no-color", "do not colorize output");

    let matches = match opts.parse(&args[1..]) {
        Ok(m)  => m,
        Err(e) => error(e.description())
    };

    if matches.opt_present("h") {
        let brief = "Usage: fd [PATTERN]";
        print!("{}", opts.usage(&brief));
        process::exit(1);
    }

    let empty = String::new();
    let pattern = matches.free.get(0).unwrap_or(&empty);

    let current_dir_buf = match env::current_dir() {
        Ok(cd) => cd,
        Err(_) => error("Could not get current directory!")
    };
    let current_dir = current_dir_buf.as_path();

    let config = FdOptions {
        // The search will be case-sensitive if the command line flag is set or
        // if the pattern has an uppercase character (smart case).
        case_sensitive:    matches.opt_present("sensitive") ||
                           pattern.chars().any(char::is_uppercase),
        search_full_path: !matches.opt_present("filename"),
        search_hidden:     matches.opt_present("hidden"),
        colored:          !matches.opt_present("no-color"),
        follow_links:      matches.opt_present("follow")
    };

    match RegexBuilder::new(pattern)
              .case_insensitive(!config.case_sensitive)
              .build() {
        Ok(re)   => scan(&current_dir, &re, &config),
        Err(err) => error(err.description())
    }
}
<commit_msg>Configurable search depth, closes #13<commit_after>extern crate walkdir;
extern crate regex;
extern crate getopts;
extern crate ansi_term;

use std::env;
use std::error::Error;
use std::ffi::OsStr;
use std::io::Write;
use std::path::Path;
use std::process;

use walkdir::{WalkDir, DirEntry, WalkDirIterator};
use regex::{Regex, RegexBuilder};
use getopts::Options;
use ansi_term::Colour;

struct FdOptions {
    case_sensitive: bool,
    search_full_path: bool,
    search_hidden: bool,
    follow_links: bool,
    colored: bool,
    max_depth: usize
}

/// Print a search result to the console.
fn print_entry(entry: &DirEntry, path_str: &str, config: &FdOptions) {
    if config.colored {
        let style = match entry {
            e if e.path_is_symbolic_link() => Colour::Purple,
            e if e.path().is_dir()         => Colour::Cyan,
            _                              => Colour::White
        };
        println!("{}", style.paint(path_str));
    } else {
        println!("{}", path_str);
    }
}

/// Check if filename of entry starts with a dot.
fn is_hidden(entry: &DirEntry) -> bool {
    entry.file_name()
         .to_str()
         .map(|s| s.starts_with("."))
         .unwrap_or(false)
}

/// Recursively scan the given root path and search for files / pathnames
/// matching the pattern.
fn scan(root: &Path, pattern: &Regex, config: &FdOptions) {
    let walker = WalkDir::new(root)
                     .follow_links(config.follow_links)
                     .max_depth(config.max_depth)
                     .into_iter()
                     .filter_entry(|e| config.search_hidden || !is_hidden(e))
                     .filter_map(|e| e.ok())
                     .filter(|e| e.path() != root);

    for entry in walker {
        let path_rel = match entry.path().strip_prefix(root) {
            Ok(p) => p,
            Err(_) => continue
        };

        if let Some(path_str) = path_rel.to_str() {
            let res =
                if config.search_full_path {
                    pattern.find(path_str)
                } else {
                    if !path_rel.is_file() { continue }

                    path_rel.file_name()
                            .and_then(OsStr::to_str)
                            .and_then(|s| pattern.find(s))
                };

            res.map(|_| print_entry(&entry, path_str, &config));
        }
    }
}

/// Print error message to stderr and exit with status `1`.
fn error(message: &str) -> ! {
    writeln!(&mut std::io::stderr(), "{}", message)
        .expect("Failed writing to stderr");
    process::exit(1);
}

fn main() {
    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help message");
    opts.optflag("s", "sensitive",
                      "case-sensitive search (default: smart case)");
    opts.optflag("f", "filename",
                      "search filenames only (default: full path)");
    opts.optflag("", "hidden",
                      "search hidden files/directories (default: off)");
    opts.optflag("F", "follow", "follow symlinks (default: off)");
    opts.optflag("n", "no-color", "do not colorize output");
    opts.optopt("d", "max-depth", "maximum search depth", "DEPTH");

    let matches = match opts.parse(&args[1..]) {
        Ok(m)  => m,
        Err(e) => error(e.description())
    };

    if matches.opt_present("h") {
        let brief = "Usage: fd [PATTERN]";
        print!("{}", opts.usage(&brief));
        process::exit(1);
    }

    let empty = String::new();
    let pattern = matches.free.get(0).unwrap_or(&empty);

    let current_dir_buf = match env::current_dir() {
        Ok(cd) => cd,
        Err(_) => error("Could not get current directory!")
    };
    let current_dir = current_dir_buf.as_path();

    let config = FdOptions {
        // The search will be case-sensitive if the command line flag is set or
        // if the pattern has an uppercase character (smart case).
        case_sensitive:    matches.opt_present("sensitive") ||
                           pattern.chars().any(char::is_uppercase),
        search_full_path: !matches.opt_present("filename"),
        search_hidden:     matches.opt_present("hidden"),
        colored:          !matches.opt_present("no-color"),
        follow_links:      matches.opt_present("follow"),
        max_depth:
            matches.opt_str("max-depth")
                   .and_then(|ds| usize::from_str_radix(&ds, 10).ok())
                   .unwrap_or(25)
    };

    match RegexBuilder::new(pattern)
              .case_insensitive(!config.case_sensitive)
              .build() {
        Ok(re)   => scan(&current_dir, &re, &config),
        Err(err) => error(err.description())
    }
}
<|endoftext|>


<commit_before>mod error;

// My IP address providers
mod myip;

// DNS providers
mod dns;

// Configuration
mod config;

#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate xmlrpc;
extern crate rustc_serialize;
extern crate regex;
extern crate ip;
extern crate hyper;

use clap::{Arg, App};
use std::env;

use config::Config;
use dns::DNSProvider;
use dns::DNSProviderFactory;
use error::Result;
use myip::GetMyIpAddr;
use myip::IpProvider;
use std::process;

fn main() {
    let config = build_config();

    match main_with_errors(&config) {
        Ok(_) => info!("Process ends with success"),
        Err(err) => {
            error!("Process failed with result: {}", err);
            process::exit(-1);
        }
    }
}

fn build_config() -> Config {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Generic DNS update, useful to update your dynamic IP address into your DNS provider zone file, e.g. Gandi or Go Daddy.\n\
            IP address can be read from several HTTP providers or from stdin.\n\
            Only Gandi DNS provider is implemented in this version.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            -f --force 'Force new zonefile creation even if IP address isn\'t modified'
            -r --record-name=[record_name] 'Name of the A record to update or create (without domain)'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("ip_provider")
            .help("IP address provider to use to get your own IP address.\n                                       \
                Available values for <ip-provider>:\n                                        \
                 opendns       : OpenDNS\n                                        \
                 -             : read IP address from stdin\n                                        \
                 sfrlaboxfibre : French 'SFR Labox Fibre' subscribers")
            .short("i")
            .long("ip-provider")
            .takes_value(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    match matches.occurrences_of("verbose") {
        0 => env::set_var("RUST_LOG", "INFO"),
        1 => {
            println!("Verbose mode");
            env::set_var("RUST_LOG", "DEBUG");
        }
        2 | _=> {
            println!("More verbose mode");
            env::set_var("RUST_LOG", "TRACE");
        }
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let force = matches.is_present("force");
    debug!("Force: {}", force);

    let ip_provider = value_t_or_exit!(matches.value_of("ip_provider"), IpProvider);
    debug!("IP address provider: {:?}", ip_provider);

    Config {
        apikey: apikey.to_owned(),
        domain: domain.to_owned(),
        record_name: record_name.to_owned(),
        force: force,
        ip_provider: ip_provider,
    }
}

fn main_with_errors(config: &Config) -> Result<()> {

    let my_ip = try!(config.ip_provider.get_my_ip_addr());

    info!("My IP address: {}", my_ip);

    // Force Gandi DNS provider for now
    let mut dns_provider = dns::DNSProviderFactory::build(config);

    try!(dns_provider.init(&config.domain));

    match my_ip {
        ip::IpAddr::V6(_)
            if !dns_provider.handle_ipv6_addr() => panic!("You cannot use IP v6 addresses with the selected DNS provider"),
        _ => (),
    }

    let maybe_checked = try!(dns_provider.is_record_already_declared(&config.record_name));

    match maybe_checked {
        Some(ip_addr) => {
            debug!("Record already declared, with IP address: {}", &ip_addr);

            if !config.force && (&ip_addr == &my_ip) {
                info!("IP address not modified, no record to update");
                Ok(())
            } else {
                info!("Update record '{}' with IP address '{}'", &config.record_name, &my_ip);
                Ok(try!(dns_provider.update_record(&config.record_name, &my_ip)))
            }
        }
        None => Ok(try!(dns_provider.create_record(&config.record_name, &my_ip)))
    }
}
<commit_msg>Check IP address version (v4 or v6) sooner in the whole process<commit_after>mod error;

// My IP address providers
mod myip;

// DNS providers
mod dns;

// Configuration
mod config;

#[macro_use]
extern crate clap;

#[macro_use]
extern crate log;
extern crate env_logger;

extern crate xmlrpc;
extern crate rustc_serialize;
extern crate regex;
extern crate ip;
extern crate hyper;

use clap::{Arg, App};
use std::env;

use config::Config;
use dns::DNSProvider;
use dns::DNSProviderFactory;
use error::Result;
use myip::GetMyIpAddr;
use myip::IpProvider;
use std::process;

fn main() {
    let config = build_config();

    match main_with_errors(&config) {
        Ok(_) => info!("Process ends with success"),
        Err(err) => {
            error!("Process failed with result: {}", err);
            process::exit(-1);
        }
    }
}

fn build_config() -> Config {
    let matches = App::new("gdu")
        .version(&crate_version!()[..])
        .author("Damien Lecan <dev@dlecan.com>")
        .about("Generic DNS update, useful to update your dynamic IP address into your DNS provider zone file, e.g. Gandi or Go Daddy.\n\
            IP address can be read from several HTTP providers or from stdin.\n\
            Only Gandi DNS provider is implemented in this version.")
        .args_from_usage(
            "-a --apikey=[apikey] 'Your API key provided by Gandi'
            -d --domain=[domain] 'The domain name whose active zonefile will be updated, e.g. \"domain.com\"'
            -n --dry-run 'Dry run, don't really update Gandi zone file'
            -f --force 'Force new zonefile creation even if IP address isn\'t modified'
            -r --record-name=[record_name] 'Name of the A record to update or create (without domain)'
            [verbose]... -v 'Verbose mode'")
        .arg(Arg::with_name("ip_provider")
            .help("IP address provider to use to get your own IP address.\n                                       \
                Available values for <ip-provider>:\n                                        \
                 opendns       : OpenDNS\n                                        \
                 -             : read IP address from stdin\n                                        \
                 sfrlaboxfibre : French 'SFR Labox Fibre' subscribers")
            .short("i")
            .long("ip-provider")
            .takes_value(true)
            .multiple(false))
        .get_matches();

    // Init logging to DEBUG only if user requires it
    match matches.occurrences_of("verbose") {
        0 => env::set_var("RUST_LOG", "INFO"),
        1 => {
            println!("Verbose mode");
            env::set_var("RUST_LOG", "DEBUG");
        }
        2 | _=> {
            println!("More verbose mode");
            env::set_var("RUST_LOG", "TRACE");
        }
    }
    env_logger::init().unwrap();

    let apikey = matches.value_of("apikey").unwrap();
    debug!("Using apikey: {}", apikey);

    let domain = matches.value_of("domain").unwrap();
    debug!("Using domain: {}", domain);

    let record_name = matches.value_of("record_name").unwrap();
    debug!("Using record name: {}", record_name);

    let dry_run = matches.is_present("dry-run");
    debug!("Dry run: {}", dry_run);

    let force = matches.is_present("force");
    debug!("Force: {}", force);

    let ip_provider = value_t_or_exit!(matches.value_of("ip_provider"), IpProvider);
    debug!("IP address provider: {:?}", ip_provider);

    Config {
        apikey: apikey.to_owned(),
        domain: domain.to_owned(),
        record_name: record_name.to_owned(),
        force: force,
        ip_provider: ip_provider,
    }
}

fn main_with_errors(config: &Config) -> Result<()> {

    let my_ip = try!(config.ip_provider.get_my_ip_addr());

    info!("My IP address: {}", my_ip);

    let mut dns_provider = dns::DNSProviderFactory::build(config);

    match my_ip {
        ip::IpAddr::V6(_)
            if !dns_provider.handle_ipv6_addr() => panic!("You cannot use IP v6 addresses with the selected DNS provider"),
        _ => (),
    }

    try!(dns_provider.init(&config.domain));

    let maybe_checked = try!(dns_provider.is_record_already_declared(&config.record_name));

    match maybe_checked {
        Some(ip_addr) => {
            debug!("Record already declared, with IP address: {}", &ip_addr);

            if !config.force && (&ip_addr == &my_ip) {
                info!("IP address not modified, no record to update");
                Ok(())
            } else {
                info!("Update record '{}' with IP address '{}'", &config.record_name, &my_ip);
                Ok(try!(dns_provider.update_record(&config.record_name, &my_ip)))
            }
        }
        None => Ok(try!(dns_provider.create_record(&config.record_name, &my_ip)))
    }
}
<|endoftext|>


<commit_before>use std::net::{TcpListener, TcpStream};
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use std::error::Error;
use std::collections::HashMap;

fn main() {

    let listener = TcpListener::bind("127.0.0.1:8000")
        .expect("failed to start server");

    #[derive(Debug)]
    enum HTTPMethod {
        POST,
        GET,
        PUT,
        HEAD,
        OPTIONS,
        DELETE,
        CONNECT,
        TRACE,
        NONE,
        Other(String),
    }

    impl Default for HTTPMethod {
        fn default() -> HTTPMethod { HTTPMethod::NONE }
    }

    #[derive(Debug)]
    #[derive(Default)]
    struct Request {
        method: HTTPMethod,
        request_target: String,
        http_version: String,
        content: Option<String>,
        headers: HashMap<String, String>,
    }

    #[derive(Debug)]
    #[derive(Default)]
    struct Response {
        http_version: String,
        status_code: String,
        reason_phrase: String,
        content_type: String,
        content: Vec<u8>,
    }

    impl Response {
        // couldn't figure how to make &[u8] + as_bytes() work,
        // instead of Vec<u8> + into_bytes()
        fn get_headers_as_bytes(&self) -> Vec<u8> {
            let content_length = self.content.len();
            let bytes = format!("{} {} {}\r\n\
                                Content-Type: {}\r\n\
                                Content-Length: {}\r\n\r\n",
                self.http_version,
                self.status_code,
                self.reason_phrase,
                self.content_type,
                content_length
                ).into_bytes();
            bytes
        }
    }

    fn read_request_headers<R: Read>(reader: &mut BufReader<R>,
                                     request: &mut Request) {
        let mut l: String = String::new();

        {
            // first line contains method, target, version
            reader.read_line(&mut l).unwrap();
            l = l.trim_right().to_string();
            let mut iter = l.split_whitespace();
            request.method = match iter.next().unwrap() {
                "GET" => HTTPMethod::GET,
                "POST" => HTTPMethod::POST,
                "PUT" => HTTPMethod::PUT,
                "HEAD" => HTTPMethod::HEAD,
                "OPTIONS" => HTTPMethod::OPTIONS,
                "DELETE" => HTTPMethod::DELETE,
                "CONNECT" => HTTPMethod::CONNECT,
                "TRACE" => HTTPMethod::TRACE,
                m => HTTPMethod::Other(m.to_string()),
            };
            request.request_target = iter.next().unwrap().to_string();
            request.http_version = iter.next().unwrap().to_string();
        }

        loop {
            // loop on headers. Empty line closes the headers block.
            l = String::new();
            reader.read_line(&mut l).unwrap();
            l = l.trim_right().to_string();

            match l.as_str() {
                "" => {
                    break;
                },
                header => {
                    match header.find(":") {
                        Some(byte) => {
                            let (name, value) = header.split_at(byte);
                            let value = value[1..].trim();
                            request.headers.insert(name.to_string(),
                                                   value.to_string());
                        },
                        None => println!("No ':' for this header: {:?}",
                                         header),
                    };
                },
            }
        }
    }

    fn read_request_body<R: Read>(reader: &mut BufReader<R>,
                                  request: &mut Request) {
        // reading request body based on Content-Length
        match request.headers.get("Content-Length") {
            Some(value) => {
                let mut buf = String::new();
                let length: u64 = value.parse().unwrap();
                let mut handle = reader.by_ref().take(length);
                let read = handle.read_to_string(&mut buf).unwrap();
                assert_eq!(length, read as u64);
                request.content = Some(buf);
            },
            None => println!("No body"),
        }
    }

    fn read_request(stream: &TcpStream) -> Request {
        let mut reader = BufReader::new(stream);
        let mut request: Request = Default::default();

        read_request_headers(&mut reader, &mut request);

        read_request_body(&mut reader, &mut request);

        let mut stream = stream;
        stream.flush().expect("stream couldn't be flushed");
        println!("Finished reading request");

        request
    }

    fn build_response(request: &Request) -> Response {
        let (content_type, content): (&str, Vec<u8>) = match request.request_target.as_str() {
            "/" => ("text/html",String::from("Index").into_bytes()),
            resource => {
                // TODO: find a better way to get file type
                let file_type = match resource.rfind(".") {
                    Some(byte) => {
                        match resource.split_at(byte) {
                            ("", fn_ext) => "text/plain",
                            (fn_core, "") => "text/plain",
                            (_, fn_ext) => {
                                println!("{}", fn_ext);
                                match fn_ext {
                                    ".ico" => "image/x-icon",
                                    _ => "text/html",
                                }
                            },
                        }
                    },
                    None => "text/plain",
                };
                println!("{}", file_type);
                match File::open(resource.trim_left_matches("/")) {
                    Ok(mut file) => {
                        let mut contents = Vec::new();
                        file.read_to_end(&mut contents)
                            .expect("couldn't read file");
                        (file_type, contents)
                    },
                    Err(why) => {
                        println!("couldn't read {}: {}",
                                         resource,
                                         why.description());
                        ("text/html",String::from("404").into_bytes())
                    },
                }
            },
        };

        Response {
            http_version: "HTTP/1.1".to_string(),
            status_code: "200".to_string(),
            reason_phrase: "OK".to_string(),
            content_type: content_type.to_string(),
            content: content,
        }
    }

    fn handle_client(stream: TcpStream) {
        println!("Connection received");

        let request = read_request(&stream);
        println!("{:?}", request);

        let response = build_response(&request);

        let mut ostream = stream.try_clone().expect("clone failed...");
        ostream.write_all(response.get_headers_as_bytes().as_slice())
            .expect("write failed");
        ostream.write_all(response.content.as_slice())
            .expect("write failed");

        ostream.flush().expect("stream couldn't be flushed");
    }

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_client(stream);
            }
            Err(e) => println!("{:?}", e)
        }
    }
}
<commit_msg>1. Move request_line read to its own read_request_line() function 2. Move functions and structs outside of main() 3. Use "if let" construct in read_request_body()<commit_after>use std::net::{TcpListener, TcpStream};
use std::io::prelude::*;
use std::io::BufReader;
use std::fs::File;
use std::error::Error;
use std::collections::HashMap;

#[derive(Debug)]
enum HTTPMethod {
    POST,
    GET,
    PUT,
    HEAD,
    OPTIONS,
    DELETE,
    CONNECT,
    TRACE,
    NONE,
    Other(String),
}

impl Default for HTTPMethod {
    fn default() -> HTTPMethod { HTTPMethod::NONE }
}

#[derive(Debug)]
#[derive(Default)]
struct Request {
    method: HTTPMethod,
    request_target: String,
    http_version: String,
    content: Option<String>,
    headers: HashMap<String, String>,
}

#[derive(Debug)]
#[derive(Default)]
struct Response {
    http_version: String,
    status_code: String,
    reason_phrase: String,
    content_type: String,
    content: Vec<u8>,
}

impl Response {
    // couldn't figure how to make &[u8] + as_bytes() work,
    // instead of Vec<u8> + into_bytes()
    fn get_headers_as_bytes(&self) -> Vec<u8> {
        let content_length = self.content.len();
        let bytes = format!("{} {} {}\r\n\
                            Content-Type: {}\r\n\
                            Content-Length: {}\r\n\r\n",
            self.http_version,
            self.status_code,
            self.reason_phrase,
            self.content_type,
            content_length
            ).into_bytes();
        bytes
    }
}

fn read_request_line<R: Read>(reader: &mut BufReader<R>,
                              request: &mut Request) {

    let mut l: String = String::new();
    // first line of a request contains method, target, version
    reader.read_line(&mut l).unwrap();
    l = l.trim_right().to_string();
    let mut iter = l.split_whitespace();
    request.method = match iter.next().unwrap() {
        "GET" => HTTPMethod::GET,
        "POST" => HTTPMethod::POST,
        "PUT" => HTTPMethod::PUT,
        "HEAD" => HTTPMethod::HEAD,
        "OPTIONS" => HTTPMethod::OPTIONS,
        "DELETE" => HTTPMethod::DELETE,
        "CONNECT" => HTTPMethod::CONNECT,
        "TRACE" => HTTPMethod::TRACE,
        m => HTTPMethod::Other(m.to_string()),
    };
    request.request_target = iter.next().unwrap().to_string();
    request.http_version = iter.next().unwrap().to_string();
}

fn read_request_headers<R: Read>(reader: &mut BufReader<R>,
                                 request: &mut Request) {

    let mut l: String;
    loop {
        // loop on headers. Empty line closes the headers block.
        l = String::new();
        reader.read_line(&mut l).unwrap();
        l = l.trim_right().to_string();

        match l.as_str() {
            "" => {
                break;
            },
            header => {
                match header.find(":") {
                    Some(byte) => {
                        let (name, value) = header.split_at(byte);
                        let value = value[1..].trim();
                        request.headers.insert(name.to_string(),
                                               value.to_string());
                    },
                    None => println!("No ':' for this header: {:?}",
                                     header),
                };
            },
        }
    }
}

fn read_request_body<R: Read>(reader: &mut BufReader<R>,
                              request: &mut Request) {
    // read request body based on Content-Length
    let clh = request.headers.get("Content-Length");
    if let Some(value) = clh {
        let mut buf = String::new();
        let length: u64 = value.parse().unwrap();
        let mut handle = reader.by_ref().take(length);
        let read = handle.read_to_string(&mut buf).unwrap();
        // check that we read the same number of bytes as expected
        assert_eq!(length, read as u64);
        request.content = Some(buf);
    } else {
        println!("No body");
    }
}

fn read_request(stream: &TcpStream) -> Request {
    let mut reader = BufReader::new(stream);
    let mut request: Request = Default::default();

    read_request_line(&mut reader, &mut request);
    read_request_headers(&mut reader, &mut request);
    read_request_body(&mut reader, &mut request);

    let mut stream = stream;
    stream.flush().expect("stream couldn't be flushed");
    println!("Finished reading request");

    request
}

fn build_response(request: &Request) -> Response {
    let (content_type, content): (&str, Vec<u8>) = match request.request_target.as_str() {
        "/" => ("text/html","Index".to_string().into_bytes()),
        resource => {
            // TODO: find a better way to infer file type
            let file_type = match resource.rfind(".") {
                Some(byte) => {
                    match resource.split_at(byte) {
                        ("", _) => "text/plain",
                        (_, "") => "text/plain",
                        (_, fn_ext) => {
                            println!("{}", fn_ext);
                            match fn_ext {
                                ".ico" => "image/x-icon",
                                ".html" | ".htm" => "text/html",
                                _ => "text/plain",
                            }
                        },
                    }
                },
                None => "text/plain",
            };
            println!("{}", file_type);
            match File::open(resource.trim_left_matches("/")) {
                Ok(mut file) => {
                    let mut contents = Vec::new();
                    file.read_to_end(&mut contents)
                        .expect("couldn't read file");
                    (file_type, contents)
                },
                Err(why) => {
                    println!("couldn't read {}: {}",
                                     resource,
                                     why.description());
                    ("text/html",String::from("404").into_bytes())
                },
            }
        },
    };

    Response {
        http_version: "HTTP/1.1".to_string(),
        status_code: "200".to_string(),
        reason_phrase: "OK".to_string(),
        content_type: content_type.to_string(),
        content: content,
    }
}

fn handle_client(stream: TcpStream) {
    println!("Connection received");

    let request = read_request(&stream);
    println!("{:?}", request);

    let response = build_response(&request);

    let mut ostream = stream.try_clone().expect("clone failed...");
    ostream.write_all(response.get_headers_as_bytes().as_slice())
        .expect("write failed");
    ostream.write_all(response.content.as_slice())
        .expect("write failed");

    ostream.flush().expect("stream couldn't be flushed");
}

fn main() {

    let listener = TcpListener::bind("127.0.0.1:8000")
        .expect("failed to start server");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_client(stream);
            }
            Err(e) => println!("{:?}", e)
        }
    }
}
<|endoftext|>


<commit_before>// Spec found at https://tools.ietf.org/html/rfc959

/*
 * FIXME Filezilla says: Le serveur ne supporte pas les caractères non-ASCII.
 * FIXME: The list command should specify which files are directory.
 */

extern crate bytes;
#[macro_use]
extern crate cfg_if;
extern crate futures;
extern crate time;
extern crate tokio_core;
extern crate tokio_io;

mod cmd; // FIXME: rename this module.
mod codec;
mod ftp;

use std::cell::RefCell;
use std::ffi::OsStr;
use std::fmt::Display;
use std::fs::{read_dir, DirEntry, Metadata};
use std::io::{self, Write};
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::{Component, PathBuf};
use std::rc::Rc;
use std::net::TcpListener as StdListener;
use std::net::TcpStream as StdStream;

use futures::{AsyncSink, Future, Sink, Stream};
use futures::stream::SplitSink;
use tokio_core::reactor::{Core, Handle};
use tokio_core::net::{TcpListener, TcpStream};
use tokio_io::AsyncRead;
use tokio_io::codec::{Encoder, Framed};

use cmd::{Command, TransferType};
use codec::{FtpCodec, StringCodec};
use ftp::{Answer, ResultCode};

const DEFAULT_PORT: u16 = 4321;
const MONTHS: [&'static str; 12] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

type Writer = SplitSink<Framed<TcpStream, FtpCodec>>;

cfg_if! {
    if #[cfg(windows)] {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::windows::prelude::*;
            (time::at(time::Timespec::new(meta.last_write_time())), meta.file_size())
        }
    } else {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::unix::prelude::*;
            (time::at(time::Timespec::new(meta.mtime(), 0)), meta.size())
        }
    }
}

fn add_file_info(entry: DirEntry, out: &mut String) {
    let path = entry.path(); // TODO: handle error.
    let extra = if path.is_dir() { "/" } else { "" };
    let is_dir = if path.is_dir() { "d" } else { "-" };

    let meta = ::std::fs::metadata(&path).unwrap(); // TODO: handle error.
    let (time, file_size) = get_file_info(&meta);
    let path = if path.starts_with("./") {
        path.strip_prefix("./")
            .unwrap() // TODO: handle error.
            .to_str()
            .unwrap() // TODO: handle error.
    } else {
        path.to_str().unwrap()
    };
    let file_str = format!("{} {} {} {} {}:{} {}{}\r\n",
                           is_dir,
                           file_size,
                           MONTHS[time.tm_mon as usize],
                           time.tm_mday,
                           time.tm_hour,
                           time.tm_min,
                           path,
                           extra);
    out.push_str(&file_str);
    println!("==> {:?}", &file_str);
}

#[allow(dead_code)]
struct Client {
    address: String,
    cwd: PathBuf,
    data_port: Option<u16>,
    data_writer: Rc<RefCell<Option<StdStream>>>,
    handle: Handle,
    name: Option<String>,
    transfer_type: TransferType,
    writer: Writer,
}

impl Client {
    fn new(address: String, handle: Handle, writer: Writer) -> Client {
        Client {
            address,
            cwd: PathBuf::from("/"),
            data_port: None,
            data_writer: Rc::new(RefCell::new(None)),
            handle,
            name: None,
            transfer_type: TransferType::Ascii,
            writer,
        }
    }

    fn handle_cmd(&mut self, cmd: Command) {
        println!("Received command: {:?}", cmd);
        match cmd {
            Command::Auth => self.send(Answer::new(ResultCode::CommandNotImplemented, "Not implemented")),
            Command::Cwd(directory) => {
                // TODO: Actually implement the command. Since chroot works only on UNIX
                // platforms, we can't use it for that. :'(
                self.send(Answer::new(ResultCode::Ok, &format!("Directory changed to \"{}\"", directory)));
            },
            Command::List(path) => self.list(path),
            Command::Pasv => self.pasv(),
            Command::Port(port) => {
                self.data_port = Some(port);
                self.send(Answer::new(ResultCode::Ok, &format!("Data port is now {}", port)));
            },
            Command::Pwd => {
                let message = format!("\"{}\" ", self.cwd.to_str().unwrap());
                self.send(Answer::new(ResultCode::PATHNAMECreated, &message))
            }, // TODO: handle error.
            Command::Syst => self.send(Answer::new(ResultCode::Ok, "I won't tell!")),
            Command::Type(typ) => {
                self.transfer_type = typ;
                self.send(Answer::new(ResultCode::Ok, "Transfer type changed successfully"));
            },
            Command::Unknown => self.send(Answer::new(ResultCode::UnknownCommand, "Not implemented")),
            Command::User(content) => {
                if content.is_empty() {
                    self.send(Answer::new(ResultCode::InvalidParameterOrArgument, "Invalid username"))
                } else {
                    self.name = Some(content.to_owned());
                    self.send(Answer::new(ResultCode::UserloggedIn, &format!("Welcome {}!", content)))
                }
            }
        }
    }

    fn list(&mut self, path: Option<PathBuf>) {
        if self.data_writer.borrow().is_some() {
            let mut tmp = PathBuf::from(".");
            {
                let path = path.as_ref().unwrap_or(&self.cwd);
                // TODO: would it be better to check if the directory is a child on the root?
                for item in path.components().skip(1) {
                    match item {
                        Component::Normal(ref s) if s != &OsStr::new("..") => tmp.push(s),
                        _ => {}
                    }
                }
            }
            if tmp.is_dir() {
                self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Starting to list directory..."));
                let mut out = String::new();
                for entry in read_dir(tmp).unwrap() { // TODO: handle error.
                    add_file_info(entry.unwrap(), &mut out); // TODO: handle error.
                }
                self.send_data(&out);
                println!("-> and done!");
            } else {
                self.send(Answer::new(ResultCode::LocalErrorInProcessing,
                                      &format!("\"{}\" doesn't exist", tmp.to_str().unwrap())))
            }
        } else {
            self.send(Answer::new(ResultCode::ConnectionClosed, "No opened data connection"));
        }
        if self.data_writer.borrow().is_some() {
            *self.data_writer.borrow_mut() = None;
            self.send(Answer::new(ResultCode::ClosingDataConnection, "Transfer done"));
        }
    }

    fn pasv(&mut self) {
        // TODO: I believe this command should be blocking.
        let port =
            if let Some(port) = self.data_port {
                port
            } else {
                DEFAULT_PORT
            };
        if self.data_writer.borrow().is_some() {
            return self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Already listening..."));
        }
        self.send(Answer::new(ResultCode::EnteringPassiveMode,
                              &format!("127,0,0,1,{},{}", port >> 8, port & 0xFF)));

        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
        let listener = StdListener::bind(&addr).unwrap(); // TODO: handle error

        println!("Waiting clients on port {}...", port);
        let data_writer = self.data_writer.clone();
        match listener.incoming().next() {
            Some(Ok(client)) => {
                *data_writer.borrow_mut() = Some(client);
            }
            _ => {
                println!("[PASV] Error while getting client...");
            }
        }
    }

    fn send(&mut self, answer: Answer) {
        send(&mut self.writer, answer);
    }

    fn send_data(&mut self, data: &str) {
        if let Some(ref mut writer) = *self.data_writer.borrow_mut() {
            write!(writer, "{}", data).unwrap();
        }
    }
}

fn handle_client(stream: TcpStream, handle: Handle, address: String) -> Box<Future<Item=(), Error=io::Error>> {
    let (writer, reader) = stream.framed(FtpCodec).split();
    Box::new(writer.send(Answer::new(ResultCode::ServiceReadyForNewUser, "Welcome to this FTP server!"))
        .and_then(|writer| {
              let mut client = Client::new(address, handle, writer);
              reader.for_each(move |cmd| {
                  client.handle_cmd(cmd);
                  Ok(())
              })
        }
    ))
}

fn main() {
    let mut core = Core::new()
        .expect("Cannot create tokio Core");
    let handle = core.handle();

    let port = 1234;
    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
    let listener = TcpListener::bind(&addr, &handle).unwrap();

    println!("Waiting clients on port {}...", port);
    let server = listener.incoming()
        .for_each(|(stream, addr)| {
            let address = format!("[address : {}]", addr);

            println!("New client: {}", address);
            let future = handle_client(stream, handle.clone(), address);
            println!("Waiting another client...");
            future
        });

    core.run(server)
        .expect("Run tokio server");
}

fn send<S: Encoder>(writer: &mut SplitSink<Framed<TcpStream, S>>, data: S::Item)
where S::Error: Display
{
    // TODO: not sure about that. Do you know a better way of doing it?
    let mut error = None;
    match writer.start_send(data) {
        Ok(AsyncSink::Ready) => {
            if let Err(poll_error) = writer.poll_complete() {
                error = Some(poll_error.to_string());
            }
        },
        Ok(AsyncSink::NotReady(_)) => error = Some("not ready to send to client".to_string()),
        Err(send_error) =>
            error = Some(format!("cannot send a message to the web process: {}", send_error)),
    }
    if let Some(error) = error {
        panic!("Error: {}", error); // Handle error.
    }
}
<commit_msg>Project 3: Switch to an async data listener<commit_after>// Spec found at https://tools.ietf.org/html/rfc959

/*
 * FIXME Filezilla says: Le serveur ne supporte pas les caractères non-ASCII.
 * FIXME: The list command should specify which files are directory.
 */

extern crate bytes;
#[macro_use]
extern crate cfg_if;
extern crate futures;
extern crate time;
extern crate tokio_core;
extern crate tokio_io;

mod cmd; // FIXME: rename this module.
mod codec;
mod ftp;

use std::cell::RefCell;
use std::ffi::OsStr;
use std::fmt::Display;
use std::fs::{read_dir, DirEntry, Metadata};
use std::io;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::{Component, PathBuf};
use std::rc::Rc;

use futures::{AsyncSink, Future, Sink, Stream};
use futures::stream::SplitSink;
use tokio_core::reactor::{Core, Handle};
use tokio_core::net::{TcpListener, TcpStream};
use tokio_io::AsyncRead;
use tokio_io::codec::{Encoder, Framed};

use cmd::{Command, TransferType};
use codec::{FtpCodec, StringCodec};
use ftp::{Answer, ResultCode};

const DEFAULT_PORT: u16 = 4321;
const MONTHS: [&'static str; 12] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

type DataWriter = SplitSink<Framed<TcpStream, StringCodec>>;
type Writer = SplitSink<Framed<TcpStream, FtpCodec>>;

cfg_if! {
    if #[cfg(windows)] {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::windows::prelude::*;
            (time::at(time::Timespec::new(meta.last_write_time())), meta.file_size())
        }
    } else {
        fn get_file_info(meta: &Metadata) -> (time::Tm, u64) {
            use std::os::unix::prelude::*;
            (time::at(time::Timespec::new(meta.mtime(), 0)), meta.size())
        }
    }
}

fn add_file_info(entry: DirEntry, out: &mut String) {
    let path = entry.path(); // TODO: handle error.
    let extra = if path.is_dir() { "/" } else { "" };
    let is_dir = if path.is_dir() { "d" } else { "-" };

    let meta = ::std::fs::metadata(&path).unwrap(); // TODO: handle error.
    let (time, file_size) = get_file_info(&meta);
    let path = if path.starts_with("./") {
        path.strip_prefix("./")
            .unwrap() // TODO: handle error.
            .to_str()
            .unwrap() // TODO: handle error.
    } else {
        path.to_str().unwrap()
    };
    let file_str = format!("{} {} {} {} {}:{} {}{}\r\n",
                           is_dir,
                           file_size,
                           MONTHS[time.tm_mon as usize],
                           time.tm_mday,
                           time.tm_hour,
                           time.tm_min,
                           path,
                           extra);
    out.push_str(&file_str);
    println!("==> {:?}", &file_str);
}

#[allow(dead_code)]
struct Client {
    address: String,
    cwd: PathBuf,
    data_port: Option<u16>,
    data_writer: Rc<RefCell<Option<DataWriter>>>,
    handle: Handle,
    name: Option<String>,
    transfer_type: TransferType,
    writer: Writer,
}

impl Client {
    fn new(address: String, handle: Handle, writer: Writer) -> Client {
        Client {
            address,
            cwd: PathBuf::from("/"),
            data_port: None,
            data_writer: Rc::new(RefCell::new(None)),
            handle,
            name: None,
            transfer_type: TransferType::Ascii,
            writer,
        }
    }

    fn handle_cmd(&mut self, cmd: Command) {
        println!("Received command: {:?}", cmd);
        match cmd {
            Command::Auth => self.send(Answer::new(ResultCode::CommandNotImplemented, "Not implemented")),
            Command::Cwd(directory) => {
                // TODO: Actually implement the command. Since chroot works only on UNIX
                // platforms, we can't use it for that. :'(
                self.send(Answer::new(ResultCode::Ok, &format!("Directory changed to \"{}\"", directory)));
            },
            Command::List(path) => self.list(path),
            Command::Pasv => self.pasv(),
            Command::Port(port) => {
                self.data_port = Some(port);
                self.send(Answer::new(ResultCode::Ok, &format!("Data port is now {}", port)));
            },
            Command::Pwd => {
                let message = format!("\"{}\" ", self.cwd.to_str().unwrap());
                self.send(Answer::new(ResultCode::PATHNAMECreated, &message))
            }, // TODO: handle error.
            Command::Syst => self.send(Answer::new(ResultCode::Ok, "I won't tell!")),
            Command::Type(typ) => {
                self.transfer_type = typ;
                self.send(Answer::new(ResultCode::Ok, "Transfer type changed successfully"));
            },
            Command::Unknown => self.send(Answer::new(ResultCode::UnknownCommand, "Not implemented")),
            Command::User(content) => {
                if content.is_empty() {
                    self.send(Answer::new(ResultCode::InvalidParameterOrArgument, "Invalid username"))
                } else {
                    self.name = Some(content.to_owned());
                    self.send(Answer::new(ResultCode::UserloggedIn, &format!("Welcome {}!", content)))
                }
            }
        }
    }

    fn list(&mut self, path: Option<PathBuf>) {
        if self.data_writer.borrow().is_some() {
            let mut tmp = PathBuf::from(".");
            {
                let path = path.as_ref().unwrap_or(&self.cwd);
                // TODO: would it be better to check if the directory is a child on the root?
                for item in path.components().skip(1) {
                    match item {
                        Component::Normal(ref s) if s != &OsStr::new("..") => tmp.push(s),
                        _ => {}
                    }
                }
            }
            if tmp.is_dir() {
                self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Starting to list directory..."));
                let mut out = String::new();
                for entry in read_dir(tmp).unwrap() { // TODO: handle error.
                    add_file_info(entry.unwrap(), &mut out); // TODO: handle error.
                }
                self.send_data(out);
                println!("-> and done!");
            } else {
                self.send(Answer::new(ResultCode::LocalErrorInProcessing,
                                      &format!("\"{}\" doesn't exist", tmp.to_str().unwrap())))
            }
        } else {
            self.send(Answer::new(ResultCode::ConnectionClosed, "No opened data connection"));
        }
        if self.data_writer.borrow().is_some() {
            *self.data_writer.borrow_mut() = None;
            self.send(Answer::new(ResultCode::ClosingDataConnection, "Transfer done"));
        }
    }

    fn pasv(&mut self) {
        // TODO: I believe this command should be blocking.
        let port =
            if let Some(port) = self.data_port {
                port
            } else {
                DEFAULT_PORT
            };
        if self.data_writer.borrow().is_some() {
            return self.send(Answer::new(ResultCode::DataConnectionAlreadyOpen, "Already listening..."));
        }
        self.send(Answer::new(ResultCode::EnteringPassiveMode,
                              &format!("127,0,0,1,{},{}", port >> 8, port & 0xFF)));

        let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
        let listener = TcpListener::bind(&addr, &self.handle).unwrap(); // TODO: handle error

        println!("Waiting clients on port {}...", port);
        let data_writer = self.data_writer.clone();
        let future = listener.incoming()
            .into_future()
            .map_err(|_| ())
            .and_then(move |(client, _rest)| {
                if let Some((stream, _addr)) = client {
                    let (writer, _reader) = stream.framed(StringCodec).split();
                    *data_writer.borrow_mut() = Some(writer);
                }
                Ok(())
            });
        self.handle.spawn(future);
    }

    fn send(&mut self, answer: Answer) {
        send(&mut self.writer, answer);
    }

    fn send_data(&mut self, data: String) {
        if let Some(ref mut writer) = *self.data_writer.borrow_mut() {
            send(writer, data);
        }
    }
}

fn handle_client(stream: TcpStream, handle: Handle, address: String) -> Box<Future<Item=(), Error=io::Error>> {
    let (writer, reader) = stream.framed(FtpCodec).split();
    Box::new(writer.send(Answer::new(ResultCode::ServiceReadyForNewUser, "Welcome to this FTP server!"))
        .and_then(|writer| {
              let mut client = Client::new(address, handle, writer);
              reader.for_each(move |cmd| {
                  client.handle_cmd(cmd);
                  Ok(())
              })
        }
    ))
}

fn main() {
    let mut core = Core::new()
        .expect("Cannot create tokio Core");
    let handle = core.handle();

    let port = 1234;
    let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port);
    let listener = TcpListener::bind(&addr, &handle).unwrap();

    println!("Waiting clients on port {}...", port);
    let server = listener.incoming()
        .for_each(|(stream, addr)| {
            let address = format!("[address : {}]", addr);

            println!("New client: {}", address);
            let future = handle_client(stream, handle.clone(), address);
            println!("Waiting another client...");
            future
        });

    core.run(server)
        .expect("Run tokio server");
}

fn send<S: Encoder>(writer: &mut SplitSink<Framed<TcpStream, S>>, data: S::Item)
where S::Error: Display
{
    // TODO: not sure about that. Do you know a better way of doing it?
    let mut error = None;
    match writer.start_send(data) {
        Ok(AsyncSink::Ready) => {
            if let Err(poll_error) = writer.poll_complete() {
                error = Some(poll_error.to_string());
            }
        },
        Ok(AsyncSink::NotReady(_)) => error = Some("not ready to send to client".to_string()),
        Err(send_error) =>
            error = Some(format!("cannot send a message to the web process: {}", send_error)),
    }
    if let Some(error) = error {
        panic!("Error: {}", error); // Handle error.
    }
}
<|endoftext|>


<commit_before>#![feature(globs, macro_rules, phase)]

extern crate debug;
extern crate piston;
extern crate sdl2;
extern crate sdl2_game_window;
extern crate gfx;
#[phase(plugin)]
extern crate gfx_macros;
extern crate image;
extern crate libc;
extern crate cgmath;
extern crate time;

use sdl2_game_window::GameWindowSDL2 as Window;
use piston::input;
use piston::cam;
use piston::{
    AssetStore,
    GameIterator,
    GameIteratorSettings,
    GameWindow,
    GameWindowSettings,
    Input,
    Render,
    Update,
};

use array::*;
use texture::Texture;

pub mod array;
pub mod cube;
pub mod fps_counter;
pub mod shader;
pub mod texture;
pub mod vecmath;

fn main() {
    let mut window = Window::new(
        piston::shader_version::opengl::OpenGL_3_3,
        GameWindowSettings {
            title: "Hematite".to_string(),
            size: [854, 480],
            fullscreen: false,
            exit_on_esc: true,
        }
    );
    let (mut device, frame) = window.gfx();

    let asset_store = AssetStore::from_folder("../assets");

    // Load texture.
    let texture = asset_store.path("texture.png").unwrap();
    let texture = Texture::from_path(&texture, &mut device).unwrap();

    let game_iter_settings = GameIteratorSettings {
        updates_per_second: 120,
        max_frames_per_second: 60,
    };

    let mut renderer = shader::Renderer::new(device, frame, texture.tex);

    renderer.set_projection(cam::CameraPerspective {
        fov: 70.0,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: {
            let (w, h) = window.get_size();
            (w as f32) / (h as f32)
        }
    }.projection());

    let mut first_person_settings = cam::FirstPersonSettings::default();
    first_person_settings.speed_horizontal = 8.0;
    first_person_settings.speed_vertical = 4.0;
    let mut first_person = cam::FirstPerson::new(
        [0.5, 0.5, 4.0],
        first_person_settings
    );

    // Disable V-Sync.
    sdl2::video::gl_set_swap_interval(0);

    let mut fps_counter = fps_counter::FPSCounter::new();

    let mut capture_cursor = false;
    println!("Press C to capture mouse");
    let mut extrapolate_time = false;
    println!("Press X to extrapolate time");
    let mut update_cam_render = false;
    println!("Press Z to update first person camera when rendering");

    let buffer = {
        let mut tri = vec![];
        for face in cube::FaceIterator::new() {
            let xyz = face.vertices([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
            let [u0, v1, u1, v0] = [0.0, 0.75, 0.25, 1.0];
            let uv = [
                [u1, v0],
                [u0, v0],
                [u0, v1],
                [u1, v1]
            ];
            let v = [
                (xyz[0], uv[0], [1.0, 0.0, 0.0]),
                (xyz[1], uv[1], [0.0, 1.0, 0.0]),
                (xyz[2], uv[2], [0.0, 0.0, 1.0]),
                (xyz[3], uv[3], [1.0, 0.0, 1.0])
            ].map(|(xyz, uv, rgb)| shader::Vertex { xyz: xyz, uv: uv, rgb: rgb });

            // Split the clockwise quad into two clockwise triangles.
            tri.push_all([v[0], v[1], v[2]]);
            tri.push_all([v[2], v[3], v[0]]);
        }
        renderer.create_buffer(tri.as_slice())
    };

    let mut last_render = time::precise_time_ns();
    let mut events = GameIterator::new(&mut window, &game_iter_settings);
    for e in events {
        match e {
            Render(_args) => {
                if update_cam_render {
                    let now = time::precise_time_ns();
                    let dt = (now - last_render) as f64 / 1_000_000_000.0f64;
                    first_person.update(dt);
                    last_render = now;
                }

                let ext_dt = _args.ext_dt * if extrapolate_time { 1.0 } else { 0.0 };
                renderer.set_view(first_person.camera(ext_dt).orthogonal());
                renderer.reset();
                renderer.render(buffer);
                renderer.end_frame();

                let fps = fps_counter.update();
                let title = format!("Hematite @ {}FPS", fps);
                events.game_window.window.set_title(title.as_slice());
            }
            Input(input::KeyPress { key: input::keyboard::C }) => {
                println!("Turned cursor capture {}",
                    if capture_cursor { "off" } else { "on" });
                capture_cursor = !capture_cursor;

                events.game_window.capture_cursor(capture_cursor);
            },
            Input(input::KeyPress { key: input::keyboard::X }) => {
                println!("Turned extrapolated time {}",
                    if extrapolate_time { "off" } else { "on" });
                extrapolate_time = !extrapolate_time;
            },
            Input(input::KeyPress { key: input::keyboard::Z }) => {
                println!("Turned update camera on render {}",
                    if update_cam_render { "off" } else { "on" });
                update_cam_render = !update_cam_render;
            },
            Input(input::MouseRelativeMove { .. }) => {
                if !capture_cursor {
                    // Don't send the mouse event to the FPS controller.
                    continue;
                }
            }
            _ => {}
        }

        // Camera controller.
        match e {
            Input(ref args) => first_person.input(args),
            Update(piston::UpdateArgs { dt, .. }) =>
                if !update_cam_render { first_person.update(dt) },
            _ => {}
        }
    }
}
<commit_msg>Removed X/Z<commit_after>#![feature(globs, macro_rules, phase)]

extern crate debug;
extern crate piston;
extern crate sdl2;
extern crate sdl2_game_window;
extern crate gfx;
#[phase(plugin)]
extern crate gfx_macros;
extern crate image;
extern crate libc;
extern crate cgmath;
extern crate time;

use sdl2_game_window::GameWindowSDL2 as Window;
use piston::input;
use piston::cam;
use piston::{
    AssetStore,
    GameIterator,
    GameIteratorSettings,
    GameWindow,
    GameWindowSettings,
    Input,
    Render,
    Update,
};

use array::*;
use texture::Texture;

pub mod array;
pub mod cube;
pub mod fps_counter;
pub mod shader;
pub mod texture;
pub mod vecmath;

fn main() {
    let mut window = Window::new(
        piston::shader_version::opengl::OpenGL_3_3,
        GameWindowSettings {
            title: "Hematite".to_string(),
            size: [854, 480],
            fullscreen: false,
            exit_on_esc: true,
        }
    );
    let (mut device, frame) = window.gfx();

    let asset_store = AssetStore::from_folder("../assets");

    // Load texture.
    let texture = asset_store.path("texture.png").unwrap();
    let texture = Texture::from_path(&texture, &mut device).unwrap();

    let game_iter_settings = GameIteratorSettings {
        updates_per_second: 120,
        max_frames_per_second: 60,
    };

    let mut renderer = shader::Renderer::new(device, frame, texture.tex);

    renderer.set_projection(cam::CameraPerspective {
        fov: 70.0,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: {
            let (w, h) = window.get_size();
            (w as f32) / (h as f32)
        }
    }.projection());

    let mut first_person_settings = cam::FirstPersonSettings::default();
    first_person_settings.speed_horizontal = 8.0;
    first_person_settings.speed_vertical = 4.0;
    let mut first_person = cam::FirstPerson::new(
        [0.5, 0.5, 4.0],
        first_person_settings
    );

    // Disable V-Sync.
    sdl2::video::gl_set_swap_interval(0);

    let mut fps_counter = fps_counter::FPSCounter::new();

    let mut capture_cursor = false;
    println!("Press C to capture mouse");

    let buffer = {
        let mut tri = vec![];
        for face in cube::FaceIterator::new() {
            let xyz = face.vertices([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
            let [u0, v1, u1, v0] = [0.0, 0.75, 0.25, 1.0];
            let uv = [
                [u1, v0],
                [u0, v0],
                [u0, v1],
                [u1, v1]
            ];
            let v = [
                (xyz[0], uv[0], [1.0, 0.0, 0.0]),
                (xyz[1], uv[1], [0.0, 1.0, 0.0]),
                (xyz[2], uv[2], [0.0, 0.0, 1.0]),
                (xyz[3], uv[3], [1.0, 0.0, 1.0])
            ].map(|(xyz, uv, rgb)| shader::Vertex { xyz: xyz, uv: uv, rgb: rgb });

            // Split the clockwise quad into two clockwise triangles.
            tri.push_all([v[0], v[1], v[2]]);
            tri.push_all([v[2], v[3], v[0]]);
        }
        renderer.create_buffer(tri.as_slice())
    };

    let mut last_render = time::precise_time_ns();
    let mut events = GameIterator::new(&mut window, &game_iter_settings);
    for e in events {
        match e {
            Render(_args) => {
                // Update camera.
                let now = time::precise_time_ns();
                let dt = (now - last_render) as f64 / 1_000_000_000.0f64;
                first_person.update(dt);
                last_render = now;

                renderer.set_view(first_person.camera(0.0).orthogonal());
                renderer.reset();
                renderer.render(buffer);
                renderer.end_frame();

                let fps = fps_counter.update();
                let title = format!("Hematite @ {}FPS", fps);
                events.game_window.window.set_title(title.as_slice());
            }
            Input(input::KeyPress { key: input::keyboard::C }) => {
                println!("Turned cursor capture {}",
                    if capture_cursor { "off" } else { "on" });
                capture_cursor = !capture_cursor;

                events.game_window.capture_cursor(capture_cursor);
            },
            Input(input::MouseRelativeMove { .. }) => {
                if !capture_cursor {
                    // Don't send the mouse event to the FPS controller.
                    continue;
                }
            }
            _ => {}
        }

        // Camera controller.
        match e {
            Input(ref args) => first_person.input(args),
            _ => {}
        }
    }
}
<|endoftext|>


<commit_before>extern crate futures;
extern crate tokio_core;
extern crate tokio_proto;
extern crate tokio_service;

use std::io;
use std::str;
use std::collections::BTreeMap;
use tokio_core::io::{Codec, EasyBuf};
use tokio_core::io::{Io, Framed};
use tokio_proto::pipeline::ServerProto;
use tokio_proto::TcpServer;
use tokio_service::Service;
use futures::{future, Future, BoxFuture};

pub struct CliCodec {
    /// Description of the I/O object for the info command
    info: String,

    /// Offset within the incoming EasyBuf at which newline search last ended
    search_offset: usize,
}

impl CliCodec {
    /// Initializes with the given connection info
    fn new(io_info: String) -> CliCodec {
        CliCodec { info: io_info, search_offset: 0 }
    }
}

impl Codec for CliCodec {
    /// Input type is a tuple with the I/O info, the command name, and an optional argument string
    type In = (String, String, Option<String>);

    /// Response type is just a string
    type Out = String;

    /// Find a newline, decode to a tuple of command name and argument string
    fn decode(&mut self, buf: &mut EasyBuf) -> io::Result<Option<Self::In>> {
        // Find newline
        let m = buf
            .as_slice()
            .iter()
            .skip(self.search_offset)
            .position(|&b| b == b'\n');

        match m {
            // Process the line if a newline was found
            Some(idx) => {
                println!("Found newline at position {}, started at {}", idx, self.search_offset); // XXX

                let line = buf.drain_to(self.search_offset + idx);
                buf.drain_to(1);

                self.search_offset = 0;

                match str::from_utf8(line.as_slice()) {
                    // Return the string on successful UTF-8 decode
                    // (TODO: ignore blank lines)
                    Ok(s) => {
                        // TODO: Find a better way to split the string
                        let s = s.trim();

                        let request = match s.find(' ') {
                            Some(i) => {
                                let (cmd, args) = s.split_at(i);
                                (
                                    self.info.clone(),
                                    cmd.trim().to_string(),
                                    Some(args.trim().to_string())
                                )
                            },
                            None => (self.info.clone(), s.to_string(), None)
                        };

                        Ok(Some(request))
                    },

                    // Return an error if invalid UTF-8 is received
                    Err(e) => Err(io::Error::new(io::ErrorKind::Other, format!("invalid UTF-8: {:?}", e))),
                }
            },

            // Save our position if a newline wasn't found
            _ => {
                println!("Haven't received newline yet!  We started at offset {}, now at {}", self.search_offset, buf.len()); // XXX

                self.search_offset = buf.len();

                Ok(None)
            }
        }
    }

    /// Append the String to the outgoing buffer
    fn encode(&mut self, msg: String, buf: &mut Vec<u8>) -> io::Result<()> {
        buf.extend(msg.as_bytes());
        buf.push(b'\n');
        Ok(())
    }
}

pub struct CliProto;

impl<T: Io + 'static + std::fmt::Debug> ServerProto<T> for CliProto {
    type Request = (String, String, Option<String>);
    type Response = String;

    type Transport = Framed<T, CliCodec>;
    type BindTransport = Result<Self::Transport, io::Error>;

    fn bind_transport(&self, io: T) -> Self::BindTransport {
        let info = match io {
            tokio_core::net::TcpStream(tcp) => {
                let addr = tcp.peer_addr().unwrap();
                format!(
                    "Client address: {}\nClient port: {}\n",
                    addr.ip, addr.port
                    )
            },
            _ => {
                format!("{:?}", &io)
            }
        };
        Ok(io.framed(CliCodec::new(info)))
    }
}

trait CliCommand {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn call(&self, info: String, args: Option<String>) -> String;
}

#[derive(Default)]
pub struct CliServer<'a> {
    commands: BTreeMap<&'a str, &'a CliCommand>,
}

impl<'a> CliServer<'a> {
    fn new() -> CliServer<'a> {
        CliServer { commands: BTreeMap::new() }
    }

    fn add_command(&mut self, cmd: &'a CliCommand) {
        self.commands.insert(cmd.name(), cmd);
    }
}

impl<'a> Service for CliServer<'a> {
    type Request = (String, String, Option<String>);
    type Response = String;

    type Error = io::Error;

    type Future = BoxFuture<Self::Response, Self::Error>;

    fn call(&self, req: Self::Request) -> Self::Future {
        let (info, cmdname, args) = req;

        match self.commands.get(&cmdname[..]) {
            Some(cmd) => {
                println!("Calling command {} for IO info {:?}", cmdname, info);
                // TODO: have commands return futures?
                future::ok(cmd.call(info, args)).boxed()
            },
            None => {
                println!("No match found for command {}", cmdname);
                future::ok(format!("Unknown command: {}", cmdname)).boxed()
            }
        }
    }
}

// TODO: Consider registering commands by passing two strings and a closure
struct EchoCommand;
impl CliCommand for EchoCommand {
    fn name(&self) -> &str {
        "echo"
    }

    fn description(&self) -> &str {
        "Prints the command line."
    }

    fn call(&self, _info: String, args: Option<String>) -> String {
        match args {
            Some(s) => s,
            None => "".to_string()
        }
    }
}

struct InfoCommand;
impl CliCommand for InfoCommand {
    fn name(&self) -> &str { "info" }
    fn description(&self) -> &str { "Prints connection information." }

    fn call(&self, info: String, _args: Option<String>) -> String {
        info
    }
}

static ECHO: EchoCommand = EchoCommand;
static INFO: InfoCommand = InfoCommand;

fn main() {
    let addr = "0.0.0.0:14311".parse().unwrap();
    let server = TcpServer::new(CliProto, addr);

    println!("Serving on {}", addr);
    server.serve(|| {
        let mut cli = CliServer::new();
        cli.add_command(&ECHO);
        cli.add_command(&INFO);
        Ok(cli)
    });
}
<commit_msg>Limit to TCP only, format connection info like the original cliserver.<commit_after>extern crate futures;
extern crate tokio_core;
extern crate tokio_proto;
extern crate tokio_service;

use std::io;
use std::str;
use std::collections::BTreeMap;
use tokio_core::io::{Codec, EasyBuf};
use tokio_core::io::{Io, Framed};
use tokio_core::net::TcpStream;
use tokio_proto::pipeline::ServerProto;
use tokio_proto::TcpServer;
use tokio_service::Service;
use futures::{future, Future, BoxFuture};

pub struct CliCodec {
    /// Description of the I/O object for the info command
    info: String,

    /// Offset within the incoming EasyBuf at which newline search last ended
    search_offset: usize,
}

impl CliCodec {
    /// Initializes with the given connection info
    fn new(io_info: String) -> CliCodec {
        CliCodec { info: io_info, search_offset: 0 }
    }
}

impl Codec for CliCodec {
    /// Input type is a tuple with the I/O info, the command name, and an optional argument string
    type In = (String, String, Option<String>);

    /// Response type is just a string
    type Out = String;

    /// Find a newline, decode to a tuple of command name and argument string
    fn decode(&mut self, buf: &mut EasyBuf) -> io::Result<Option<Self::In>> {
        // Find newline
        let m = buf
            .as_slice()
            .iter()
            .skip(self.search_offset)
            .position(|&b| b == b'\n');

        match m {
            // Process the line if a newline was found
            Some(idx) => {
                println!("Found newline at position {}, started at {}", idx, self.search_offset); // XXX

                let line = buf.drain_to(self.search_offset + idx);
                buf.drain_to(1);

                self.search_offset = 0;

                match str::from_utf8(line.as_slice()) {
                    // Return the string on successful UTF-8 decode
                    // (TODO: ignore blank lines)
                    Ok(s) => {
                        // TODO: Find a better way to split the string
                        let s = s.trim();

                        let request = match s.find(' ') {
                            Some(i) => {
                                let (cmd, args) = s.split_at(i);
                                (
                                    self.info.clone(),
                                    cmd.trim().to_string(),
                                    Some(args.trim().to_string())
                                )
                            },
                            None => (self.info.clone(), s.to_string(), None)
                        };

                        Ok(Some(request))
                    },

                    // Return an error if invalid UTF-8 is received
                    Err(e) => Err(io::Error::new(io::ErrorKind::Other, format!("invalid UTF-8: {:?}", e))),
                }
            },

            // Save our position if a newline wasn't found
            _ => {
                println!("Haven't received newline yet!  We started at offset {}, now at {}", self.search_offset, buf.len()); // XXX

                self.search_offset = buf.len();

                Ok(None)
            }
        }
    }

    /// Append the String to the outgoing buffer
    fn encode(&mut self, msg: String, buf: &mut Vec<u8>) -> io::Result<()> {
        buf.extend(msg.as_bytes());
        buf.push(b'\n');
        Ok(())
    }
}

pub struct CliProto;

impl ServerProto<TcpStream> for CliProto {
    type Request = (String, String, Option<String>);
    type Response = String;

    type Transport = Framed<TcpStream, CliCodec>;
    type BindTransport = Result<Self::Transport, io::Error>;

    fn bind_transport(&self, io: TcpStream) -> Self::BindTransport {
        let addr = io.peer_addr().unwrap();
        let info = format!("Client address: {}\nClient port: {}", addr.ip(), addr.port());
        Ok(io.framed(CliCodec::new(info)))
    }
}

trait CliCommand {
    fn name(&self) -> &str;
    fn description(&self) -> &str;
    fn call(&self, info: String, args: Option<String>) -> String;
}

#[derive(Default)]
pub struct CliServer<'a> {
    commands: BTreeMap<&'a str, &'a CliCommand>,
}

impl<'a> CliServer<'a> {
    fn new() -> CliServer<'a> {
        CliServer { commands: BTreeMap::new() }
    }

    fn add_command(&mut self, cmd: &'a CliCommand) {
        self.commands.insert(cmd.name(), cmd);
    }
}

impl<'a> Service for CliServer<'a> {
    type Request = (String, String, Option<String>);
    type Response = String;

    type Error = io::Error;

    type Future = BoxFuture<Self::Response, Self::Error>;

    fn call(&self, req: Self::Request) -> Self::Future {
        let (info, cmdname, args) = req;

        match self.commands.get(&cmdname[..]) {
            Some(cmd) => {
                println!("Calling command {} for IO info {:?}", cmdname, info);
                // TODO: have commands return futures?
                future::ok(cmd.call(info, args)).boxed()
            },
            None => {
                println!("No match found for command {}", cmdname);
                future::ok(format!("Unknown command: {}", cmdname)).boxed()
            }
        }
    }
}

// TODO: Consider registering commands by passing two strings and a closure
struct EchoCommand;
impl CliCommand for EchoCommand {
    fn name(&self) -> &str {
        "echo"
    }

    fn description(&self) -> &str {
        "Prints the command line."
    }

    fn call(&self, _info: String, args: Option<String>) -> String {
        match args {
            Some(s) => s,
            None => "".to_string()
        }
    }
}

struct InfoCommand;
impl CliCommand for InfoCommand {
    fn name(&self) -> &str { "info" }
    fn description(&self) -> &str { "Prints connection information." }

    fn call(&self, info: String, _args: Option<String>) -> String {
        info
    }
}

static ECHO: EchoCommand = EchoCommand;
static INFO: InfoCommand = InfoCommand;

fn main() {
    let addr = "0.0.0.0:14311".parse().unwrap();
    let server = TcpServer::new(CliProto, addr);

    println!("Serving on {}", addr);
    server.serve(|| {
        let mut cli = CliServer::new();
        cli.add_command(&ECHO);
        cli.add_command(&INFO);
        Ok(cli)
    });
}
<|endoftext|>


<commit_before>// Crates
extern crate chrono;
extern crate docopt;
extern crate rustc_serialize;
extern crate time;
extern crate libaltdate;

// Standard library imports

//Crate imports
use chrono::{NaiveDate, Datelike};
use docopt::Docopt;
use libaltdate::ddate;

const YEAR_OFFSET: i32 = 1900;

static VERSION: &'static str = "ddate (RUST implementaion of gnucoreutils) 0.1
Copyright (C) 2016 Marco Kaulea
License GPLv2: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Marco 'don' Kaulea.
";

const USAGE: &'static str = "
ddate

USAGE:
    ddate [options] [<date>]

Options:
    -h --help               Dispaly this help message and exit
    -v --version            Output version information and exit
    -d --discordian         Switch to output discordian dates. This is the default
    -t --timestamp          Date specifies a timestamp, instead of an isodate
";

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_date: Option<String>,
    flag_help: bool,
    flag_version: bool,
    flag_discordian: bool,
    flag_timestamp: bool,
}

#[derive(Debug)]
enum InputType {
    Iso6801,
    UnixTimestamp,
}

fn get_input_type(args: &Args) -> InputType {
    if args.flag_timestamp {
        InputType::UnixTimestamp
    } else {
        InputType::Iso6801
    }
}

fn parse_date(raw_date: &String, input_type: InputType) -> NaiveDate {
    match input_type {
        InputType::UnixTimestamp => {
            let timestamp = time::at(time::Timespec{
                sec: raw_date.parse().expect("Timestamp"), nsec: 0});
            NaiveDate::from_yo(timestamp.tm_year + YEAR_OFFSET, timestamp.tm_yday as u32)
        },
        InputType::Iso6801 => NaiveDate::parse_from_str(raw_date.as_str(), "%Y-%m-%d")
                                        .expect("Can't parse date")
    }
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                             .and_then(|d| d.decode())
                             .unwrap_or_else(|e| e.exit());
    if args.flag_version {
        println!("{}", VERSION);
        return;
        }

    let input_type = get_input_type(&args);
    println!("Input Type: {:?}", input_type);

    let input_date = match args.arg_date {
        None => {
            let today = chrono::offset::local::Local::today();
            today.naive_local()
        },
        Some(raw_date) => parse_date(&raw_date, input_type),

    };
    let date = ddate::convert(input_date.ordinal0() as u16,
                   input_date.year() as i32).unwrap();
    println!("{:?}, ", date);
}

<commit_msg>Clarify error messages<commit_after>// Crates
extern crate chrono;
extern crate docopt;
extern crate rustc_serialize;
extern crate time;
extern crate libaltdate;

// Standard library imports

//Crate imports
use chrono::{NaiveDate, Datelike};
use docopt::Docopt;
use libaltdate::ddate;

const YEAR_OFFSET: i32 = 1900;

static VERSION: &'static str = "ddate (RUST implementaion of gnucoreutils) 0.1
Copyright (C) 2016 Marco Kaulea
License GPLv2: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by Marco 'don' Kaulea.
";

const USAGE: &'static str = "
ddate

USAGE:
    ddate [options] [<date>]

Options:
    -h --help               Dispaly this help message and exit
    -v --version            Output version information and exit
    -d --discordian         Switch to output discordian dates. This is the default
    -t --timestamp          Date specifies a timestamp, instead of an isodate
";

#[derive(Debug, RustcDecodable)]
struct Args {
    arg_date: Option<String>,
    flag_help: bool,
    flag_version: bool,
    flag_discordian: bool,
    flag_timestamp: bool,
}

#[derive(Debug)]
enum InputType {
    Iso6801,
    UnixTimestamp,
}

fn get_input_type(args: &Args) -> InputType {
    if args.flag_timestamp {
        InputType::UnixTimestamp
    } else {
        InputType::Iso6801
    }
}

fn parse_date(raw_date: &String, input_type: InputType) -> NaiveDate {
    match input_type {
        InputType::UnixTimestamp => {
            let timestamp = time::at(time::Timespec{
                sec: raw_date.parse().expect("Could not parse timestamp"),
                nsec: 0});
            NaiveDate::from_yo(timestamp.tm_year + YEAR_OFFSET, timestamp.tm_yday as u32)
        },
        InputType::Iso6801 => NaiveDate::parse_from_str(raw_date.as_str(), "%Y-%m-%d")
                                        .expect("Could not parse date")
    }
}

fn main() {
    let args: Args = Docopt::new(USAGE)
                             .and_then(|d| d.decode())
                             .unwrap_or_else(|e| e.exit());
    if args.flag_version {
        println!("{}", VERSION);
        return;
        }

    let input_type = get_input_type(&args);

    let input_date = match args.arg_date {
        None => {
            let today = chrono::offset::local::Local::today();
            today.naive_local()
        },
        Some(raw_date) => parse_date(&raw_date, input_type),

    };
    let date = ddate::convert(input_date.ordinal0() as u16,
                   input_date.year() as i32).unwrap();
    println!("{:?}, ", date);
}

<|endoftext|>