<commit_before>import io::{reader, reader_util};
import result::{ok, err, extensions};

import std::map::{hashmap, str_hash, hash_from_strs};
import std::json;

import elasticsearch::{client, search_builder, index_builder, json_dict_builder};
import mongrel2::{connection, request};
import mre::mre;
import mre::response::{response, http_200, http_400, http_404, redirect};
import mu_context = mustache::context;
import zmq_context = zmq::context;
import zmq::error;

import post::post;

fn render_200(req: request, mu: mustache::context, path: str,
              data: hashmap<str, mustache::data>) -> response {
    let template = mu.render_file(path, data);
    http_200(req, str::bytes(template))
}

fn json_to_mustache(j: json::json) -> mustache::data {
    alt j {
      json::string(s) { mustache::str(s) }
      json::num(n) { mustache::str(#fmt("%f", n)) }
      json::boolean(b) { mustache::bool(b) }
      json::list(v) { mustache::vec(v.map(json_to_mustache)) }
      json::dict(d) {
        let m = str_hash();
        d.items { |k, v| m.insert(k, json_to_mustache(v)); };
        mustache::map(m)
      }
      json::null { mustache::bool(false) }
    }
}

fn main() {
    let ctx =
        alt zmq::init(1) {
          ok(ctx) { ctx }
          err(e) { fail e.to_str() }
        };

    let mu = mustache::context("views", ".mustache");

    let es = elasticsearch::connect_with_zmq(ctx, "tcp://localhost:9700");

    let m2 = mongrel2::connect(ctx,
        "F0D32575-2ABB-4957-BC8B-12DAC8AFF13A",
        "tcp://127.0.0.1:9998",
        "tcp://127.0.0.1:9999");

    let mre = mre::mre(m2, io::stdout());

    fn post_to_mustache(post: post::post) -> mustache::data {
        mustache::map(hash_from_strs([
            ("post", mustache::map(hash_from_strs([
                ("_id", mustache::str(post.id)),
                ("title", mustache::str(post.title())),
                ("body", mustache::str(post.body()))
            ])))
        ]))
    }

    mre.router.add("GET", "^/$") { |req, _m|
        let posts = post::all(es).map(post_to_mustache);

        render_200(req, mu, "index", hash_from_strs([
            ("posts", mustache::vec(posts))
        ]))
    }

    mre.router.add("GET", "^/posts/(?<id>\\w+)$") { |req, m|
        alt post::find(es, m.named("id")) {
          none { http_404(req) }
          some(post) {
            let m = alt check post_to_mustache(post) {
              mustache::map(m) { m }
            };

            render_200(req, mu, "show", m)
          }
        }
    }

    mre.router.add("GET", "^/new-post$") { |req, _m|
        let post = post::post("");

        let m = alt check post_to_mustache(post) {
          mustache::map(m) { m }
        };

        render_200(req, mu, "new", m)
    }

    mre.router.add("GET", "^/posts/(?<id>\\w+)/edit$") { |req, m|
        alt post::find(es, m.named("id")) {
          none { http_404(req) }
          some(post) {
            let m = alt check post_to_mustache(post) {
              mustache::map(m) { m }
            };

            render_200(req, mu, "edit", m)
          }
        }
    }

    mre.router.add("POST", "^/posts$") { |req, _m|
        let form = uri::decode_qs(req.body);
        let post = post::post("");

        alt form.find("title") {
          some(title) { post.set_title(title[0]) }
          none {}
        }

        alt form.find("body") {
          some(body) { post.set_body(body[0]) }
          none {}
        }

        alt post.save(es) {
          none { http_400(req) }
          some(id) { redirect(req, "/posts/" + id) }
        }
    }

    mre.router.add("POST", "^/posts/(?<id>\\w+)$") { |req, m|
        let id = m.named("id");
        let form = uri::decode_qs(req.body);

        alt post::find(es, id) {
          none { http_404(req) }
          some(post) {
            alt form.find("title") {
              some(title) { post.set_title(title[0]) }
              none {}
            }

            alt form.find("body") {
              some(body) { post.set_body(body[0]) }
              none {}
            }

            alt post.save(es) {
              none { http_400(req) }
              some(id) { redirect(req, "/posts/" + id) }
            }
          }
        }
    }

    mre.run();

    m2.term();
    ctx.term();
}
<commit_msg>Remove dead code.<commit_after>import io::{reader, reader_util};
import result::{ok, err, extensions};

import std::map::{hashmap, str_hash, hash_from_strs};
import std::json;

import elasticsearch::{client, search_builder, index_builder, json_dict_builder};
import mongrel2::{connection, request};
import mre::mre;
import mre::response::{response, http_200, http_400, http_404, redirect};
import mu_context = mustache::context;
import zmq_context = zmq::context;
import zmq::error;

import post::post;

fn render_200(req: request, mu: mustache::context, path: str,
              data: hashmap<str, mustache::data>) -> response {
    let template = mu.render_file(path, data);
    http_200(req, str::bytes(template))
}

fn main() {
    let ctx =
        alt zmq::init(1) {
          ok(ctx) { ctx }
          err(e) { fail e.to_str() }
        };

    let mu = mustache::context("views", ".mustache");

    let es = elasticsearch::connect_with_zmq(ctx, "tcp://localhost:9700");

    let m2 = mongrel2::connect(ctx,
        "F0D32575-2ABB-4957-BC8B-12DAC8AFF13A",
        "tcp://127.0.0.1:9998",
        "tcp://127.0.0.1:9999");

    let mre = mre::mre(m2, io::stdout());

    fn post_to_mustache(post: post::post) -> mustache::data {
        mustache::map(hash_from_strs([
            ("post", mustache::map(hash_from_strs([
                ("_id", mustache::str(post.id)),
                ("title", mustache::str(post.title())),
                ("body", mustache::str(post.body()))
            ])))
        ]))
    }

    mre.router.add("GET", "^/$") { |req, _m|
        let posts = post::all(es).map(post_to_mustache);

        render_200(req, mu, "index", hash_from_strs([
            ("posts", mustache::vec(posts))
        ]))
    }

    mre.router.add("GET", "^/posts/(?<id>\\w+)$") { |req, m|
        alt post::find(es, m.named("id")) {
          none { http_404(req) }
          some(post) {
            let m = alt check post_to_mustache(post) {
              mustache::map(m) { m }
            };

            render_200(req, mu, "show", m)
          }
        }
    }

    mre.router.add("GET", "^/new-post$") { |req, _m|
        let post = post::post("");

        let m = alt check post_to_mustache(post) {
          mustache::map(m) { m }
        };

        render_200(req, mu, "new", m)
    }

    mre.router.add("GET", "^/posts/(?<id>\\w+)/edit$") { |req, m|
        alt post::find(es, m.named("id")) {
          none { http_404(req) }
          some(post) {
            let m = alt check post_to_mustache(post) {
              mustache::map(m) { m }
            };

            render_200(req, mu, "edit", m)
          }
        }
    }

    mre.router.add("POST", "^/posts$") { |req, _m|
        let form = uri::decode_qs(req.body);
        let post = post::post("");

        alt form.find("title") {
          some(title) { post.set_title(title[0]) }
          none {}
        }

        alt form.find("body") {
          some(body) { post.set_body(body[0]) }
          none {}
        }

        alt post.save(es) {
          none { http_400(req) }
          some(id) { redirect(req, "/posts/" + id) }
        }
    }

    mre.router.add("POST", "^/posts/(?<id>\\w+)$") { |req, m|
        let id = m.named("id");
        let form = uri::decode_qs(req.body);

        alt post::find(es, id) {
          none { http_404(req) }
          some(post) {
            alt form.find("title") {
              some(title) { post.set_title(title[0]) }
              none {}
            }

            alt form.find("body") {
              some(body) { post.set_body(body[0]) }
              none {}
            }

            alt post.save(es) {
              none { http_400(req) }
              some(id) { redirect(req, "/posts/" + id) }
            }
          }
        }
    }

    mre.run();

    m2.term();
    ctx.term();
}
<|endoftext|>


<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;
use core::grid::{Grid, Origin, Extent, ExtentInt};

#[test]
fn test_bbox() {
    use std::u32;

    let grid = Grid::web_mercator();

    let extent000 = grid.tile_extent(0, 0, 0);
    assert_eq!(extent000, Extent {minx: -20037508.342789248, miny: -20037508.342789248, maxx: 20037508.342789248, maxy: 20037508.342789248});

    let extent = grid.tile_extent_xyz(486, 332, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322683, miny: 7005300.768279828, maxx: -978393.9620502591, maxy: 7044436.526761841});
    let extent = grid.tile_extent(486, 691, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322683, miny: 7005300.768279828, maxx: -978393.9620502591, maxy: 7044436.526761841});

    //overflow
    let extent = grid.tile_extent_xyz(486, u32::MAX, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322683, miny: -20037508.342789248, maxx: -978393.9620502591, maxy: -19998372.58430724});

    let extent_ch = grid.tile_extent_xyz(1073, 717, 11);
    assert_eq!(extent_ch, Extent { minx: 958826.0828092434, miny: 5987771.04774756, maxx: 978393.9620502479, maxy: 6007338.926988564 });

    let wgs84extent000 = Grid::wgs84().tile_extent(0, 0, 0);
    assert_eq!(wgs84extent000, Extent { minx: -180.0, miny: -90.0, maxx: 0.0, maxy: 90.0 });
}

