<commit_before>use cairo;
use cairo::MatrixTrait;
use cairo::Pattern as CairoPattern;
use std::cell::RefCell;
use std::f64;
use std::rc::*;

use aspect_ratio::*;
use attributes::Attribute;
use bbox::*;
use coord_units::CoordUnits;
use drawing_ctx::DrawingCtx;
use error::RenderingError;
use float_eq_cairo::ApproxEqCairo;
use handle::RsvgHandle;
use length::*;
use node::*;
use parsers::{parse, parse_and_validate};
use property_bag::PropertyBag;
use state::ComputedValues;
use viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone)]
struct Pattern {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<String>,
    pub x: Option<Length>,
    pub y: Option<Length>,
    pub width: Option<Length>,
    pub height: Option<Length>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: Option<Weak<Node>>,
}

impl Default for Pattern {
    fn default() -> Pattern {
        // These are per the spec

        Pattern {
            units: Some(PatternUnits::default()),
            content_units: Some(PatternContentUnits::default()),
            vbox: Some(None),
            preserve_aspect_ratio: Some(AspectRatio::default()),
            affine: Some(cairo::Matrix::identity()),
            fallback: None,
            x: Some(Length::default()),
            y: Some(Length::default()),
            width: Some(Length::default()),
            height: Some(Length::default()),
            node: None,
        }
    }
}

// All of the Pattern's fields are Option<foo> values, because
// those fields can be omitted in the SVG file.  We need to resolve
// them to default values, or to fallback values that come from
// another Pattern.
//
// For the fallback case, this would need something like
//
//    if self.foo.is_none () { self.foo = fallback.foo; }
//
// And for the default case, it would be like
//    if self.foo.is_none () { self.foo = Some (default_value); }
//
// Both can be replaced by
//
//    self.foo = self.foo.take ().or (bar);
//
// So we define a macro for that.
macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Pattern {
    fn unresolved() -> Pattern {
        Pattern {
            units: None,
            content_units: None,
            vbox: None,
            preserve_aspect_ratio: None,
            affine: None,
            fallback: None,
            x: None,
            y: None,
            width: None,
            height: None,
            node: None,
        }
    }

    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = self.node {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }

    fn resolve_from_defaults(&mut self) {
        self.resolve_from_fallback(&Pattern::default());
    }

    fn resolve_from_fallback(&mut self, fallback: &Pattern) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.is_some() {
                self.node = fallback.node.clone();
            } else {
                self.node = None;
            }
        }
    }
}

pub struct NodePattern {
    pattern: RefCell<Pattern>,
}

impl NodePattern {
    pub fn new() -> NodePattern {
        NodePattern {
            pattern: RefCell::new(Pattern::unresolved()),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(&self, node: &RsvgNode, _: *const RsvgHandle, pbag: &PropertyBag) -> NodeResult {
        // pattern element has overflow:hidden
        // https://www.w3.org/TR/SVG/styling.html#UAStyleSheet
        node.set_overflow_hidden();

        let mut p = self.pattern.borrow_mut();

        p.node = Some(Rc::downgrade(node));

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::PatternUnits => p.units = Some(parse("patternUnits", value, ())?),

                Attribute::PatternContentUnits => {
                    p.content_units = Some(parse("patternContentUnits", value, ())?)
                }

                Attribute::ViewBox => p.vbox = Some(Some(parse("viewBox", value, ())?)),

                Attribute::PreserveAspectRatio => {
                    p.preserve_aspect_ratio = Some(parse("preserveAspectRatio", value, ())?)
                }

                Attribute::PatternTransform => {
                    p.affine = Some(parse("patternTransform", value, ())?)
                }

                Attribute::XlinkHref => p.fallback = Some(value.to_owned()),

                Attribute::X => p.x = Some(parse("x", value, LengthDir::Horizontal)?),

                Attribute::Y => p.y = Some(parse("y", value, LengthDir::Vertical)?),

                Attribute::Width => {
                    p.width = Some(parse_and_validate(
                        "width",
                        value,
                        LengthDir::Horizontal,
                        Length::check_nonnegative,
                    )?)
                }

                Attribute::Height => {
                    p.height = Some(parse_and_validate(
                        "height",
                        value,
                        LengthDir::Vertical,
                        Length::check_nonnegative,
                    )?)
                }

                _ => (),
            }
        }

        Ok(())
    }
}

fn resolve_pattern(pattern: &Pattern, draw_ctx: &mut DrawingCtx) -> Pattern {
    let mut result = pattern.clone();

    while !result.is_resolved() {
        if let Some(acquired) = draw_ctx.get_acquired_node_of_type(
            result.fallback.as_ref().map(String::as_ref),
            NodeType::Pattern,
        ) {
            acquired
                .get()
                .with_impl(|i: &NodePattern| result.resolve_from_fallback(&*i.pattern.borrow()));
        } else {
            result.resolve_from_defaults();
        }
    }

    result
}

fn set_pattern_on_draw_context(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(pattern.is_resolved());

    if pattern.node.is_none() {
        // This means we didn't find any children among the fallbacks,
        // so there is nothing to render.
        return Ok(false);
    }

    let units = pattern.units.unwrap();
    let content_units = pattern.content_units.unwrap();
    let pattern_affine = pattern.affine.unwrap();
    let vbox = pattern.vbox.unwrap();
    let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

    let (pattern_x, pattern_y, pattern_width, pattern_height) = {
        let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            draw_ctx.get_view_params()
        };

        let pattern_x = pattern.x.unwrap().normalize(values, &params);
        let pattern_y = pattern.y.unwrap().normalize(values, &params);
        let pattern_width = pattern.width.unwrap().normalize(values, &params);
        let pattern_height = pattern.height.unwrap().normalize(values, &params);

        (pattern_x, pattern_y, pattern_width, pattern_height)
    };

    // Work out the size of the rectangle so it takes into account the object bounding box

    let bbwscale: f64;
    let bbhscale: f64;

    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            bbwscale = bbrect.width;
            bbhscale = bbrect.height;
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            bbwscale = 1.0;
            bbhscale = 1.0;
        }
    }

    let cr = draw_ctx.get_cairo_context();
    let affine = cr.get_matrix();
    let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

    let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
    let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

    let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
    let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

    let scaled_width = pattern_width * bbwscale;
    let scaled_height = pattern_height * bbhscale;

    if scaled_width.abs() < f64::EPSILON || scaled_height.abs() < f64::EPSILON || pw < 1 || ph < 1 {
        return Ok(false);
    }

    scwscale = f64::from(pw) / scaled_width;
    schscale = f64::from(ph) / scaled_height;

    let mut affine: cairo::Matrix = cairo::Matrix::identity();

    // Create the pattern coordinate system
    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            affine.translate(
                bbrect.x + pattern_x * bbrect.width,
                bbrect.y + pattern_y * bbrect.height,
            );
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            affine.translate(pattern_x, pattern_y);
        }
    }

    // Apply the pattern transform
    affine = cairo::Matrix::multiply(&affine, &pattern_affine);

    let mut caffine: cairo::Matrix;

    let pushed_view_box: bool;

    // Create the pattern contents coordinate system
    let _params = if let Some(vbox) = vbox {
        // If there is a vbox, use that
        let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
            vbox.0.width,
            vbox.0.height,
            0.0,
            0.0,
            pattern_width * bbwscale,
            pattern_height * bbhscale,
        );

        x -= vbox.0.x * w / vbox.0.width;
        y -= vbox.0.y * h / vbox.0.height;

        caffine = cairo::Matrix::new(w / vbox.0.width, 0.0, 0.0, h / vbox.0.height, x, y);

        draw_ctx.push_view_box(vbox.0.width, vbox.0.height)
    } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
        // If coords are in terms of the bounding box, use them
        let bbrect = bbox.rect.unwrap();

        caffine = cairo::Matrix::identity();
        caffine.scale(bbrect.width, bbrect.height);

        draw_ctx.push_view_box(1.0, 1.0)
    } else {
        caffine = cairo::Matrix::identity();
        draw_ctx.get_view_params()
    };

    if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
        let mut scalematrix = cairo::Matrix::identity();
        scalematrix.scale(scwscale, schscale);
        caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

        scalematrix = cairo::Matrix::identity();
        scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

        affine = cairo::Matrix::multiply(&scalematrix, &affine);
    }

    // Draw to another surface

    let cr_save = draw_ctx.get_cairo_context();

    let surface = cr_save
        .get_target()
        .create_similar(cairo::Content::ColorAlpha, pw, ph);

    let cr_pattern = cairo::Context::new(&surface);

    draw_ctx.set_cairo_context(&cr_pattern);

    // Set up transformations to be determined by the contents units

    // Draw everything
    let pattern_node = pattern.node.clone().unwrap().upgrade().unwrap();
    let pattern_cascaded = pattern_node.get_cascaded_values();
    let pattern_values = pattern_cascaded.get();

    cr_pattern.set_matrix(caffine);

    let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
        pattern_node.draw_children(&pattern_cascaded, dc, false)
    });

    // Return to the original coordinate system and rendering context

    draw_ctx.set_cairo_context(&cr_save);

    // Set the final surface as a Cairo pattern into the Cairo context

    let surface_pattern = cairo::SurfacePattern::create(&surface);
    surface_pattern.set_extend(cairo::Extend::Repeat);

    let mut matrix = affine;
    matrix.invert();

    surface_pattern.set_matrix(matrix);
    surface_pattern.set_filter(cairo::Filter::Best);

    cr_save.set_source(&surface_pattern);

    res.and_then(|_| Ok(true))
}

