<commit_before>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use core::config::ApplicationCfg;
use datasource::postgis::PostgisInput;
use core::grid::Grid;
use service::mvt::{MvtService, Tileset};
use core::{Config, read_config, parse_config};
use core::config::DEFAULT_CONFIG;
use serde_json;
use cache::{Tilecache, Nocache, Filecache};

use nickel::{Nickel, Options, HttpRouter, MediaType, Request, Response, MiddlewareResult,
             StaticFilesHandler};
use hyper::header::{CacheControl, CacheDirective, AccessControlAllowOrigin,
                    AccessControlAllowMethods, ContentEncoding, Encoding};
use hyper::method::Method;
use hyper::header;
use std::collections::HashMap;
use std::str::FromStr;
use clap::ArgMatches;
use std::str;
use std::process;
use open;

fn log_request<'mw>(req: &mut Request<MvtService>,
                    res: Response<'mw, MvtService>)
                    -> MiddlewareResult<'mw, MvtService> {
    info!("{} {}", req.origin.method, req.origin.uri);
    res.next_middleware()
}

header! { (ContentType, "Content-Type") => [String] }

#[derive(RustcEncodable)]
struct TilesetInfo {
    name: String,
    layerinfos: String,
    hasviewer: bool,
}

impl TilesetInfo {
    fn from_tileset(set: &Tileset) -> TilesetInfo {
        let mut hasviewer = true;
        let layerinfos: Vec<String> = set.layers
            .iter()
            .map(|l| {
                let geom_type = l.geometry_type.clone().unwrap_or("UNKNOWN".to_string());
                hasviewer = hasviewer &&
                            ["POINT",
                             "LINESTRING",
                             "POLYGON",
                             "MULTPOINT",
                             "MULTILINESTRING",
                             "MULTIPOLYGON"]
                                    .contains(&(&geom_type as &str));
                format!("{} [{}]", &l.name, &geom_type)
            })
            .collect();
        TilesetInfo {
            name: set.name.clone(),
            layerinfos: format!("{}", layerinfos.join(", ")),
            hasviewer: hasviewer,
        }
    }
}

struct StaticFiles {
    files: HashMap<&'static str, (&'static [u8], MediaType)>,
}

impl StaticFiles {
    fn init() -> StaticFiles {
        let mut static_files = StaticFiles { files: HashMap::new() };
        static_files.add("favicon.ico",
                         include_bytes!("static/favicon.ico"),
                         MediaType::Ico);
        static_files.add("index.html",
                         include_bytes!("static/index.html"),
                         MediaType::Html);
        static_files.add("viewer.js",
                         include_bytes!("static/viewer.js"),
                         MediaType::Js);
        static_files.add("viewer.css",
                         include_bytes!("static/viewer.css"),
                         MediaType::Css);
        static_files.add("maputnik.html",
                         include_bytes!("static/maputnik.html"),
                         MediaType::Html);
        static_files.add("maputnik.js",
                         include_bytes!("static/maputnik.js"),
                         MediaType::Js);
        static_files.add("maputnik-vendor.js",
                         include_bytes!("static/maputnik-vendor.js"),
                         MediaType::Js);
        static_files.add("img/maputnik.png",
                         include_bytes!("static/img/maputnik.png"),
                         MediaType::Png);
        static_files.add("fonts/Roboto-Regular.ttf",
                         include_bytes!("static/fonts/Roboto-Regular.ttf"),
                         MediaType::Ttf);
        static_files.add("fonts/Roboto-Medium.ttf",
                         include_bytes!("static/fonts/Roboto-Medium.ttf"),
                         MediaType::Ttf);
        static_files
    }
    fn add(&mut self, name: &'static str, data: &'static [u8], media_type: MediaType) {
        self.files.insert(name, (data, media_type));
    }
    fn content(&self, base: Option<&str>, name: String) -> Option<&(&[u8], MediaType)> {
        let mut key = if name == "." {
            "index.html".to_string()
        } else {
            name
        };
        if let Some(path) = base {
            key = format!("{}/{}", path, key);
        }
        self.files.get(&key as &str)
    }
}

pub fn service_from_args(args: &ArgMatches) -> (MvtService, ApplicationCfg) {
    if let Some(cfgpath) = args.value_of("config") {
        info!("Reading configuration from '{}'", cfgpath);
        let config =
            read_config(cfgpath).unwrap_or_else(|err| {
                                                    println!("Error reading configuration - {} ",
                                                             err);
                                                    process::exit(1)
                                                });
        let mut svc =
            MvtService::from_config(&config).unwrap_or_else(|err| {
                                                                println!("Error reading configuration - {} ", err);
                                                                process::exit(1)
                                                            });
        svc.connect();
        (svc, config)
    } else {
        let config = parse_config(DEFAULT_CONFIG.to_string(), "").unwrap();
        let cache = match args.value_of("cache") {
            None => Tilecache::Nocache(Nocache),
            Some(dir) => {
                Tilecache::Filecache(Filecache {
                                         basepath: dir.to_string(),
                                         baseurl: None,
                                     })
            }
        };
        let simplify = bool::from_str(args.value_of("simplify").unwrap_or("true")).unwrap_or(false);
        let clip = bool::from_str(args.value_of("clip").unwrap_or("true")).unwrap_or(false);
        if let Some(dbconn) = args.value_of("dbconn") {
            let pg = PostgisInput::new(dbconn).connected();
            let grid = Grid::web_mercator();
            let detect_geometry_types = true; //TODO: add option (maybe slow for many geometries)
            let mut layers = pg.detect_layers(detect_geometry_types);
            let mut tilesets = Vec::new();
            while let Some(mut l) = layers.pop() {
                let extent = pg.layer_extent(&l);
                l.simplify = Some(simplify);
                if simplify {
                    // Limit features by default unless simplify is set to false
                    l.query_limit = Some(1000);
                }
                l.buffer_size = match l.geometry_type {
                    Some(ref geom) => {
                        if clip {
                            if geom.contains("POLYGON") {
                                Some(1)
                            } else {
                                Some(0)
                            }
                        } else {
                            None
                        }
                    }
                    None => None,
                };
                let tileset = Tileset {
                    name: l.name.clone(),
                    extent: extent,
                    layers: vec![l],
                };
                tilesets.push(tileset);
            }
            let svc = MvtService {
                input: pg,
                grid: grid,
                tilesets: tilesets,
                cache: cache,
            };
            (svc, config)
        } else {
            println!("Either 'config' or 'dbconn' is required");
            process::exit(1)
        }
    }
}