#[test]
fn test_grid_calculations() {
    let grid = Grid::web_mercator();

    assert_eq!(grid.pixel_width(10), 152.8740565703525);
    assert_eq!(grid.scale_denominator(10), 577791.7098721985);

    assert_eq!(grid.level_limit(0), (1, 1));
    assert_eq!(grid.level_limit(10), (1024, 1024));

    let limits = grid.tile_limits(grid.tile_extent(0, 0, 0), 0);
    assert_eq!(limits[0], ExtentInt {minx: 0, miny: 0, maxx: 1, maxy: 1 });
    assert_eq!(limits[10], ExtentInt {minx: 0, miny: 0, maxx: 1024, maxy: 1024 });

    let limits = grid.tile_limits(Extent {minx: -1017529.7205322683, miny: 7005300.768279828, maxx: -978393.9620502591, maxy: 7044436.526761841}, 0);
    assert_eq!(limits[0], ExtentInt {minx: 0, miny: 0, maxx: 1, maxy: 1 });
    assert_eq!(limits[10], ExtentInt {minx: 486, miny: 691, maxx: 487, maxy: 692});

    let extent = grid.tile_extent(133, 165, 8);
    assert_eq!(extent, grid.tile_extent_xyz(133, 90, 8));
    assert_eq!(extent, Extent { minx: 782715.1696402021, miny: 5792092.25533751, maxx: 939258.2035682425, maxy: 5948635.289265554 });
    let limits = grid.tile_limits(extent, 0);
    assert_eq!(limits[8], ExtentInt {minx: 133, miny: 165, maxx: 134, maxy: 166});
}

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

    let toml = r#"
        [grid]
        predefined = "web_mercator"
        "#;
    let config = parse_config(toml.to_string(), "").unwrap();
    let grid = Grid::from_config(&config).unwrap();
    assert_eq!(grid.extent, Extent {minx: -20037508.3427892480, miny: -20037508.3427892480,
                                    maxx: 20037508.3427892480, maxy: 20037508.3427892480});

    let toml = r#"
        [grid]
        width = 256
        height = 256
        extent = { minx = 2420000.0, miny = 1030000.0, maxx = 2900000.0, maxy = 1350000.0 }
        srid = 2056
        units = "M"
        resolutions = [4000.0,3750.0,3500.0,3250.0,3000.0,2750.0,2500.0,2250.0,2000.0,1750.0,1500.0,1250.0,1000.0,750.0,650.0,500.0,250.0,100.0,50.0,20.0,10.0,5.0,2.5,2.0,1.5,1.0,0.5,0.25,0.1]
        origin = "TopLeft"
        "#;
    let config = parse_config(toml.to_string(), "").unwrap();
    let grid = Grid::from_config(&config).unwrap();
    assert_eq!(grid.extent, Extent {minx: 2420000.0, miny: 1030000.0,
                                    maxx: 2900000.0, maxy: 1350000.0});
    assert_eq!(grid.origin, Origin::TopLeft);

    let extent = grid.tile_extent(10, 4, 17); // lake of Zurich
    assert_eq!(extent, Extent {minx: 2676000., miny: 1222000., maxx: 2701600., maxy: 1247600.});
    //BBOX ZH: (2669255.48 1223902.28, 2716899.60125 1283304.23625)
    let extent = grid.tile_extent_xyz(10, 4, 17);
    assert_eq!(extent, Extent {minx: 2676000., miny: -109951160275600., maxx: 2701600., maxy: -109951160250000.});
}

mod web_mercator {

// --- Web Mercator calculations ---
// Credits: Mercantile by Sean C. Gillies (https://github.com/mapbox/mercantile)

use core::grid::Extent;
use std::f64::consts;

#[derive(PartialEq,Debug)]
pub struct LngLat {
    pub lon: f64,
    pub lat: f64,
}

/// Returns the upper left (lon, lat) of a tile
fn ul(xtile: u32, ytile: u32, zoom: u8) -> LngLat {
    let n = (zoom as f64).exp2();
    let lon_deg = xtile as f64 / n * 360.0 - 180.0;
    let lat_rad = (consts::PI * (1.0_f64 - 2.0_f64 * ytile as f64 / n)).sinh().atan();
    let lat_deg = lat_rad.to_degrees();
    LngLat {lon: lon_deg, lat: lat_deg}
}

/// Returns the Spherical Mercator (x, y) in meters
fn xy(lon: f64, lat: f64) -> (f64, f64) {
    //lng, lat = truncate_lnglat(lng, lat)
    let x = 6378137.0_f64 * lon.to_radians();
    let y = 6378137.0_f64 *
        ((consts::PI*0.25) + (0.5_f64 * lat.to_radians())).tan().ln();
    (x, y)
}

/// Returns the Spherical Mercator bounding box of a tile
fn tile_extent(xtile: u32, ytile: u32, zoom: u8) -> Extent {
    let a = ul(xtile, ytile, zoom);
    let (ax, ay) = xy(a.lon, a.lat);
    let b = ul(xtile+1, ytile+1, zoom);
    let (bx, by) = xy(b.lon, b.lat);
    Extent {minx: ax, miny: by, maxx: bx, maxy: ay}
}

/// Returns the (lon, lat) bounding box of a tile
fn tile_bounds(xtile: u32, ytile: u32, zoom: u8) -> Extent {
    let a = ul(xtile, ytile, zoom);
    let b = ul(xtile+1, ytile+1, zoom);
    Extent {minx: a.lon, miny: b.lat, maxx: b.lon, maxy: a.lat}
}

#[test]
fn test_ul() {
    let lnglat = ul(486, 332, 10);
    assert_eq!(lnglat, LngLat {lon: -9.140625, lat: 53.33087298301705});
}

#[test]
fn test_xy() {
    let ul = ul(486, 332, 10);
    let xy_ = xy(ul.lon, ul.lat);
    assert_eq!(xy_, (-1017529.7205322663, 7044436.526761846));
    assert_eq!(xy(0.0, 0.0), (0.0, -0.0000000007081154551613622));
}

#[test]
fn test_merc_tile_extent() {
    let extent = tile_extent(486, 332, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322663, miny: 7005300.768279833, maxx: -978393.962050256, maxy: 7044436.526761846});
}

#[test]
fn test_merc_tile_bounds() {
    let bbox = tile_bounds(486, 332, 10);
    assert_eq!(bbox, Extent {minx: -9.140625, miny: 53.120405283106564, maxx: -8.7890625, maxy: 53.33087298301705});
}

}
<commit_msg>Workaround for floating point difference on x86_64-apple-darwin<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;
use core::grid::{Grid, Origin, Extent, ExtentInt};

#[test]
fn test_bbox() {
    use std::u32;

    let grid = Grid::web_mercator();

    let extent000 = grid.tile_extent(0, 0, 0);
    assert_eq!(extent000, Extent {minx: -20037508.342789248, miny: -20037508.342789248, maxx: 20037508.342789248, maxy: 20037508.342789248});

    let extent = grid.tile_extent_xyz(486, 332, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322683, miny: 7005300.768279828, maxx: -978393.9620502591, maxy: 7044436.526761841});
    let extent = grid.tile_extent(486, 691, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322683, miny: 7005300.768279828, maxx: -978393.9620502591, maxy: 7044436.526761841});

    //overflow
    let extent = grid.tile_extent_xyz(486, u32::MAX, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322683, miny: -20037508.342789248, maxx: -978393.9620502591, maxy: -19998372.58430724});

    let extent_ch = grid.tile_extent_xyz(1073, 717, 11);
    assert_eq!(extent_ch, Extent { minx: 958826.0828092434, miny: 5987771.04774756, maxx: 978393.9620502479, maxy: 6007338.926988564 });

    let wgs84extent000 = Grid::wgs84().tile_extent(0, 0, 0);
    assert_eq!(wgs84extent000, Extent { minx: -180.0, miny: -90.0, maxx: 0.0, maxy: 90.0 });
}

#[test]
fn test_grid_calculations() {
    let grid = Grid::web_mercator();

    assert_eq!(grid.pixel_width(10), 152.8740565703525);
    assert_eq!(grid.scale_denominator(10), 577791.7098721985);

    assert_eq!(grid.level_limit(0), (1, 1));
    assert_eq!(grid.level_limit(10), (1024, 1024));

    let limits = grid.tile_limits(grid.tile_extent(0, 0, 0), 0);
    assert_eq!(limits[0], ExtentInt {minx: 0, miny: 0, maxx: 1, maxy: 1 });
    assert_eq!(limits[10], ExtentInt {minx: 0, miny: 0, maxx: 1024, maxy: 1024 });

    let limits = grid.tile_limits(Extent {minx: -1017529.7205322683, miny: 7005300.768279828, maxx: -978393.9620502591, maxy: 7044436.526761841}, 0);
    assert_eq!(limits[0], ExtentInt {minx: 0, miny: 0, maxx: 1, maxy: 1 });
    assert_eq!(limits[10], ExtentInt {minx: 486, miny: 691, maxx: 487, maxy: 692});

    let extent = grid.tile_extent(133, 165, 8);
    assert_eq!(extent, grid.tile_extent_xyz(133, 90, 8));
    assert_eq!(extent, Extent { minx: 782715.1696402021, miny: 5792092.25533751, maxx: 939258.2035682425, maxy: 5948635.289265554 });
    let limits = grid.tile_limits(extent, 0);
    assert_eq!(limits[8], ExtentInt {minx: 133, miny: 165, maxx: 134, maxy: 166});
}

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

    let toml = r#"
        [grid]
        predefined = "web_mercator"
        "#;
    let config = parse_config(toml.to_string(), "").unwrap();
    let grid = Grid::from_config(&config).unwrap();
    assert_eq!(grid.extent, Extent {minx: -20037508.3427892480, miny: -20037508.3427892480,
                                    maxx: 20037508.3427892480, maxy: 20037508.3427892480});

    let toml = r#"
        [grid]
        width = 256
        height = 256
        extent = { minx = 2420000.0, miny = 1030000.0, maxx = 2900000.0, maxy = 1350000.0 }
        srid = 2056
        units = "M"
        resolutions = [4000.0,3750.0,3500.0,3250.0,3000.0,2750.0,2500.0,2250.0,2000.0,1750.0,1500.0,1250.0,1000.0,750.0,650.0,500.0,250.0,100.0,50.0,20.0,10.0,5.0,2.5,2.0,1.5,1.0,0.5,0.25,0.1]
        origin = "TopLeft"
        "#;
    let config = parse_config(toml.to_string(), "").unwrap();
    let grid = Grid::from_config(&config).unwrap();
    assert_eq!(grid.extent, Extent {minx: 2420000.0, miny: 1030000.0,
                                    maxx: 2900000.0, maxy: 1350000.0});
    assert_eq!(grid.origin, Origin::TopLeft);

    let extent = grid.tile_extent(10, 4, 17); // lake of Zurich
    assert_eq!(extent, Extent {minx: 2676000., miny: 1222000., maxx: 2701600., maxy: 1247600.});
    //BBOX ZH: (2669255.48 1223902.28, 2716899.60125 1283304.23625)
    let extent = grid.tile_extent_xyz(10, 4, 17);
    assert_eq!(extent, Extent {minx: 2676000., miny: -109951160275600., maxx: 2701600., maxy: -109951160250000.});
}