fn resolve_fallbacks_and_set_pattern(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    let resolved = resolve_pattern(pattern, draw_ctx);

    set_pattern_on_draw_context(&resolved, values, draw_ctx, bbox)
}

pub fn pattern_resolve_fallbacks_and_set_pattern(
    node: &RsvgNode,
    draw_ctx: &mut DrawingCtx,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(node.get_type() == NodeType::Pattern);

    node.with_impl(|node_pattern: &NodePattern| {
        let pattern = &*node_pattern.pattern.borrow();
        let cascaded = node.get_cascaded_values();
        let values = cascaded.get();

        resolve_fallbacks_and_set_pattern(pattern, values, draw_ctx, bbox)
    })
}

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

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = Pattern::unresolved();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<commit_msg>Remove unused variable<commit_after>use cairo;
use cairo::MatrixTrait;
use cairo::Pattern as CairoPattern;
use std::cell::RefCell;
use std::f64;
use std::rc::*;

use aspect_ratio::*;
use attributes::Attribute;
use bbox::*;
use coord_units::CoordUnits;
use drawing_ctx::DrawingCtx;
use error::RenderingError;
use float_eq_cairo::ApproxEqCairo;
use handle::RsvgHandle;
use length::*;
use node::*;
use parsers::{parse, parse_and_validate};
use property_bag::PropertyBag;
use state::ComputedValues;
use viewbox::*;

coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox);
coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse);

#[derive(Clone)]
struct Pattern {
    pub units: Option<PatternUnits>,
    pub content_units: Option<PatternContentUnits>,
    // This Option<Option<ViewBox>> is a bit strange.  We want a field
    // with value None to mean, "this field isn't resolved yet".  However,
    // the vbox can very well be *not* specified in the SVG file.
    // In that case, the fully resolved pattern will have a .vbox=Some(None) value.
    pub vbox: Option<Option<ViewBox>>,
    pub preserve_aspect_ratio: Option<AspectRatio>,
    pub affine: Option<cairo::Matrix>,
    pub fallback: Option<String>,
    pub x: Option<Length>,
    pub y: Option<Length>,
    pub width: Option<Length>,
    pub height: Option<Length>,

    // Point back to our corresponding node, or to the fallback node which has children.
    // If the value is None, it means we are fully resolved and didn't find any children
    // among the fallbacks.
    pub node: Option<Weak<Node>>,
}

impl Default for Pattern {
    fn default() -> Pattern {
        // These are per the spec

        Pattern {
            units: Some(PatternUnits::default()),
            content_units: Some(PatternContentUnits::default()),
            vbox: Some(None),
            preserve_aspect_ratio: Some(AspectRatio::default()),
            affine: Some(cairo::Matrix::identity()),
            fallback: None,
            x: Some(Length::default()),
            y: Some(Length::default()),
            width: Some(Length::default()),
            height: Some(Length::default()),
            node: None,
        }
    }
}

// All of the Pattern's fields are Option<foo> values, because
// those fields can be omitted in the SVG file.  We need to resolve
// them to default values, or to fallback values that come from
// another Pattern.
//
// For the fallback case, this would need something like
//
//    if self.foo.is_none () { self.foo = fallback.foo; }
//
// And for the default case, it would be like
//    if self.foo.is_none () { self.foo = Some (default_value); }
//
// Both can be replaced by
//
//    self.foo = self.foo.take ().or (bar);
//
// So we define a macro for that.
macro_rules! fallback_to (
    ($dest:expr, $default:expr) => (
        $dest = $dest.take ().or ($default)
    );
);

impl Pattern {
    fn unresolved() -> Pattern {
        Pattern {
            units: None,
            content_units: None,
            vbox: None,
            preserve_aspect_ratio: None,
            affine: None,
            fallback: None,
            x: None,
            y: None,
            width: None,
            height: None,
            node: None,
        }
    }

    fn is_resolved(&self) -> bool {
        self.units.is_some()
            && self.content_units.is_some()
            && self.vbox.is_some()
            && self.preserve_aspect_ratio.is_some()
            && self.affine.is_some()
            && self.x.is_some()
            && self.y.is_some()
            && self.width.is_some()
            && self.height.is_some()
            && self.children_are_resolved()
    }

    fn children_are_resolved(&self) -> bool {
        if let Some(ref weak) = self.node {
            let strong_node = &weak.clone().upgrade().unwrap();
            strong_node.has_children()
        } else {
            // We are an empty pattern; there is nothing further that
            // can be resolved for children.
            true
        }
    }

    fn resolve_from_defaults(&mut self) {
        self.resolve_from_fallback(&Pattern::default());
    }

    fn resolve_from_fallback(&mut self, fallback: &Pattern) {
        fallback_to!(self.units, fallback.units);
        fallback_to!(self.content_units, fallback.content_units);
        fallback_to!(self.vbox, fallback.vbox);
        fallback_to!(self.preserve_aspect_ratio, fallback.preserve_aspect_ratio);
        fallback_to!(self.affine, fallback.affine);
        fallback_to!(self.x, fallback.x);
        fallback_to!(self.y, fallback.y);
        fallback_to!(self.width, fallback.width);
        fallback_to!(self.height, fallback.height);

        self.fallback = fallback.fallback.clone();

        if !self.children_are_resolved() {
            if fallback.node.is_some() {
                self.node = fallback.node.clone();
            } else {
                self.node = None;
            }
        }
    }
}

pub struct NodePattern {
    pattern: RefCell<Pattern>,
}

impl NodePattern {
    pub fn new() -> NodePattern {
        NodePattern {
            pattern: RefCell::new(Pattern::unresolved()),
        }
    }
}

impl NodeTrait for NodePattern {
    fn set_atts(&self, node: &RsvgNode, _: *const RsvgHandle, pbag: &PropertyBag) -> NodeResult {
        // pattern element has overflow:hidden
        // https://www.w3.org/TR/SVG/styling.html#UAStyleSheet
        node.set_overflow_hidden();

        let mut p = self.pattern.borrow_mut();

        p.node = Some(Rc::downgrade(node));

        for (_key, attr, value) in pbag.iter() {
            match attr {
                Attribute::PatternUnits => p.units = Some(parse("patternUnits", value, ())?),

                Attribute::PatternContentUnits => {
                    p.content_units = Some(parse("patternContentUnits", value, ())?)
                }

                Attribute::ViewBox => p.vbox = Some(Some(parse("viewBox", value, ())?)),

                Attribute::PreserveAspectRatio => {
                    p.preserve_aspect_ratio = Some(parse("preserveAspectRatio", value, ())?)
                }

                Attribute::PatternTransform => {
                    p.affine = Some(parse("patternTransform", value, ())?)
                }

                Attribute::XlinkHref => p.fallback = Some(value.to_owned()),

                Attribute::X => p.x = Some(parse("x", value, LengthDir::Horizontal)?),

                Attribute::Y => p.y = Some(parse("y", value, LengthDir::Vertical)?),

                Attribute::Width => {
                    p.width = Some(parse_and_validate(
                        "width",
                        value,
                        LengthDir::Horizontal,
                        Length::check_nonnegative,
                    )?)
                }

                Attribute::Height => {
                    p.height = Some(parse_and_validate(
                        "height",
                        value,
                        LengthDir::Vertical,
                        Length::check_nonnegative,
                    )?)
                }

                _ => (),
            }
        }

        Ok(())
    }
}

fn resolve_pattern(pattern: &Pattern, draw_ctx: &mut DrawingCtx) -> Pattern {
    let mut result = pattern.clone();

    while !result.is_resolved() {
        if let Some(acquired) = draw_ctx.get_acquired_node_of_type(
            result.fallback.as_ref().map(String::as_ref),
            NodeType::Pattern,
        ) {
            acquired
                .get()
                .with_impl(|i: &NodePattern| result.resolve_from_fallback(&*i.pattern.borrow()));
        } else {
            result.resolve_from_defaults();
        }
    }

    result
}