#[allow(unreachable_code)]
pub fn webserver(args: &ArgMatches) {
    let (mut service, config) = service_from_args(args);

    let mvt_viewer = config.service.mvt.viewer;
    let bind: &str = &config.webserver.bind.unwrap_or("127.0.0.1".to_string());
    let port = config.webserver.port.unwrap_or(6767);
    let threads = config.webserver.threads.unwrap_or(4) as usize;
    let cache_max_age = config.webserver.cache_control_max_age.unwrap_or(0);

    service.prepare_feature_queries();
    service.init_cache();

    let mut tileset_infos: Vec<TilesetInfo> = service
        .tilesets
        .iter()
        .map(|set| TilesetInfo::from_tileset(&set))
        .collect();
    tileset_infos.sort_by_key(|ti| ti.name.clone());

    let mut server = Nickel::with_data(service);
    server.options = Options::default().thread_count(Some(threads));
    // Avoid thread exhaustion caused by hypers keep_alive handling (https://github.com/hyperium/hyper/issues/368)
    server.keep_alive_timeout(None);
    server.utilize(log_request);

    server.get("/**(.style)?.json",
               middleware! { |_req, mut res|
        res.set(MediaType::Json);
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);
    });

    server.get("/index.json",
               middleware! { |_req, res|
        let service: &MvtService = res.server_data();
        let json = service.get_mvt_metadata().unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    // Font list for Maputnik
    server.get("/fontstacks.json",
               middleware! { |_req, _res|
        "[]"
    });

    server.get("/:tileset.json",
               middleware! { |req, res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        let host = req.origin.headers.get::<header::Host>().unwrap();
        let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
        let json = service.get_tilejson(&baseurl, &tileset).unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    server.get("/:tileset.style.json",
               middleware! { |req, res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        let host = req.origin.headers.get::<header::Host>().unwrap();
        let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
        let json = service.get_stylejson(&baseurl, &tileset).unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    server.get("/:tileset/metadata.json",
               middleware! { |req, res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        let json = service.get_mbtiles_metadata(&tileset).unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    server.get("/:tileset/:z/:x/:y.pbf",
               middleware! { |req, mut res|
        let service: &MvtService = res.server_data();

        let tileset = req.param("tileset").unwrap();
        let z = req.param("z").unwrap().parse::<u8>().unwrap();
        let x = req.param("x").unwrap().parse::<u32>().unwrap();
        let y = req.param("y").unwrap().parse::<u32>().unwrap();

        let accept_encoding = req.origin.headers.get::<header::AcceptEncoding>();
        let gzip = accept_encoding.is_some() && accept_encoding.unwrap().iter().any(
                   |ref qit| qit.item == Encoding::Gzip );
        let tile = service.tile_cached(tileset, x, y, z, gzip);
        if gzip {
            res.set_header_fallback(|| ContentEncoding(vec![Encoding::Gzip]));
        }
        res.set_header_fallback(|| ContentType("application/x-protobuf".to_owned()));
        res.set_header_fallback(|| CacheControl(vec![CacheDirective::MaxAge(cache_max_age)]));
        //res.set_header_fallback(|| ContentLength(tile.len() as u64));
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);

        tile
    });

    if mvt_viewer {
        let static_files = StaticFiles::init();
        server.get("/(:base/)?:static",
                   middleware! { |req, mut res|
            let mut name = req.param("static").unwrap().to_string();
            if let Some(format) = req.param("format") {
                name = format!("{}.{}", name, format);
            }
            if let Some(content) = static_files.content(req.param("base"), name) {
                res.set(content.1);
                return res.send(content.0)
            }
        });
    }

    server.get("/**", StaticFilesHandler::new("public/"));

    let _listening = server
        .listen((bind, port))
        .expect("Failed to launch server");

    let openbrowser = bool::from_str(args.value_of("openbrowser").unwrap_or("true"))
        .unwrap_or(false);
    if openbrowser && mvt_viewer {
        let _res = open::that(format!("http://{}:{}", bind, port));
    }
}

pub fn gen_config(args: &ArgMatches) -> String {
    let toml = r#"
[webserver]
# Bind address. Use 0.0.0.0 to listen on all adresses.
bind = "127.0.0.1"
port = 6767
threads = 4
#cache_control_max_age = 43200
"#;
    let mut config;
    if let Some(_dbconn) = args.value_of("dbconn") {
        let (service, _) = service_from_args(args);
        config = service.gen_runtime_config();
    } else {
        config = MvtService::gen_config();
    }
    config.push_str(toml);
    config
}

#[test]
fn test_gen_config() {
    use core::parse_config;

    let args = ArgMatches::new();
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url,
               "postgresql://user:pass@host/database");
}

#[test]
#[ignore]
fn test_runtime_config() {
    use std::env;
    use clap::App;
    use core::parse_config;

    if env::var("DBCONN").is_err() {
        panic!("DBCONN undefined");
    }
    let args = App::new("test")
        .args_from_usage("--dbconn=[SPEC] 'PostGIS connection postgresql://USER@HOST/DBNAME'")
        .get_matches_from(vec!["", "--dbconn", &env::var("DBCONN").unwrap()]);
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url, env::var("DBCONN").unwrap());
}
<commit_msg>Set default cache max_age to 5'<commit_after>//
// Copyright (c) Pirmin Kalberer. All rights reserved.
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//

use core::config::ApplicationCfg;
use datasource::postgis::PostgisInput;
use core::grid::Grid;
use service::mvt::{MvtService, Tileset};
use core::{Config, read_config, parse_config};
use core::config::DEFAULT_CONFIG;
use serde_json;
use cache::{Tilecache, Nocache, Filecache};

use nickel::{Nickel, Options, HttpRouter, MediaType, Request, Response, MiddlewareResult,
             StaticFilesHandler};
use hyper::header::{CacheControl, CacheDirective, AccessControlAllowOrigin,
                    AccessControlAllowMethods, ContentEncoding, Encoding};
use hyper::method::Method;
use hyper::header;
use std::collections::HashMap;
use std::str::FromStr;
use clap::ArgMatches;
use std::str;
use std::process;
use open;

fn log_request<'mw>(req: &mut Request<MvtService>,
                    res: Response<'mw, MvtService>)
                    -> MiddlewareResult<'mw, MvtService> {
    info!("{} {}", req.origin.method, req.origin.uri);
    res.next_middleware()
}

header! { (ContentType, "Content-Type") => [String] }

#[derive(RustcEncodable)]
struct TilesetInfo {
    name: String,
    layerinfos: String,
    hasviewer: bool,
}

impl TilesetInfo {
    fn from_tileset(set: &Tileset) -> TilesetInfo {
        let mut hasviewer = true;
        let layerinfos: Vec<String> = set.layers
            .iter()
            .map(|l| {
                let geom_type = l.geometry_type.clone().unwrap_or("UNKNOWN".to_string());
                hasviewer = hasviewer &&
                            ["POINT",
                             "LINESTRING",
                             "POLYGON",
                             "MULTPOINT",
                             "MULTILINESTRING",
                             "MULTIPOLYGON"]
                                    .contains(&(&geom_type as &str));
                format!("{} [{}]", &l.name, &geom_type)
            })
            .collect();
        TilesetInfo {
            name: set.name.clone(),
            layerinfos: format!("{}", layerinfos.join(", ")),
            hasviewer: hasviewer,
        }
    }
}

struct StaticFiles {
    files: HashMap<&'static str, (&'static [u8], MediaType)>,
}

impl StaticFiles {
    fn init() -> StaticFiles {
        let mut static_files = StaticFiles { files: HashMap::new() };
        static_files.add("favicon.ico",
                         include_bytes!("static/favicon.ico"),
                         MediaType::Ico);
        static_files.add("index.html",
                         include_bytes!("static/index.html"),
                         MediaType::Html);
        static_files.add("viewer.js",
                         include_bytes!("static/viewer.js"),
                         MediaType::Js);
        static_files.add("viewer.css",
                         include_bytes!("static/viewer.css"),
                         MediaType::Css);
        static_files.add("maputnik.html",
                         include_bytes!("static/maputnik.html"),
                         MediaType::Html);
        static_files.add("maputnik.js",
                         include_bytes!("static/maputnik.js"),
                         MediaType::Js);
        static_files.add("maputnik-vendor.js",
                         include_bytes!("static/maputnik-vendor.js"),
                         MediaType::Js);
        static_files.add("img/maputnik.png",
                         include_bytes!("static/img/maputnik.png"),
                         MediaType::Png);
        static_files.add("fonts/Roboto-Regular.ttf",
                         include_bytes!("static/fonts/Roboto-Regular.ttf"),
                         MediaType::Ttf);
        static_files.add("fonts/Roboto-Medium.ttf",
                         include_bytes!("static/fonts/Roboto-Medium.ttf"),
                         MediaType::Ttf);
        static_files
    }
    fn add(&mut self, name: &'static str, data: &'static [u8], media_type: MediaType) {
        self.files.insert(name, (data, media_type));
    }
    fn content(&self, base: Option<&str>, name: String) -> Option<&(&[u8], MediaType)> {
        let mut key = if name == "." {
            "index.html".to_string()
        } else {
            name
        };
        if let Some(path) = base {
            key = format!("{}/{}", path, key);
        }
        self.files.get(&key as &str)
    }
}

pub fn service_from_args(args: &ArgMatches) -> (MvtService, ApplicationCfg) {
    if let Some(cfgpath) = args.value_of("config") {
        info!("Reading configuration from '{}'", cfgpath);
        let config =
            read_config(cfgpath).unwrap_or_else(|err| {
                                                    println!("Error reading configuration - {} ",
                                                             err);
                                                    process::exit(1)
                                                });
        let mut svc =
            MvtService::from_config(&config).unwrap_or_else(|err| {
                                                                println!("Error reading configuration - {} ", err);
                                                                process::exit(1)
                                                            });
        svc.connect();
        (svc, config)
    } else {
        let config = parse_config(DEFAULT_CONFIG.to_string(), "").unwrap();
        let cache = match args.value_of("cache") {
            None => Tilecache::Nocache(Nocache),
            Some(dir) => {
                Tilecache::Filecache(Filecache {
                                         basepath: dir.to_string(),
                                         baseurl: None,
                                     })
            }
        };
        let simplify = bool::from_str(args.value_of("simplify").unwrap_or("true")).unwrap_or(false);
        let clip = bool::from_str(args.value_of("clip").unwrap_or("true")).unwrap_or(false);
        if let Some(dbconn) = args.value_of("dbconn") {
            let pg = PostgisInput::new(dbconn).connected();
            let grid = Grid::web_mercator();
            let detect_geometry_types = true; //TODO: add option (maybe slow for many geometries)
            let mut layers = pg.detect_layers(detect_geometry_types);
            let mut tilesets = Vec::new();
            while let Some(mut l) = layers.pop() {
                let extent = pg.layer_extent(&l);
                l.simplify = Some(simplify);
                if simplify {
                    // Limit features by default unless simplify is set to false
                    l.query_limit = Some(1000);
                }
                l.buffer_size = match l.geometry_type {
                    Some(ref geom) => {
                        if clip {
                            if geom.contains("POLYGON") {
                                Some(1)
                            } else {
                                Some(0)
                            }
                        } else {
                            None
                        }
                    }
                    None => None,
                };
                let tileset = Tileset {
                    name: l.name.clone(),
                    extent: extent,
                    layers: vec![l],
                };
                tilesets.push(tileset);
            }
            let svc = MvtService {
                input: pg,
                grid: grid,
                tilesets: tilesets,
                cache: cache,
            };
            (svc, config)
        } else {
            println!("Either 'config' or 'dbconn' is required");
            process::exit(1)
        }
    }
}

pub fn webserver(args: &ArgMatches) {
    let (mut service, config) = service_from_args(args);

    let mvt_viewer = config.service.mvt.viewer;
    let bind: &str = &config.webserver.bind.unwrap_or("127.0.0.1".to_string());
    let port = config.webserver.port.unwrap_or(6767);
    let threads = config.webserver.threads.unwrap_or(4) as usize;
    let cache_max_age = config.webserver.cache_control_max_age.unwrap_or(300);

    service.prepare_feature_queries();
    service.init_cache();

    let mut tileset_infos: Vec<TilesetInfo> = service
        .tilesets
        .iter()
        .map(|set| TilesetInfo::from_tileset(&set))
        .collect();
    tileset_infos.sort_by_key(|ti| ti.name.clone());

    let mut server = Nickel::with_data(service);
    server.options = Options::default().thread_count(Some(threads));
    // Avoid thread exhaustion caused by hypers keep_alive handling (https://github.com/hyperium/hyper/issues/368)
    server.keep_alive_timeout(None);
    server.utilize(log_request);

    server.get("/**(.style)?.json",
               middleware! { |_req, mut res|
        res.set(MediaType::Json);
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);
    });

    server.get("/index.json",
               middleware! { |_req, res|
        let service: &MvtService = res.server_data();
        let json = service.get_mvt_metadata().unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    // Font list for Maputnik
    server.get("/fontstacks.json",
               middleware! { |_req, _res|
        "[]"
    });

    server.get("/:tileset.json",
               middleware! { |req, res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        let host = req.origin.headers.get::<header::Host>().unwrap();
        let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
        let json = service.get_tilejson(&baseurl, &tileset).unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    server.get("/:tileset.style.json",
               middleware! { |req, res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        let host = req.origin.headers.get::<header::Host>().unwrap();
        let baseurl = format!("http://{}:{}", host.hostname, host.port.unwrap_or(80));
        let json = service.get_stylejson(&baseurl, &tileset).unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    server.get("/:tileset/metadata.json",
               middleware! { |req, res|
        let service: &MvtService = res.server_data();
        let tileset = req.param("tileset").unwrap();
        let json = service.get_mbtiles_metadata(&tileset).unwrap();
        serde_json::to_vec(&json).unwrap()
    });

    server.get("/:tileset/:z/:x/:y.pbf",
               middleware! { |req, mut res|
        let service: &MvtService = res.server_data();

        let tileset = req.param("tileset").unwrap();
        let z = req.param("z").unwrap().parse::<u8>().unwrap();
        let x = req.param("x").unwrap().parse::<u32>().unwrap();
        let y = req.param("y").unwrap().parse::<u32>().unwrap();

        let accept_encoding = req.origin.headers.get::<header::AcceptEncoding>();
        let gzip = accept_encoding.is_some() && accept_encoding.unwrap().iter().any(
                   |ref qit| qit.item == Encoding::Gzip );
        let tile = service.tile_cached(tileset, x, y, z, gzip);
        if gzip {
            res.set_header_fallback(|| ContentEncoding(vec![Encoding::Gzip]));
        }
        res.set_header_fallback(|| ContentType("application/x-protobuf".to_owned()));
        res.set_header_fallback(|| CacheControl(vec![CacheDirective::MaxAge(cache_max_age)]));
        //res.set_header_fallback(|| ContentLength(tile.len() as u64));
        res.set(AccessControlAllowMethods(vec![Method::Get]));
        res.set(AccessControlAllowOrigin::Any);

        tile
    });

    if mvt_viewer {
        let static_files = StaticFiles::init();
        server.get("/(:base/)?:static",
                   middleware! { |req, mut res|
            let mut name = req.param("static").unwrap().to_string();
            if let Some(format) = req.param("format") {
                name = format!("{}.{}", name, format);
            }
            if let Some(content) = static_files.content(req.param("base"), name) {
                res.set(content.1);
                return res.send(content.0)
            }
        });
    }

    server.get("/**", StaticFilesHandler::new("public/"));

    let _listening = server
        .listen((bind, port))
        .expect("Failed to launch server");

    let openbrowser = bool::from_str(args.value_of("openbrowser").unwrap_or("true"))
        .unwrap_or(false);
    if openbrowser && mvt_viewer {
        let _res = open::that(format!("http://{}:{}", bind, port));
    }
}

pub fn gen_config(args: &ArgMatches) -> String {
    let toml = r#"
[webserver]
# Bind address. Use 0.0.0.0 to listen on all adresses.
bind = "127.0.0.1"
port = 6767
threads = 4
#cache_control_max_age = 43200
"#;
    let mut config;
    if let Some(_dbconn) = args.value_of("dbconn") {
        let (service, _) = service_from_args(args);
        config = service.gen_runtime_config();
    } else {
        config = MvtService::gen_config();
    }
    config.push_str(toml);
    config
}

#[test]
fn test_gen_config() {
    use core::parse_config;

    let args = ArgMatches::new();
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url,
               "postgresql://user:pass@host/database");
}

#[test]
#[ignore]
fn test_runtime_config() {
    use std::env;
    use clap::App;
    use core::parse_config;

    if env::var("DBCONN").is_err() {
        panic!("DBCONN undefined");
    }
    let args = App::new("test")
        .args_from_usage("--dbconn=[SPEC] 'PostGIS connection postgresql://USER@HOST/DBNAME'")
        .get_matches_from(vec!["", "--dbconn", &env::var("DBCONN").unwrap()]);
    let toml = gen_config(&args);
    println!("{}", toml);
    assert_eq!(Some("# t-rex configuration"), toml.lines().next());

    let config = parse_config(toml, "").unwrap();
    let service = MvtService::from_config(&config).unwrap();
    assert_eq!(service.input.connection_url, env::var("DBCONN").unwrap());
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::{BuiltDisplayList, DisplayListWithCache, ColorF, DynamicProperties, Epoch, FontRenderMode};
use api::{PipelineId, PropertyBinding, PropertyBindingId, PropertyValue, MixBlendMode, StackingContext};
use api::MemoryReport;
use api::units::*;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use crate::composite::CompositorKind;
use crate::clip::{ClipStore, ClipDataStore};
use crate::picture::{SliceId, TileCacheParams};
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
use crate::hit_test::{HitTester, HitTestingScene, HitTestingSceneStats};
use crate::internal_types::{FastHashMap, FastHashSet};
use crate::prim_store::{PrimitiveStore, PrimitiveStoreStats, PictureIndex};
use std::sync::Arc;

/// Stores a map of the animated property bindings for the current display list. These
/// can be used to animate the transform and/or opacity of a display list without
/// re-submitting the display list itself.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct SceneProperties {
    transform_properties: FastHashMap<PropertyBindingId, LayoutTransform>,
    float_properties: FastHashMap<PropertyBindingId, f32>,
    color_properties: FastHashMap<PropertyBindingId, ColorF>,
    current_properties: DynamicProperties,
    pending_properties: Option<DynamicProperties>,
}

impl SceneProperties {
    pub fn new() -> Self {
        SceneProperties {
            transform_properties: FastHashMap::default(),
            float_properties: FastHashMap::default(),
            color_properties: FastHashMap::default(),
            current_properties: DynamicProperties::default(),
            pending_properties: None,
        }
    }

    /// Set the current property list for this display list.
    pub fn set_properties(&mut self, properties: DynamicProperties) {
        self.pending_properties = Some(properties);
    }

    /// Add to the current property list for this display list.
    pub fn add_transforms(&mut self, transforms: Vec<PropertyValue<LayoutTransform>>) {
        let mut pending_properties = self.pending_properties
            .take()
            .unwrap_or_default();

        pending_properties.transforms.extend(transforms);

        self.pending_properties = Some(pending_properties);
    }

    /// Flush any pending updates to the scene properties. Returns
    /// true if the properties have changed since the last flush
    /// was called. This code allows properties to be changed by
    /// multiple set_properties and add_properties calls during a
    /// single transaction, and still correctly determine if any
    /// properties have changed. This can have significant power
    /// saving implications, allowing a frame build to be skipped
    /// if the properties haven't changed in many cases.
    pub fn flush_pending_updates(&mut self) -> bool {
        let mut properties_changed = false;

        if let Some(ref pending_properties) = self.pending_properties {
            if *pending_properties != self.current_properties {
                self.transform_properties.clear();
                self.float_properties.clear();

                for property in &pending_properties.transforms {
                    self.transform_properties
                        .insert(property.key.id, property.value);
                }

                for property in &pending_properties.floats {
                    self.float_properties
                        .insert(property.key.id, property.value);
                }

                for property in &pending_properties.colors {
                    self.color_properties
                        .insert(property.key.id, property.value);
                }

                self.current_properties = pending_properties.clone();
                properties_changed = true;
            }
        }

        properties_changed
    }

    /// Get the current value for a transform property.
    pub fn resolve_layout_transform(
        &self,
        property: &PropertyBinding<LayoutTransform>,
    ) -> LayoutTransform {
        match *property {
            PropertyBinding::Value(value) => value,
            PropertyBinding::Binding(ref key, v) => {
                self.transform_properties
                    .get(&key.id)
                    .cloned()
                    .unwrap_or(v)
            }
        }
    }

    /// Get the current value for a float property.
    pub fn resolve_float(
        &self,
        property: &PropertyBinding<f32>
    ) -> f32 {
        match *property {
            PropertyBinding::Value(value) => value,
            PropertyBinding::Binding(ref key, v) => {
                self.float_properties
                    .get(&key.id)
                    .cloned()
                    .unwrap_or(v)
            }
        }
    }

    pub fn float_properties(&self) -> &FastHashMap<PropertyBindingId, f32> {
        &self.float_properties
    }

    /// Get the current value for a color property.
    pub fn resolve_color(
        &self,
        property: &PropertyBinding<ColorF>
    ) -> ColorF {
        match *property {
            PropertyBinding::Value(value) => value,
            PropertyBinding::Binding(ref key, v) => {
                self.color_properties
                    .get(&key.id)
                    .cloned()
                    .unwrap_or(v)
            }
        }
    }

    pub fn color_properties(&self) -> &FastHashMap<PropertyBindingId, ColorF> {
        &self.color_properties
    }

}

/// A representation of the layout within the display port for a given document or iframe.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone)]
pub struct ScenePipeline {
    pub pipeline_id: PipelineId,
    pub viewport_size: LayoutSize,
    pub content_size: LayoutSize,
    pub background_color: Option<ColorF>,
    pub display_list: DisplayListWithCache,
}

/// A complete representation of the layout bundling visible pipelines together.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone)]
pub struct Scene {
    pub root_pipeline_id: Option<PipelineId>,
    pub pipelines: FastHashMap<PipelineId, ScenePipeline>,
    pub pipeline_epochs: FastHashMap<PipelineId, Epoch>,
}

impl Scene {
    pub fn new() -> Self {
        Scene {
            root_pipeline_id: None,
            pipelines: FastHashMap::default(),
            pipeline_epochs: FastHashMap::default(),
        }
    }

    pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
        self.root_pipeline_id = Some(pipeline_id);
    }

    pub fn set_display_list(
        &mut self,
        pipeline_id: PipelineId,
        epoch: Epoch,
        display_list: BuiltDisplayList,
        background_color: Option<ColorF>,
        viewport_size: LayoutSize,
        content_size: LayoutSize,
    ) {
        // Adds a cache to the given display list. If this pipeline already had
        // a display list before, that display list is updated and used instead.
        let display_list = match self.pipelines.remove(&pipeline_id) {
            Some(mut pipeline) => {
                pipeline.display_list.update(display_list);
                pipeline.display_list
            }
            None => DisplayListWithCache::new_from_list(display_list)
        };

        let new_pipeline = ScenePipeline {
            pipeline_id,
            viewport_size,
            content_size,
            background_color,
            display_list,
        };

        self.pipelines.insert(pipeline_id, new_pipeline);
        self.pipeline_epochs.insert(pipeline_id, epoch);
    }

    pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
        if self.root_pipeline_id == Some(pipeline_id) {
            self.root_pipeline_id = None;
        }
        self.pipelines.remove(&pipeline_id);
        self.pipeline_epochs.remove(&pipeline_id);
    }

    pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
        self.pipeline_epochs.insert(pipeline_id, epoch);
    }

    pub fn has_root_pipeline(&self) -> bool {
        if let Some(ref root_id) = self.root_pipeline_id {
            return self.pipelines.contains_key(root_id);
        }

        false
    }

    pub fn report_memory(
        &self,
        ops: &mut MallocSizeOfOps,
        report: &mut MemoryReport
    ) {
        for (_, pipeline) in &self.pipelines {
            report.display_list += pipeline.display_list.size_of(ops)
        }
    }
}

pub trait StackingContextHelpers {
    fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
}

impl StackingContextHelpers for StackingContext {
    fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
        match self.mix_blend_mode {
            MixBlendMode::Normal => None,
            _ => Some(self.mix_blend_mode),
        }
    }
}

/// WebRender's internal representation of the scene.
pub struct BuiltScene {
    pub has_root_pipeline: bool,
    pub pipeline_epochs: FastHashMap<PipelineId, Epoch>,
    pub output_rect: DeviceIntRect,
    pub background_color: Option<ColorF>,
    pub root_pic_index: PictureIndex,
    pub prim_store: PrimitiveStore,
    pub clip_store: ClipStore,
    pub config: FrameBuilderConfig,
    pub spatial_tree: SpatialTree,
    pub hit_testing_scene: Arc<HitTestingScene>,
    pub content_slice_count: usize,
    pub picture_cache_spatial_nodes: FastHashSet<SpatialNodeIndex>,
    pub tile_caches: FastHashMap<SliceId, TileCacheParams>,
}

impl BuiltScene {
    pub fn empty() -> Self {
        BuiltScene {
            has_root_pipeline: false,
            pipeline_epochs: FastHashMap::default(),
            output_rect: DeviceIntRect::zero(),
            background_color: None,
            root_pic_index: PictureIndex(0),
            prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()),
            clip_store: ClipStore::new(),
            spatial_tree: SpatialTree::new(),
            hit_testing_scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())),
            content_slice_count: 0,
            picture_cache_spatial_nodes: FastHashSet::default(),
            tile_caches: FastHashMap::default(),
            config: FrameBuilderConfig {
                default_font_render_mode: FontRenderMode::Mono,
                dual_source_blending_is_enabled: true,
                dual_source_blending_is_supported: false,
                chase_primitive: ChasePrimitive::Nothing,
                global_enable_picture_caching: false,
                testing: false,
                gpu_supports_fast_clears: false,
                gpu_supports_advanced_blend: false,
                advanced_blend_is_coherent: false,
                batch_lookback_count: 0,
                background_color: None,
                compositor_kind: CompositorKind::default(),
                tile_size_override: None,
                max_depth_ids: 0,
                max_target_size: 0,
            },
        }
    }

    /// Get the memory usage statistics to pre-allocate for the next scene.
    pub fn get_stats(&self) -> SceneStats {
        SceneStats {
            prim_store_stats: self.prim_store.get_stats(),
            hit_test_stats: self.hit_testing_scene.get_stats(),
        }
    }

    pub fn create_hit_tester(
        &mut self,
        clip_data_store: &ClipDataStore,
    ) -> HitTester {
        HitTester::new(
            Arc::clone(&self.hit_testing_scene),
            &self.spatial_tree,
            &self.clip_store,
            clip_data_store,
        )
    }
}