mod web_mercator {

// --- Web Mercator calculations ---
// Credits: Mercantile by Sean C. Gillies (https://github.com/mapbox/mercantile)

use core::grid::Extent;
use std::f64::consts;

#[derive(PartialEq,Debug)]
pub struct LngLat {
    pub lon: f64,
    pub lat: f64,
}

/// Returns the upper left (lon, lat) of a tile
fn ul(xtile: u32, ytile: u32, zoom: u8) -> LngLat {
    let n = (zoom as f64).exp2();
    let lon_deg = xtile as f64 / n * 360.0 - 180.0;
    let lat_rad = (consts::PI * (1.0_f64 - 2.0_f64 * ytile as f64 / n)).sinh().atan();
    let lat_deg = lat_rad.to_degrees();
    LngLat {lon: lon_deg, lat: lat_deg}
}

/// Returns the Spherical Mercator (x, y) in meters
fn xy(lon: f64, lat: f64) -> (f64, f64) {
    //lng, lat = truncate_lnglat(lng, lat)
    let x = 6378137.0_f64 * lon.to_radians();
    let y = 6378137.0_f64 *
        ((consts::PI*0.25) + (0.5_f64 * lat.to_radians())).tan().ln();
    (x, y)
}

/// Returns the Spherical Mercator bounding box of a tile
fn tile_extent(xtile: u32, ytile: u32, zoom: u8) -> Extent {
    let a = ul(xtile, ytile, zoom);
    let (ax, ay) = xy(a.lon, a.lat);
    let b = ul(xtile+1, ytile+1, zoom);
    let (bx, by) = xy(b.lon, b.lat);
    Extent {minx: ax, miny: by, maxx: bx, maxy: ay}
}

/// Returns the (lon, lat) bounding box of a tile
fn tile_bounds(xtile: u32, ytile: u32, zoom: u8) -> Extent {
    let a = ul(xtile, ytile, zoom);
    let b = ul(xtile+1, ytile+1, zoom);
    Extent {minx: a.lon, miny: b.lat, maxx: b.lon, maxy: a.lat}
}

#[test]
fn test_ul() {
    let lnglat = ul(486, 332, 10);
    assert_eq!(lnglat, LngLat {lon: -9.140625, lat: 53.33087298301705});
}

#[test]
fn test_xy() {
    let ul = ul(486, 332, 10);
    let xy_ = xy(ul.lon, ul.lat);
    assert_eq!(xy_, (-1017529.7205322663, 7044436.526761846));
    assert_eq!(xy(0.0, 0.0), (0.0, -0.0000000007081154551613622));
}

#[test]
fn test_merc_tile_extent() {
    let extent = tile_extent(486, 332, 10);
    assert_eq!(extent, Extent {minx: -1017529.7205322663, miny: 7005300.768279833, maxx: -978393.962050256, maxy: 7044436.526761846});
}

#[test]
fn test_merc_tile_bounds() {
    let bbox = tile_bounds(486, 332, 10);
    #[cfg(not(target_os = "macos"))]
    assert_eq!(bbox, Extent {minx: -9.140625, miny: 53.120405283106564, maxx: -8.7890625, maxy: 53.33087298301705});
    #[cfg(target_os = "macos")]
    assert_eq!(bbox, Extent {minx: -9.140625, miny: 53.12040528310657, maxx: -8.7890625, maxy: 53.33087298301705});
}

}
<|endoftext|>


<commit_before>use crate::keyboard::{Error, KeyboardRegKey};
use crate::language::LanguageRegKey;
use crate::platform::*;
use crate::types::*;
use registry::{Data, Hive, RegKey, Security};
use std::{
    collections::hash_map::HashMap,
    convert::{TryFrom, TryInto},
};

fn enabled_input_methods() -> InputList {
    let langs = crate::enabled_languages().unwrap();
    let mut imes: Vec<String> = vec![];
    for lang in langs {
        imes.append(&mut bcp47langs::get_user_language_input_methods(&lang).unwrap());
    }
    InputList::try_from(imes).unwrap()
}

fn log_important_regkeys() {
    log::trace!("  == REGKEY WATCH ==");
    log::trace!("");

    let substitutes_key = Hive::CurrentUser
        .open(r"Keyboard Layout\Substitutes", Security::Read)
        .unwrap();

    let user_profile_key = Hive::CurrentUser
        .create(r"Control Panel\International\User Profile", Security::Read)
        .unwrap();

    let preload_key = Hive::CurrentUser
        .open(r"Keyboard Layout\Preload", Security::Read)
        .unwrap();

    let user_profile_subkeys = user_profile_key
        .keys()
        .filter_map(Result::ok)
        .map(|x| x.open(Security::Read))
        .filter_map(Result::ok);

    for key in user_profile_subkeys {
        log::trace!("{}", key);
        for value in key.values().filter_map(Result::ok) {
            let (inner_name, inner_data) = value.into_inner();

            let name = inner_name.to_string_lossy().to_string();
            let data = format!("{}", inner_data);
            log::trace!("  '{}' = '{}'", name, data);
        }
        log::trace!("");
    }

    log::trace!("{}", preload_key);
    for value in preload_key.values().filter_map(Result::ok) {
        let (inner_name, inner_data) = value.into_inner();

        let name = inner_name.to_string_lossy().to_string();
        let data = format!("{}", inner_data);
        log::trace!("  '{}' = '{}'", name, data);
    }
    log::trace!("");

    log::trace!("{}", substitutes_key);
    for value in substitutes_key.values().filter_map(Result::ok) {
        let (inner_name, inner_data) = value.into_inner();

        let name = inner_name.to_string_lossy().to_string();
        let data = format!("{}", inner_data);
        log::trace!("  '{}' = '{}'", name, data);
    }
    log::trace!("");

    log::trace!("  == If you see a suspicious REGKEY in your neighbourhood, call 112 ==")
}

pub fn enable(tag: &str, product_code: &str, lang_name: Option<&str>) -> Result<(), Error> {
    log::info!("Enabling '{}' with product code '{}'", tag, product_code);
    log::info!("Lang name: {:?}", lang_name);

    log_important_regkeys();

    log::info!("Regenerating registry for keyboards, just in case.");
    regenerate_registry();

    let original_layout = winuser::current_keyboard();
    let record = match KeyboardRegKey::find_by_product_code(product_code) {
        Some(v) => v,
        None => return Err(Error::NotFound),
    };

    // Check language is enabled or LCID check will fail
    log::info!("Enabling language by tag");
    crate::enable_language(tag).unwrap();

    // Get all languages and keyboards
    let mut keyboards = crate::win8::enabled_keyboards()
        .unwrap()
        .into_iter()
        .collect::<HashMap<_, _>>();

    log::trace!("bcp47langs::lcid_from_bcp47(tag).unwrap()");
    let lcid = bcp47langs::lcid_from_bcp47(tag).unwrap();
    let tip = format!("{:04X}:{}", lcid, record.regkey_id());
    keyboards
        .entry(tag.to_string())
        .and_modify(|x| x.push(tip.clone()))
        .or_insert(vec![tip.clone()]);
    log_important_regkeys();

    // Remove all inputs internal
    log::trace!("bcp47langs::remove_inputs_for_all_languages().unwrap();");
    bcp47langs::remove_inputs_for_all_languages().unwrap();
    log_important_regkeys();

    // Build input method list
    let mut first = true;
    for (lang_tag, tips) in keyboards {
        let _lcid = match bcp47langs::lcid_from_bcp47(&lang_tag) {
            Some(v) => v,
            None => continue,
        };

        let inputs = InputList::try_from(tips).unwrap();

        // Flag 256 seems to clear everything.
        let flag = if first { 256 } else { 0 };
        first = false;

        input::install_layout(inputs, flag).unwrap();
    }

    log::info!("Regenerating registry for keyboards");
    regenerate_registry();
    log_important_regkeys();

    log::info!("Resetting current active keyboard");
    winuser::set_active_keyboard(original_layout);
    Ok(())
}

pub fn remove_invalid_kbids() {
    let installed_imes: Vec<String> = KeyboardRegKey::installed()
        .iter()
        .map(|x| x.regkey_id().to_owned())
        .collect();

    let enabled_imes = enabled_input_methods();
    let filtered_imes: Vec<InputListItem> = enabled_imes
        .into_inner()
        .into_iter()
        .filter(|i| {
            let kbid = i.kbid().to_string().to_lowercase();
            // Only handle custom keyboards
            if kbid.starts_with("a") {
                return true;
            }
            installed_imes.contains(&kbid)
        })
        .collect();

    bcp47langs::remove_inputs_for_all_languages().unwrap();
    input::install_layout(InputList::from(filtered_imes), 0).unwrap();
}

pub fn regenerate_registry() {
    let user_profile_key = Hive::CurrentUser
        .open(r"Control Panel\International\User Profile", Security::Read)
        .unwrap();
    let substitutes_key = Hive::CurrentUser
        .open(
            r"Keyboard Layout\Substitutes",
            Security::Read | Security::Write,
        )
        .unwrap();
    let preload_key = Hive::CurrentUser
        .open(r"Keyboard Layout\Preload", Security::Read | Security::Write)
        .unwrap();

    regenerate_given_registry(user_profile_key, substitutes_key, preload_key);
}

fn enable_default_user_lang(tag: &str) {
    let default_user_registry = registry::Hive::load_file(
        r"C:\Users\Default\NTUSER.DAT",
        Security::Read | Security::Write,
    )
    .unwrap();
    let language_key = LanguageRegKey::find_by_tag(tag).unwrap().regkey;
    let substitutes_key = Hive::CurrentUser
        .open(
            r"Keyboard Layout\Substitutes",
            Security::Read | Security::Write,
        )
        .unwrap();

    let default_user_profile_key = default_user_registry
        .create(
            format!(r"Control Panel\International\User Profile\{}", tag),
            Security::Read | Security::Write,
        )
        .unwrap();
    let default_substitutes_key = default_user_registry
        .open(
            r"Keyboard Layout\Substitutes",
            Security::Read | Security::Write,
        )
        .unwrap();

    for value in language_key.values() {
        let value = value.unwrap();
        default_user_profile_key
            .set_value(value.name(), value.data())
            .unwrap();
    }

    for value in substitutes_key.values() {
        let value = value.unwrap();
        default_substitutes_key
            .set_value(value.name(), value.data())
            .unwrap();
    }

    let preload_key = default_user_registry
        .open(r"Keyboard Layout\Preload", Security::Read | Security::Write)
        .unwrap();
    regenerate_given_registry(default_user_profile_key, substitutes_key, preload_key);
}