fn set_pattern_on_draw_context(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(pattern.is_resolved());

    if pattern.node.is_none() {
        // This means we didn't find any children among the fallbacks,
        // so there is nothing to render.
        return Ok(false);
    }

    let units = pattern.units.unwrap();
    let content_units = pattern.content_units.unwrap();
    let pattern_affine = pattern.affine.unwrap();
    let vbox = pattern.vbox.unwrap();
    let preserve_aspect_ratio = pattern.preserve_aspect_ratio.unwrap();

    let (pattern_x, pattern_y, pattern_width, pattern_height) = {
        let params = if units == PatternUnits(CoordUnits::ObjectBoundingBox) {
            draw_ctx.push_view_box(1.0, 1.0)
        } else {
            draw_ctx.get_view_params()
        };

        let pattern_x = pattern.x.unwrap().normalize(values, &params);
        let pattern_y = pattern.y.unwrap().normalize(values, &params);
        let pattern_width = pattern.width.unwrap().normalize(values, &params);
        let pattern_height = pattern.height.unwrap().normalize(values, &params);

        (pattern_x, pattern_y, pattern_width, pattern_height)
    };

    // Work out the size of the rectangle so it takes into account the object bounding box

    let bbwscale: f64;
    let bbhscale: f64;

    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            bbwscale = bbrect.width;
            bbhscale = bbrect.height;
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            bbwscale = 1.0;
            bbhscale = 1.0;
        }
    }

    let cr = draw_ctx.get_cairo_context();
    let affine = cr.get_matrix();
    let taffine = cairo::Matrix::multiply(&pattern_affine, &affine);

    let mut scwscale = (taffine.xx * taffine.xx + taffine.xy * taffine.xy).sqrt();
    let mut schscale = (taffine.yx * taffine.yx + taffine.yy * taffine.yy).sqrt();

    let pw: i32 = (pattern_width * bbwscale * scwscale) as i32;
    let ph: i32 = (pattern_height * bbhscale * schscale) as i32;

    let scaled_width = pattern_width * bbwscale;
    let scaled_height = pattern_height * bbhscale;

    if scaled_width.abs() < f64::EPSILON || scaled_height.abs() < f64::EPSILON || pw < 1 || ph < 1 {
        return Ok(false);
    }

    scwscale = f64::from(pw) / scaled_width;
    schscale = f64::from(ph) / scaled_height;

    let mut affine: cairo::Matrix = cairo::Matrix::identity();

    // Create the pattern coordinate system
    match units {
        PatternUnits(CoordUnits::ObjectBoundingBox) => {
            let bbrect = bbox.rect.unwrap();
            affine.translate(
                bbrect.x + pattern_x * bbrect.width,
                bbrect.y + pattern_y * bbrect.height,
            );
        }

        PatternUnits(CoordUnits::UserSpaceOnUse) => {
            affine.translate(pattern_x, pattern_y);
        }
    }

    // Apply the pattern transform
    affine = cairo::Matrix::multiply(&affine, &pattern_affine);

    let mut caffine: cairo::Matrix;

    // Create the pattern contents coordinate system
    let _params = if let Some(vbox) = vbox {
        // If there is a vbox, use that
        let (mut x, mut y, w, h) = preserve_aspect_ratio.compute(
            vbox.0.width,
            vbox.0.height,
            0.0,
            0.0,
            pattern_width * bbwscale,
            pattern_height * bbhscale,
        );

        x -= vbox.0.x * w / vbox.0.width;
        y -= vbox.0.y * h / vbox.0.height;

        caffine = cairo::Matrix::new(w / vbox.0.width, 0.0, 0.0, h / vbox.0.height, x, y);

        draw_ctx.push_view_box(vbox.0.width, vbox.0.height)
    } else if content_units == PatternContentUnits(CoordUnits::ObjectBoundingBox) {
        // If coords are in terms of the bounding box, use them
        let bbrect = bbox.rect.unwrap();

        caffine = cairo::Matrix::identity();
        caffine.scale(bbrect.width, bbrect.height);

        draw_ctx.push_view_box(1.0, 1.0)
    } else {
        caffine = cairo::Matrix::identity();
        draw_ctx.get_view_params()
    };

    if !scwscale.approx_eq_cairo(&1.0) || !schscale.approx_eq_cairo(&1.0) {
        let mut scalematrix = cairo::Matrix::identity();
        scalematrix.scale(scwscale, schscale);
        caffine = cairo::Matrix::multiply(&caffine, &scalematrix);

        scalematrix = cairo::Matrix::identity();
        scalematrix.scale(1.0 / scwscale, 1.0 / schscale);

        affine = cairo::Matrix::multiply(&scalematrix, &affine);
    }

    // Draw to another surface

    let cr_save = draw_ctx.get_cairo_context();

    let surface = cr_save
        .get_target()
        .create_similar(cairo::Content::ColorAlpha, pw, ph);

    let cr_pattern = cairo::Context::new(&surface);

    draw_ctx.set_cairo_context(&cr_pattern);

    // Set up transformations to be determined by the contents units

    // Draw everything
    let pattern_node = pattern.node.clone().unwrap().upgrade().unwrap();
    let pattern_cascaded = pattern_node.get_cascaded_values();
    let pattern_values = pattern_cascaded.get();

    cr_pattern.set_matrix(caffine);

    let res = draw_ctx.with_discrete_layer(&pattern_node, pattern_values, false, &mut |dc| {
        pattern_node.draw_children(&pattern_cascaded, dc, false)
    });

    // Return to the original coordinate system and rendering context

    draw_ctx.set_cairo_context(&cr_save);

    // Set the final surface as a Cairo pattern into the Cairo context

    let surface_pattern = cairo::SurfacePattern::create(&surface);
    surface_pattern.set_extend(cairo::Extend::Repeat);

    let mut matrix = affine;
    matrix.invert();

    surface_pattern.set_matrix(matrix);
    surface_pattern.set_filter(cairo::Filter::Best);

    cr_save.set_source(&surface_pattern);

    res.and_then(|_| Ok(true))
}

fn resolve_fallbacks_and_set_pattern(
    pattern: &Pattern,
    values: &ComputedValues,
    draw_ctx: &mut DrawingCtx,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    let resolved = resolve_pattern(pattern, draw_ctx);

    set_pattern_on_draw_context(&resolved, values, draw_ctx, bbox)
}