/// Stores the allocation sizes of various arrays in the built
/// scene. This is retrieved from the current frame builder
/// and used to reserve an approximately correct capacity of
/// the arrays for the next scene that is getting built.
pub struct SceneStats {
    pub prim_store_stats: PrimitiveStoreStats,
    pub hit_test_stats: HitTestingSceneStats,
}

impl SceneStats {
    pub fn empty() -> Self {
        SceneStats {
            prim_store_stats: PrimitiveStoreStats::empty(),
            hit_test_stats: HitTestingSceneStats::empty(),
        }
    }
}
<commit_msg>Auto merge of #4005 - moz-gfx:github-sync, r=auto<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use api::{BuiltDisplayList, DisplayListWithCache, ColorF, DynamicProperties, Epoch, FontRenderMode};
use api::{PipelineId, PropertyBinding, PropertyBindingId, PropertyValue, MixBlendMode, StackingContext};
use api::MemoryReport;
use api::units::*;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use crate::composite::CompositorKind;
use crate::clip::{ClipStore, ClipDataStore};
use crate::picture::{SliceId, TileCacheParams};
use crate::spatial_tree::{SpatialTree, SpatialNodeIndex};
use crate::frame_builder::{ChasePrimitive, FrameBuilderConfig};
use crate::hit_test::{HitTester, HitTestingScene, HitTestingSceneStats};
use crate::internal_types::{FastHashMap, FastHashSet};
use crate::prim_store::{PrimitiveStore, PrimitiveStoreStats, PictureIndex};
use std::sync::Arc;