fn regenerate_given_registry(
    user_profile_key: RegKey,
    substitutes_key: RegKey,
    preload_key: RegKey,
) {
    log::debug!("regenerate_given_registry");
    let lang_keys: Vec<_> = user_profile_key
        .keys()
        .map(|k| k.unwrap().open(Security::Read).unwrap())
        .collect();

    log::trace!("Lang keys: {:?}", lang_keys);

    // Get known keyboard ids from Control Panel configured language list
    let keyboard_ids: Vec<_> = lang_keys
        .iter()
        .flat_map(|k| k.values())
        .map(|v| v.unwrap().name().to_string_lossy())
        .filter(|n| n.contains(":"))
        .map(|v| InputListItem::try_from(&*v))
        // .map(|n| n.split(":").last().unwrap().to_string())
        .filter_map(Result::ok)
        .collect();

    log::trace!("Keyboard IDs: {:?}", &keyboard_ids);

    // Get all substitutes into a list
    let subs = substitutes_key
        .values()
        .filter_map(Result::ok)
        .map(|x| {
            let x = x.into_inner();
            (x.0.to_string_lossy(), x.1.to_string())
        })
        .collect::<Vec<_>>();

    log::trace!("Substitutions: {:?}", &subs);

    // Clean up all invalid substitutions
    for (value_id, kbd_id) in subs.iter() {
        if keyboard_ids
            .iter()
            .find(|x| &format!("{:08x}", x.tip_id) == kbd_id)
            .is_none()
        {
            log::debug!("Deleting substitute: {:?}", value_id);
            substitutes_key.delete_value(value_id).unwrap();
        }
    }

    // Delete all preload values
    for value in preload_key.values() {
        let name = value.unwrap().name().to_owned();
        preload_key.delete_value(name).unwrap();
    }

    log::trace!("Cleared all preload keys");

    // Check if substitutes contains lang_id
    for (i, item) in keyboard_ids.iter().enumerate() {
        let lcid = format!("{:08x}", item.lang_id);
        let tip = format!("{:08x}", item.tip_id);

        let value = if subs.iter().find(|sub| sub.0 == lcid).is_some() {
            log::trace!("Adding substitute lcid: {}", &lcid);
            lcid.try_into().unwrap()
        } else {
            log::trace!("Adding TIP: {}", &tip);
            tip.try_into().unwrap()
        };

        preload_key
            .set_value((i + 1).to_string(), &Data::String(value))
            .unwrap();
    }
}
<commit_msg>This line is not our friend<commit_after>use crate::keyboard::{Error, KeyboardRegKey};
use crate::language::LanguageRegKey;
use crate::platform::*;
use crate::types::*;
use registry::{Data, Hive, RegKey, Security};
use std::{
    collections::hash_map::HashMap,
    convert::{TryFrom, TryInto},
};

fn enabled_input_methods() -> InputList {
    let langs = crate::enabled_languages().unwrap();
    let mut imes: Vec<String> = vec![];
    for lang in langs {
        imes.append(&mut bcp47langs::get_user_language_input_methods(&lang).unwrap());
    }
    InputList::try_from(imes).unwrap()
}

fn log_important_regkeys() {
    log::trace!("  == REGKEY WATCH ==");
    log::trace!("");

    let substitutes_key = Hive::CurrentUser
        .open(r"Keyboard Layout\Substitutes", Security::Read)
        .unwrap();

    let user_profile_key = Hive::CurrentUser
        .create(r"Control Panel\International\User Profile", Security::Read)
        .unwrap();

    let preload_key = Hive::CurrentUser
        .open(r"Keyboard Layout\Preload", Security::Read)
        .unwrap();

    let user_profile_subkeys = user_profile_key
        .keys()
        .filter_map(Result::ok)
        .map(|x| x.open(Security::Read))
        .filter_map(Result::ok);

    for key in user_profile_subkeys {
        log::trace!("{}", key);
        for value in key.values().filter_map(Result::ok) {
            let (inner_name, inner_data) = value.into_inner();

            let name = inner_name.to_string_lossy().to_string();
            let data = format!("{}", inner_data);
            log::trace!("  '{}' = '{}'", name, data);
        }
        log::trace!("");
    }

    log::trace!("{}", preload_key);
    for value in preload_key.values().filter_map(Result::ok) {
        let (inner_name, inner_data) = value.into_inner();

        let name = inner_name.to_string_lossy().to_string();
        let data = format!("{}", inner_data);
        log::trace!("  '{}' = '{}'", name, data);
    }
    log::trace!("");

    log::trace!("{}", substitutes_key);
    for value in substitutes_key.values().filter_map(Result::ok) {
        let (inner_name, inner_data) = value.into_inner();

        let name = inner_name.to_string_lossy().to_string();
        let data = format!("{}", inner_data);
        log::trace!("  '{}' = '{}'", name, data);
    }
    log::trace!("");

    log::trace!("  == If you see a suspicious REGKEY in your neighbourhood, call 112 ==")
}

pub fn enable(tag: &str, product_code: &str, lang_name: Option<&str>) -> Result<(), Error> {
    log::info!("Enabling '{}' with product code '{}'", tag, product_code);
    log::info!("Lang name: {:?}", lang_name);

    log_important_regkeys();

    log::info!("Regenerating registry for keyboards, just in case.");
    regenerate_registry();

    let original_layout = winuser::current_keyboard();
    let record = match KeyboardRegKey::find_by_product_code(product_code) {
        Some(v) => v,
        None => return Err(Error::NotFound),
    };

    // Check language is enabled or LCID check will fail
    log::info!("Enabling language by tag");
    crate::enable_language(tag).unwrap();

    // Get all languages and keyboards
    let mut keyboards = crate::win8::enabled_keyboards()
        .unwrap()
        .into_iter()
        .collect::<HashMap<_, _>>();

    log::trace!("bcp47langs::lcid_from_bcp47(tag).unwrap()");
    let lcid = bcp47langs::lcid_from_bcp47(tag).unwrap();
    let tip = format!("{:04X}:{}", lcid, record.regkey_id());
    keyboards
        .entry(tag.to_string())
        .and_modify(|x| x.push(tip.clone()))
        .or_insert(vec![tip.clone()]);
    log_important_regkeys();

    // Remove all inputs internal
    // log::trace!("bcp47langs::remove_inputs_for_all_languages().unwrap();");
    // bcp47langs::remove_inputs_for_all_languages().unwrap();
    // log_important_regkeys();

    // Build input method list
    let mut first = true;
    for (lang_tag, tips) in keyboards {
        let _lcid = match bcp47langs::lcid_from_bcp47(&lang_tag) {
            Some(v) => v,
            None => continue,
        };

        let inputs = InputList::try_from(tips).unwrap();

        // Flag 256 seems to clear everything.
        let flag = if first { 256 } else { 0 };
        first = false;

        input::install_layout(inputs, flag).unwrap();
    }

    log::info!("Regenerating registry for keyboards");
    regenerate_registry();
    log_important_regkeys();

    log::info!("Resetting current active keyboard");
    winuser::set_active_keyboard(original_layout);
    Ok(())
}

pub fn remove_invalid_kbids() {
    let installed_imes: Vec<String> = KeyboardRegKey::installed()
        .iter()
        .map(|x| x.regkey_id().to_owned())
        .collect();

    let enabled_imes = enabled_input_methods();
    let filtered_imes: Vec<InputListItem> = enabled_imes
        .into_inner()
        .into_iter()
        .filter(|i| {
            let kbid = i.kbid().to_string().to_lowercase();
            // Only handle custom keyboards
            if kbid.starts_with("a") {
                return true;
            }
            installed_imes.contains(&kbid)
        })
        .collect();

    bcp47langs::remove_inputs_for_all_languages().unwrap();
    input::install_layout(InputList::from(filtered_imes), 0).unwrap();
}

pub fn regenerate_registry() {
    let user_profile_key = Hive::CurrentUser
        .open(r"Control Panel\International\User Profile", Security::Read)
        .unwrap();
    let substitutes_key = Hive::CurrentUser
        .open(
            r"Keyboard Layout\Substitutes",
            Security::Read | Security::Write,
        )
        .unwrap();
    let preload_key = Hive::CurrentUser
        .open(r"Keyboard Layout\Preload", Security::Read | Security::Write)
        .unwrap();

    regenerate_given_registry(user_profile_key, substitutes_key, preload_key);
}

fn enable_default_user_lang(tag: &str) {
    let default_user_registry = registry::Hive::load_file(
        r"C:\Users\Default\NTUSER.DAT",
        Security::Read | Security::Write,
    )
    .unwrap();
    let language_key = LanguageRegKey::find_by_tag(tag).unwrap().regkey;
    let substitutes_key = Hive::CurrentUser
        .open(
            r"Keyboard Layout\Substitutes",
            Security::Read | Security::Write,
        )
        .unwrap();

    let default_user_profile_key = default_user_registry
        .create(
            format!(r"Control Panel\International\User Profile\{}", tag),
            Security::Read | Security::Write,
        )
        .unwrap();
    let default_substitutes_key = default_user_registry
        .open(
            r"Keyboard Layout\Substitutes",
            Security::Read | Security::Write,
        )
        .unwrap();

    for value in language_key.values() {
        let value = value.unwrap();
        default_user_profile_key
            .set_value(value.name(), value.data())
            .unwrap();
    }

    for value in substitutes_key.values() {
        let value = value.unwrap();
        default_substitutes_key
            .set_value(value.name(), value.data())
            .unwrap();
    }

    let preload_key = default_user_registry
        .open(r"Keyboard Layout\Preload", Security::Read | Security::Write)
        .unwrap();
    regenerate_given_registry(default_user_profile_key, substitutes_key, preload_key);
}

fn regenerate_given_registry(
    user_profile_key: RegKey,
    substitutes_key: RegKey,
    preload_key: RegKey,
) {
    log::debug!("regenerate_given_registry");
    let lang_keys: Vec<_> = user_profile_key
        .keys()
        .map(|k| k.unwrap().open(Security::Read).unwrap())
        .collect();

    log::trace!("Lang keys: {:?}", lang_keys);

    // Get known keyboard ids from Control Panel configured language list
    let keyboard_ids: Vec<_> = lang_keys
        .iter()
        .flat_map(|k| k.values())
        .map(|v| v.unwrap().name().to_string_lossy())
        .filter(|n| n.contains(":"))
        .map(|v| InputListItem::try_from(&*v))
        // .map(|n| n.split(":").last().unwrap().to_string())
        .filter_map(Result::ok)
        .collect();

    log::trace!("Keyboard IDs: {:?}", &keyboard_ids);

    // Get all substitutes into a list
    let subs = substitutes_key
        .values()
        .filter_map(Result::ok)
        .map(|x| {
            let x = x.into_inner();
            (x.0.to_string_lossy(), x.1.to_string())
        })
        .collect::<Vec<_>>();

    log::trace!("Substitutions: {:?}", &subs);

    // Clean up all invalid substitutions
    for (value_id, kbd_id) in subs.iter() {
        if keyboard_ids
            .iter()
            .find(|x| &format!("{:08x}", x.tip_id) == kbd_id)
            .is_none()
        {
            log::debug!("Deleting substitute: {:?}", value_id);
            substitutes_key.delete_value(value_id).unwrap();
        }
    }

    // Delete all preload values
    for value in preload_key.values() {
        let name = value.unwrap().name().to_owned();
        preload_key.delete_value(name).unwrap();
    }

    log::trace!("Cleared all preload keys");

    // Check if substitutes contains lang_id
    for (i, item) in keyboard_ids.iter().enumerate() {
        let lcid = format!("{:08x}", item.lang_id);
        let tip = format!("{:08x}", item.tip_id);

        let value = if subs.iter().find(|sub| sub.0 == lcid).is_some() {
            log::trace!("Adding substitute lcid: {}", &lcid);
            lcid.try_into().unwrap()
        } else {
            log::trace!("Adding TIP: {}", &tip);
            tip.try_into().unwrap()
        };

        preload_key
            .set_value((i + 1).to_string(), &Data::String(value))
            .unwrap();
    }
}
<|endoftext|>