pub fn pattern_resolve_fallbacks_and_set_pattern(
    node: &RsvgNode,
    draw_ctx: &mut DrawingCtx,
    bbox: &BoundingBox,
) -> Result<bool, RenderingError> {
    assert!(node.get_type() == NodeType::Pattern);

    node.with_impl(|node_pattern: &NodePattern| {
        let pattern = &*node_pattern.pattern.borrow();
        let cascaded = node.get_cascaded_values();
        let values = cascaded.get();

        resolve_fallbacks_and_set_pattern(pattern, values, draw_ctx, bbox)
    })
}

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

    #[test]
    fn pattern_resolved_from_defaults_is_really_resolved() {
        let mut pat = Pattern::unresolved();

        pat.resolve_from_defaults();
        assert!(pat.is_resolved());
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate sputnikvm;
extern crate serde_json;
extern crate gethrpc;

use serde_json::{Value};
use std::process;
use std::fs::File;
use std::path::Path;
use std::io::{BufReader};
use std::str::FromStr;
use std::collections::{HashMap, HashSet};

use sputnikvm::{Gas, Address, U256, M256, read_hex};
use sputnikvm::vm::{BlockHeader, Context, SeqTransactionVM, Transaction, VM, Log, Patch,
                    AccountCommitment, Account, FRONTIER_PATCH, HOMESTEAD_PATCH};
use sputnikvm::vm::errors::RequireError;
use gethrpc::{GethRPCClient, NormalGethRPCClient, RecordGethRPCClient, CachedGethRPCClient, RPCCall, RPCBlock, RPCTransaction, RPCLog};

fn from_rpc_block(block: &RPCBlock) -> BlockHeader {
    BlockHeader {
        coinbase: Address::from_str(&block.miner).unwrap(),
        timestamp: M256::from_str(&block.timestamp).unwrap(),
        number: M256::from_str(&block.number).unwrap(),
        difficulty: M256::from_str(&block.difficulty).unwrap(),
        gas_limit: Gas::from_str(&block.gasLimit).unwrap(),
    }
}

fn from_rpc_transaction(transaction: &RPCTransaction) -> Transaction {
    if transaction.to.is_empty() {
        Transaction::ContractCreation {
            caller: Address::from_str(&transaction.from).unwrap(),
            value: U256::from_str(&transaction.value).unwrap(),
            gas_limit: Gas::from_str(&transaction.gas).unwrap(),
            gas_price: Gas::from_str(&transaction.gasPrice).unwrap(),
            init: read_hex(&transaction.input).unwrap(),
        }
    } else {
        Transaction::MessageCall {
            caller: Address::from_str(&transaction.from).unwrap(),
            address: Address::from_str(&transaction.to).unwrap(),
            value: U256::from_str(&transaction.value).unwrap(),
            gas_limit: Gas::from_str(&transaction.gas).unwrap(),
            gas_price: Gas::from_str(&transaction.gasPrice).unwrap(),
            data: read_hex(&transaction.input).unwrap(),
        }
    }
}

fn from_rpc_log(log: &RPCLog) -> Log {
    let mut topics: Vec<M256> = Vec::new();
    for topic in &log.topics {
        topics.push(M256::from_str(&topic).unwrap());
    }
    Log {
        address: Address::from_str(&log.address).unwrap(),
        data: read_hex(&log.data).unwrap(),
        topics: topics,
    }
}

fn handle_fire<T: GethRPCClient>(client: &mut T, vm: &mut SeqTransactionVM, last_block_id: usize) {
    let last_block_number = format!("0x{:x}", last_block_id);
    loop {
        match vm.fire() {
            Ok(()) => {
                println!("VM exited with {:?}.", vm.status());
                break;
            },
            Err(RequireError::Account(address)) => {
                println!("Feeding VM account at 0x{:x} ...", address);
                let nonce = M256::from_str(&client.get_transaction_count(&format!("0x{:x}", address),
                                                                         &last_block_number)).unwrap();
                let balance = U256::from_str(&client.get_balance(&format!("0x{:x}", address),
                                                                 &last_block_number)).unwrap();
                let code = read_hex(&client.get_code(&format!("0x{:x}", address),
                                                     &last_block_number)).unwrap();
                if !client.account_exist(&format!("0x{:x}", address), last_block_id) {
                    vm.commit_account(AccountCommitment::Nonexist(address)).unwrap();
                } else {
                    vm.commit_account(AccountCommitment::Full {
                        nonce: nonce,
                        address: address,
                        balance: balance,
                        code: code,
                    }).unwrap();
                }
            },
            Err(RequireError::AccountStorage(address, index)) => {
                println!("Feeding VM account storage at 0x{:x} with index 0x{:x} ...", address, index);
                let value = M256::from_str(&client.get_storage_at(&format!("0x{:x}", address),
                                                                  &format!("0x{:x}", index),
                                                                  &last_block_number)).unwrap();
                vm.commit_account(AccountCommitment::Storage {
                    address: address,
                    index: index,
                    value: value,
                }).unwrap();
            },
            Err(RequireError::AccountCode(address)) => {
                println!("Feeding VM account code at 0x{:x} ...", address);
                let code = read_hex(&client.get_code(&format!("0x{:x}", address),
                                                     &last_block_number)).unwrap();
                vm.commit_account(AccountCommitment::Code {
                    address: address,
                    code: code,
                }).unwrap();
            },
            Err(RequireError::Blockhash(number)) => {
                println!("Feeding blockhash with number 0x{:x} ...", number);
                let hash = M256::from_str(&client.get_block_by_number(&format!("0x{:x}", number))
                                          .hash).unwrap();
                vm.commit_blockhash(number, hash);
            },
        }
    }
}

fn is_miner_or_uncle<T: GethRPCClient>(client: &mut T, address: Address, block: &RPCBlock) -> bool {
    // Give up balance testing if the address is a miner or an uncle.

    let miner = Address::from_str(&block.miner).unwrap();
    if miner == address {
        return true;
    }
    if block.uncles.len() > 0 {
        for i in 0..block.uncles.len() {
            let uncle = client.get_uncle_by_block_number_and_index(&block.number,
                                                                   &format!("0x{:x}", i));
            let uncle_miner = Address::from_str(&uncle.miner).unwrap();
            if uncle_miner == address {
                return true;
            }
        }
    }

    return false;
}

fn test_block<T: GethRPCClient>(client: &mut T, number: usize, patch: &'static Patch) {
    let block = client.get_block_by_number(format!("0x{:x}", number).as_str());
    println!("block {} ({}), transaction count: {}", number, block.number, block.transactions.len());
    let last_id = number - 1;
    let last_number = format!("0x{:x}", last_id);
    let cur_number = block.number.to_string();
    let block_header = from_rpc_block(&block);

    let mut last_vm: Option<SeqTransactionVM> = None;
    for transaction_hash in &block.transactions {
        println!("\nworking on transaction {}", transaction_hash);
        let transaction = from_rpc_transaction(&client.get_transaction_by_hash(&transaction_hash));
        let receipt = client.get_transaction_receipt(&transaction_hash);

        let mut vm = if last_vm.is_none() {
            SeqTransactionVM::new(transaction, block_header.clone(), patch)
        } else {
            SeqTransactionVM::with_previous(transaction, block_header.clone(), patch, last_vm.as_ref().unwrap())
        };

        handle_fire(client, &mut vm, last_id);

        assert!(Gas::from_str(&receipt.gasUsed).unwrap() == vm.real_used_gas());
        assert!(receipt.logs.len() == vm.logs().len());
        for i in 0..receipt.logs.len() {
            assert!(from_rpc_log(&receipt.logs[i]) == vm.logs()[i]);
        }

        last_vm = Some(vm);
    }

    if last_vm.is_some() {
        for account in last_vm.as_ref().unwrap().accounts() {
            match account {
                &Account::Full {
                    address,
                    balance,
                    ref changing_storage,
                    ..
                } => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let expected_balance = client.get_balance(&format!("0x{:x}", address),
                                                                  &cur_number);
                        println!("address: 0x{:x}, expected balance: {}, actual: 0x{:x}",
                                 address, expected_balance, balance);
                        assert!(U256::from_str(&expected_balance).unwrap() == balance);
                    }
                    let changing_storage: HashMap<M256, M256> = changing_storage.clone().into();
                    for (key, value) in changing_storage {
                        let expected_value = client.get_storage_at(&format!("0x{:x}", address),
                                                                   &format!("0x{:x}", key),
                                                                   &cur_number);
                        println!("address: 0x{:x}, index: 0x{:x}, expected: {}, actual: 0x{:x}",
                                 address, key, expected_value, value);
                        assert!(M256::from_str(&expected_value).unwrap() == value);
                    }
                },
                &Account::Create {
                    address,
                    balance,
                    ref storage,
                    ..
                } => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let expected_balance = client.get_balance(&format!("0x{:x}", address),
                                                                  &cur_number);
                        assert!(U256::from_str(&expected_balance).unwrap() == balance);
                    }
                    let storage: HashMap<M256, M256> = storage.clone().into();
                    for (key, value) in storage {
                        let expected_value = client.get_storage_at(&format!("0x{:x}", address),
                                                                   &format!("0x{:x}", key),
                                                                   &cur_number);
                        assert!(M256::from_str(&expected_value).unwrap() == value);
                    }
                },
                &Account::IncreaseBalance(address, balance) => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let last_balance = client.get_balance(&format!("0x{:x}", address),
                                                              &last_number);
                        let cur_balance = client.get_balance(&format!("0x{:x}", address),
                                                             &cur_number);

                        assert!(U256::from_str(&last_balance).unwrap() + balance ==
                                U256::from_str(&cur_balance).unwrap());
                    }
                },
                &Account::DecreaseBalance(address, balance) => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let last_balance = client.get_balance(&format!("0x{:x}", address),
                                                              &last_number);
                        let cur_balance = client.get_balance(&format!("0x{:x}", address),
                                                             &cur_number);

                        assert!(U256::from_str(&last_balance).unwrap() - balance ==
                                U256::from_str(&cur_balance).unwrap());
                    }
                },
            }
        }
    }
}

fn test_blocks<T: GethRPCClient>(client: &mut T, number: &str, patch: &'static Patch) {
    if number.contains(".json") {
        let file = File::open(number).unwrap();
        let numbers: Vec<usize> = serde_json::from_reader(file).unwrap();
        for n in numbers {
            test_block(client, n, patch);
        }
    } else if number.contains("..") {
        let number: Vec<&str> = number.split("..").collect();
        let from = usize::from_str_radix(&number[0], 10).unwrap();
        let to = usize::from_str_radix(&number[1], 10).unwrap();
        for n in from..to {
            test_block(client, n, patch);
        }
    } else if number.contains(",") {
        let numbers: Vec<&str> = number.split("..").collect();
        for number in numbers {
            let n = usize::from_str_radix(number, 10).unwrap();
            test_block(client, n, patch);
        }
    } else {
        let number = usize::from_str_radix(&number, 10).unwrap();
        test_block(client, number, patch);
    }
}

fn main() {
    let matches = clap_app!(regtests =>
        (version: "0.1")
        (author: "Ethereum Classic Contributors")
        (about: "Performs an regression test on the entire Ethereum Classic blockchain.\n\nSteps to reproduce:\n* Install Ethereum Classic Geth: `$ go install github.com/ethereumproject/go-ethereum/cmd/geth`.\n* Run Geth with this command: `$ ~/go/bin/geth --rpc --rpcaddr 127.0.0.1 --rpcport 8545`.\n* Wait for the chain to sync.\n* Change directory into the `regtests` directory `$ cd regtests`\n* Run this command: `$ RUST_BACKTRACE=1 cargo run --bin regtests -- -r http://127.0.0.1:8545")
        (@arg RPC: -r --rpc +takes_value +required "Domain of Ethereum Classic Geth's RPC endpoint. e.g. `-r http://127.0.0.1:8545`.")
        (@arg NUMBER: -n --number +takes_value +required "Block number to run this test. Radix is 10. e.g. `-n 49439`.")
        (@arg RECORD: --record +takes_value "Record to file path.")
        (@arg PATCH: -p --patch +takes_value "Patch to be used, homestead or frontier.")
    ).get_matches();

    let address = matches.value_of("RPC").unwrap();
    let number = matches.value_of("NUMBER").unwrap();
    let record = matches.value_of("RECORD");
    let patch = match matches.value_of("PATCH") {
        Some("frontier") => &FRONTIER_PATCH,
        Some("homestead") => &HOMESTEAD_PATCH,
        Some(_) => panic!("Unknown patch."),
        None => &FRONTIER_PATCH,
    };

    if address.contains(".json") {
        let file = File::open(address).unwrap();
        let cached: serde_json::Value = serde_json::from_reader(file).unwrap();
        let mut client = CachedGethRPCClient::from_value(cached);
        test_blocks(&mut client, number, patch);
    } else {
        match record {
            Some(val) => {
                let mut file = File::create(val).unwrap();
                let mut client = RecordGethRPCClient::new(address);
                test_blocks(&mut client, number, patch);
                serde_json::to_writer(&mut file, &client.to_value());
            },
            None => {
                let mut client = NormalGethRPCClient::new(address);
                test_blocks(&mut client, number, patch);
            }
        }
    }
}