/// Stores a map of the animated property bindings for the current display list. These
/// can be used to animate the transform and/or opacity of a display list without
/// re-submitting the display list itself.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
pub struct SceneProperties {
    transform_properties: FastHashMap<PropertyBindingId, LayoutTransform>,
    float_properties: FastHashMap<PropertyBindingId, f32>,
    color_properties: FastHashMap<PropertyBindingId, ColorF>,
    current_properties: DynamicProperties,
    pending_properties: Option<DynamicProperties>,
}

impl SceneProperties {
    pub fn new() -> Self {
        SceneProperties {
            transform_properties: FastHashMap::default(),
            float_properties: FastHashMap::default(),
            color_properties: FastHashMap::default(),
            current_properties: DynamicProperties::default(),
            pending_properties: None,
        }
    }

    /// Set the current property list for this display list.
    pub fn set_properties(&mut self, properties: DynamicProperties) {
        self.pending_properties = Some(properties);
    }

    /// Add to the current property list for this display list.
    pub fn add_transforms(&mut self, transforms: Vec<PropertyValue<LayoutTransform>>) {
        let mut pending_properties = self.pending_properties
            .take()
            .unwrap_or_default();

        pending_properties.transforms.extend(transforms);

        self.pending_properties = Some(pending_properties);
    }

    /// Flush any pending updates to the scene properties. Returns
    /// true if the properties have changed since the last flush
    /// was called. This code allows properties to be changed by
    /// multiple set_properties and add_properties calls during a
    /// single transaction, and still correctly determine if any
    /// properties have changed. This can have significant power
    /// saving implications, allowing a frame build to be skipped
    /// if the properties haven't changed in many cases.
    pub fn flush_pending_updates(&mut self) -> bool {
        let mut properties_changed = false;

        if let Some(ref pending_properties) = self.pending_properties {
            if *pending_properties != self.current_properties {
                self.transform_properties.clear();
                self.float_properties.clear();
                self.color_properties.clear();

                for property in &pending_properties.transforms {
                    self.transform_properties
                        .insert(property.key.id, property.value);
                }

                for property in &pending_properties.floats {
                    self.float_properties
                        .insert(property.key.id, property.value);
                }

                for property in &pending_properties.colors {
                    self.color_properties
                        .insert(property.key.id, property.value);
                }

                self.current_properties = pending_properties.clone();
                properties_changed = true;
            }
        }

        properties_changed
    }