<commit_before>
extern crate regex;

use std::process::{Command, Stdio, Child};
use std::error::Error;
use std::io::prelude::*;
use std::io::BufReader;
use std::convert::Into;

use lib::DownloadError;

use mysql::conn::pool::{MyPool,MyPooledConn};
use mysql::conn::Stmt;

macro_rules! regex(
    ($s:expr) => (regex::Regex::new($s).unwrap());
);

pub struct Converter<'a> {
    ffmpeg_cmd: &'a str,
    mp3_quality: &'a i16,
    pool: MyPool,
}

impl<'a> Converter<'a> {
    pub fn new(ffmpeg_cmd: &'a str,mp3_quality: &'a i16, pool: MyPool) -> Converter<'a> {
        Converter{ffmpeg_cmd: ffmpeg_cmd,mp3_quality: mp3_quality, pool: pool}
    }

    ///Merge audo & video file to one, using ffmpeg, saving directly at the dest. folder
    pub fn merge_files(&self, qid: &i64, video_file: &'a str,audio_file: &'a str, output_file: &'a str, show_progress: bool) -> Result<(), DownloadError>{

        let max_frames: i64 = try!(self.get_max_frames(video_file));
        println!("Total frames: {}",max_frames);

        let mut child = try!(self.create_merge_cmd(audio_file, video_file, output_file));
        let stdout = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let mut conn = self.pool.get_conn().unwrap();
        let mut statement = self.prepare_progress_updater(&mut conn);
        let re = regex!(r"frame=\s*(\d+)");

        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                    if show_progress {
                        if re.is_match(&text) {
                            let cap = re.captures(&text).unwrap();
                            println!("frame: {}", cap.at(1).unwrap());
                            try!(self.update_progress(&mut statement,
                                    self.caclulate_progress(&max_frames,&cap.at(1).unwrap()).to_string(), qid)
                                );
                        }
                    }
                },
            }
        }

        try!(child.wait());

        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));
        println!("Stderr: {}", stderr);

        Ok(())
    }

    pub fn extract_audio(&self, video_file: &'a str, output_file: &'a str, convert_mp3: bool) -> Result<(), DownloadError> {
        let mut child;
        if convert_mp3 {
            child = try!(self.create_audio_extrac_mp3_convert_cmd(video_file, output_file));
        }else {
            child = try!(self.create_audio_extract_cmd(video_file, output_file));
        }

        let mut stdout_buffer = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        //let mut stdout: String = String::new();
        //try!(stdout_buffer.read_to_string(&mut stdout));
        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());

        println!("Stderr: {}", stderr);

        Ok(())
    }

    fn caclulate_progress<'b>(&self, max_frames: &i64, current_frame: &str) -> i64 {
        let frame = current_frame.parse::<i64>().unwrap();
        frame / max_frames * 100
    }

    ///retrives the max frames from a video file, needed a percentual progress calculation
    fn get_max_frames(&self, video_file: &str) -> Result<i64,DownloadError> {
        let mut child = try!(self.create_fps_get_cmd(video_file));
        let mut stdout_buffer = BufReader::new(child.stdout.take().unwrap());
        let mut stdout: String = String::new();
        try!(stdout_buffer.read_to_string(&mut stdout));
        //println!("total frames stdout: {:?}", stdout.trim());
        try!(child.wait());

        let regex_duration = regex!(r"Duration: ((\d\d):(\d\d):(\d\d)\.\d\d)");
        let regex_fps = regex!(r"(\d+)\sfps");

        if regex_duration.is_match(&stdout) && regex_fps.is_match(&stdout) {
            let cap_fps = regex_fps.captures(&stdout).unwrap();

            let cap_duration = regex_duration.captures(&stdout).unwrap();
            println!("Found duration: {}",cap_duration.at(0).unwrap());
            let fps = cap_fps.at(1).unwrap().parse::<i64>().unwrap();
            let mut seconds = cap_duration.at(4).unwrap().parse::<i64>().unwrap();
            seconds += cap_duration.at(3).unwrap().parse::<i64>().unwrap() * 60;
            seconds += cap_duration.at(2).unwrap().parse::<i64>().unwrap() * 60 * 60 ;
            Ok(seconds * fps)
        }else{
            Err(DownloadError::FFMPEGError(format!("Couldn't get max frames {}",stdout)))
        }
    }

    ///Merges an audio & an video file together.
    ///Due to ffmpeg not giving out new lines we need to use tr, till the ffmpeg bindings are better
    ///This removes the option to use .arg() -> params must be handled carefully
    fn create_merge_cmd(&self, audio_file: &str, video_file: &str, output_file: &str) -> Result<Child,DownloadError> {
        self.create_bash_cmd(self.format_merge_cmd(audio_file, video_file, output_file))
    }

    ///Creates a cmd to gain the amount of frames in a video, for progress calculation
    fn create_fps_get_cmd(&self, video_file: &str) -> Result<Child, DownloadError> {
        self.create_bash_cmd(self.format_frame_get_cmd(video_file))
    }

    ///Create a ffmpeg instance with the audio extract cmd
    fn create_audio_extract_cmd(&self, video_file: &str, output_file: &str) -> Result<Child, DownloadError> {
        self.create_bash_cmd(self.format_extract_audio_cmd(video_file, output_file))
    }

    fn create_audio_extrac_mp3_convert_cmd(&self, video_file: &str, output_file: &str) -> Result<Child, DownloadError> {
        self.create_bash_cmd(self.format_convert_audio_mp3_cmd(video_file,output_file))
    }

    ///Create an bash cmd
    fn create_bash_cmd(&self, cmd: String) -> Result<Child, DownloadError> {
        match Command::new("bash")
                            .arg("-c")
                            .arg(cmd)
                            .stdin(Stdio::null())
                            .stdout(Stdio::piped())
                            .stderr(Stdio::piped())
                            .spawn() {
                Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
                Ok(process) => Ok(process),
        }
    }

    ///Formats a command to gain the total amount of frames in a video file
    ///which will be used for the progress calculation
    fn format_frame_get_cmd(&self, video_file: &str) -> String {
        let a = format!(r#"{}ffprobe -i {} 2>&1"#,self.ffmpeg_cmd, video_file);
        println!("ffmpeg-fps cmd: {}", a);
        a
    }

    ///Creates a ffmpeg_cmd containing the path to ffmpeg, as defined in the config
    ///and all the needed arguments, which can't be set using .arg, see create_merge_cmd.
    fn format_merge_cmd(&self, audio_file: &str, video_file: &str, output_file: &str) -> String {
        let a = format!(r#"{}ffmpeg -stats -threads 0 -i "{}" -i "{}" -map 0 -map 1 -codec copy -shortest "{}" 2>&1 |& tr '\r' '\n'"#,
            self.ffmpeg_cmd,
            video_file,
            audio_file,
            output_file);
        println!("ffmpeg cmd: {}", a);
        a
    }

    ///Create ffmpeg cmd to extract raw audio from a video file
    fn format_extract_audio_cmd(&self, video_file: &str, output_file: &str) -> String {
        let a = format!(r#"{}ffmpeg -stats -threads 0 -i "{}" -vn -acodec 'copy' "{}" 2>&1 |& tr '\r' '\n'"#,
            self.ffmpeg_cmd,
            video_file,
            output_file);
        println!("ffmpeg cmd: {}",a);
        a
    }

    ///Create ffmpeg cmd to extract audio from a video file & convert it directly to mp3
    fn format_convert_audio_mp3_cmd(&self, video_file: &str, output_file: &str) -> String {
        let a = format!(r#"{}ffmpeg -stats -threads 0 -i "{}" -codec:a libmp3lame -qscale:a {} "{}" 2>&1 |& tr '\r' '\n'"#,
            self.ffmpeg_cmd,
            video_file,
            self.mp3_quality,
            output_file);
        println!("ffmpeg cmd: {}", a);
        a
    }

    // MyPooledConn does only live when MyOpts is alive -> lifetime needs to be declared
    //TODO: think about generalizing this, while using the local pool
    fn prepare_progress_updater(&'a self,conn: &'a mut MyPooledConn) -> Stmt<'a> { // no livetime needed: struct livetime used
        conn.prepare("UPDATE querydetails SET progress = ? WHERE qid = ?").unwrap()
    }

    ///updater called from the stdout progress
    fn update_progress(&self,stmt: &mut Stmt, progress: String, qid: &i64) -> Result<(),DownloadError>{
        try!(stmt.execute(&[&progress,qid]).map(|_| Ok(())))
    }
}<commit_msg>disabled unneeded stdout reader<commit_after>
extern crate regex;

use std::process::{Command, Stdio, Child};
use std::error::Error;
use std::io::prelude::*;
use std::io::BufReader;
use std::convert::Into;

use lib::DownloadError;

use mysql::conn::pool::{MyPool,MyPooledConn};
use mysql::conn::Stmt;

macro_rules! regex(
    ($s:expr) => (regex::Regex::new($s).unwrap());
);

pub struct Converter<'a> {
    ffmpeg_cmd: &'a str,
    mp3_quality: &'a i16,
    pool: MyPool,
}