#[cfg(test)]
#[test]
fn test_all_previously_failed_frontier_blocks() {
    let cached: serde_json::Value = serde_json::from_str(include_str!("../../res/frontier_records.json")).unwrap();
    let numbers: Vec<usize> = serde_json::from_str(include_str!("../../res/frontier_numbers.json")).unwrap();
    let mut client = CachedGethRPCClient::from_value(cached);
    for n in numbers {
        test_block(&mut client, n, &FRONTIER_PATCH);
    }
}
<commit_msg>debug: show used gas<commit_after>#[macro_use]
extern crate clap;
extern crate sputnikvm;
extern crate serde_json;
extern crate gethrpc;

use serde_json::{Value};
use std::process;
use std::fs::File;
use std::path::Path;
use std::io::{BufReader};
use std::str::FromStr;
use std::collections::{HashMap, HashSet};

use sputnikvm::{Gas, Address, U256, M256, read_hex};
use sputnikvm::vm::{BlockHeader, Context, SeqTransactionVM, Transaction, VM, Log, Patch,
                    AccountCommitment, Account, FRONTIER_PATCH, HOMESTEAD_PATCH};
use sputnikvm::vm::errors::RequireError;
use gethrpc::{GethRPCClient, NormalGethRPCClient, RecordGethRPCClient, CachedGethRPCClient, RPCCall, RPCBlock, RPCTransaction, RPCLog};

fn from_rpc_block(block: &RPCBlock) -> BlockHeader {
    BlockHeader {
        coinbase: Address::from_str(&block.miner).unwrap(),
        timestamp: M256::from_str(&block.timestamp).unwrap(),
        number: M256::from_str(&block.number).unwrap(),
        difficulty: M256::from_str(&block.difficulty).unwrap(),
        gas_limit: Gas::from_str(&block.gasLimit).unwrap(),
    }
}

fn from_rpc_transaction(transaction: &RPCTransaction) -> Transaction {
    if transaction.to.is_empty() {
        Transaction::ContractCreation {
            caller: Address::from_str(&transaction.from).unwrap(),
            value: U256::from_str(&transaction.value).unwrap(),
            gas_limit: Gas::from_str(&transaction.gas).unwrap(),
            gas_price: Gas::from_str(&transaction.gasPrice).unwrap(),
            init: read_hex(&transaction.input).unwrap(),
        }
    } else {
        Transaction::MessageCall {
            caller: Address::from_str(&transaction.from).unwrap(),
            address: Address::from_str(&transaction.to).unwrap(),
            value: U256::from_str(&transaction.value).unwrap(),
            gas_limit: Gas::from_str(&transaction.gas).unwrap(),
            gas_price: Gas::from_str(&transaction.gasPrice).unwrap(),
            data: read_hex(&transaction.input).unwrap(),
        }
    }
}

fn from_rpc_log(log: &RPCLog) -> Log {
    let mut topics: Vec<M256> = Vec::new();
    for topic in &log.topics {
        topics.push(M256::from_str(&topic).unwrap());
    }
    Log {
        address: Address::from_str(&log.address).unwrap(),
        data: read_hex(&log.data).unwrap(),
        topics: topics,
    }
}

fn handle_fire<T: GethRPCClient>(client: &mut T, vm: &mut SeqTransactionVM, last_block_id: usize) {
    let last_block_number = format!("0x{:x}", last_block_id);
    loop {
        match vm.fire() {
            Ok(()) => {
                println!("VM exited with {:?}.", vm.status());
                break;
            },
            Err(RequireError::Account(address)) => {
                println!("Feeding VM account at 0x{:x} ...", address);
                let nonce = M256::from_str(&client.get_transaction_count(&format!("0x{:x}", address),
                                                                         &last_block_number)).unwrap();
                let balance = U256::from_str(&client.get_balance(&format!("0x{:x}", address),
                                                                 &last_block_number)).unwrap();
                let code = read_hex(&client.get_code(&format!("0x{:x}", address),
                                                     &last_block_number)).unwrap();
                if !client.account_exist(&format!("0x{:x}", address), last_block_id) {
                    vm.commit_account(AccountCommitment::Nonexist(address)).unwrap();
                } else {
                    vm.commit_account(AccountCommitment::Full {
                        nonce: nonce,
                        address: address,
                        balance: balance,
                        code: code,
                    }).unwrap();
                }
            },
            Err(RequireError::AccountStorage(address, index)) => {
                println!("Feeding VM account storage at 0x{:x} with index 0x{:x} ...", address, index);
                let value = M256::from_str(&client.get_storage_at(&format!("0x{:x}", address),
                                                                  &format!("0x{:x}", index),
                                                                  &last_block_number)).unwrap();
                vm.commit_account(AccountCommitment::Storage {
                    address: address,
                    index: index,
                    value: value,
                }).unwrap();
            },
            Err(RequireError::AccountCode(address)) => {
                println!("Feeding VM account code at 0x{:x} ...", address);
                let code = read_hex(&client.get_code(&format!("0x{:x}", address),
                                                     &last_block_number)).unwrap();
                vm.commit_account(AccountCommitment::Code {
                    address: address,
                    code: code,
                }).unwrap();
            },
            Err(RequireError::Blockhash(number)) => {
                println!("Feeding blockhash with number 0x{:x} ...", number);
                let hash = M256::from_str(&client.get_block_by_number(&format!("0x{:x}", number))
                                          .hash).unwrap();
                vm.commit_blockhash(number, hash);
            },
        }
    }
}

fn is_miner_or_uncle<T: GethRPCClient>(client: &mut T, address: Address, block: &RPCBlock) -> bool {
    // Give up balance testing if the address is a miner or an uncle.

    let miner = Address::from_str(&block.miner).unwrap();
    if miner == address {
        return true;
    }
    if block.uncles.len() > 0 {
        for i in 0..block.uncles.len() {
            let uncle = client.get_uncle_by_block_number_and_index(&block.number,
                                                                   &format!("0x{:x}", i));
            let uncle_miner = Address::from_str(&uncle.miner).unwrap();
            if uncle_miner == address {
                return true;
            }
        }
    }

    return false;
}

fn test_block<T: GethRPCClient>(client: &mut T, number: usize, patch: &'static Patch) {
    let block = client.get_block_by_number(format!("0x{:x}", number).as_str());
    println!("block {} ({}), transaction count: {}", number, block.number, block.transactions.len());
    let last_id = number - 1;
    let last_number = format!("0x{:x}", last_id);
    let cur_number = block.number.to_string();
    let block_header = from_rpc_block(&block);

    let mut last_vm: Option<SeqTransactionVM> = None;
    for transaction_hash in &block.transactions {
        println!("\nworking on transaction {}", transaction_hash);
        let transaction = from_rpc_transaction(&client.get_transaction_by_hash(&transaction_hash));
        let receipt = client.get_transaction_receipt(&transaction_hash);

        let mut vm = if last_vm.is_none() {
            SeqTransactionVM::new(transaction, block_header.clone(), patch)
        } else {
            SeqTransactionVM::with_previous(transaction, block_header.clone(), patch, last_vm.as_ref().unwrap())
        };

        handle_fire(client, &mut vm, last_id);

        println!("expected: {}, actual: 0x{:x}", receipt.gasUsed, vm.real_used_gas());
        assert!(Gas::from_str(&receipt.gasUsed).unwrap() == vm.real_used_gas());
        assert!(receipt.logs.len() == vm.logs().len());
        for i in 0..receipt.logs.len() {
            assert!(from_rpc_log(&receipt.logs[i]) == vm.logs()[i]);
        }

        last_vm = Some(vm);
    }

    if last_vm.is_some() {
        for account in last_vm.as_ref().unwrap().accounts() {
            match account {
                &Account::Full {
                    address,
                    balance,
                    ref changing_storage,
                    ..
                } => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let expected_balance = client.get_balance(&format!("0x{:x}", address),
                                                                  &cur_number);
                        println!("address: 0x{:x}, expected balance: {}, actual: 0x{:x}",
                                 address, expected_balance, balance);
                        assert!(U256::from_str(&expected_balance).unwrap() == balance);
                    }
                    let changing_storage: HashMap<M256, M256> = changing_storage.clone().into();
                    for (key, value) in changing_storage {
                        let expected_value = client.get_storage_at(&format!("0x{:x}", address),
                                                                   &format!("0x{:x}", key),
                                                                   &cur_number);
                        println!("address: 0x{:x}, index: 0x{:x}, expected: {}, actual: 0x{:x}",
                                 address, key, expected_value, value);
                        assert!(M256::from_str(&expected_value).unwrap() == value);
                    }
                },
                &Account::Create {
                    address,
                    balance,
                    ref storage,
                    ..
                } => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let expected_balance = client.get_balance(&format!("0x{:x}", address),
                                                                  &cur_number);
                        assert!(U256::from_str(&expected_balance).unwrap() == balance);
                    }
                    let storage: HashMap<M256, M256> = storage.clone().into();
                    for (key, value) in storage {
                        let expected_value = client.get_storage_at(&format!("0x{:x}", address),
                                                                   &format!("0x{:x}", key),
                                                                   &cur_number);
                        assert!(M256::from_str(&expected_value).unwrap() == value);
                    }
                },
                &Account::IncreaseBalance(address, balance) => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let last_balance = client.get_balance(&format!("0x{:x}", address),
                                                              &last_number);
                        let cur_balance = client.get_balance(&format!("0x{:x}", address),
                                                             &cur_number);

                        assert!(U256::from_str(&last_balance).unwrap() + balance ==
                                U256::from_str(&cur_balance).unwrap());
                    }
                },
                &Account::DecreaseBalance(address, balance) => {
                    if !is_miner_or_uncle(client, address, &block) {
                        let last_balance = client.get_balance(&format!("0x{:x}", address),
                                                              &last_number);
                        let cur_balance = client.get_balance(&format!("0x{:x}", address),
                                                             &cur_number);

                        assert!(U256::from_str(&last_balance).unwrap() - balance ==
                                U256::from_str(&cur_balance).unwrap());
                    }
                },
            }
        }
    }
}