    /// Get the current value for a transform property.
    pub fn resolve_layout_transform(
        &self,
        property: &PropertyBinding<LayoutTransform>,
    ) -> LayoutTransform {
        match *property {
            PropertyBinding::Value(value) => value,
            PropertyBinding::Binding(ref key, v) => {
                self.transform_properties
                    .get(&key.id)
                    .cloned()
                    .unwrap_or(v)
            }
        }
    }

    /// Get the current value for a float property.
    pub fn resolve_float(
        &self,
        property: &PropertyBinding<f32>
    ) -> f32 {
        match *property {
            PropertyBinding::Value(value) => value,
            PropertyBinding::Binding(ref key, v) => {
                self.float_properties
                    .get(&key.id)
                    .cloned()
                    .unwrap_or(v)
            }
        }
    }

    pub fn float_properties(&self) -> &FastHashMap<PropertyBindingId, f32> {
        &self.float_properties
    }

    /// Get the current value for a color property.
    pub fn resolve_color(
        &self,
        property: &PropertyBinding<ColorF>
    ) -> ColorF {
        match *property {
            PropertyBinding::Value(value) => value,
            PropertyBinding::Binding(ref key, v) => {
                self.color_properties
                    .get(&key.id)
                    .cloned()
                    .unwrap_or(v)
            }
        }
    }

    pub fn color_properties(&self) -> &FastHashMap<PropertyBindingId, ColorF> {
        &self.color_properties
    }

}

/// A representation of the layout within the display port for a given document or iframe.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone)]
pub struct ScenePipeline {
    pub pipeline_id: PipelineId,
    pub viewport_size: LayoutSize,
    pub content_size: LayoutSize,
    pub background_color: Option<ColorF>,
    pub display_list: DisplayListWithCache,
}

/// A complete representation of the layout bundling visible pipelines together.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Clone)]
pub struct Scene {
    pub root_pipeline_id: Option<PipelineId>,
    pub pipelines: FastHashMap<PipelineId, ScenePipeline>,
    pub pipeline_epochs: FastHashMap<PipelineId, Epoch>,
}

impl Scene {
    pub fn new() -> Self {
        Scene {
            root_pipeline_id: None,
            pipelines: FastHashMap::default(),
            pipeline_epochs: FastHashMap::default(),
        }
    }

    pub fn set_root_pipeline_id(&mut self, pipeline_id: PipelineId) {
        self.root_pipeline_id = Some(pipeline_id);
    }

    pub fn set_display_list(
        &mut self,
        pipeline_id: PipelineId,
        epoch: Epoch,
        display_list: BuiltDisplayList,
        background_color: Option<ColorF>,
        viewport_size: LayoutSize,
        content_size: LayoutSize,
    ) {
        // Adds a cache to the given display list. If this pipeline already had
        // a display list before, that display list is updated and used instead.
        let display_list = match self.pipelines.remove(&pipeline_id) {
            Some(mut pipeline) => {
                pipeline.display_list.update(display_list);
                pipeline.display_list
            }
            None => DisplayListWithCache::new_from_list(display_list)
        };

        let new_pipeline = ScenePipeline {
            pipeline_id,
            viewport_size,
            content_size,
            background_color,
            display_list,
        };

        self.pipelines.insert(pipeline_id, new_pipeline);
        self.pipeline_epochs.insert(pipeline_id, epoch);
    }

    pub fn remove_pipeline(&mut self, pipeline_id: PipelineId) {
        if self.root_pipeline_id == Some(pipeline_id) {
            self.root_pipeline_id = None;
        }
        self.pipelines.remove(&pipeline_id);
        self.pipeline_epochs.remove(&pipeline_id);
    }

    pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
        self.pipeline_epochs.insert(pipeline_id, epoch);
    }

    pub fn has_root_pipeline(&self) -> bool {
        if let Some(ref root_id) = self.root_pipeline_id {
            return self.pipelines.contains_key(root_id);
        }

        false
    }

    pub fn report_memory(
        &self,
        ops: &mut MallocSizeOfOps,
        report: &mut MemoryReport
    ) {
        for (_, pipeline) in &self.pipelines {
            report.display_list += pipeline.display_list.size_of(ops)
        }
    }
}

pub trait StackingContextHelpers {
    fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode>;
}

impl StackingContextHelpers for StackingContext {
    fn mix_blend_mode_for_compositing(&self) -> Option<MixBlendMode> {
        match self.mix_blend_mode {
            MixBlendMode::Normal => None,
            _ => Some(self.mix_blend_mode),
        }
    }
}

/// WebRender's internal representation of the scene.
pub struct BuiltScene {
    pub has_root_pipeline: bool,
    pub pipeline_epochs: FastHashMap<PipelineId, Epoch>,
    pub output_rect: DeviceIntRect,
    pub background_color: Option<ColorF>,
    pub root_pic_index: PictureIndex,
    pub prim_store: PrimitiveStore,
    pub clip_store: ClipStore,
    pub config: FrameBuilderConfig,
    pub spatial_tree: SpatialTree,
    pub hit_testing_scene: Arc<HitTestingScene>,
    pub content_slice_count: usize,
    pub picture_cache_spatial_nodes: FastHashSet<SpatialNodeIndex>,
    pub tile_caches: FastHashMap<SliceId, TileCacheParams>,
}

impl BuiltScene {
    pub fn empty() -> Self {
        BuiltScene {
            has_root_pipeline: false,
            pipeline_epochs: FastHashMap::default(),
            output_rect: DeviceIntRect::zero(),
            background_color: None,
            root_pic_index: PictureIndex(0),
            prim_store: PrimitiveStore::new(&PrimitiveStoreStats::empty()),
            clip_store: ClipStore::new(),
            spatial_tree: SpatialTree::new(),
            hit_testing_scene: Arc::new(HitTestingScene::new(&HitTestingSceneStats::empty())),
            content_slice_count: 0,
            picture_cache_spatial_nodes: FastHashSet::default(),
            tile_caches: FastHashMap::default(),
            config: FrameBuilderConfig {
                default_font_render_mode: FontRenderMode::Mono,
                dual_source_blending_is_enabled: true,
                dual_source_blending_is_supported: false,
                chase_primitive: ChasePrimitive::Nothing,
                global_enable_picture_caching: false,
                testing: false,
                gpu_supports_fast_clears: false,
                gpu_supports_advanced_blend: false,
                advanced_blend_is_coherent: false,
                batch_lookback_count: 0,
                background_color: None,
                compositor_kind: CompositorKind::default(),
                tile_size_override: None,
                max_depth_ids: 0,
                max_target_size: 0,
            },
        }
    }

    /// Get the memory usage statistics to pre-allocate for the next scene.
    pub fn get_stats(&self) -> SceneStats {
        SceneStats {
            prim_store_stats: self.prim_store.get_stats(),
            hit_test_stats: self.hit_testing_scene.get_stats(),
        }
    }

    pub fn create_hit_tester(
        &mut self,
        clip_data_store: &ClipDataStore,
    ) -> HitTester {
        HitTester::new(
            Arc::clone(&self.hit_testing_scene),
            &self.spatial_tree,
            &self.clip_store,
            clip_data_store,
        )
    }
}

/// Stores the allocation sizes of various arrays in the built
/// scene. This is retrieved from the current frame builder
/// and used to reserve an approximately correct capacity of
/// the arrays for the next scene that is getting built.
pub struct SceneStats {
    pub prim_store_stats: PrimitiveStoreStats,
    pub hit_test_stats: HitTestingSceneStats,
}

impl SceneStats {
    pub fn empty() -> Self {
        SceneStats {
            prim_store_stats: PrimitiveStoreStats::empty(),
            hit_test_stats: HitTestingSceneStats::empty(),
        }
    }
}
<|endoftext|>


<commit_before>#![allow(unstable)]
extern crate zaldinar;
extern crate getopts;

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

fn print_err(msg: String) {
    if let Err(e) = writeln!(&mut io::stderr(), "{}", msg) {
        panic!("Failed to write to stderr.\
            \nOriginal error output: {}\
            \nSecondary error writing to stderr: {}", msg, e);
    }
}