impl<'a> Converter<'a> {
    pub fn new(ffmpeg_cmd: &'a str,mp3_quality: &'a i16, pool: MyPool) -> Converter<'a> {
        Converter{ffmpeg_cmd: ffmpeg_cmd,mp3_quality: mp3_quality, pool: pool}
    }

    ///Merge audo & video file to one, using ffmpeg, saving directly at the dest. folder
    pub fn merge_files(&self, qid: &i64, video_file: &'a str,audio_file: &'a str, output_file: &'a str, show_progress: bool) -> Result<(), DownloadError>{
        println!("progress {}",show_progress);
        let max_frames: i64 = try!(self.get_max_frames(video_file));
        println!("Total frames: {}",max_frames);

        let mut child = try!(self.create_merge_cmd(audio_file, video_file, output_file));
        let stdout = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        let mut conn = self.pool.get_conn().unwrap();
        let mut statement = self.prepare_progress_updater(&mut conn);
        let re = regex!(r"frame=\s*(\d+)");

        for line in stdout.lines(){
            match line{
                Err(why) => panic!("couldn't read cmd stdout: {}", Error::description(&why)),
                Ok(text) => {
                    if show_progress {
                        if re.is_match(&text) {
                            let cap = re.captures(&text).unwrap();
                            println!("frame: {}", cap.at(1).unwrap());
                            try!(self.update_progress(&mut statement,
                                    self.caclulate_progress(&max_frames,&cap.at(1).unwrap()).to_string(), qid)
                                );
                        }
                    }
                },
            }
        }

        try!(child.wait());

        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));
        println!("Stderr: {}", stderr);

        Ok(())
    }

    pub fn extract_audio(&self, video_file: &'a str, output_file: &'a str, convert_mp3: bool) -> Result<(), DownloadError> {
        let mut child;
        if convert_mp3 {
            child = try!(self.create_audio_extrac_mp3_convert_cmd(video_file, output_file));
        }else {
            child = try!(self.create_audio_extract_cmd(video_file, output_file));
        }

        //let mut stdout_buffer = BufReader::new(child.stdout.take().unwrap());
        let mut stderr_buffer = BufReader::new(child.stderr.take().unwrap());

        //let mut stdout: String = String::new();
        //try!(stdout_buffer.read_to_string(&mut stdout));
        let mut stderr: String = String::new();
        try!(stderr_buffer.read_to_string(&mut stderr));

        try!(child.wait());

        println!("Stderr: {}", stderr);

        Ok(())
    }

    fn caclulate_progress<'b>(&self, max_frames: &i64, current_frame: &str) -> i64 {
        let frame = current_frame.parse::<i64>().unwrap();
        frame / max_frames * 100
    }

    ///retrives the max frames from a video file, needed a percentual progress calculation
    fn get_max_frames(&self, video_file: &str) -> Result<i64,DownloadError> {
        let mut child = try!(self.create_fps_get_cmd(video_file));
        let mut stdout_buffer = BufReader::new(child.stdout.take().unwrap());
        let mut stdout: String = String::new();
        try!(stdout_buffer.read_to_string(&mut stdout));
        //println!("total frames stdout: {:?}", stdout.trim());
        try!(child.wait());

        let regex_duration = regex!(r"Duration: ((\d\d):(\d\d):(\d\d)\.\d\d)");
        let regex_fps = regex!(r"(\d+)\sfps");

        if regex_duration.is_match(&stdout) && regex_fps.is_match(&stdout) {
            let cap_fps = regex_fps.captures(&stdout).unwrap();

            let cap_duration = regex_duration.captures(&stdout).unwrap();
            println!("Found duration: {}",cap_duration.at(0).unwrap());
            let fps = cap_fps.at(1).unwrap().parse::<i64>().unwrap();
            let mut seconds = cap_duration.at(4).unwrap().parse::<i64>().unwrap();
            seconds += cap_duration.at(3).unwrap().parse::<i64>().unwrap() * 60;
            seconds += cap_duration.at(2).unwrap().parse::<i64>().unwrap() * 60 * 60 ;
            Ok(seconds * fps)
        }else{
            Err(DownloadError::FFMPEGError(format!("Couldn't get max frames {}",stdout)))
        }
    }

    ///Merges an audio & an video file together.
    ///Due to ffmpeg not giving out new lines we need to use tr, till the ffmpeg bindings are better
    ///This removes the option to use .arg() -> params must be handled carefully
    fn create_merge_cmd(&self, audio_file: &str, video_file: &str, output_file: &str) -> Result<Child,DownloadError> {
        self.create_bash_cmd(self.format_merge_cmd(audio_file, video_file, output_file))
    }

    ///Creates a cmd to gain the amount of frames in a video, for progress calculation
    fn create_fps_get_cmd(&self, video_file: &str) -> Result<Child, DownloadError> {
        self.create_bash_cmd(self.format_frame_get_cmd(video_file))
    }

    ///Create a ffmpeg instance with the audio extract cmd
    fn create_audio_extract_cmd(&self, video_file: &str, output_file: &str) -> Result<Child, DownloadError> {
        self.create_bash_cmd(self.format_extract_audio_cmd(video_file, output_file))
    }

    fn create_audio_extrac_mp3_convert_cmd(&self, video_file: &str, output_file: &str) -> Result<Child, DownloadError> {
        self.create_bash_cmd(self.format_convert_audio_mp3_cmd(video_file,output_file))
    }

    ///Create an bash cmd
    fn create_bash_cmd(&self, cmd: String) -> Result<Child, DownloadError> {
        match Command::new("bash")
                            .arg("-c")
                            .arg(cmd)
                            .stdin(Stdio::null())
                            .stdout(Stdio::piped())
                            .stderr(Stdio::piped())
                            .spawn() {
                Err(why) => Err(DownloadError::InternalError(Error::description(&why).into())),
                Ok(process) => Ok(process),
        }
    }

    ///Formats a command to gain the total amount of frames in a video file
    ///which will be used for the progress calculation
    fn format_frame_get_cmd(&self, video_file: &str) -> String {
        let a = format!(r#"{}ffprobe -i {} 2>&1"#,self.ffmpeg_cmd, video_file);
        println!("ffmpeg-fps cmd: {}", a);
        a
    }

    ///Creates a ffmpeg_cmd containing the path to ffmpeg, as defined in the config
    ///and all the needed arguments, which can't be set using .arg, see create_merge_cmd.
    fn format_merge_cmd(&self, audio_file: &str, video_file: &str, output_file: &str) -> String {
        let a = format!(r#"{}ffmpeg -stats -threads 0 -i "{}" -i "{}" -map 0 -map 1 -codec copy -shortest "{}" 2>&1 |& tr '\r' '\n'"#,
            self.ffmpeg_cmd,
            video_file,
            audio_file,
            output_file);
        println!("ffmpeg cmd: {}", a);
        a
    }

    ///Create ffmpeg cmd to extract raw audio from a video file
    fn format_extract_audio_cmd(&self, video_file: &str, output_file: &str) -> String {
        let a = format!(r#"{}ffmpeg -stats -threads 0 -i "{}" -vn -acodec 'copy' "{}" 2>&1 |& tr '\r' '\n'"#,
            self.ffmpeg_cmd,
            video_file,
            output_file);
        println!("ffmpeg cmd: {}",a);
        a
    }

    ///Create ffmpeg cmd to extract audio from a video file & convert it directly to mp3
    fn format_convert_audio_mp3_cmd(&self, video_file: &str, output_file: &str) -> String {
        let a = format!(r#"{}ffmpeg -stats -threads 0 -i "{}" -codec:a libmp3lame -qscale:a {} "{}" 2>&1 |& tr '\r' '\n'"#,
            self.ffmpeg_cmd,
            video_file,
            self.mp3_quality,
            output_file);
        println!("ffmpeg cmd: {}", a);
        a
    }

    // MyPooledConn does only live when MyOpts is alive -> lifetime needs to be declared
    //TODO: think about generalizing this, while using the local pool
    fn prepare_progress_updater(&'a self,conn: &'a mut MyPooledConn) -> Stmt<'a> { // no livetime needed: struct livetime used
        conn.prepare("UPDATE querydetails SET progress = ? WHERE qid = ?").unwrap()
    }

    ///updater called from the stdout progress
    fn update_progress(&self,stmt: &mut Stmt, progress: String, qid: &i64) -> Result<(),DownloadError>{
        try!(stmt.execute(&[&progress,qid]).map(|_| Ok(())))
    }
}<|endoftext|>


<commit_before>use syn;
use syn::parse;
use syn::spanned::Spanned;
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::{quote, quote_spanned, ToTokens};

use byteorder::{ByteOrder, LittleEndian};

/// Enum representing the result size of a value/expression/register/etc in bytes.
#[derive(Debug, PartialOrd, PartialEq, Ord, Eq, Hash, Clone, Copy)]
pub enum Size {
    BYTE  = 1,
    WORD  = 2,
    DWORD = 4,
    FWORD = 6,
    QWORD = 8,
    PWORD = 10,
    OWORD = 16,
    HWORD = 32,
}

impl Size {
    pub fn in_bytes(self) -> u8 {
        self as u8
    }

    pub fn as_literal(self) -> syn::Ident {
        syn::Ident::new(match self {
            Size::BYTE  => "i8",
            Size::WORD  => "i16",
            Size::DWORD => "i32",
            Size::FWORD => "i48",
            Size::QWORD => "i64",
            Size::PWORD => "i80",
            Size::OWORD => "i128",
            Size::HWORD => "i256"
        }, Span::call_site())
    }
}

/// An abstract representation of a dynasm runtime statement to be emitted
#[derive(Debug, Clone)]
pub enum Stmt {
    // simply push data into the instruction stream. unsigned
    Const(u64, Size),
    // push data that is stored inside of an expression. unsigned
    ExprUnsigned(TokenTree, Size),
    // push signed data into the instruction stream. signed
    ExprSigned(TokenTree, Size),

    // extend the instruction stream with unsigned bytes
    Extend(Vec<u8>),
    // extend the instruction stream with unsigned bytes
    ExprExtend(TokenTree),
    // align the instruction stream to some alignment
    Align(TokenTree),

    // label declarations
    GlobalLabel(syn::Ident),
    LocalLabel(syn::Ident),
    DynamicLabel(TokenTree),

    // and their respective relocations (as expressions as they differ per assembler)
    GlobalJumpTarget(  syn::Ident,    TokenTree),
    ForwardJumpTarget( syn::Ident,    TokenTree),
    BackwardJumpTarget(syn::Ident,    TokenTree),
    DynamicJumpTarget(TokenTree, TokenTree),
    BareJumpTarget(   TokenTree, TokenTree),

    // a random statement that has to be inserted between assembly hunks
    Stmt(TokenTree)
}

// convenience methods
impl Stmt {
    #![allow(dead_code)]

    pub fn u8(value: u8) -> Stmt {
        Stmt::Const(u64::from(value), Size::BYTE)
    }

    pub fn u16(value: u16) -> Stmt {
        Stmt::Const(u64::from(value), Size::WORD)
    }

    pub fn u32(value: u32) -> Stmt {
        Stmt::Const(u64::from(value), Size::DWORD)
    }

    pub fn u64(value: u64) -> Stmt {
        Stmt::Const(value, Size::QWORD)
    }
}