fn test_blocks<T: GethRPCClient>(client: &mut T, number: &str, patch: &'static Patch) {
    if number.contains(".json") {
        let file = File::open(number).unwrap();
        let numbers: Vec<usize> = serde_json::from_reader(file).unwrap();
        for n in numbers {
            test_block(client, n, patch);
        }
    } else if number.contains("..") {
        let number: Vec<&str> = number.split("..").collect();
        let from = usize::from_str_radix(&number[0], 10).unwrap();
        let to = usize::from_str_radix(&number[1], 10).unwrap();
        for n in from..to {
            test_block(client, n, patch);
        }
    } else if number.contains(",") {
        let numbers: Vec<&str> = number.split("..").collect();
        for number in numbers {
            let n = usize::from_str_radix(number, 10).unwrap();
            test_block(client, n, patch);
        }
    } else {
        let number = usize::from_str_radix(&number, 10).unwrap();
        test_block(client, number, patch);
    }
}

fn main() {
    let matches = clap_app!(regtests =>
        (version: "0.1")
        (author: "Ethereum Classic Contributors")
        (about: "Performs an regression test on the entire Ethereum Classic blockchain.\n\nSteps to reproduce:\n* Install Ethereum Classic Geth: `$ go install github.com/ethereumproject/go-ethereum/cmd/geth`.\n* Run Geth with this command: `$ ~/go/bin/geth --rpc --rpcaddr 127.0.0.1 --rpcport 8545`.\n* Wait for the chain to sync.\n* Change directory into the `regtests` directory `$ cd regtests`\n* Run this command: `$ RUST_BACKTRACE=1 cargo run --bin regtests -- -r http://127.0.0.1:8545")
        (@arg RPC: -r --rpc +takes_value +required "Domain of Ethereum Classic Geth's RPC endpoint. e.g. `-r http://127.0.0.1:8545`.")
        (@arg NUMBER: -n --number +takes_value +required "Block number to run this test. Radix is 10. e.g. `-n 49439`.")
        (@arg RECORD: --record +takes_value "Record to file path.")
        (@arg PATCH: -p --patch +takes_value "Patch to be used, homestead or frontier.")
    ).get_matches();

    let address = matches.value_of("RPC").unwrap();
    let number = matches.value_of("NUMBER").unwrap();
    let record = matches.value_of("RECORD");
    let patch = match matches.value_of("PATCH") {
        Some("frontier") => &FRONTIER_PATCH,
        Some("homestead") => &HOMESTEAD_PATCH,
        Some(_) => panic!("Unknown patch."),
        None => &FRONTIER_PATCH,
    };

    if address.contains(".json") {
        let file = File::open(address).unwrap();
        let cached: serde_json::Value = serde_json::from_reader(file).unwrap();
        let mut client = CachedGethRPCClient::from_value(cached);
        test_blocks(&mut client, number, patch);
    } else {
        match record {
            Some(val) => {
                let mut file = File::create(val).unwrap();
                let mut client = RecordGethRPCClient::new(address);
                test_blocks(&mut client, number, patch);
                serde_json::to_writer(&mut file, &client.to_value());
            },
            None => {
                let mut client = NormalGethRPCClient::new(address);
                test_blocks(&mut client, number, patch);
            }
        }
    }
}

#[cfg(test)]
#[test]
fn test_all_previously_failed_frontier_blocks() {
    let cached: serde_json::Value = serde_json::from_str(include_str!("../../res/frontier_records.json")).unwrap();
    let numbers: Vec<usize> = serde_json::from_str(include_str!("../../res/frontier_numbers.json")).unwrap();
    let mut client = CachedGethRPCClient::from_value(cached);
    for n in numbers {
        test_block(&mut client, n, &FRONTIER_PATCH);
    }
}
<|endoftext|>


<commit_before>
use std::fs;
use std::path::{Path, PathBuf};
use std::io;
use std::char::from_u32;
use std::io::Write;
use std::process::{Command, Stdio, ExitStatus};
use std::ffi::{OsStr, OsString};
use hyper::{self, Client};
use openssl::crypto::hash::Hasher;

use rand::random;

pub fn ensure_dir_exists<P: AsRef<Path>, F: FnOnce(&Path)>(path: P, callback: F) -> io::Result<bool> {
	if !is_directory(path.as_ref()) {
		callback(path.as_ref());
		fs::create_dir_all(path.as_ref()).map(|()| true)
	} else {
		Ok(false)
	}
}

pub fn is_directory<P: AsRef<Path>>(path: P) -> bool {
	fs::metadata(path).ok().as_ref().map(fs::Metadata::is_dir) == Some(true)
}

pub fn is_file<P: AsRef<Path>>(path: P) -> bool {
	fs::metadata(path).ok().as_ref().map(fs::Metadata::is_file) == Some(true)
}

pub fn path_exists<P: AsRef<Path>>(path: P) -> bool {
	fs::metadata(path).is_ok()
}

pub fn random_string(length: usize) -> String {
	let chars = b"abcdefghijklmnopqrstuvwxyz0123456789_";
	(0..length).map(|_| from_u32(chars[random::<usize>() % chars.len()] as u32).unwrap()).collect()
}

pub fn if_not_empty<S: PartialEq<str>>(s: S) -> Option<S> {
	if s == *"" {
		None
	} else {
		Some(s)
	}
}

pub fn write_file(path: &Path, contents: &str) -> io::Result<()> {
	let mut file = try!(fs::OpenOptions::new()
		.write(true)
		.truncate(true)
		.create(true)
		.open(path));

	try!(io::Write::write_all(&mut file, contents.as_bytes()));

	try!(file.sync_data());

	Ok(())
}

pub fn read_file(path: &Path) -> io::Result<String> {
	let mut file = try!(fs::OpenOptions::new()
		.read(true)
		.open(path));

	let mut contents = String::new();

	try!(io::Read::read_to_string(&mut file, &mut contents));

	Ok(contents)
}

pub fn filter_file<F: FnMut(&str) -> bool>(src: &Path, dest: &Path, mut filter: F) -> io::Result<usize> {
	let src_file = try!(fs::File::open(src));
	let dest_file = try!(fs::File::create(dest));

	let mut reader = io::BufReader::new(src_file);
	let mut writer = io::BufWriter::new(dest_file);
	let mut removed = 0;

	for result in io::BufRead::lines(&mut reader) {
		let line = try!(result);
		if filter(&line) {
			try!(writeln!(&mut writer, "{}", &line));
		} else {
			removed += 1;
		}
	}

	try!(writer.flush());

	Ok(removed)
}

pub fn match_file<T, F: FnMut(&str) -> Option<T>>(src: &Path, mut f: F) -> io::Result<Option<T>> {
	let src_file = try!(fs::File::open(src));

	let mut reader = io::BufReader::new(src_file);

	for result in io::BufRead::lines(&mut reader) {
		let line = try!(result);
		if let Some(r) = f(&line) {
			return Ok(Some(r));
		}
	}

	Ok(None)
}

pub fn append_file(dest: &Path, line: &str) -> io::Result<()> {
	let mut dest_file = try!(fs::OpenOptions::new()
		.write(true)
		.append(true)
		.create(true)
		.open(dest)
		);

	try!(writeln!(&mut dest_file, "{}", line));

	try!(dest_file.sync_data());

	Ok(())
}

pub fn tee_file<W: io::Write>(path: &Path, mut w: &mut W) -> io::Result<()> {
	let mut file = try!(fs::OpenOptions::new()
		.read(true)
		.open(path));

	let buffer_size = 0x10000;
	let mut buffer = vec![0u8; buffer_size];

	loop {
		let bytes_read = try!(io::Read::read(&mut file, &mut buffer));

		if bytes_read != 0 {
			try!(io::Write::write_all(w, &mut buffer[0..bytes_read]));
		} else {
			return Ok(());
		}
	}
}