fn main() {
    let args = os::args();
    let program = args[0].as_slice();
    let opts = &[
        getopts::optopt("c", "config", "set config file name", "FILE"),
        getopts::optflag("h", "help", "print this help menu"),
        getopts::optflag("v", "version", "print program version"),
    ];

    let matches = match getopts::getopts(args.tail(), opts) {
        Ok(v) => v,
        Err(e) => {
            print_err(e.to_string());
            return;
        }
    };

    if matches.opt_present("help") {
        println!("{}", getopts::usage(getopts::short_usage(program, opts).as_slice(), opts));
        return;
    } else if matches.opt_present("version") {
        println!("zaldinar version {}", zaldinar::VERSION);
        return;
    }

    let config_path = match matches.opt_str("config") {
        Some(v) => {
            let absolute = match os::make_absolute(&Path::new(v)) {
                Ok(v) => v,
                Err(e) => {
                    print_err(format!("Failed to make path absolute: {}", e));
                    return;
                }
            };
            println!("Using configuration: {}", absolute.display());
            absolute
        },
        None => Path::new("config.json"),
    };

    loop {
        let config = match zaldinar::ClientConfiguration::load_from_file(&config_path) {
            Ok(v) => v,
            Err(e) => {
                print_err(format!("Error loading configuration: {}", e));
                std::os::set_exit_status(1);
                return;
            },
        };

        match zaldinar::client::run(config) {
            Ok(zaldinar::client::ExecutingState::Done) => {
                println!("Done, exiting.");
                break
            },
            Ok(zaldinar::client::ExecutingState::Running) => {
                println!("Restarting zaldinar main loop.");
                continue;
            },
            Ok(zaldinar::client::ExecutingState::Restart) => {
                println!("Restarting zaldinar using exec.");
                continue; // TODO: exec
            }
            Err(e) => {
                println!("Error running client: {}", e);
                std::os::set_exit_status(1);
                // There is no need to stop other tasks at this point, because the only time
                // client.connect() returns Err is before any tasks are started
            },
        };
    }
}
<commit_msg>Make print_err into a macro in main.rs<commit_after>#![allow(unstable)]
extern crate zaldinar;
extern crate getopts;

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

macro_rules! print_err {
    ($($arg:tt)*) => (
        if let Err(e) = write!(&mut ::std::io::stdio::stderr_raw(), $($arg)*) {
            panic!("Failed to write to stderr.\
                \nOriginal error output: {}\
                \nSecondary error writing to stderr: {}", format!($($arg)*), e);
        }
    )
}

fn main() {
    let args = os::args();
    let program = args[0].as_slice();
    let opts = &[
        getopts::optopt("c", "config", "set config file name", "FILE"),
        getopts::optflag("h", "help", "print this help menu"),
        getopts::optflag("v", "version", "print program version"),
    ];

    let matches = match getopts::getopts(args.tail(), opts) {
        Ok(v) => v,
        Err(e) => {
            print_err!("{}", e.to_string());
            return;
        }
    };

    if matches.opt_present("help") {
        println!("{}", getopts::usage(getopts::short_usage(program, opts).as_slice(), opts));
        return;
    } else if matches.opt_present("version") {
        println!("zaldinar version {}", zaldinar::VERSION);
        return;
    }

    let config_path = match matches.opt_str("config") {
        Some(v) => {
            let absolute = match os::make_absolute(&Path::new(v)) {
                Ok(v) => v,
                Err(e) => {
                    print_err!("Failed to make path absolute: {}", e);
                    return;
                }
            };
            println!("Using configuration: {}", absolute.display());
            absolute
        },
        None => Path::new("config.json"),
    };

    loop {
        let config = match zaldinar::ClientConfiguration::load_from_file(&config_path) {
            Ok(v) => v,
            Err(e) => {
                print_err!("Error loading configuration: {}", e);
                std::os::set_exit_status(1);
                return;
            },
        };

        match zaldinar::client::run(config) {
            Ok(zaldinar::client::ExecutingState::Done) => {
                println!("Done, exiting.");
                break
            },
            Ok(zaldinar::client::ExecutingState::Running) => {
                println!("Restarting zaldinar main loop.");
                continue;
            },
            Ok(zaldinar::client::ExecutingState::Restart) => {
                println!("Restarting zaldinar using exec.");
                continue; // TODO: exec
            }
            Err(e) => {
                println!("Error running client: {}", e);
                std::os::set_exit_status(1);
                // There is no need to stop other tasks at this point, because the only time
                // client.connect() returns Err is before any tasks are started
            },
        };
    }
}
<|endoftext|>


<commit_before>extern crate hkg;
extern crate rustbox;
extern crate rustc_serialize;
extern crate chrono;
extern crate kuchiki;

use kuchiki::traits::*;

use std::default::Default;

use rustbox::{Color, RustBox, Key};
use rustc_serialize::json;
use rustc_serialize::json::Json;

use chrono::*;

use hkg::utility::cache;
use hkg::model::ListTopicItem;
use hkg::model::ShowItem;
use hkg::model::UrlQueryItem;

use std::path::Path;

use std::io::prelude::*;
use std::fs::File;

#[derive(PartialEq, Eq, Copy, Clone)]
enum Status {
    List,
    Show,
}