/// Converts a sequence of abstract Statements to actual tokens
pub fn serialize(name: &TokenTree, stmts: Vec<Stmt>) -> TokenStream {
    // first, try to fold constants into a byte stream
    let mut folded_stmts = Vec::new();
    let mut const_buffer = Vec::new();
    for stmt in stmts {
        match stmt {
            Stmt::Const(value, size) => {
                match size {
                    Size::BYTE => const_buffer.push(value as u8),
                    Size::WORD => {
                        let mut buffer = [0u8; 2];
                        LittleEndian::write_u16(&mut buffer, value as u16);
                        const_buffer.extend(&buffer);
                    },
                    Size::DWORD => {
                        let mut buffer = [0u8; 4];
                        LittleEndian::write_u32(&mut buffer, value as u32);
                        const_buffer.extend(&buffer);
                    },
                    Size::QWORD => {
                        let mut buffer = [0u8; 8];
                        LittleEndian::write_u64(&mut buffer, value as u64);
                        const_buffer.extend(&buffer);
                    },
                    _ => unimplemented!()
                }
            },
            Stmt::Extend(data) => {
                const_buffer.extend(data);
            },
            s => {
                // empty the const buffer
                if !const_buffer.is_empty() {
                    folded_stmts.push(Stmt::Extend(const_buffer));
                    const_buffer = Vec::new();
                }
                folded_stmts.push(s);
            }
        }
        while const_buffer.len() > 32 {
            let new_buffer = const_buffer.split_off(32);
            folded_stmts.push(Stmt::Extend(const_buffer));
            const_buffer = new_buffer;
        }
    }
    if !const_buffer.is_empty() {
        folded_stmts.push(Stmt::Extend(const_buffer));
    }

    // and now do the final output pass in one go
    let mut output = TokenStream::new();

    for stmt in folded_stmts {
        let (method, args) = match stmt {
            Stmt::Const(_, _) => unreachable!(),
            Stmt::ExprUnsigned(expr, Size::BYTE)  => ("push",     vec![expr]),
            Stmt::ExprUnsigned(expr, Size::WORD)  => ("push_u16", vec![expr]),
            Stmt::ExprUnsigned(expr, Size::DWORD) => ("push_u32", vec![expr]),
            Stmt::ExprUnsigned(expr, Size::QWORD) => ("push_u64", vec![expr]),
            Stmt::ExprUnsigned(_, _) => unimplemented!(),
            Stmt::ExprSigned(  expr, Size::BYTE)  => ("push_i8",  vec![expr]),
            Stmt::ExprSigned(  expr, Size::WORD)  => ("push_i16", vec![expr]),
            Stmt::ExprSigned(  expr, Size::DWORD) => ("push_i32", vec![expr]),
            Stmt::ExprSigned(  expr, Size::QWORD) => ("push_i64", vec![expr]),
            Stmt::ExprSigned(_, _) => unimplemented!(),
            Stmt::Extend(data)     => ("extend", vec![proc_macro2::Literal::byte_string(&data).into()]),
            Stmt::ExprExtend(expr) => ("extend", vec![expr]),
            Stmt::Align(expr)      => ("align", vec![expr]),
            Stmt::GlobalLabel(n) => ("global_label", vec![expr_string_from_ident(&n)]),
            Stmt::LocalLabel(n)  => ("local_label", vec![expr_string_from_ident(&n)]),
            Stmt::DynamicLabel(expr) => ("dynamic_label", vec![expr]),
            Stmt::GlobalJumpTarget(n,     reloc) => ("global_reloc"  , vec![expr_string_from_ident(&n), reloc]),
            Stmt::ForwardJumpTarget(n,    reloc) => ("forward_reloc" , vec![expr_string_from_ident(&n), reloc]),
            Stmt::BackwardJumpTarget(n,   reloc) => ("backward_reloc", vec![expr_string_from_ident(&n), reloc]),
            Stmt::DynamicJumpTarget(expr, reloc) => ("dynamic_reloc" , vec![expr, reloc]),
            Stmt::BareJumpTarget(expr, reloc)    => ("bare_reloc"    , vec![expr, reloc]),
            Stmt::Stmt(s) => {
                output.extend(quote! {
                    #s ;
                });
                continue;
            }
        };

        // and construct the appropriate method call
        let method = syn::Ident::new(method, Span::call_site());
        output.extend(quote! {
            #name . #method ( #( #args ),* ) ;
        })
    }

    // if we have nothing to emit, expand to nothing. Else, wrap it into a block.
    if output.is_empty() {
        output
    } else {
        quote!{
            {
                #output
            }
        }
    }
}

// below here are all kinds of utility functions to quickly generate TokenTree constructs
// this collection is arbitrary and purely based on what special things are needed for assembler
// codegen implementations

// Makes a None-delimited TokenTree item out of anything that can be converted to tokens.
// This is a useful shortcut to escape issues around not-properly delimited tokenstreams
// because it is guaranteed to be parsed back properly to its source ast at type-level.
pub fn delimited<T: ToTokens>(expr: T) -> TokenTree {
    let span = expr.span();
    let mut group = proc_macro2::Group::new(
        proc_macro2::Delimiter::None, expr.into_token_stream()
    );
    group.set_span(span);
    proc_macro2::TokenTree::Group(group)
}

// expression of value 0. sometimes needed.
pub fn expr_zero() -> TokenTree {
    proc_macro2::Literal::u8_unsuffixed(0).into()
}

// given an ident, makes it into a "string"
pub fn expr_string_from_ident(i: &syn::Ident) -> TokenTree {
    let name = i.to_string();
    proc_macro2::Literal::string(&name).into()
}

//
pub fn expr_dynscale(scale: &TokenTree, rest: &TokenTree) -> (TokenTree, TokenTree) {
    let tempval = expr_encode_x64_sib_scale(&scale);
    (delimited(quote! {
        let temp = #tempval
    }), delimited(quote! {
         #rest | ((temp & 3) << 6)
    }))
}