#[derive(Debug)]
pub enum DownloadError {
	Status(hyper::status::StatusCode),
	Network(hyper::Error),
	File(io::Error),
}
pub type DownloadResult<T> = Result<T, DownloadError>;

pub fn download_file<P: AsRef<Path>>(url: hyper::Url, path: P, mut hasher: Option<&mut Hasher>) -> DownloadResult<()> {
	let client = Client::new();

	let mut res = try!(client.get(url).send().map_err(DownloadError::Network));
	if res.status != hyper::Ok { return Err(DownloadError::Status(res.status)); }

	let buffer_size = 0x10000;
	let mut buffer = vec![0u8; buffer_size];

	let mut file = try!(fs::File::create(path).map_err(DownloadError::File));

	loop {
		let bytes_read = try!(io::Read::read(&mut res, &mut buffer)
			.map_err(hyper::Error::Io)
			.map_err(DownloadError::Network)
			);

		if bytes_read != 0 {
			if let Some(ref mut h) = hasher {
				try!(io::Write::write_all(*h, &mut buffer[0..bytes_read]).map_err(DownloadError::File));
			}
			try!(io::Write::write_all(&mut file, &mut buffer[0..bytes_read]).map_err(DownloadError::File));
		} else {
			try!(file.sync_data().map_err(DownloadError::File));
			return Ok(());
		}
	}
}

pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
	#[cfg(windows)]
	fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::windows::fs::symlink_dir(src, dest)
	}
	#[cfg(not(windows))]
	fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::unix::fs::symlink(src, dest)
	}

	let _ = remove_dir(dest);
	symlink_dir_inner(src, dest)
}

pub fn symlink_file(src: &Path, dest: &Path) -> io::Result<()> {
	#[cfg(windows)]
	fn symlink_file_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::windows::fs::symlink_file(src, dest)
	}
	#[cfg(not(windows))]
	fn symlink_file_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::unix::fs::symlink(src, dest)
	}

	let _ = fs::remove_file(dest);
	symlink_file_inner(src, dest)
}

pub fn hardlink(src: &Path, dest: &Path) -> io::Result<()> {
	let _ = fs::remove_file(dest);
	fs::hard_link(src, dest)
}

#[derive(Debug)]
pub enum CommandError {
	Io(io::Error),
	Status(ExitStatus),
}

pub type CommandResult<T> = Result<T, CommandError>;

pub fn cmd_status(cmd: &mut Command) -> CommandResult<()> {
	cmd.status().map_err(CommandError::Io).and_then(|s| {
		if s.success() {
			Ok(())
		} else {
			Err(CommandError::Status(s))
		}
	})
}

pub fn remove_dir(path: &Path) -> io::Result<()> {
	if try!(fs::symlink_metadata(path)).file_type().is_symlink() {
		if cfg!(windows) {
			fs::remove_dir(path)
		} else {
			fs::remove_file(path)
		}
	} else {
		fs::remove_dir_all(path)
	}
}

pub fn copy_dir(src: &Path, dest: &Path) -> CommandResult<()> {
	#[cfg(windows)]
	fn copy_dir_inner(src: &Path, dest: &Path) -> CommandResult<()> {
		cmd_status(Command::new("robocopy").arg(src).arg(dest).arg("/E"))
	}
	#[cfg(not(windows))]
	fn copy_dir_inner(src: &Path, dest: &Path) -> CommandResult<()> {
		cmd_status(Command::new("cp").arg("-R").arg(src).arg(dest))
	}

	let _ = remove_dir(dest);
	copy_dir_inner(src, dest)
}

pub fn prefix_arg<S: AsRef<OsStr>>(name: &str, s: S) -> OsString {
	let mut arg = OsString::from(name);
	arg.push(s);
	arg
}

pub fn open_browser(path: &Path) -> io::Result<bool> {
	#[cfg(not(windows))]
	fn has_cmd(cmd: &&str) -> bool {
		cmd_status(Command::new("which")
			.arg(cmd)
			.stdin(Stdio::null())
			.stdout(Stdio::null())
			.stderr(Stdio::null())).is_ok()
	}
	#[cfg(not(windows))]
	fn inner(path: &Path) -> io::Result<bool> {
		let commands = ["xdg-open", "open", "firefox", "chromium"];
		if let Some(cmd) = commands.iter().map(|s| *s).filter(has_cmd).next() {
			Command::new(cmd)
				.arg(path)
				.stdin(Stdio::null())
				.stdout(Stdio::null())
				.stderr(Stdio::null())
				.spawn()
				.map(|_| true)
		} else {
			Ok(false)
		}
	}
	#[cfg(windows)]
	fn inner(path: &Path) -> io::Result<bool> {
		Command::new("cmd")
			.arg("/C")
			.arg("start")
			.arg(path)
			.stdin(Stdio::null())
			.stdout(Stdio::null())
			.stderr(Stdio::null())
			.spawn()
			.map(|_| true)
	}
	inner(path)
}
pub fn home_dir() -> Option<PathBuf> {
	#[cfg(not(windows))]
	fn inner() -> Option<PathBuf> {
		::std::env::home_dir()
	}
	#[cfg(windows)]
	fn inner() -> Option<PathBuf> {
		windows::get_special_folder(&windows::FOLDERID_Profile).ok()
	}

	inner()
}

#[cfg(windows)]
pub mod windows {
	use winapi::*;
	use std::io;
	use std::path::PathBuf;
	use std::ptr;
	use std::slice;
	use std::ffi::OsString;
	use std::os::windows::ffi::OsStringExt;
	use shell32;
	use ole32;

	#[allow(non_upper_case_globals)]
	pub const FOLDERID_LocalAppData: GUID = GUID {
		Data1: 0xF1B32785,
		Data2: 0x6FBA,
		Data3: 0x4FCF,
		Data4: [0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91],
	};
	#[allow(non_upper_case_globals)]
	pub const FOLDERID_Profile: GUID = GUID {
		Data1: 0x5E6C858F,
		Data2: 0x0E22,
		Data3: 0x4760,
		Data4: [0x9A, 0xFE, 0xEA, 0x33, 0x17, 0xB6, 0x71, 0x73],
	};

	pub fn get_special_folder(id: &shtypes::KNOWNFOLDERID) -> io::Result<PathBuf> {

		let mut path = ptr::null_mut();
		let result;

		unsafe {
			let code = shell32::SHGetKnownFolderPath(id, 0, ptr::null_mut(), &mut path);
			if code == 0 {
				let mut length = 0usize;
				while *path.offset(length as isize) != 0 {
					length += 1;
				}
				let slice = slice::from_raw_parts(path, length);
				result = Ok(OsString::from_wide(slice).into());
			} else {
				result = Err(io::Error::from_raw_os_error(code));
			}
			ole32::CoTaskMemFree(path as *mut _);
		}
		result
	}
}
<commit_msg>Correctly interpret robocopy exit codes<commit_after>
use std::fs;
use std::path::{Path, PathBuf};
use std::io;
use std::char::from_u32;
use std::io::Write;
use std::process::{Command, Stdio, ExitStatus};
use std::ffi::{OsStr, OsString};
use hyper::{self, Client};
use openssl::crypto::hash::Hasher;

use rand::random;

pub fn ensure_dir_exists<P: AsRef<Path>, F: FnOnce(&Path)>(path: P, callback: F) -> io::Result<bool> {
	if !is_directory(path.as_ref()) {
		callback(path.as_ref());
		fs::create_dir_all(path.as_ref()).map(|()| true)
	} else {
		Ok(false)
	}
}

pub fn is_directory<P: AsRef<Path>>(path: P) -> bool {
	fs::metadata(path).ok().as_ref().map(fs::Metadata::is_dir) == Some(true)
}

pub fn is_file<P: AsRef<Path>>(path: P) -> bool {
	fs::metadata(path).ok().as_ref().map(fs::Metadata::is_file) == Some(true)
}

pub fn path_exists<P: AsRef<Path>>(path: P) -> bool {
	fs::metadata(path).is_ok()
}

pub fn random_string(length: usize) -> String {
	let chars = b"abcdefghijklmnopqrstuvwxyz0123456789_";
	(0..length).map(|_| from_u32(chars[random::<usize>() % chars.len()] as u32).unwrap()).collect()
}

pub fn if_not_empty<S: PartialEq<str>>(s: S) -> Option<S> {
	if s == *"" {
		None
	} else {
		Some(s)
	}
}

pub fn write_file(path: &Path, contents: &str) -> io::Result<()> {
	let mut file = try!(fs::OpenOptions::new()
		.write(true)
		.truncate(true)
		.create(true)
		.open(path));

	try!(io::Write::write_all(&mut file, contents.as_bytes()));

	try!(file.sync_data());

	Ok(())
}

pub fn read_file(path: &Path) -> io::Result<String> {
	let mut file = try!(fs::OpenOptions::new()
		.read(true)
		.open(path));

	let mut contents = String::new();

	try!(io::Read::read_to_string(&mut file, &mut contents));

	Ok(contents)
}