fn main() {

    // GUI init
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    rustbox.print(1,
                  1,
                  rustbox::RB_NORMAL,
                  Color::White,
                  Color::Black,
                  &format!("start => {}", Local::now()));

    let title = String::from("高登");
    let s = cache::readfile(String::from("data/topics.json"));
    let collection: Vec<ListTopicItem> = json::decode(&s).unwrap();

    // initialize show with empty page
    let mut show_file;
    let mut show_item = ShowItem {
        url_query: UrlQueryItem { message: String::from("") },
        replies: vec![],
        page: 0,
        max_page: 0,
        reply_count: String::from(""),
        title: String::from(""),
    };

    let mut status = String::from("> ");

    let mut state = Status::List;
    let mut prev_state = state;
    let mut prev_width = rustbox.width();

    let mut list = hkg::screen::list::List::new(&rustbox);
    let mut show = hkg::screen::show::Show::new(&rustbox);

    loop {

        // show UI
        if prev_state != state {
            hkg::screen::common::clear(&rustbox); // clear screen when switching state
            prev_state = state;
        }

        let url = &collection[1].title.url;

        rustbox.print(1, 2, rustbox::RB_NORMAL, Color::White, Color::Black, url);

        rustbox.print(1,
                      3,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("before parse => {}", Local::now()));

        let p = "data/html/6360604/show_1.html";
        let document = kuchiki::parse_html().from_utf8().from_file(&p).unwrap();

        let replies_data = document.select(".repliers tr[userid][username]")
                                   .unwrap()
                                   .collect::<Vec<_>>();

        let mut ss = String::new();
        let mut offsetY = 0;
        for (index, tr) in replies_data.iter().enumerate() {
            let tr_attrs = (&tr.attributes).borrow();
            let userid = tr_attrs.get("userid").unwrap();
            let username = tr_attrs.get("username").unwrap();

            let content = tr.as_node().select(".repliers_right .ContentGrid").unwrap()
                                .next().unwrap() // first
                                .as_node().text_contents(); // text

            let splits = content.split("\u{000A}")
                                .into_iter()
                                .filter(|x| (**x).trim().len() > 0)
                                .collect::<Vec<_>>();
            let splits2 = splits.clone();
            for (jndex, sentence) in splits.iter().enumerate() {
                let tmp_str = format!("[{:0>2}][{:0>2}]={}\n", index, jndex, sentence.trim());
                ss.push_str(&tmp_str);
                rustbox.print(1,
                              index + jndex + 5 + offsetY,
                              rustbox::RB_NORMAL,
                              Color::White,
                              Color::Black,
                              &tmp_str);
            }

            offsetY += splits2.len() - 1;

        }

        let mut f = File::create("foo.txt").unwrap();
        // let uu :Vec<u8> = ss.chars;
        f.write_all(ss.as_bytes());

        rustbox.print(1,
                      4,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("after parse => {}", Local::now()));

        // match state {
        //     Status::List => {
        //         list.print(&title, &collection);
        //     }
        //     Status::Show => {
        //         show.print(&title, &show_item);
        //     }
        // }

        print_status(&rustbox, &status);

        rustbox.present();

        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {

                if prev_width != rustbox.width() {
                    hkg::screen::common::clear(&rustbox);
                    prev_width = rustbox.width();
                }

                match key {
                    Key::Char('q') => {
                        break;
                    }
                    Key::PageUp => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PU");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollUp(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::PageDown => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PD");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollDown(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Up => {
                        let w = rustbox.width();
                        status = format_status(status, w, "U");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp > 1 {
                                    list.select_topic(tmp - 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollUp(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }

                    }
                    Key::Down => {
                        let w = rustbox.width();
                        status = format_status(status, w, "D");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp < list.body_height() {
                                    list.select_topic(tmp + 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollDown(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Left => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.page > 1 {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page - 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Right => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.max_page > show_item.page {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page + 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Enter => {
                        let w = rustbox.width();
                        status = format_status(status, w, "E");
                        match state {
                            Status::List => {

                                let index = list.get_selected_topic();
                                if index > 0 {
                                    let topic_item = &collection[index - 1];

                                    let postid = &topic_item.title.url_query.message;

                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = postid,
                                                                 page = 1);

                                    if Path::new(&show_file_path).exists() {
                                        show_file = cache::readfile(String::from(show_file_path));
                                        show_item = json::decode(&show_file).unwrap();
                                        show.resetY();
                                        hkg::screen::common::clear(&rustbox);
                                        state = Status::Show;
                                    } else {
                                        let w = rustbox.width();
                                        status = format_status(status,
                                                               w,
                                                               &format!(" postid {} not found.",
                                                                        postid));
                                    }
                                }
                            }
                            Status::Show => {}
                        }
                    }
                    Key::Backspace => {
                        let w = rustbox.width();
                        status = format_status(status, w, "B");
                        match state {
                            Status::List => {}
                            Status::Show => {
                                state = Status::List;
                            }
                        }
                    }

                    _ => {}
                }
            }
            Err(e) => panic!("{}", e),
            _ => {}
        }
    }
}

fn print_status(rustbox: &rustbox::RustBox, status: &str) {
    // for status bar only
    let w = rustbox.width();
    let h = rustbox.height();

    let status_width = if w > status.len() {
        w - status.len()
    } else {
        0
    };
    let status_spacing = (0..status_width).map(|_| " ").collect::<Vec<_>>().join("");

    rustbox.print(0,
                  h - 1,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  &format!("{status}{status_spacing}",
                           status = status,
                           status_spacing = status_spacing));

}

fn format_status(status: String, w: usize, s: &str) -> String {
    if status.len() >= w {
        String::from(format!("{}{}", &"> ", s))
    } else {
        String::from(format!("{}{}", &status, s))
    }
}

// for (jndex, elm) in tr.as_node().select(".repliers_right .ContentGrid").unwrap().enumerate() {
//     let content = elm.as_node().text_contents();
//     let name = &elm.name;
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}][{:<2}]={:?}", index, jndex, content));
// }

// let content = tr.text_contents();
// rustbox.print(1,
//               index + 4,
//               rustbox::RB_NORMAL,
//               Color::White,
//               Color::Black,
//               &format!("[{:<2}]={:?}", index, content));

// for (jndex, div) in tr.as_node().children().enumerate() {
//     let content = div.as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().collect::<Vec<_>>().iter().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// let c = &tr.as_node().select(".repliers_right .ContentGrid").unwrap().collect::<Vec<_>>()[0];
// let content = c.as_node().as_text();
// rustbox.print(1, index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index, content));

// fn date_operation_example(rustbox: &rustbox::RustBox) {
//     let now = Local::now();
//
//     let dt1 = match Local.datetime_from_str("30/4/2016 9:22", "%d/%m/%Y %H:%M") {
//         Ok(v) => v,
//         Err(e) => Local::now(),
//     };
//
//     let dt2 = now.checked_sub(Duration::seconds(46)).unwrap();
//     let dt3 = now.checked_sub(Duration::minutes(6)).unwrap();
//     let dt4 = now.checked_sub(Duration::days(17)).unwrap();
//     let dt5 = now.checked_sub(Duration::weeks(9)).unwrap();
//
//     rustbox.print(0,
//                   0,
//                   rustbox::RB_BOLD,
//                   Color::White,
//                   Color::Black,
//                   &format!("{} {} {} {}",
//                    duration_format(&(now - dt2)),
//                    duration_format(&(now - dt3)),
//                    duration_format(&(now - dt4)),
//                    duration_format(&(now - dt5))
//               ));
//
// }

// fn debug_load_and_print_topics() {
//     let s = cache::readfile(String::from("topics.json"));
//     let collection: Vec<TopicItem> = json::decode(&s).unwrap();
//
//     println!("topics {:?}", collection.len());
//     debug_print_topics(collection);
// }
//
// fn debug_print_topics(collection: Vec<TopicItem>) {
//     for (i, item) in collection.iter().enumerate() {
//
//         println!("item[{}]= {title} {author_name} {last_replied_date} {last_replied_time} \
//                   {reply_count} {rating}",
//                  i,
//                  title = item.titles[0].text,
//                  author_name = item.author.name,
//                  last_replied_date = item.last_replied_date,
//                  last_replied_time = item.last_replied_time,
//                  reply_count = item.reply_count,
//                  rating = item.rating);
//     }
// }
<commit_msg>show - extract replies content to files<commit_after>extern crate hkg;
extern crate rustbox;
extern crate rustc_serialize;
extern crate chrono;
extern crate kuchiki;

use kuchiki::traits::*;

use std::default::Default;

use rustbox::{Color, RustBox, Key};
use rustc_serialize::json;
use rustc_serialize::json::Json;

use chrono::*;

use hkg::utility::cache;
use hkg::model::ListTopicItem;
use hkg::model::ShowItem;
use hkg::model::UrlQueryItem;

use std::path::Path;

use std::io::prelude::*;
use std::fs::File;

#[derive(PartialEq, Eq, Copy, Clone)]
enum Status {
    List,
    Show,
}

fn main() {

    // GUI init
    let rustbox = match RustBox::init(Default::default()) {
        Result::Ok(v) => v,
        Result::Err(e) => panic!("{}", e),
    };

    rustbox.print(1,
                  1,
                  rustbox::RB_NORMAL,
                  Color::White,
                  Color::Black,
                  &format!("start => {}", Local::now()));

    let title = String::from("高登");
    let s = cache::readfile(String::from("data/topics.json"));
    let collection: Vec<ListTopicItem> = json::decode(&s).unwrap();

    // initialize show with empty page
    let mut show_file;
    let mut show_item = ShowItem {
        url_query: UrlQueryItem { message: String::from("") },
        replies: vec![],
        page: 0,
        max_page: 0,
        reply_count: String::from(""),
        title: String::from(""),
    };

    let mut status = String::from("> ");

    let mut state = Status::List;
    let mut prev_state = state;
    let mut prev_width = rustbox.width();

    let mut list = hkg::screen::list::List::new(&rustbox);
    let mut show = hkg::screen::show::Show::new(&rustbox);

    loop {

        // show UI
        if prev_state != state {
            hkg::screen::common::clear(&rustbox); // clear screen when switching state
            prev_state = state;
        }

        let url = &collection[1].title.url;

        rustbox.print(1, 2, rustbox::RB_NORMAL, Color::White, Color::Black, url);

        rustbox.print(1,
                      3,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("before parse => {}", Local::now()));

        let postid = "6360604";
        let page = 1;
        let path1 = format!("data/html/{postid}/show_{page}.html", postid = postid, page = page);
        let document = kuchiki::parse_html().from_utf8().from_file(&path1).unwrap();

        let replies_data = document.select(".repliers tr[userid][username]")
                                   .unwrap()
                                   .collect::<Vec<_>>();

        let mut ss = String::new();
        for (index, tr) in replies_data.iter().enumerate() {
            let tr_attrs = (&tr.attributes).borrow();
            let userid = tr_attrs.get("userid").unwrap();
            let username = tr_attrs.get("username").unwrap();

            let n1 = tr.as_node().select(".repliers_right .ContentGrid").unwrap()
                                .next().unwrap() // first

                                ; // text
            let path2 = format!("data/{postid}/show_{page}_{replyindex}", postid = postid, page = page, replyindex = index);
            let content = n1.as_node().serialize_to_file(path2);
            let tmp_str = format!("[{:0>2}]={:?}\n", index, content);
            ss.push_str(&tmp_str);
            rustbox.print(1,
                          index + 5,
                          rustbox::RB_NORMAL,
                          Color::White,
                          Color::Black,
                          &tmp_str);

        }

        let mut f = File::create("foo.txt").unwrap();
        // let uu :Vec<u8> = ss.chars;
        f.write_all(ss.as_bytes());

        rustbox.print(1,
                      4,
                      rustbox::RB_NORMAL,
                      Color::White,
                      Color::Black,
                      &format!("after parse => {}", Local::now()));

        // match state {
        //     Status::List => {
        //         list.print(&title, &collection);
        //     }
        //     Status::Show => {
        //         show.print(&title, &show_item);
        //     }
        // }

        print_status(&rustbox, &status);

        rustbox.present();

        match rustbox.poll_event(false) {
            Ok(rustbox::Event::KeyEvent(key)) => {

                if prev_width != rustbox.width() {
                    hkg::screen::common::clear(&rustbox);
                    prev_width = rustbox.width();
                }

                match key {
                    Key::Char('q') => {
                        break;
                    }
                    Key::PageUp => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PU");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollUp(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::PageDown => {
                        let w = rustbox.width();
                        status = format_status(status, w, " PD");

                        match state {
                            Status::List => {}
                            Status::Show => {
                                let bh = show.body_height();
                                if show.scrollDown(bh) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Up => {
                        let w = rustbox.width();
                        status = format_status(status, w, "U");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp > 1 {
                                    list.select_topic(tmp - 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollUp(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }

                    }
                    Key::Down => {
                        let w = rustbox.width();
                        status = format_status(status, w, "D");

                        match state {
                            Status::List => {
                                let tmp = list.get_selected_topic();
                                if tmp < list.body_height() {
                                    list.select_topic(tmp + 1);
                                }
                            }
                            Status::Show => {
                                if show.scrollDown(2) {
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Left => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.page > 1 {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page - 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Right => {
                        match state {
                            Status::List => {}
                            Status::Show => {
                                if show_item.max_page > show_item.page {
                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = show_item.url_query
                                                                                .message,
                                                                 page = show_item.page + 1);

                                    show_file = cache::readfile(String::from(show_file_path));
                                    show_item = json::decode(&show_file).unwrap();
                                    show.resetY();
                                    hkg::screen::common::clear(&rustbox);
                                }
                            }
                        }
                    }
                    Key::Enter => {
                        let w = rustbox.width();
                        status = format_status(status, w, "E");
                        match state {
                            Status::List => {

                                let index = list.get_selected_topic();
                                if index > 0 {
                                    let topic_item = &collection[index - 1];

                                    let postid = &topic_item.title.url_query.message;

                                    let show_file_path = format!("data/{postid}/show_{page}.json",
                                                                 postid = postid,
                                                                 page = 1);

                                    if Path::new(&show_file_path).exists() {
                                        show_file = cache::readfile(String::from(show_file_path));
                                        show_item = json::decode(&show_file).unwrap();
                                        show.resetY();
                                        hkg::screen::common::clear(&rustbox);
                                        state = Status::Show;
                                    } else {
                                        let w = rustbox.width();
                                        status = format_status(status,
                                                               w,
                                                               &format!(" postid {} not found.",
                                                                        postid));
                                    }
                                }
                            }
                            Status::Show => {}
                        }
                    }
                    Key::Backspace => {
                        let w = rustbox.width();
                        status = format_status(status, w, "B");
                        match state {
                            Status::List => {}
                            Status::Show => {
                                state = Status::List;
                            }
                        }
                    }

                    _ => {}
                }
            }
            Err(e) => panic!("{}", e),
            _ => {}
        }
    }
}

fn print_status(rustbox: &rustbox::RustBox, status: &str) {
    // for status bar only
    let w = rustbox.width();
    let h = rustbox.height();

    let status_width = if w > status.len() {
        w - status.len()
    } else {
        0
    };
    let status_spacing = (0..status_width).map(|_| " ").collect::<Vec<_>>().join("");

    rustbox.print(0,
                  h - 1,
                  rustbox::RB_BOLD,
                  Color::White,
                  Color::Black,
                  &format!("{status}{status_spacing}",
                           status = status,
                           status_spacing = status_spacing));

}

fn format_status(status: String, w: usize, s: &str) -> String {
    if status.len() >= w {
        String::from(format!("{}{}", &"> ", s))
    } else {
        String::from(format!("{}{}", &status, s))
    }
}

// for (jndex, elm) in tr.as_node().select(".repliers_right .ContentGrid").unwrap().enumerate() {
//     let content = elm.as_node().text_contents();
//     let name = &elm.name;
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}][{:<2}]={:?}", index, jndex, content));
// }

// let content = tr.text_contents();
// rustbox.print(1,
//               index + 4,
//               rustbox::RB_NORMAL,
//               Color::White,
//               Color::Black,
//               &format!("[{:<2}]={:?}", index, content));

// for (jndex, div) in tr.as_node().children().enumerate() {
//     let content = div.as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// for (jndex, div) in tr.as_node().select(".repliers_right").unwrap().collect::<Vec<_>>().iter().enumerate() {
//     let content = div.as_node().as_text();
//     rustbox.print(1, jndex + index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index + jndex, content));
// }

// let c = &tr.as_node().select(".repliers_right .ContentGrid").unwrap().collect::<Vec<_>>()[0];
// let content = c.as_node().as_text();
// rustbox.print(1, index + 4, rustbox::RB_NORMAL, Color::White, Color::Black, &format!("[{:<2}]={:?}", index, content));

// fn date_operation_example(rustbox: &rustbox::RustBox) {
//     let now = Local::now();
//
//     let dt1 = match Local.datetime_from_str("30/4/2016 9:22", "%d/%m/%Y %H:%M") {
//         Ok(v) => v,
//         Err(e) => Local::now(),
//     };
//
//     let dt2 = now.checked_sub(Duration::seconds(46)).unwrap();
//     let dt3 = now.checked_sub(Duration::minutes(6)).unwrap();
//     let dt4 = now.checked_sub(Duration::days(17)).unwrap();
//     let dt5 = now.checked_sub(Duration::weeks(9)).unwrap();
//
//     rustbox.print(0,
//                   0,
//                   rustbox::RB_BOLD,
//                   Color::White,
//                   Color::Black,
//                   &format!("{} {} {} {}",
//                    duration_format(&(now - dt2)),
//                    duration_format(&(now - dt3)),
//                    duration_format(&(now - dt4)),
//                    duration_format(&(now - dt5))
//               ));
//
// }

// fn debug_load_and_print_topics() {
//     let s = cache::readfile(String::from("topics.json"));
//     let collection: Vec<TopicItem> = json::decode(&s).unwrap();
//
//     println!("topics {:?}", collection.len());
//     debug_print_topics(collection);
// }
//
// fn debug_print_topics(collection: Vec<TopicItem>) {
//     for (i, item) in collection.iter().enumerate() {
//
//         println!("item[{}]= {title} {author_name} {last_replied_date} {last_replied_time} \
//                   {reply_count} {rating}",
//                  i,
//                  title = item.titles[0].text,
//                  author_name = item.author.name,
//                  last_replied_date = item.last_replied_date,
//                  last_replied_time = item.last_replied_time,
//                  reply_count = item.reply_count,
//                  rating = item.rating);
//     }
// }
<|endoftext|>


<commit_before>extern crate getopts;
use getopts::{optopt, optflag, OptGroup};
use std::os;

use device_info::{DefaultDevice, DefaultInterface, UserDefinedDevice, UserDefinedName, DeviceInfo};
mod device_info;
mod ioctl;

enum ParseArgsResult {
    HelpRequested,
    DevicesObtained(DeviceInfo),
    CommandLineError(getopts::Fail_)
}

// Returns either a Devices struct filled either with the default values, or None if the help option is present
fn parse_args(opts: &[OptGroup], args: &[String]) -> ParseArgsResult {
    match getopts::getopts(args, opts) {
        Err(e) => CommandLineError(e),

        Ok(options) => {
            if options.opt_present("h") {
                HelpRequested
            } else {
                DevicesObtained(DeviceInfo::new(match options.opt_str("d") {
                                                    Some(s) => UserDefinedDevice(s),
                                                    None => DefaultDevice
                                                },
                                                match options.opt_str("i") {
                                                    Some(s) => UserDefinedName(s),
                                                    None => DefaultInterface,
                                                }))
            }
        }
    }
}

fn main() {
    let opts = [
        optopt("d", "device", "set the cloning device", "<cloning device>"),
        optflag("h", "help", "print this help"),
        optopt("i", "interface", "set the interface name", "<interface name>")
    ];

    let args = os::args();
    let prog_name = args[0].as_slice();

    match parse_args(opts, args.as_slice()) {
        HelpRequested => {
            println!("{}", getopts::usage(format!("{}: A virtual ethernet device creator", prog_name).as_slice(), opts));
        },

        CommandLineError(e) => {
            println!("{}", e);
        },

        DevicesObtained(d) => {
            match ioctl::get_tap_descriptor(&d) {
                ioctl::FDOpened(fd) => {
                    println!("Opened tap device successfully: {}", fd);
                    std::io::timer::sleep(std::time::duration::Duration::seconds(30));
                },
                err @ _ => {
                    println!("{}", err);
                }
            };
        }
    };
}
<commit_msg>Listen on the open device and print bytes received<commit_after>extern crate getopts;
use getopts::{optopt, optflag, OptGroup};
use std::os;

use device_info::{DefaultDevice, DefaultInterface, UserDefinedDevice, UserDefinedName, DeviceInfo};
use ioctl::c_file_descriptor;

mod device_info;
mod ioctl;
mod common;

static MAXLEN: uint = 1514;

enum ParseArgsResult {
    HelpRequested,
    DevicesObtained(DeviceInfo),
    CommandLineError(getopts::Fail_)
}

fn listen(fd: &mut c_file_descriptor::CFileDescriptor) {
    let mut buf: [u8, ..MAXLEN] = [0, ..MAXLEN];
    loop {
        match fd.read(buf) {
            Ok(len) => {
                if len > 0 && len <= MAXLEN {
                    println!("{} bytes received!", len);
                    common::print_byte_table(buf.iter().take(len));
                }
            },
            _ => ()
        }
    }
}

// Returns either a Devices struct filled either with the default values, or None if the help option is present
fn parse_args(opts: &[OptGroup], args: &[String]) -> ParseArgsResult {
    match getopts::getopts(args, opts) {
        Err(e) => CommandLineError(e),

        Ok(options) => {
            if options.opt_present("h") {
                HelpRequested
            } else {
                DevicesObtained(DeviceInfo::new(match options.opt_str("d") {
                                                    Some(s) => UserDefinedDevice(s),
                                                    None => DefaultDevice
                                                },
                                                match options.opt_str("i") {
                                                    Some(s) => UserDefinedName(s),
                                                    None => DefaultInterface,
                                                }))
            }
        }
    }
}

fn main() {
    let opts = [
        optopt("d", "device", "set the cloning device", "<cloning device>"),
        optflag("h", "help", "print this help"),
        optopt("i", "interface", "set the interface name", "<interface name>")
    ];

    let args = os::args();
    let prog_name = args[0].as_slice();

    match parse_args(opts, args.as_slice()) {
        HelpRequested => {
            println!("{}", getopts::usage(format!("{}: A virtual ethernet device creator", prog_name).as_slice(), opts));
        },

        CommandLineError(e) => {
            println!("{}", e);
        },

        DevicesObtained(d) => {
            match ioctl::get_tap_descriptor(&d) {
                ioctl::FDOpened(ref mut fd) => {
                    println!("Opened tap device successfully: {}", fd);
                    listen(fd);
                },
                err @ _ => {
                    println!("{}", err);
                }
            };
        }
    };
}
<|endoftext|>