// makes (a, b)
pub fn expr_tuple_of_u8s(span: Span, data: &[u8]) -> TokenTree {
    delimited(quote_spanned! {span=>
        (#(#data),*)
    })
}

// makes sum(exprs)
pub fn expr_add_many<T: Iterator<Item=TokenTree>>(span: Span, mut exprs: T) -> Option<TokenTree> {
    let first_expr = exprs.next()?;

    let tokens = quote_spanned!{ span=>
        #first_expr #( + #exprs )*
    };

    Some(delimited(tokens))
}

// makes (size_of<ty>() * value)
pub fn expr_size_of_scale(ty: &syn::Path, value: &TokenTree, size: Size) -> TokenTree {
    let span = value.span();
    let size = size.as_literal();

    delimited(quote_spanned! { span=>
        (::std::mem::size_of::<#ty>() as #size) * #value
    })
}

/// returns orig | ((expr & mask) << shift)
pub fn expr_mask_shift_or(orig: &TokenTree, expr: &TokenTree, mask: u64, shift: i8) -> TokenTree {
    let span = expr.span();

    let mask: TokenTree = proc_macro2::Literal::u64_unsuffixed(mask).into();

    delimited(if shift >= 0 {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(shift).into();
        quote_spanned! { span=>
            #orig | ((#expr & #mask) << #shift)
        }
    } else {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(-shift).into();
        quote_spanned! { span=>
            #orig | ((#expr & #mask) >> #shift)
        }
    })
}

/// returns orig & !((expr & mask) << shift)
pub fn expr_mask_shift_inverted_and(orig: &TokenTree, expr: &TokenTree, mask: u64, shift: i8) -> TokenTree {
    let span = expr.span();

    let mask: TokenTree = proc_macro2::Literal::u64_unsuffixed(mask).into();

    delimited(if shift >= 0 {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(shift).into();
        quote_spanned! { span=>
            #orig & !((#expr & #mask) << #shift)
        }
    } else {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(-shift).into();
        quote_spanned! { span=>
            #orig & !((#expr & #mask) >> #shift)
        }
    })
}

/// returns (offset_of!(path, attr) as size)
pub fn expr_offset_of(path: &syn::Path, attr: &syn::Ident, size: Size) -> TokenTree {
    // generate a P<Expr> that resolves into the offset of an attribute to a type.
    // this is somewhat ridiculously complex because we can't expand macros here

    let span = path.span();
    let size = size.as_literal();

    delimited(quote_spanned! { span=>
        unsafe {
            let #path {#attr: _, ..};
            let temp: #path = ::std::mem::uninitialized();
            let rv = &temp.#attr as *const _ as usize - &temp as *const _ as usize;
            ::std::mem::forget(temp);
            rv as #size
        }
    })
}

// returns std::mem::size_of<path>()
pub fn expr_size_of(path: &syn::Path) -> TokenTree {
    // generate a P<Expr> that returns the size of type at path
    let span = path.span();

    delimited(quote_spanned! { span=>
        ::std::mem::size_of::<#path>()
    })
}

// makes the following
// match size {
//    8 => 3,
//    4 => 2,
//    2 => 1,
//    1 => 0,
//    _ => panic!r("Type size not representable as scale")
//}
pub fn expr_encode_x64_sib_scale(size: &TokenTree) -> TokenTree {
    let span = size.span();

    delimited(quote_spanned! { span=>
        match #size {
            8 => 3,
            4 => 2,
            2 => 1,
            1 => 0,
            _ => panic!("Type size not representable as scale")
        }
    })
}

// Reparses a tokentree into an expression
pub fn reparse(tt: &TokenTree) -> parse::Result<syn::Expr> {
    syn::parse2(tt.into_token_stream())
}
<commit_msg>ÃRemove the use of mem::initialized in offset_of calculations as it is being deprecated, in favour of mem::MaybeUninit<commit_after>use syn;
use syn::parse;
use syn::spanned::Spanned;
use proc_macro2::{Span, TokenStream, TokenTree};
use quote::{quote, quote_spanned, ToTokens};

use byteorder::{ByteOrder, LittleEndian};

/// Enum representing the result size of a value/expression/register/etc in bytes.
#[derive(Debug, PartialOrd, PartialEq, Ord, Eq, Hash, Clone, Copy)]
pub enum Size {
    BYTE  = 1,
    WORD  = 2,
    DWORD = 4,
    FWORD = 6,
    QWORD = 8,
    PWORD = 10,
    OWORD = 16,
    HWORD = 32,
}

impl Size {
    pub fn in_bytes(self) -> u8 {
        self as u8
    }

    pub fn as_literal(self) -> syn::Ident {
        syn::Ident::new(match self {
            Size::BYTE  => "i8",
            Size::WORD  => "i16",
            Size::DWORD => "i32",
            Size::FWORD => "i48",
            Size::QWORD => "i64",
            Size::PWORD => "i80",
            Size::OWORD => "i128",
            Size::HWORD => "i256"
        }, Span::call_site())
    }
}

/// An abstract representation of a dynasm runtime statement to be emitted
#[derive(Debug, Clone)]
pub enum Stmt {
    // simply push data into the instruction stream. unsigned
    Const(u64, Size),
    // push data that is stored inside of an expression. unsigned
    ExprUnsigned(TokenTree, Size),
    // push signed data into the instruction stream. signed
    ExprSigned(TokenTree, Size),

    // extend the instruction stream with unsigned bytes
    Extend(Vec<u8>),
    // extend the instruction stream with unsigned bytes
    ExprExtend(TokenTree),
    // align the instruction stream to some alignment
    Align(TokenTree),

    // label declarations
    GlobalLabel(syn::Ident),
    LocalLabel(syn::Ident),
    DynamicLabel(TokenTree),

    // and their respective relocations (as expressions as they differ per assembler)
    GlobalJumpTarget(  syn::Ident,    TokenTree),
    ForwardJumpTarget( syn::Ident,    TokenTree),
    BackwardJumpTarget(syn::Ident,    TokenTree),
    DynamicJumpTarget(TokenTree, TokenTree),
    BareJumpTarget(   TokenTree, TokenTree),

    // a random statement that has to be inserted between assembly hunks
    Stmt(TokenTree)
}

// convenience methods
impl Stmt {
    #![allow(dead_code)]

    pub fn u8(value: u8) -> Stmt {
        Stmt::Const(u64::from(value), Size::BYTE)
    }

    pub fn u16(value: u16) -> Stmt {
        Stmt::Const(u64::from(value), Size::WORD)
    }

    pub fn u32(value: u32) -> Stmt {
        Stmt::Const(u64::from(value), Size::DWORD)
    }

    pub fn u64(value: u64) -> Stmt {
        Stmt::Const(value, Size::QWORD)
    }
}

/// Converts a sequence of abstract Statements to actual tokens
pub fn serialize(name: &TokenTree, stmts: Vec<Stmt>) -> TokenStream {
    // first, try to fold constants into a byte stream
    let mut folded_stmts = Vec::new();
    let mut const_buffer = Vec::new();
    for stmt in stmts {
        match stmt {
            Stmt::Const(value, size) => {
                match size {
                    Size::BYTE => const_buffer.push(value as u8),
                    Size::WORD => {
                        let mut buffer = [0u8; 2];
                        LittleEndian::write_u16(&mut buffer, value as u16);
                        const_buffer.extend(&buffer);
                    },
                    Size::DWORD => {
                        let mut buffer = [0u8; 4];
                        LittleEndian::write_u32(&mut buffer, value as u32);
                        const_buffer.extend(&buffer);
                    },
                    Size::QWORD => {
                        let mut buffer = [0u8; 8];
                        LittleEndian::write_u64(&mut buffer, value as u64);
                        const_buffer.extend(&buffer);
                    },
                    _ => unimplemented!()
                }
            },
            Stmt::Extend(data) => {
                const_buffer.extend(data);
            },
            s => {
                // empty the const buffer
                if !const_buffer.is_empty() {
                    folded_stmts.push(Stmt::Extend(const_buffer));
                    const_buffer = Vec::new();
                }
                folded_stmts.push(s);
            }
        }
        while const_buffer.len() > 32 {
            let new_buffer = const_buffer.split_off(32);
            folded_stmts.push(Stmt::Extend(const_buffer));
            const_buffer = new_buffer;
        }
    }
    if !const_buffer.is_empty() {
        folded_stmts.push(Stmt::Extend(const_buffer));
    }

    // and now do the final output pass in one go
    let mut output = TokenStream::new();

    for stmt in folded_stmts {
        let (method, args) = match stmt {
            Stmt::Const(_, _) => unreachable!(),
            Stmt::ExprUnsigned(expr, Size::BYTE)  => ("push",     vec![expr]),
            Stmt::ExprUnsigned(expr, Size::WORD)  => ("push_u16", vec![expr]),
            Stmt::ExprUnsigned(expr, Size::DWORD) => ("push_u32", vec![expr]),
            Stmt::ExprUnsigned(expr, Size::QWORD) => ("push_u64", vec![expr]),
            Stmt::ExprUnsigned(_, _) => unimplemented!(),
            Stmt::ExprSigned(  expr, Size::BYTE)  => ("push_i8",  vec![expr]),
            Stmt::ExprSigned(  expr, Size::WORD)  => ("push_i16", vec![expr]),
            Stmt::ExprSigned(  expr, Size::DWORD) => ("push_i32", vec![expr]),
            Stmt::ExprSigned(  expr, Size::QWORD) => ("push_i64", vec![expr]),
            Stmt::ExprSigned(_, _) => unimplemented!(),
            Stmt::Extend(data)     => ("extend", vec![proc_macro2::Literal::byte_string(&data).into()]),
            Stmt::ExprExtend(expr) => ("extend", vec![expr]),
            Stmt::Align(expr)      => ("align", vec![expr]),
            Stmt::GlobalLabel(n) => ("global_label", vec![expr_string_from_ident(&n)]),
            Stmt::LocalLabel(n)  => ("local_label", vec![expr_string_from_ident(&n)]),
            Stmt::DynamicLabel(expr) => ("dynamic_label", vec![expr]),
            Stmt::GlobalJumpTarget(n,     reloc) => ("global_reloc"  , vec![expr_string_from_ident(&n), reloc]),
            Stmt::ForwardJumpTarget(n,    reloc) => ("forward_reloc" , vec![expr_string_from_ident(&n), reloc]),
            Stmt::BackwardJumpTarget(n,   reloc) => ("backward_reloc", vec![expr_string_from_ident(&n), reloc]),
            Stmt::DynamicJumpTarget(expr, reloc) => ("dynamic_reloc" , vec![expr, reloc]),
            Stmt::BareJumpTarget(expr, reloc)    => ("bare_reloc"    , vec![expr, reloc]),
            Stmt::Stmt(s) => {
                output.extend(quote! {
                    #s ;
                });
                continue;
            }
        };

        // and construct the appropriate method call
        let method = syn::Ident::new(method, Span::call_site());
        output.extend(quote! {
            #name . #method ( #( #args ),* ) ;
        })
    }

    // if we have nothing to emit, expand to nothing. Else, wrap it into a block.
    if output.is_empty() {
        output
    } else {
        quote!{
            {
                #output
            }
        }
    }
}

// below here are all kinds of utility functions to quickly generate TokenTree constructs
// this collection is arbitrary and purely based on what special things are needed for assembler
// codegen implementations

// Makes a None-delimited TokenTree item out of anything that can be converted to tokens.
// This is a useful shortcut to escape issues around not-properly delimited tokenstreams
// because it is guaranteed to be parsed back properly to its source ast at type-level.
pub fn delimited<T: ToTokens>(expr: T) -> TokenTree {
    let span = expr.span();
    let mut group = proc_macro2::Group::new(
        proc_macro2::Delimiter::None, expr.into_token_stream()
    );
    group.set_span(span);
    proc_macro2::TokenTree::Group(group)
}

// expression of value 0. sometimes needed.
pub fn expr_zero() -> TokenTree {
    proc_macro2::Literal::u8_unsuffixed(0).into()
}

// given an ident, makes it into a "string"
pub fn expr_string_from_ident(i: &syn::Ident) -> TokenTree {
    let name = i.to_string();
    proc_macro2::Literal::string(&name).into()
}

//
pub fn expr_dynscale(scale: &TokenTree, rest: &TokenTree) -> (TokenTree, TokenTree) {
    let tempval = expr_encode_x64_sib_scale(&scale);
    (delimited(quote! {
        let temp = #tempval
    }), delimited(quote! {
         #rest | ((temp & 3) << 6)
    }))
}

// makes (a, b)
pub fn expr_tuple_of_u8s(span: Span, data: &[u8]) -> TokenTree {
    delimited(quote_spanned! {span=>
        (#(#data),*)
    })
}

// makes sum(exprs)
pub fn expr_add_many<T: Iterator<Item=TokenTree>>(span: Span, mut exprs: T) -> Option<TokenTree> {
    let first_expr = exprs.next()?;

    let tokens = quote_spanned!{ span=>
        #first_expr #( + #exprs )*
    };

    Some(delimited(tokens))
}

// makes (size_of<ty>() * value)
pub fn expr_size_of_scale(ty: &syn::Path, value: &TokenTree, size: Size) -> TokenTree {
    let span = value.span();
    let size = size.as_literal();

    delimited(quote_spanned! { span=>
        (::std::mem::size_of::<#ty>() as #size) * #value
    })
}

/// returns orig | ((expr & mask) << shift)
pub fn expr_mask_shift_or(orig: &TokenTree, expr: &TokenTree, mask: u64, shift: i8) -> TokenTree {
    let span = expr.span();

    let mask: TokenTree = proc_macro2::Literal::u64_unsuffixed(mask).into();

    delimited(if shift >= 0 {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(shift).into();
        quote_spanned! { span=>
            #orig | ((#expr & #mask) << #shift)
        }
    } else {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(-shift).into();
        quote_spanned! { span=>
            #orig | ((#expr & #mask) >> #shift)
        }
    })
}

/// returns orig & !((expr & mask) << shift)
pub fn expr_mask_shift_inverted_and(orig: &TokenTree, expr: &TokenTree, mask: u64, shift: i8) -> TokenTree {
    let span = expr.span();

    let mask: TokenTree = proc_macro2::Literal::u64_unsuffixed(mask).into();

    delimited(if shift >= 0 {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(shift).into();
        quote_spanned! { span=>
            #orig & !((#expr & #mask) << #shift)
        }
    } else {
        let shift: TokenTree = proc_macro2::Literal::i8_unsuffixed(-shift).into();
        quote_spanned! { span=>
            #orig & !((#expr & #mask) >> #shift)
        }
    })
}

/// returns (offset_of!(path, attr) as size)
pub fn expr_offset_of(path: &syn::Path, attr: &syn::Ident, size: Size) -> TokenTree {
    // generate a P<Expr> that resolves into the offset of an attribute to a type.
    // this is somewhat ridiculously complex because we can't expand macros here

    let span = path.span();
    let size = size.as_literal();

    delimited(quote_spanned! { span=>
        unsafe {
            let #path {#attr: _, ..};
            let temp = ::std::mem::MaybeUninit::<#path>::uninit();
            let rv = &(*temp.as_ptr()).#attr as *const _ as usize - temp.as_ptr() as usize;
            rv as #size
        }
    })
}

// returns std::mem::size_of<path>()
pub fn expr_size_of(path: &syn::Path) -> TokenTree {
    // generate a P<Expr> that returns the size of type at path
    let span = path.span();

    delimited(quote_spanned! { span=>
        ::std::mem::size_of::<#path>()
    })
}

// makes the following
// match size {
//    8 => 3,
//    4 => 2,
//    2 => 1,
//    1 => 0,
//    _ => panic!r("Type size not representable as scale")
//}
pub fn expr_encode_x64_sib_scale(size: &TokenTree) -> TokenTree {
    let span = size.span();

    delimited(quote_spanned! { span=>
        match #size {
            8 => 3,
            4 => 2,
            2 => 1,
            1 => 0,
            _ => panic!("Type size not representable as scale")
        }
    })
}

// Reparses a tokentree into an expression
pub fn reparse(tt: &TokenTree) -> parse::Result<syn::Expr> {
    syn::parse2(tt.into_token_stream())
}
<|endoftext|>