pub fn filter_file<F: FnMut(&str) -> bool>(src: &Path, dest: &Path, mut filter: F) -> io::Result<usize> {
	let src_file = try!(fs::File::open(src));
	let dest_file = try!(fs::File::create(dest));

	let mut reader = io::BufReader::new(src_file);
	let mut writer = io::BufWriter::new(dest_file);
	let mut removed = 0;

	for result in io::BufRead::lines(&mut reader) {
		let line = try!(result);
		if filter(&line) {
			try!(writeln!(&mut writer, "{}", &line));
		} else {
			removed += 1;
		}
	}

	try!(writer.flush());

	Ok(removed)
}

pub fn match_file<T, F: FnMut(&str) -> Option<T>>(src: &Path, mut f: F) -> io::Result<Option<T>> {
	let src_file = try!(fs::File::open(src));

	let mut reader = io::BufReader::new(src_file);

	for result in io::BufRead::lines(&mut reader) {
		let line = try!(result);
		if let Some(r) = f(&line) {
			return Ok(Some(r));
		}
	}

	Ok(None)
}

pub fn append_file(dest: &Path, line: &str) -> io::Result<()> {
	let mut dest_file = try!(fs::OpenOptions::new()
		.write(true)
		.append(true)
		.create(true)
		.open(dest)
		);

	try!(writeln!(&mut dest_file, "{}", line));

	try!(dest_file.sync_data());

	Ok(())
}

pub fn tee_file<W: io::Write>(path: &Path, mut w: &mut W) -> io::Result<()> {
	let mut file = try!(fs::OpenOptions::new()
		.read(true)
		.open(path));

	let buffer_size = 0x10000;
	let mut buffer = vec![0u8; buffer_size];

	loop {
		let bytes_read = try!(io::Read::read(&mut file, &mut buffer));

		if bytes_read != 0 {
			try!(io::Write::write_all(w, &mut buffer[0..bytes_read]));
		} else {
			return Ok(());
		}
	}
}

#[derive(Debug)]
pub enum DownloadError {
	Status(hyper::status::StatusCode),
	Network(hyper::Error),
	File(io::Error),
}
pub type DownloadResult<T> = Result<T, DownloadError>;

pub fn download_file<P: AsRef<Path>>(url: hyper::Url, path: P, mut hasher: Option<&mut Hasher>) -> DownloadResult<()> {
	let client = Client::new();

	let mut res = try!(client.get(url).send().map_err(DownloadError::Network));
	if res.status != hyper::Ok { return Err(DownloadError::Status(res.status)); }

	let buffer_size = 0x10000;
	let mut buffer = vec![0u8; buffer_size];

	let mut file = try!(fs::File::create(path).map_err(DownloadError::File));

	loop {
		let bytes_read = try!(io::Read::read(&mut res, &mut buffer)
			.map_err(hyper::Error::Io)
			.map_err(DownloadError::Network)
			);

		if bytes_read != 0 {
			if let Some(ref mut h) = hasher {
				try!(io::Write::write_all(*h, &mut buffer[0..bytes_read]).map_err(DownloadError::File));
			}
			try!(io::Write::write_all(&mut file, &mut buffer[0..bytes_read]).map_err(DownloadError::File));
		} else {
			try!(file.sync_data().map_err(DownloadError::File));
			return Ok(());
		}
	}
}

pub fn symlink_dir(src: &Path, dest: &Path) -> io::Result<()> {
	#[cfg(windows)]
	fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::windows::fs::symlink_dir(src, dest)
	}
	#[cfg(not(windows))]
	fn symlink_dir_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::unix::fs::symlink(src, dest)
	}

	let _ = remove_dir(dest);
	symlink_dir_inner(src, dest)
}

pub fn symlink_file(src: &Path, dest: &Path) -> io::Result<()> {
	#[cfg(windows)]
	fn symlink_file_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::windows::fs::symlink_file(src, dest)
	}
	#[cfg(not(windows))]
	fn symlink_file_inner(src: &Path, dest: &Path) -> io::Result<()> {
		::std::os::unix::fs::symlink(src, dest)
	}

	let _ = fs::remove_file(dest);
	symlink_file_inner(src, dest)
}

pub fn hardlink(src: &Path, dest: &Path) -> io::Result<()> {
	let _ = fs::remove_file(dest);
	fs::hard_link(src, dest)
}

#[derive(Debug)]
pub enum CommandError {
	Io(io::Error),
	Status(ExitStatus),
}

pub type CommandResult<T> = Result<T, CommandError>;

pub fn cmd_status(cmd: &mut Command) -> CommandResult<()> {
	cmd.status().map_err(CommandError::Io).and_then(|s| {
		if s.success() {
			Ok(())
		} else {
			Err(CommandError::Status(s))
		}
	})
}

pub fn remove_dir(path: &Path) -> io::Result<()> {
	if try!(fs::symlink_metadata(path)).file_type().is_symlink() {
		if cfg!(windows) {
			fs::remove_dir(path)
		} else {
			fs::remove_file(path)
		}
	} else {
		fs::remove_dir_all(path)
	}
}

pub fn copy_dir(src: &Path, dest: &Path) -> CommandResult<()> {
	#[cfg(windows)]
	fn copy_dir_inner(src: &Path, dest: &Path) -> CommandResult<()> {
		Command::new("robocopy")
			.arg(src).arg(dest).arg("/E")
			.arg("/NFL").arg("/NDL").arg("/NJH").arg("/NJS").arg("/nc").arg("/ns").arg("/np")
			.status().map_err(CommandError::Io)
			.and_then(|s| {
				match s.code() {
					// Robocopy has non-zero exit codes for successful copies...
					Some(value) if value < 8 => Ok(()),
					_ => Err(CommandError::Status(s)),
				}
			})
	}
	#[cfg(not(windows))]
	fn copy_dir_inner(src: &Path, dest: &Path) -> CommandResult<()> {
		cmd_status(Command::new("cp").arg("-R").arg(src).arg(dest))
	}

	let _ = remove_dir(dest);
	copy_dir_inner(src, dest)
}

pub fn prefix_arg<S: AsRef<OsStr>>(name: &str, s: S) -> OsString {
	let mut arg = OsString::from(name);
	arg.push(s);
	arg
}

pub fn open_browser(path: &Path) -> io::Result<bool> {
	#[cfg(not(windows))]
	fn has_cmd(cmd: &&str) -> bool {
		cmd_status(Command::new("which")
			.arg(cmd)
			.stdin(Stdio::null())
			.stdout(Stdio::null())
			.stderr(Stdio::null())).is_ok()
	}
	#[cfg(not(windows))]
	fn inner(path: &Path) -> io::Result<bool> {
		let commands = ["xdg-open", "open", "firefox", "chromium"];
		if let Some(cmd) = commands.iter().map(|s| *s).filter(has_cmd).next() {
			Command::new(cmd)
				.arg(path)
				.stdin(Stdio::null())
				.stdout(Stdio::null())
				.stderr(Stdio::null())
				.spawn()
				.map(|_| true)
		} else {
			Ok(false)
		}
	}
	#[cfg(windows)]
	fn inner(path: &Path) -> io::Result<bool> {
		Command::new("cmd")
			.arg("/C")
			.arg("start")
			.arg(path)
			.stdin(Stdio::null())
			.stdout(Stdio::null())
			.stderr(Stdio::null())
			.spawn()
			.map(|_| true)
	}
	inner(path)
}
pub fn home_dir() -> Option<PathBuf> {
	#[cfg(not(windows))]
	fn inner() -> Option<PathBuf> {
		::std::env::home_dir()
	}
	#[cfg(windows)]
	fn inner() -> Option<PathBuf> {
		windows::get_special_folder(&windows::FOLDERID_Profile).ok()
	}

	inner()
}

#[cfg(windows)]
pub mod windows {
	use winapi::*;
	use std::io;
	use std::path::PathBuf;
	use std::ptr;
	use std::slice;
	use std::ffi::OsString;
	use std::os::windows::ffi::OsStringExt;
	use shell32;
	use ole32;

	#[allow(non_upper_case_globals)]
	pub const FOLDERID_LocalAppData: GUID = GUID {
		Data1: 0xF1B32785,
		Data2: 0x6FBA,
		Data3: 0x4FCF,
		Data4: [0x9D, 0x55, 0x7B, 0x8E, 0x7F, 0x15, 0x70, 0x91],
	};
	#[allow(non_upper_case_globals)]
	pub const FOLDERID_Profile: GUID = GUID {
		Data1: 0x5E6C858F,
		Data2: 0x0E22,
		Data3: 0x4760,
		Data4: [0x9A, 0xFE, 0xEA, 0x33, 0x17, 0xB6, 0x71, 0x73],
	};

	pub fn get_special_folder(id: &shtypes::KNOWNFOLDERID) -> io::Result<PathBuf> {

		let mut path = ptr::null_mut();
		let result;

		unsafe {
			let code = shell32::SHGetKnownFolderPath(id, 0, ptr::null_mut(), &mut path);
			if code == 0 {
				let mut length = 0usize;
				while *path.offset(length as isize) != 0 {
					length += 1;
				}
				let slice = slice::from_raw_parts(path, length);
				result = Ok(OsString::from_wide(slice).into());
			} else {
				result = Err(io::Error::from_raw_os_error(code));
			}
			ole32::CoTaskMemFree(path as *mut _);
		}
		result
	}
}
<|endoftext|>