<commit_before>use std::sync::Arc;

use std::thread;
use std::sync::atomic::{AtomicUsize, Ordering};

use {Cursive, Printer};
use align::HAlign;
use theme::{ColorStyle, Effect};
use view::View;

pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>;

/// Animated bar showing a progress value.
///
/// This bar has an internal counter, and adapts the length of the displayed
/// bar to the relative position of the counter between a minimum and maximum
/// values.
///
/// It also prints a customizable text in the center of the bar, which
/// defaults to the progression percentage.
///
/// # Example
///
/// ```
/// # use cursive::prelude::*;
/// let bar = ProgressBar::new()
///                       .with_task(|ticker| {
///                           // This closure is called in parallel.
///                           for _ in 0..100 {
///                               // Here we can communicate some
///                               // advancement back to the bar.
///                               ticker(1);
///                           }
///                       });
/// ```
pub struct ProgressBar {
    min: usize,
    max: usize,
    value: Arc<AtomicUsize>,
    // TODO: use a Promise instead?
    label_maker: Box<Fn(usize, (usize, usize)) -> String>,
}

pub type Ticker = Box<Fn(usize) + Send>;

fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    let percent = 101 * (value - min) / (1 + max - min);
    format!("{} %", percent)
}

new_default!(ProgressBar);

impl ProgressBar {
    /// Creates a new progress bar.
    ///
    /// Default values:
    ///
    /// * `min`: 0
    /// * `max`: 100
    /// * `value`: 0
    pub fn new() -> Self {
        ProgressBar {
            min: 0,
            max: 100,
            value: Arc::new(AtomicUsize::new(0)),
            label_maker: Box::new(make_percentage),
        }
    }

    /// Sets the value to follow.
    ///
    /// Use this to manually control the progress to display
    /// by directly modifying the value pointed to by `value`.
    pub fn with_value(mut self, value: Arc<AtomicUsize>) -> Self {
        self.value = value;
        self
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// `f` will be given a `Ticker` to increment the bar's progress.
    ///
    /// This does not reset the value, so it can be called several times
    /// to advance the progress in multiple sessions.
    pub fn start<F: FnOnce(Ticker) + Send + 'static>(&mut self, f: F) {
        let value = self.value.clone();
        let ticker: Ticker = Box::new(move |ticks| {
            value.fetch_add(ticks, Ordering::Relaxed);
        });

        thread::spawn(move || {
            f(ticker);
        });
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// Chainable variant.
    pub fn with_task<F: FnOnce(Ticker) + Send + 'static>(mut self, task: F)
                                                         -> Self {
        self.start(task);
        self
    }

    /// Sets the label generator.
    ///
    /// The given function will be called with `(value, (min, max))`.
    /// Its output will be used as the label to print inside the progress bar.
    ///
    /// The default one shows a percentage progress:
    ///
    /// ```
    /// fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    ///     let percent = 101 * (value - min) / (1 + max - min);
    ///     format!("{} %", percent)
    /// }
    /// ```
    pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>
        (mut self, label_maker: F)
         -> Self {
        self.label_maker = Box::new(label_maker);
        self
    }

    /// Sets the minimum value.
    ///
    /// When `value` equals `min`, the bar is at the minimum level.
    pub fn min(mut self, min: usize) -> Self {
        self.min = min;
        self
    }

    /// Sets the maximum value.
    ///
    /// When `value` equals `max`, the bar is at the maximum level.
    pub fn max(mut self, max: usize) -> Self {
        self.max = max;
        self
    }

    /// Sets the `min` and `max` range for the value.
    pub fn range(self, min: usize, max: usize) -> Self {
        self.min(min).max(max)
    }

    /// Sets the current value.
    ///
    /// Value is clamped between `min` and `max`.
    pub fn set_value(&mut self, value: usize) {
        self.value.store(value, Ordering::Relaxed);
    }
}

impl View for ProgressBar {
    fn draw(&self, printer: &Printer) {
        // Now, the bar itself...
        let available = printer.size.x;

        let value = self.value.load(Ordering::Relaxed);
        let length = ((1 + available) * (value - self.min)) /
                     (1 + self.max - self.min);

        let label = (self.label_maker)(value, (self.min, self.max));
        let offset = HAlign::Center.get_offset(label.len(), printer.size.x);

        printer.with_color(ColorStyle::Highlight, |printer| {
            printer.with_effect(Effect::Reverse, |printer| {
                printer.print((offset, 0), &label);
            });
            let printer = &printer.sub_printer((0, 0), (length, 1), true);
            printer.print_hline((0, 0), length, " ");
            printer.print((offset, 0), &label);
        });
    }
}
<commit_msg>Make progressbar more robust to invalid min/max values<commit_after>use std::sync::Arc;

use std::thread;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::cmp;

use {Cursive, Printer};
use align::HAlign;
use theme::{ColorStyle, Effect};
use view::View;

pub type CbPromise = Option<Box<Fn(&mut Cursive) + Send>>;

/// Animated bar showing a progress value.
///
/// This bar has an internal counter, and adapts the length of the displayed
/// bar to the relative position of the counter between a minimum and maximum
/// values.
///
/// It also prints a customizable text in the center of the bar, which
/// defaults to the progression percentage.
///
/// # Example
///
/// ```
/// # use cursive::prelude::*;
/// let bar = ProgressBar::new()
///                       .with_task(|ticker| {
///                           // This closure is called in parallel.
///                           for _ in 0..100 {
///                               // Here we can communicate some
///                               // advancement back to the bar.
///                               ticker(1);
///                           }
///                       });
/// ```
pub struct ProgressBar {
    min: usize,
    max: usize,
    value: Arc<AtomicUsize>,
    // TODO: use a Promise instead?
    label_maker: Box<Fn(usize, (usize, usize)) -> String>,
}

pub type Ticker = Box<Fn(usize) + Send>;

fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    if value < min {
        // ?? Negative progress?
        let percent = 101 * (min - value) / (1 + max - min);
        format!("-{} %", percent)
    } else {
        let percent = 101 * (value - min) / (1 + max - min);
        format!("{} %", percent)
    }
}

new_default!(ProgressBar);

impl ProgressBar {
    /// Creates a new progress bar.
    ///
    /// Default values:
    ///
    /// * `min`: 0
    /// * `max`: 100
    /// * `value`: 0
    pub fn new() -> Self {
        ProgressBar {
            min: 0,
            max: 100,
            value: Arc::new(AtomicUsize::new(0)),
            label_maker: Box::new(make_percentage),
        }
    }

    /// Sets the value to follow.
    ///
    /// Use this to manually control the progress to display
    /// by directly modifying the value pointed to by `value`.
    pub fn with_value(mut self, value: Arc<AtomicUsize>) -> Self {
        self.value = value;
        self
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// `f` will be given a `Ticker` to increment the bar's progress.
    ///
    /// This does not reset the value, so it can be called several times
    /// to advance the progress in multiple sessions.
    pub fn start<F: FnOnce(Ticker) + Send + 'static>(&mut self, f: F) {
        let value = self.value.clone();
        let ticker: Ticker = Box::new(move |ticks| {
            value.fetch_add(ticks, Ordering::Relaxed);
        });

        thread::spawn(move || {
            f(ticker);
        });
    }

    /// Starts a function in a separate thread, and monitor the progress.
    ///
    /// Chainable variant.
    pub fn with_task<F: FnOnce(Ticker) + Send + 'static>(mut self, task: F)
                                                         -> Self {
        self.start(task);
        self
    }

    /// Sets the label generator.
    ///
    /// The given function will be called with `(value, (min, max))`.
    /// Its output will be used as the label to print inside the progress bar.
    ///
    /// The default one shows a percentage progress:
    ///
    /// ```
    /// fn make_percentage(value: usize, (min, max): (usize, usize)) -> String {
    ///     let percent = 101 * (value - min) / (1 + max - min);
    ///     format!("{} %", percent)
    /// }
    /// ```
    pub fn with_label<F: Fn(usize, (usize, usize)) -> String + 'static>
        (mut self, label_maker: F)
         -> Self {
        self.label_maker = Box::new(label_maker);
        self
    }

    /// Sets the minimum value.
    ///
    /// When `value` equals `min`, the bar is at the minimum level.
    ///
    /// If `self.min > max`, `self.min` is set to `max`.
    pub fn min(mut self, min: usize) -> Self {
        self.min = min;
        self.max = cmp::max(self.max, self.min);

        self
    }

    /// Sets the maximum value.
    ///
    /// When `value` equals `max`, the bar is at the maximum level.
    ///
    /// If `min > self.max`, `self.max` is set to `min`.
    pub fn max(mut self, max: usize) -> Self {
        self.max = max;
        self.min = cmp::min(self.min, self.max);

        self
    }

    /// Sets the `min` and `max` range for the value.
    ///
    /// If `min > max`, swap the two values.
    pub fn range(self, min: usize, max: usize) -> Self {
        if min > max {
            self.min(max).max(min)
        } else {
            self.min(min).max(max)
        }
    }

    /// Sets the current value.
    ///
    /// Value is clamped between `min` and `max`.
    pub fn set_value(&mut self, value: usize) {
        self.value.store(value, Ordering::Relaxed);
    }
}

impl View for ProgressBar {
    fn draw(&self, printer: &Printer) {
        // Now, the bar itself...
        let available = printer.size.x;

        let value = self.value.load(Ordering::Relaxed);

        // If we're under the minimum, don't draw anything.
        // If we're over the maximum, we'll try to draw more, but the printer
        // will crop us anyway, so it's not a big deal.
        let length = if value < self.min {
            0
        } else {
            ((1 + available) * (value - self.min)) / (1 + self.max - self.min)
        };

        let label = (self.label_maker)(value, (self.min, self.max));
        let offset = HAlign::Center.get_offset(label.len(), printer.size.x);

        printer.with_color(ColorStyle::Highlight, |printer| {
            printer.with_effect(Effect::Reverse, |printer| {
                printer.print((offset, 0), &label);
            });
            let printer = &printer.sub_printer((0, 0), (length, 1), true);
            printer.print_hline((0, 0), length, " ");
            printer.print((offset, 0), &label);
        });
    }
}
<|endoftext|>


<commit_before>use orbclient::Renderer;
use std::cell::{Cell, RefCell};
use std::sync::Arc;
use orbimage;

use cell::{CheckSet, CloneCell};
use widgets::Widget;
use draw::draw_box;
use event::Event;
use rect::Rect;
use point::Point;
use theme::{Selector, Theme};
use traits::{Place, Text};

struct Entry {
    pub rect: Cell<Rect>,
    pub text: CloneCell<String>,
    pub text_offset: Cell<Point>,
    hover: Cell<bool>,
    pressed: Cell<bool>,
    index: u32,
    active: Cell<bool>,
}

impl Entry {
    fn new(text: &str, index: u32) -> Arc<Self> {
        Arc::new(Entry {
            rect: Cell::new(Rect::default()),
            text: CloneCell::new(String::from(text)),
            text_offset: Cell::new(Point::default()),
            hover: Cell::new(false),
            pressed: Cell::new(false),
            index,
            active: Cell::new(false),
        })
    }
}

impl Text for Entry {
    fn text<S: Into<String>>(&self, text: S) -> &Self {
        self.text.set(text.into());
        self
    }

    fn text_offset(&self, x: i32, y: i32) -> &Self {
        self.text_offset.set(Point::new(x, y));
        self
    }
}

impl Widget for Entry {
    fn rect(&self) -> &Cell<Rect> {
        &self.rect
    }
    fn draw(&self, renderer: &mut Renderer, _focused: bool, theme: &Theme) {
        let rect = self.rect.get();
        let offset = self.text_offset.get();

        if self.hover.get() || self.active.get() {
            let mut selector = Selector::new(Some("combo-box-entry"));

            if self.active.get() {
                selector = selector.with_pseudo_class("active");
            } else {
                selector = selector.with_pseudo_class("hover");
            }

            draw_box(
                renderer,
                Rect::new(rect.x, rect.y, rect.width, rect.height),
                theme,
                &selector,
            );
        }

        let mut point = Point::new(rect.x + offset.x, rect.y + rect.height as i32 / 2 - 8);
        for c in self.text.get().chars() {
            if point.x + 8 <= rect.width as i32 - 2 * offset.x {
                let mut selector = Selector::new(Some("combo-box-entry"));

                if self.active.get() {
                    selector = selector.with_pseudo_class("active");
                }

                renderer.char(point.x, point.y, c, theme.color("color", &selector));
            }
            point.x += 8;
        }
    }
    fn event(&self, event: Event, _focused: bool, redraw: &mut bool) -> bool {
        match event {
            Event::Mouse {
                point, left_button, ..
            } => {
                let mut click = false;

                let rect = self.rect.get();
                if rect.contains(point) {
                    if self.hover.check_set(true) {
                        *redraw = true;
                    }

                    if left_button {
                        if self.pressed.check_set(true) {
                            *redraw = true;
                        }
                    } else {
                        if self.pressed.check_set(false) {
                            click = true;
                            *redraw = true;
                        }
                    }
                } else {
                    if self.hover.check_set(false) {
                        *redraw = true;
                    }

                    if !left_button {
                        if self.pressed.check_set(false) {
                            *redraw = true;
                        }
                    }
                }

                if click {
                    return true;
                }
            }
            _ => (),
        }

        _focused
    }

    fn name(&self) -> &str {
        "ComboBoxItem"
    }
}

pub struct ComboBox {
    pub rect: Cell<Rect>,
    pressed: Cell<bool>,
    activated: Cell<bool>,
    pub offset: Cell<Point>,
    selected: Cell<Option<u32>>,
    entries: RefCell<Vec<Arc<Entry>>>,
    text: CloneCell<String>,
    flyout_height: Cell<u32>,
}

impl ComboBox {
    pub fn new() -> Arc<ComboBox> {
        Arc::new(ComboBox {
            rect: Cell::new(Rect::new(0, 0, 332, 28)),
            pressed: Cell::new(false),
            activated: Cell::new(false),
            offset: Cell::new(Point::new(4, 4)),
            selected: Cell::new(None),
            entries: RefCell::new(vec![]),
            text: CloneCell::new(String::new()),
            flyout_height: Cell::new(0),
        })
    }

    pub fn selected(&self) -> i32 {
        if let Some(selected) = self.selected.get() {
            return selected as i32;
        };

        -1
    }

    pub fn push(&self, text: &str) {
        let rect = self.rect().get();
        let entry = Entry::new(text, self.entries.borrow().len() as u32);
        entry.rect.set(Rect::new(
            rect.x + 1,
            rect.y + rect.height as i32 * (self.entries.borrow().len() as i32 + 1),
            rect.width - 2,
            rect.height,
        ));
        entry.text_offset(self.offset.get().x, self.offset.get().y);
        self.flyout_height.set(self.flyout_height.get() + rect.height);

        self.entries.borrow_mut().push(entry);

        if self.entries.borrow().len() == 1 {
            self.change_selection(0);
        }
    }

    pub fn pop(&self) -> String {
        if let Some(entry) = self.entries.borrow_mut().pop() {
            self.change_selection(0);
            return entry.text.get();
        }

        String::new()
    }

    pub fn change_selection(&self, i: u32) {
        if let Some(index) = self.selected.get() {
            if let Some(entry) = self.entries.borrow().get(index as usize) {
                entry.active.set(false)
            }
        }

        self.selected.set(Some(i));

        if let Some(index) = self.selected.get() {
            if let Some(entry) = self.entries.borrow().get(index as usize) {
                entry.active.set(true);
                self.text.set(entry.text.get());
            }
        }
    }

    pub fn text_offset(&self, x: i32, y: i32) -> &Self {
        self.offset.set(Point::new(x, y));
        self
    }

    fn draw_icon(renderer: &mut Renderer, toggle_rect: &Rect, path: &str) {
        match orbimage::Image::from_path(path) {
            Ok(image) => {
                let icon_x =
                    toggle_rect.x + toggle_rect.width as i32 / 2 - image.width() as i32 / 2;
                let icon_y =
                    toggle_rect.y + toggle_rect.height as i32 / 2 - image.height() as i32 / 2;

                renderer.image(icon_x, icon_y, image.width(), image.height(), image.data());
            }
            _ => {}
        }
    }
}

impl Widget for ComboBox {
    fn rect(&self) -> &Cell<Rect> {
        &self.rect
    }

    fn draw(&self, renderer: &mut Renderer, _focused: bool, theme: &Theme) {
        let rect = self.rect.get();
        let activated = self.activated.get();
        let offset = self.offset.get();

        // draw flyout
        if activated {
            let selector = Selector::new(Some("combo-box-flyout"));

            let flyout_rect = Rect::new(
                rect.x,
                rect.y + rect.height as i32 - 2,
                rect.width,
                self.flyout_height.get() + 2,
            );
            draw_box(renderer, flyout_rect, theme, &selector);

            // draw entries
            for entry in self.entries.borrow().iter() {
                let mut point = Point::new(entry.rect.get().x, entry.rect.get().y);

                if point.y >= rect.y
                    && point.y + rect.height as i32 <= flyout_rect.y + flyout_rect.height as i32
                {
                    entry.draw(renderer, _focused, theme);
                }
            }
        }

        // draw the combobox
        let mut selector = Selector::new(Some("combo-box"));

        if activated {
            selector = selector.with_pseudo_class("active");
        }

        draw_box(renderer, rect, theme, &selector);

        // draw toggle indicator
        selector = Selector::new(Some("combo-box-toggle"));

        if activated {
            selector = selector.with_pseudo_class("active");
        }

        let toggle_size = rect.height - 2 * offset.y as u32;

        let toggle_rect = Rect::new(
            rect.x + rect.width as i32 - toggle_size as i32 - offset.y,
            rect.y + offset.y,
            toggle_size,
            toggle_size,
        );

        draw_box(renderer, toggle_rect, theme, &selector);

        // draw selected text
        let mut point = Point::new(rect.x + offset.x - 8, rect.y + rect.height as i32 / 2 - 8);
        for c in self.text.get().chars() {
            if point.x + 8 <= rect.width as i32 - toggle_rect.width as i32 - 2 * offset.x {
                renderer.char(
                    point.x + rect.x,
                    point.y,
                    c,
                    theme.color("color", &"label".into()),
                );
            }
            point.x += 8;
        }

        // draw the toggle icon
        if activated {
            ComboBox::draw_icon(renderer, &toggle_rect, "res/icon-down-white.png")
        } else {
            ComboBox::draw_icon(renderer, &toggle_rect, "res/icon-down-black.png")
        }
    }

    fn event(&self, event: Event, focused: bool, redraw: &mut bool) -> bool {
        match event {
            Event::Mouse {
                point, left_button, ..
            } => {
                let mut ignore_event = false;
                if self.activated.get() {
                    for entry in self.entries.borrow().iter() {
                        if entry.event(event, focused, redraw) {
                            ignore_event = true;

                            self.change_selection(entry.index);
                            if self.activated.check_set(false) {
                                *redraw = true;
                            }
                        }
                    }
                }

                let rect = self.rect.get();
                if rect.contains(point) {
                    if left_button {
                        self.pressed.set(!self.pressed.get());

                        if self.activated.check_set(true) {
                            *redraw = true;
                        }
                    } else {
                        if !self.pressed.get() {
                            if self.activated.check_set(false) {
                                *redraw = true;
                            }
                        }
                    }
                } else {
                    if !ignore_event {
                        if left_button {
                            self.pressed.set(false);
                        } else {
                            if !self.pressed.get() {
                                if self.activated.check_set(false) {
                                    *redraw = true;
                                }
                            }
                        }
                    }
                }
            }
            Event::UpArrow => match self.selected.get() {
                None => {
                    self.change_selection(0);
                    *redraw = true;
                }
                Some(i) => {
                    if i > 0 {
                        self.change_selection(i - 1);
                        *redraw = true;
                    }
                }
            },
            Event::DownArrow => {
                if self.activated.get() {
                    match self.selected.get() {
                        None => {
                            self.change_selection(0);
                            *redraw = true;
                        }
                        Some(i) => {
                            if i < self.entries.borrow().len() as u32 - 1 {
                                self.change_selection(i + 1);
                                *redraw = true;
                            }
                        }
                    }
                }
            }
            Event::Enter => {
                if self.activated.check_set(false) {
                    self.pressed.set(false);
                    *redraw = true;
                }
            },
            _ => {}
        }

        focused
    }

    fn name(&self) -> &str {
        "ComboBox"
    }
}

impl Place for ComboBox {}
<commit_msg>Use include_bytes! to load icons of ComboBox.<commit_after>use orbclient::Renderer;
use std::cell::{Cell, RefCell};
use std::sync::Arc;
use orbimage;

use cell::{CheckSet, CloneCell};
use widgets::{Image, Widget};
use draw::draw_box;
use event::Event;
use rect::Rect;
use point::Point;
use theme::{Selector, Theme};
use traits::{Place, Text};

static TOGGLE_ICON: &'static [u8; 703] = include_bytes!("../../res/icon-down-black.png");
static TOGGLE_ICON_ACTIVE: &'static [u8; 706] = include_bytes!("../../res/icon-down-white.png");

struct Entry {
    pub rect: Cell<Rect>,
    pub text: CloneCell<String>,
    pub text_offset: Cell<Point>,
    hover: Cell<bool>,
    pressed: Cell<bool>,
    index: u32,
    active: Cell<bool>,
}

impl Entry {
    fn new(text: &str, index: u32) -> Arc<Self> {
        Arc::new(Entry {
            rect: Cell::new(Rect::default()),
            text: CloneCell::new(String::from(text)),
            text_offset: Cell::new(Point::default()),
            hover: Cell::new(false),
            pressed: Cell::new(false),
            index,
            active: Cell::new(false),
        })
    }
}

impl Text for Entry {
    fn text<S: Into<String>>(&self, text: S) -> &Self {
        self.text.set(text.into());
        self
    }

    fn text_offset(&self, x: i32, y: i32) -> &Self {
        self.text_offset.set(Point::new(x, y));
        self
    }
}

impl Widget for Entry {
    fn rect(&self) -> &Cell<Rect> {
        &self.rect
    }
    fn draw(&self, renderer: &mut Renderer, _focused: bool, theme: &Theme) {
        let rect = self.rect.get();
        let offset = self.text_offset.get();

        if self.hover.get() || self.active.get() {
            let mut selector = Selector::new(Some("combo-box-entry"));

            if self.active.get() {
                selector = selector.with_pseudo_class("active");
            } else {
                selector = selector.with_pseudo_class("hover");
            }

            draw_box(
                renderer,
                Rect::new(rect.x, rect.y, rect.width, rect.height),
                theme,
                &selector,
            );
        }

        let mut point = Point::new(rect.x + offset.x, rect.y + rect.height as i32 / 2 - 8);
        for c in self.text.get().chars() {
            if point.x + 8 <= rect.width as i32 - 2 * offset.x {
                let mut selector = Selector::new(Some("combo-box-entry"));

                if self.active.get() {
                    selector = selector.with_pseudo_class("active");
                }

                renderer.char(point.x, point.y, c, theme.color("color", &selector));
            }
            point.x += 8;
        }
    }
    fn event(&self, event: Event, _focused: bool, redraw: &mut bool) -> bool {
        match event {
            Event::Mouse {
                point, left_button, ..
            } => {
                let mut click = false;

                let rect = self.rect.get();
                if rect.contains(point) {
                    if self.hover.check_set(true) {
                        *redraw = true;
                    }

                    if left_button {
                        if self.pressed.check_set(true) {
                            *redraw = true;
                        }
                    } else {
                        if self.pressed.check_set(false) {
                            click = true;
                            *redraw = true;
                        }
                    }
                } else {
                    if self.hover.check_set(false) {
                        *redraw = true;
                    }

                    if !left_button {
                        if self.pressed.check_set(false) {
                            *redraw = true;
                        }
                    }
                }

                if click {
                    return true;
                }
            }
            _ => (),
        }

        _focused
    }

    fn name(&self) -> &str {
        "ComboBoxEntry"
    }
}

pub struct ComboBox {
    pub rect: Cell<Rect>,
    pressed: Cell<bool>,
    activated: Cell<bool>,
    pub offset: Cell<Point>,
    selected: Cell<Option<u32>>,
    entries: RefCell<Vec<Arc<Entry>>>,
    text: CloneCell<String>,
    flyout_height: Cell<u32>,
    toggle_icon: RefCell<Option<Arc<Image>>>,
    toggle_icon_active: RefCell<Option<Arc<Image>>>,
}

impl ComboBox {
    pub fn new() -> Arc<ComboBox> {
        let toggle_icon = RefCell::new(None);
        if let Ok(icon) = orbimage::parse_png(TOGGLE_ICON) {
            *toggle_icon.borrow_mut() = Some(Image::from_image(icon))
        };

        let toggle_icon_active = RefCell::new(None);
        if let Ok(icon) = orbimage::parse_png(TOGGLE_ICON_ACTIVE) {
            *toggle_icon_active.borrow_mut() = Some(Image::from_image(icon))
        };

        Arc::new(ComboBox {
            rect: Cell::new(Rect::new(0, 0, 332, 28)),
            pressed: Cell::new(false),
            activated: Cell::new(false),
            offset: Cell::new(Point::new(4, 4)),
            selected: Cell::new(None),
            entries: RefCell::new(vec![]),
            text: CloneCell::new(String::new()),
            flyout_height: Cell::new(0),
            toggle_icon,
            toggle_icon_active,
        })
    }

    pub fn selected(&self) -> i32 {
        if let Some(selected) = self.selected.get() {
            return selected as i32;
        };

        -1
    }

    pub fn push(&self, text: &str) {
        let rect = self.rect().get();
        let entry = Entry::new(text, self.entries.borrow().len() as u32);
        entry.rect.set(Rect::new(
            rect.x + 1,
            rect.y + rect.height as i32 * (self.entries.borrow().len() as i32 + 1),
            rect.width - 2,
            rect.height,
        ));
        entry.text_offset(self.offset.get().x, self.offset.get().y);
        self.flyout_height
            .set(self.flyout_height.get() + rect.height);

        self.entries.borrow_mut().push(entry);

        if self.entries.borrow().len() == 1 {
            self.change_selection(0);
        }
    }

    pub fn pop(&self) -> String {
        if let Some(entry) = self.entries.borrow_mut().pop() {
            self.change_selection(0);
            return entry.text.get();
        }

        String::new()
    }

    pub fn change_selection(&self, i: u32) {
        if let Some(index) = self.selected.get() {
            if let Some(entry) = self.entries.borrow().get(index as usize) {
                entry.active.set(false)
            }
        }

        self.selected.set(Some(i));

        if let Some(index) = self.selected.get() {
            if let Some(entry) = self.entries.borrow().get(index as usize) {
                entry.active.set(true);
                self.text.set(entry.text.get());
            }
        }
    }

    pub fn text_offset(&self, x: i32, y: i32) -> &Self {
        self.offset.set(Point::new(x, y));
        self
    }
}

impl Widget for ComboBox {
    fn rect(&self) -> &Cell<Rect> {
        &self.rect
    }

    fn draw(&self, renderer: &mut Renderer, _focused: bool, theme: &Theme) {
        let rect = self.rect.get();
        let activated = self.activated.get();
        let offset = self.offset.get();

        // draw flyout
        if activated {
            let selector = Selector::new(Some("combo-box-flyout"));

            let flyout_rect = Rect::new(
                rect.x,
                rect.y + rect.height as i32 - 2,
                rect.width,
                self.flyout_height.get() + 2,
            );
            draw_box(renderer, flyout_rect, theme, &selector);

            // draw entries
            for entry in self.entries.borrow().iter() {
                let mut point = Point::new(entry.rect.get().x, entry.rect.get().y);

                if point.y >= rect.y
                    && point.y + rect.height as i32 <= flyout_rect.y + flyout_rect.height as i32
                {
                    entry.draw(renderer, _focused, theme);
                }
            }
        }

        // draw the combobox
        let mut selector = Selector::new(Some("combo-box"));

        if activated {
            selector = selector.with_pseudo_class("active");
        }

        draw_box(renderer, rect, theme, &selector);

        // draw toggle indicator
        selector = Selector::new(Some("combo-box-toggle"));

        if activated {
            selector = selector.with_pseudo_class("active");
        }

        let toggle_size = rect.height - 2 * offset.y as u32;

        let toggle_rect = Rect::new(
            rect.x + rect.width as i32 - toggle_size as i32 - offset.y,
            rect.y + offset.y,
            toggle_size,
            toggle_size,
        );

        draw_box(renderer, toggle_rect, theme, &selector);

        // draw the toggle icon
        if activated {
            if let Some(ref icon) = *self.toggle_icon_active.borrow() {
                icon.position(toggle_rect.x, toggle_rect.y);
                icon.draw(renderer, _focused, theme)
            }
        } else {
            if let Some(ref icon) = *self.toggle_icon.borrow() {
                icon.position(toggle_rect.x, toggle_rect.y);
                icon.draw(renderer, _focused, theme)
            }
        }

        // draw selected text
        let mut point = Point::new(rect.x + offset.x - 8, rect.y + rect.height as i32 / 2 - 8);
        for c in self.text.get().chars() {
            if point.x + 8 <= rect.width as i32 - toggle_rect.width as i32 - 2 * offset.x {
                renderer.char(
                    point.x + rect.x,
                    point.y,
                    c,
                    theme.color("color", &"label".into()),
                );
            }
            point.x += 8;
        }
    }

    fn event(&self, event: Event, focused: bool, redraw: &mut bool) -> bool {
        match event {
            Event::Mouse {
                point, left_button, ..
            } => {
                let mut ignore_event = false;
                if self.activated.get() {
                    for entry in self.entries.borrow().iter() {
                        if entry.event(event, focused, redraw) {
                            ignore_event = true;

                            self.change_selection(entry.index);
                            if self.activated.check_set(false) {
                                *redraw = true;
                            }
                        }
                    }
                }

                let rect = self.rect.get();
                if rect.contains(point) {
                    if left_button {
                        self.pressed.set(!self.pressed.get());

                        if self.activated.check_set(true) {
                            *redraw = true;
                        }
                    } else {
                        if !self.pressed.get() {
                            if self.activated.check_set(false) {
                                *redraw = true;
                            }
                        }
                    }
                } else {
                    if !ignore_event {
                        if left_button {
                            self.pressed.set(false);
                        } else {
                            if !self.pressed.get() {
                                if self.activated.check_set(false) {
                                    *redraw = true;
                                }
                            }
                        }
                    }
                }
            }
            Event::UpArrow => match self.selected.get() {
                None => {
                    self.change_selection(0);
                    *redraw = true;
                }
                Some(i) => {
                    if i > 0 {
                        self.change_selection(i - 1);
                        *redraw = true;
                    }
                }
            },
            Event::DownArrow => {
                if self.activated.get() {
                    match self.selected.get() {
                        None => {
                            self.change_selection(0);
                            *redraw = true;
                        }
                        Some(i) => {
                            if i < self.entries.borrow().len() as u32 - 1 {
                                self.change_selection(i + 1);
                                *redraw = true;
                            }
                        }
                    }
                }
            }
            Event::Enter => {
                if self.activated.check_set(false) {
                    self.pressed.set(false);
                    *redraw = true;
                }
            }
            _ => {}
        }

        focused
    }

    fn name(&self) -> &str {
        "ComboBox"
    }
}

impl Place for ComboBox {}
<|endoftext|>


<commit_before>use std::time::SystemTime;

use anyhow::Context;
use aws_sigv4::{
    http_request::{self, SignableRequest, SigningSettings},
    SigningParams,
};
use reqwest::blocking::Client;
use retry::delay::{jitter, Exponential};
use retry::OperationResult;

#[derive(Clone)]
pub struct CloudFront {
    distribution_id: String,
    access_key: String,
    secret_key: String,
}

impl CloudFront {
    pub fn from_environment() -> Option<Self> {
        let distribution_id = dotenv::var("CLOUDFRONT_DISTRIBUTION").ok()?;
        let access_key = dotenv::var("AWS_ACCESS_KEY").expect("missing AWS_ACCESS_KEY");
        let secret_key = dotenv::var("AWS_SECRET_KEY").expect("missing AWS_SECRET_KEY");
        Some(Self {
            distribution_id,
            access_key,
            secret_key,
        })
    }

    /// Invalidate a file on CloudFront
    ///
    /// `path` is the path to the file to invalidate, such as `config.json`, or `re/ge/regex`
    #[instrument(skip(self, client))]
    pub fn invalidate(&self, client: &Client, path: &str) -> anyhow::Result<()> {
        let path = path.trim_start_matches('/');
        let url = format!(
            "https://cloudfront.amazonaws.com/2020-05-31/distribution/{}/invalidation",
            self.distribution_id
        );
        trace!(?url);

        let backoff = Exponential::from_millis(1000).map(jitter).take(2);
        retry::retry_with_index(backoff, |attempt| {
            let now = chrono::offset::Utc::now().timestamp_micros();
            let body = format!(
                r#"
<?xml version="1.0" encoding="UTF-8"?>
<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
    <CallerReference>{now}</CallerReference>
    <Paths>
        <Items>
            <Path>/{path}</Path>
        </Items>
        <Quantity>1</Quantity>
    </Paths>
</InvalidationBatch>
"#
            );
            trace!(?body);

            let request = match http::Request::post(&url)
                .body(&body)
                .context("Failed to construct HTTP request")
            {
                Ok(request) => request,
                Err(error) => return OperationResult::Err(error),
            };

            trace!("Signing invalidation request");
            let request = SignableRequest::from(&request);
            let params = SigningParams::builder()
                .access_key(&self.access_key)
                .secret_key(&self.secret_key)
                .region("us-east-1") // cloudfront is a regionless service, use the default region for signing.
                .service_name("cloudfront")
                .settings(SigningSettings::default())
                .time(SystemTime::now())
                .build()
                .unwrap(); // all required fields are set

            let (mut signature_headers, _) =
                http_request::sign(request, &params).unwrap().into_parts();

            debug!(?attempt, "Sending invalidation request");
            let response = match client
                .post(&url)
                .headers(signature_headers.take_headers().unwrap_or_default())
                .body(body)
                .send()
                .context("Failed to send invalidation request")
            {
                Ok(response) => response,
                Err(error) => return OperationResult::Retry(error),
            };

            let status = response.status();

            let result = match response.error_for_status_ref() {
                Ok(_) => {
                    debug!(?status, "Invalidation request successful");
                    Ok(())
                }
                Err(error) => {
                    let headers = response.headers().clone();
                    let body = response.text();
                    debug!(?status, ?headers, ?body, "Invalidation request failed");

                    Err(error).with_context(|| format!("Failed to invalidate {path}"))
                }
            };

            match result {
                Ok(_) => OperationResult::Ok(()),
                Err(error) if status.is_server_error() => OperationResult::Retry(error),
                Err(error) => OperationResult::Err(error),
            }
        })
        .map_err(|error| error.error)
    }
}
<commit_msg>worker::cloudfront: Increase retry attempts<commit_after>use std::time::SystemTime;

use anyhow::Context;
use aws_sigv4::{
    http_request::{self, SignableRequest, SigningSettings},
    SigningParams,
};
use reqwest::blocking::Client;
use retry::delay::{jitter, Exponential};
use retry::OperationResult;

#[derive(Clone)]
pub struct CloudFront {
    distribution_id: String,
    access_key: String,
    secret_key: String,
}

impl CloudFront {
    pub fn from_environment() -> Option<Self> {
        let distribution_id = dotenv::var("CLOUDFRONT_DISTRIBUTION").ok()?;
        let access_key = dotenv::var("AWS_ACCESS_KEY").expect("missing AWS_ACCESS_KEY");
        let secret_key = dotenv::var("AWS_SECRET_KEY").expect("missing AWS_SECRET_KEY");
        Some(Self {
            distribution_id,
            access_key,
            secret_key,
        })
    }

    /// Invalidate a file on CloudFront
    ///
    /// `path` is the path to the file to invalidate, such as `config.json`, or `re/ge/regex`
    #[instrument(skip(self, client))]
    pub fn invalidate(&self, client: &Client, path: &str) -> anyhow::Result<()> {
        let path = path.trim_start_matches('/');
        let url = format!(
            "https://cloudfront.amazonaws.com/2020-05-31/distribution/{}/invalidation",
            self.distribution_id
        );
        trace!(?url);

        let attempts = 10;
        let backoff = Exponential::from_millis(500).map(jitter).take(attempts - 1);
        retry::retry_with_index(backoff, |attempt| {
            let now = chrono::offset::Utc::now().timestamp_micros();
            let body = format!(
                r#"
<?xml version="1.0" encoding="UTF-8"?>
<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/2020-05-31/">
    <CallerReference>{now}</CallerReference>
    <Paths>
        <Items>
            <Path>/{path}</Path>
        </Items>
        <Quantity>1</Quantity>
    </Paths>
</InvalidationBatch>
"#
            );
            trace!(?body);

            let request = match http::Request::post(&url)
                .body(&body)
                .context("Failed to construct HTTP request")
            {
                Ok(request) => request,
                Err(error) => return OperationResult::Err(error),
            };

            trace!("Signing invalidation request");
            let request = SignableRequest::from(&request);
            let params = SigningParams::builder()
                .access_key(&self.access_key)
                .secret_key(&self.secret_key)
                .region("us-east-1") // cloudfront is a regionless service, use the default region for signing.
                .service_name("cloudfront")
                .settings(SigningSettings::default())
                .time(SystemTime::now())
                .build()
                .unwrap(); // all required fields are set

            let (mut signature_headers, _) =
                http_request::sign(request, &params).unwrap().into_parts();

            debug!(?attempt, "Sending invalidation request");
            let response = match client
                .post(&url)
                .headers(signature_headers.take_headers().unwrap_or_default())
                .body(body)
                .send()
                .context("Failed to send invalidation request")
            {
                Ok(response) => response,
                Err(error) => return OperationResult::Retry(error),
            };

            let status = response.status();

            let result = match response.error_for_status_ref() {
                Ok(_) => {
                    debug!(?status, "Invalidation request successful");
                    Ok(())
                }
                Err(error) => {
                    let headers = response.headers().clone();
                    let body = response.text();
                    debug!(?status, ?headers, ?body, "Invalidation request failed");

                    Err(error).with_context(|| format!("Failed to invalidate {path}"))
                }
            };

            match result {
                Ok(_) => OperationResult::Ok(()),
                Err(error) if status.is_server_error() => OperationResult::Retry(error),
                Err(error) => OperationResult::Err(error),
            }
        })
        .map_err(|error| error.error)
    }
}
<|endoftext|>


<commit_before>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use chrono::Duration;
use clap::{App, Arg, ArgMatches};
use failure::{bail, Error};
use if_chain::if_chain;
use log::info;
use serde::{Deserialize, Serialize};

use crate::api::{Api, NewRelease};
use crate::config::Config;
use crate::utils::args::ArgExt;
use crate::utils::fs::TempFile;
use crate::utils::sourcemaps::{SourceMapProcessor, UploadContext};
use crate::utils::system::propagate_exit_status;
use crate::utils::xcode::{InfoPlist, MayDetach};

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Upload react-native projects in a Xcode build step.")
        .org_project_args()
        // legacy parameter
        .arg(
            Arg::with_name("verbose")
                .long("verbose")
                .short("v")
                .hidden(true),
        )
        .arg(Arg::with_name("force").long("force").short("f").help(
            "Force the script to run, even in debug configuration.{n}This rarely \
             does what you want because the default build script does not actually \
             produce any information that the sentry build tool could pick up on.",
        ))
        .arg(Arg::with_name("allow_fetch").long("allow-fetch").help(
            "Enable sourcemap fetching from the packager.{n}If this is enabled \
             the react native packager needs to run and sourcemaps are downloade \
             from it if the simulator platform is detected.",
        ))
        .arg(
            Arg::with_name("fetch_from")
                .long("fetch-from")
                .value_name("URL")
                .help(
                    "Set the URL to fetch sourcemaps from.{n}\
                     The default is http://127.0.0.1:8081/, where the react-native \
                     packager runs by default.",
                ),
        )
        .arg(
            Arg::with_name("force_foreground")
                .long("force-foreground")
                .help(
                    "Wait for the process to finish.{n}\
                     By default part of the build process will when triggered from Xcode \
                     detach and continue in the background.  When an error happens, \
                     a dialog is shown.  If this parameter is passed, Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
                ),
        )
        .arg(
            Arg::with_name("build_script")
                .value_name("BUILD_SCRIPT")
                .index(1)
                .help(
                    "Optional path to the build script.{n}\
                     This is the path to the `react-native-xcode.sh` script you want \
                     to use.  By default the bundled build script is used.",
                ),
        )
        .arg(
            Arg::with_name("args")
                .value_name("ARGS")
                .multiple(true)
                .last(true)
                .help("Optional arguments to pass to the build script."),
        )
        .arg(
            Arg::with_name("wait")
                .long("wait")
                .help("Wait for the server to fully process uploaded files."),
        )
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if !path.is_empty() {
            return path;
        }
    }
    "node".into()
}

pub fn execute<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;
    let api = Api::current();
    let should_wrap = matches.is_present("force")
        || match env::var("CONFIGURATION") {
            Ok(config) => &config != "Debug",
            Err(_) => bail!("Need to run this from Xcode"),
        };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/scripts/react-native-xcode.sh")
    }
    .canonicalize()?;

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match InfoPlist::discover_from_env()? {
        Some(plist) => plist,
        None => bail!("Could not find info.plist"),
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::create()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let url = url.trim_end_matches('/');
            bundle_file = TempFile::create()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::create()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                bail!("Error: react-native packager did not respond in time");
            }

            api.download(
                &format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                &mut bundle_file.open()?,
            )?;
            api.download(
                &format!("{}/index.ios.map?platform=ios&dev=true", url),
                &mut sourcemap_file.open()?,
            )?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env(
                    "SENTRY_RN_SOURCEMAP_REPORT",
                    report_file.path().to_str().unwrap(),
                )
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report: SourceMapReport = serde_json::from_reader(&mut f)?;
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!(
                "~/{}",
                sourcemap_path.file_name().unwrap().to_string_lossy()
            );
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new();
        processor.add(&bundle_url, &bundle_path)?;
        processor.add(&sourcemap_url, &sourcemap_path)?;
        processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let release = api.new_release(
            &org,
            &NewRelease {
                version: format!("{}-{}", plist.bundle_id(), plist.version()),
                projects: vec![project.to_string()],
                ..Default::default()
            },
        )?;

        processor.upload(&UploadContext {
            org: &org,
            project: Some(&project),
            release: &release.version,
            dist: Some(&plist.build()),
            wait: matches.is_present("wait"),
        })?;

        Ok(())
    })
}

pub fn wrap_call() -> Result<(), Error> {
    let mut args: Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && (args[1] == "bundle" || args[1] == "ram-bundle") {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().cloned();
            } else if item.starts_with("--sourcemap-output=") {
                sourcemap_path = Some(item[19..].to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().cloned();
            } else if item.starts_with("--bundle-output=") {
                bundle_path = Some(item[16..].to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let path = format!("{}.map", &bundle_path.as_ref().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path);
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(PathBuf::from);

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<commit_msg>feat: Allow overriding dist in Xcode (#627)<commit_after>//! Implements a command for uploading react-native projects.
use std::env;
use std::fs;
use std::path::PathBuf;
use std::process;

use chrono::Duration;
use clap::{App, Arg, ArgMatches};
use failure::{bail, Error};
use if_chain::if_chain;
use log::info;
use serde::{Deserialize, Serialize};

use crate::api::{Api, NewRelease};
use crate::config::Config;
use crate::utils::args::ArgExt;
use crate::utils::fs::TempFile;
use crate::utils::sourcemaps::{SourceMapProcessor, UploadContext};
use crate::utils::system::propagate_exit_status;
use crate::utils::xcode::{InfoPlist, MayDetach};

#[derive(Serialize, Deserialize, Default, Debug)]
struct SourceMapReport {
    bundle_path: Option<PathBuf>,
    sourcemap_path: Option<PathBuf>,
}

pub fn make_app<'a, 'b: 'a>(app: App<'a, 'b>) -> App<'a, 'b> {
    app.about("Upload react-native projects in a Xcode build step.")
        .org_project_args()
        // legacy parameter
        .arg(
            Arg::with_name("verbose")
                .long("verbose")
                .short("v")
                .hidden(true),
        )
        .arg(Arg::with_name("force").long("force").short("f").help(
            "Force the script to run, even in debug configuration.{n}This rarely \
             does what you want because the default build script does not actually \
             produce any information that the sentry build tool could pick up on.",
        ))
        .arg(Arg::with_name("allow_fetch").long("allow-fetch").help(
            "Enable sourcemap fetching from the packager.{n}If this is enabled \
             the react native packager needs to run and sourcemaps are downloade \
             from it if the simulator platform is detected.",
        ))
        .arg(
            Arg::with_name("fetch_from")
                .long("fetch-from")
                .value_name("URL")
                .help(
                    "Set the URL to fetch sourcemaps from.{n}\
                     The default is http://127.0.0.1:8081/, where the react-native \
                     packager runs by default.",
                ),
        )
        .arg(
            Arg::with_name("force_foreground")
                .long("force-foreground")
                .help(
                    "Wait for the process to finish.{n}\
                     By default part of the build process will when triggered from Xcode \
                     detach and continue in the background.  When an error happens, \
                     a dialog is shown.  If this parameter is passed, Xcode will wait \
                     for the process to finish before the build finishes and output \
                     will be shown in the Xcode build output.",
                ),
        )
        .arg(
            Arg::with_name("build_script")
                .value_name("BUILD_SCRIPT")
                .index(1)
                .help(
                    "Optional path to the build script.{n}\
                     This is the path to the `react-native-xcode.sh` script you want \
                     to use.  By default the bundled build script is used.",
                ),
        )
        .arg(
            Arg::with_name("dist")
                .long("dist")
                .value_name("DISTRIBUTION")
                .multiple(true)
                .number_of_values(1)
                .help("The names of the distributions to publish. Can be supplied multiple times."),
        )
        .arg(
            Arg::with_name("args")
                .value_name("ARGS")
                .multiple(true)
                .last(true)
                .help("Optional arguments to pass to the build script."),
        )
        .arg(
            Arg::with_name("wait")
                .long("wait")
                .help("Wait for the server to fully process uploaded files."),
        )
}

fn find_node() -> String {
    if let Ok(path) = env::var("NODE_BINARY") {
        if !path.is_empty() {
            return path;
        }
    }
    "node".into()
}

pub fn execute<'a>(matches: &ArgMatches<'a>) -> Result<(), Error> {
    let config = Config::current();
    let (org, project) = config.get_org_and_project(matches)?;
    let api = Api::current();
    let should_wrap = matches.is_present("force")
        || match env::var("CONFIGURATION") {
            Ok(config) => &config != "Debug",
            Err(_) => bail!("Need to run this from Xcode"),
        };
    let base = env::current_dir()?;
    let script = if let Some(path) = matches.value_of("build_script") {
        base.join(path)
    } else {
        base.join("../node_modules/react-native/scripts/react-native-xcode.sh")
    }
    .canonicalize()?;

    // if we allow fetching and we detect a simulator run, then we need to switch
    // to simulator mode.
    let fetch_url;
    if_chain! {
        if matches.is_present("allow_fetch");
        if let Ok(val) = env::var("PLATFORM_NAME");
        if val.ends_with("simulator");
        then {
            let url = matches.value_of("fetch_from").unwrap_or("http://127.0.0.1:8081/");
            info!("Fetching sourcemaps from {}", url);
            fetch_url = Some(url);
        } else {
            info!("Using react-native build script at {}", base.display());
            fetch_url = None;
        }
    }

    // in case we are in debug mode we directly dispatch to the script
    // and exit out early.
    if !should_wrap && fetch_url.is_none() {
        info!("Running in debug mode, skipping script wrapping.");
        let rv = process::Command::new(&script).spawn()?.wait()?;
        propagate_exit_status(rv);
        return Ok(());
    }

    info!("Parsing Info.plist");
    let plist = match InfoPlist::discover_from_env()? {
        Some(plist) => plist,
        None => bail!("Could not find info.plist"),
    };
    info!("Parse result from Info.plist: {:?}", &plist);
    let report_file = TempFile::create()?;
    let node = find_node();
    info!("Using node interpreter '{}'", &node);

    MayDetach::wrap("React native symbol handling", |md| {
        let bundle_path;
        let sourcemap_path;
        let bundle_url;
        let sourcemap_url;
        let bundle_file;
        let sourcemap_file;

        // If we have a fetch URL we need to fetch them from there now.  In that
        // case we do indeed fetch it right from the running packager and then
        // store it in temporary files for later consumption.
        if let Some(url) = fetch_url {
            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let url = url.trim_end_matches('/');
            bundle_file = TempFile::create()?;
            bundle_path = bundle_file.path().to_path_buf();
            bundle_url = "~/index.ios.bundle".to_string();
            sourcemap_file = TempFile::create()?;
            sourcemap_path = sourcemap_file.path().to_path_buf();
            sourcemap_url = "~/index.ios.map".to_string();

            // wait up to 10 seconds for the server to be up.
            if !api.wait_until_available(url, Duration::seconds(10))? {
                bail!("Error: react-native packager did not respond in time");
            }

            api.download(
                &format!("{}/index.ios.bundle?platform=ios&dev=true", url),
                &mut bundle_file.open()?,
            )?;
            api.download(
                &format!("{}/index.ios.map?platform=ios&dev=true", url),
                &mut sourcemap_file.open()?,
            )?;

        // This is the case where we need to hook into the release process to
        // collect sourcemaps when they are generated.
        //
        // this invokes via an indirection of sentry-cli our wrap_call() below.
        // What is happening behind the scenes is that we switch out NODE_BINARY
        // for ourselves which is what the react-native build script normally
        // invokes.  Because we export __SENTRY_RN_WRAP_XCODE_CALL=1, the main
        // sentry-cli script will invoke our wrap_call() function below.
        //
        // That will then attempt to figure out that a react-native bundle is
        // happening to the build script, parse out the arguments, add additional
        // arguments if needed and then report the parsed arguments to a temporary
        // JSON file we load back below.
        //
        // With that we we then have all the information we need to invoke the
        // upload process.
        } else {
            let rv = process::Command::new(&script)
                .env("NODE_BINARY", env::current_exe()?.to_str().unwrap())
                .env("SENTRY_RN_REAL_NODE_BINARY", &node)
                .env(
                    "SENTRY_RN_SOURCEMAP_REPORT",
                    report_file.path().to_str().unwrap(),
                )
                .env("__SENTRY_RN_WRAP_XCODE_CALL", "1")
                .spawn()?
                .wait()?;
            propagate_exit_status(rv);

            if !matches.is_present("force_foreground") {
                md.may_detach()?;
            }
            let mut f = fs::File::open(report_file.path())?;
            let report: SourceMapReport = serde_json::from_reader(&mut f)?;
            if report.bundle_path.is_none() || report.sourcemap_path.is_none() {
                println!("Warning: build produced no sourcemaps.");
                return Ok(());
            }

            bundle_path = report.bundle_path.unwrap();
            bundle_url = format!("~/{}", bundle_path.file_name().unwrap().to_string_lossy());
            sourcemap_path = report.sourcemap_path.unwrap();
            sourcemap_url = format!(
                "~/{}",
                sourcemap_path.file_name().unwrap().to_string_lossy()
            );
        }

        // now that we have all the data, we can now process and upload the
        // sourcemaps.
        println!("Processing react-native sourcemaps for Sentry upload.");
        info!("  bundle path: {}", bundle_path.display());
        info!("  sourcemap path: {}", sourcemap_path.display());

        let mut processor = SourceMapProcessor::new();
        processor.add(&bundle_url, &bundle_path)?;
        processor.add(&sourcemap_url, &sourcemap_path)?;
        processor.rewrite(&[base.parent().unwrap().to_str().unwrap()])?;
        processor.add_sourcemap_references()?;

        let release = api.new_release(
            &org,
            &NewRelease {
                version: format!("{}-{}", plist.bundle_id(), plist.version()),
                projects: vec![project.to_string()],
                ..Default::default()
            },
        )?;

        match matches.values_of("dist") {
            None => {
                processor.upload(&UploadContext {
                    org: &org,
                    project: Some(&project),
                    release: &release.version,
                    dist: Some(&plist.build()),
                    wait: matches.is_present("wait"),
                })?;
            }
            Some(dists) => {
                for dist in dists {
                    processor.upload(&UploadContext {
                        org: &org,
                        project: Some(&project),
                        release: &release.version,
                        dist: Some(dist),
                        wait: matches.is_present("wait"),
                    })?;
                }
            }
        }

        Ok(())
    })
}

pub fn wrap_call() -> Result<(), Error> {
    let mut args: Vec<_> = env::args().skip(1).collect();
    let mut bundle_path = None;
    let mut sourcemap_path = None;

    if args.len() > 1 && (args[1] == "bundle" || args[1] == "ram-bundle") {
        let mut iter = args.iter().fuse();
        while let Some(item) = iter.next() {
            if item == "--sourcemap-output" {
                sourcemap_path = iter.next().cloned();
            } else if item.starts_with("--sourcemap-output=") {
                sourcemap_path = Some(item[19..].to_string());
            } else if item == "--bundle-output" {
                bundle_path = iter.next().cloned();
            } else if item.starts_with("--bundle-output=") {
                bundle_path = Some(item[16..].to_string());
            }
        }
    }

    let mut sourcemap_report = SourceMapReport::default();

    if sourcemap_path.is_none() && bundle_path.is_some() {
        let path = format!("{}.map", &bundle_path.as_ref().unwrap());
        sourcemap_report.sourcemap_path = Some(PathBuf::from(&path));
        args.push("--sourcemap-output".into());
        args.push(path);
    } else if let Some(path) = sourcemap_path {
        sourcemap_report.sourcemap_path = Some(PathBuf::from(path));
    }

    sourcemap_report.bundle_path = bundle_path.map(PathBuf::from);

    let rv = process::Command::new(env::var("SENTRY_RN_REAL_NODE_BINARY").unwrap())
        .args(&args)
        .spawn()?
        .wait()?;
    propagate_exit_status(rv);

    let mut f = fs::File::create(env::var("SENTRY_RN_SOURCEMAP_REPORT").unwrap())?;
    serde_json::to_writer(&mut f, &sourcemap_report)?;

    Ok(())
}
<|endoftext|>


<commit_before>extern crate gcc;
extern crate bindgen;

use std::env;
use std::path::PathBuf;
use std::fs::File;
use std::time::{SystemTime, Duration};
use std::cmp;

pub fn main() {
    println!("cargo:rustc-link-search=native=../c/windows");
    println!("cargo:rustc-link-lib=static=sodium-release");

    gcc::Config::new()
        .file("../c/netcode.c")
        .include("../c")
        .include("../c/windows")
        .define("NETCODE_ENABLE_TESTS", Some("0"))
        .define("NDEBUG", Some("0"))
        .compile("libnetcode.a");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    let pub_path = out_path.join("pub_bindings.rs");
    let private_path = out_path.join("private_bindings.rs");

    //Do some basic dependecy management
    let targets = vec!(&pub_path, &private_path);
    let source = vec!("build.rs", "../c/netcode.c", "../c/netcode.h").iter()
        .map(|v| PathBuf::from(v))
        .collect::<Vec<_>>();

    let newest_source = source.iter()
        .map(|v| {
            File::open(v)
                .and_then(|f| f.metadata())
                .and_then(|m| m.modified())
                .expect(format!("Source file {:?} not found", v).as_str())
        })
        .max()
        .unwrap();

    let oldest_target = targets.iter()
        .filter_map(|v| {
            File::open(v)
                .and_then(|f| f.metadata())
                .and_then(|m| m.modified())
                .ok()
        })
        .min()
        .unwrap_or(newest_source - Duration::from_secs(1));

    if newest_source > oldest_target {
        //Export symbols for netcode
        let pub_bindings = bindgen::Builder::default()
            .no_unstable_rust()
            .header("../c/netcode.h")
            .generate()
            .expect("Unable to generate bindings");

        pub_bindings.write_to_file(&pub_path)
            .expect("Couldn't write bindings!");

        let include = env::var("INCLUDE").unwrap();
        let sodium_include = env::var("SODIUM_LIB_DIR").unwrap();

        let private_bindings = bindgen::Builder::default()
            .no_unstable_rust()
            .header("../c/netcode.c")
            .clang_arg("-I../c")
            .clang_arg(format!("-I{}", include))
            .clang_arg(format!("-I{}", sodium_include))
            .whitelisted_function("netcode_write_packet")
            .whitelisted_function("netcode_read_packet")
            .whitelisted_function("netcode_read_connect_token")
            .whitelisted_function("netcode_decrypt_challenge_token")
            .whitelisted_function("netcode_read_challenge_token")
            .whitelisted_function("netcode_replay_protection_reset")
            .whitelisted_function("free")
            .whitelisted_var("NETCODE_CONNECTION_NUM_PACKETS")
            .generate()
            .expect("Unable to generate bindings");

        private_bindings.write_to_file(&private_path)
            .expect("Couldn't write bindings!");
    }
}<commit_msg>Building on ubuntu<commit_after>extern crate gcc;
extern crate bindgen;

use std::env;
use std::path::PathBuf;
use std::fs::File;
use std::time::{SystemTime, Duration};
use std::cmp;

pub fn main() {
/*
    #[cfg(not(target_os = "windows"))]
    {
        println!("cargo:rustc-link-search=native=../c/windows");
        println!("cargo:rustc-link-lib=static=sodium-release");
    }
*/
    gcc::Config::new()
        .file("../c/netcode.c")
        .include("../c")
        .include("../c/windows")
        .define("NETCODE_ENABLE_TESTS", Some("0"))
        .define("NDEBUG", Some("0"))
        .compile("libnetcode.a");

    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    let pub_path = out_path.join("pub_bindings.rs");
    let private_path = out_path.join("private_bindings.rs");

    //Do some basic dependecy management
    let targets = vec!(&pub_path, &private_path);
    let source = vec!("build.rs", "../c/netcode.c", "../c/netcode.h").iter()
        .map(|v| PathBuf::from(v))
        .collect::<Vec<_>>();

    let newest_source = source.iter()
        .map(|v| {
            File::open(v)
                .and_then(|f| f.metadata())
                .and_then(|m| m.modified())
                .expect(format!("Source file {:?} not found", v).as_str())
        })
        .max()
        .unwrap();

    let oldest_target = targets.iter()
        .filter_map(|v| {
            File::open(v)
                .and_then(|f| f.metadata())
                .and_then(|m| m.modified())
                .ok()
        })
        .min()
        .unwrap_or(newest_source - Duration::from_secs(1));

    if newest_source > oldest_target {
        //Export symbols for netcode
        let pub_bindings = bindgen::Builder::default()
            .no_unstable_rust()
            .header("../c/netcode.h")
            .generate()
            .expect("Unable to generate bindings");

        pub_bindings.write_to_file(&pub_path)
            .expect("Couldn't write bindings!");

        let include = env::var("INCLUDE").unwrap_or("".to_string());
        let sodium_include = env::var("SODIUM_LIB_DIR")
                                 .unwrap_or("../c/windows/sodium".to_string());

        let private_bindings = bindgen::Builder::default()
            .no_unstable_rust()
            .header("../c/netcode.c")
            .clang_arg("-I../c")
            .clang_arg(format!("-I{}", include))
            .clang_arg(format!("-I{}", sodium_include))
            .whitelisted_function("netcode_write_packet")
            .whitelisted_function("netcode_read_packet")
            .whitelisted_function("netcode_read_connect_token")
            .whitelisted_function("netcode_decrypt_challenge_token")
            .whitelisted_function("netcode_read_challenge_token")
            .whitelisted_function("netcode_replay_protection_reset")
            .whitelisted_function("free")
            .whitelisted_var("NETCODE_CONNECTION_NUM_PACKETS")
            .generate()
            .expect("Unable to generate bindings");

        private_bindings.write_to_file(&private_path)
            .expect("Couldn't write bindings!");
    }
}
<|endoftext|>


<commit_before>#![feature(async_await, await_macro, pin, arbitrary_self_types, futures_api)]

use futures::{Poll, pending, poll, pin_mut, join, select};
use futures::channel::oneshot;
use futures::executor::block_on;

#[test]
fn poll_and_pending() {
    let pending_once = async { pending!() };
    block_on(async {
        pin_mut!(pending_once);
        assert_eq!(Poll::Pending, poll!(&mut pending_once));
        assert_eq!(Poll::Ready(()), poll!(&mut pending_once));
    });
}

#[test]
fn join() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (tx2, rx2) = oneshot::channel::<i32>();

    let fut = async {
        let res = join!(rx1, rx2);
        assert_eq!((Ok(1), Ok(2)), res);
    };

    block_on(async {
        pin_mut!(fut);
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx1.send(1).unwrap();
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx2.send(2).unwrap();
        assert_eq!(Poll::Ready(()), poll!(&mut fut));
    });
}

#[test]
fn select() {
    let (tx1, mut rx1) = oneshot::channel::<i32>();
    let (_tx2, mut rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    let mut ran = false;
    block_on(async {
        select! {
            rx1 => {
                assert_eq!(Ok(1), rx1);
                ran = true;
            },
            rx2 => unreachable!(),
        }
    });
    assert!(ran);
}

#[test]
fn select_can_move_uncompleted_futures() {
    let (tx1, mut rx1) = oneshot::channel::<i32>();
    let (tx2, mut rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    tx2.send(2).unwrap();
    let mut ran = false;
    block_on(async {
        select! {
            rx1 => {
                assert_eq!(Ok(1), rx1);
                assert_eq!(Ok(2), await!(rx2));
                ran = true;
            },
            rx2 => {
                assert_eq!(Ok(2), rx2);
                assert_eq!(Ok(1), await!(rx1));
                ran = true;
            },
        }
    });
    assert!(ran);
}
<commit_msg>Add tests to prevent size regressions in async/await macros<commit_after>#![feature(async_await, await_macro, pin, arbitrary_self_types, futures_api)]

use futures::{Poll, future, pending, poll, pin_mut, join, try_join, select};
use futures::channel::oneshot;
use futures::executor::block_on;

#[test]
fn poll_and_pending() {
    let pending_once = async { pending!() };
    block_on(async {
        pin_mut!(pending_once);
        assert_eq!(Poll::Pending, poll!(&mut pending_once));
        assert_eq!(Poll::Ready(()), poll!(&mut pending_once));
    });
}

#[test]
fn join() {
    let (tx1, rx1) = oneshot::channel::<i32>();
    let (tx2, rx2) = oneshot::channel::<i32>();

    let fut = async {
        let res = join!(rx1, rx2);
        assert_eq!((Ok(1), Ok(2)), res);
    };

    block_on(async {
        pin_mut!(fut);
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx1.send(1).unwrap();
        assert_eq!(Poll::Pending, poll!(&mut fut));
        tx2.send(2).unwrap();
        assert_eq!(Poll::Ready(()), poll!(&mut fut));
    });
}

#[test]
fn select() {
    let (tx1, mut rx1) = oneshot::channel::<i32>();
    let (_tx2, mut rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    let mut ran = false;
    block_on(async {
        select! {
            rx1 => {
                assert_eq!(Ok(1), rx1);
                ran = true;
            },
            rx2 => unreachable!(),
        }
    });
    assert!(ran);
}

#[test]
fn select_can_move_uncompleted_futures() {
    let (tx1, mut rx1) = oneshot::channel::<i32>();
    let (tx2, mut rx2) = oneshot::channel::<i32>();
    tx1.send(1).unwrap();
    tx2.send(2).unwrap();
    let mut ran = false;
    block_on(async {
        select! {
            rx1 => {
                assert_eq!(Ok(1), rx1);
                assert_eq!(Ok(2), await!(rx2));
                ran = true;
            },
            rx2 => {
                assert_eq!(Ok(2), rx2);
                assert_eq!(Ok(1), await!(rx1));
                ran = true;
            },
        }
    });
    assert!(ran);
}

#[test]
fn select_size() {
    let fut = async {
        let mut ready = future::ready(0i32);
        select! {
            ready => {},
        }
    };
    assert_eq!(::std::mem::size_of_val(&fut), 40);

    let fut = async {
        let mut ready1 = future::ready(0i32);
        let mut ready2 = future::ready(0i32);
        select! {
            ready1 => {},
            ready2 => {},
        }
    };
    assert_eq!(::std::mem::size_of_val(&fut), 56);
}

#[test]
fn join_size() {
    let fut = async {
        let ready = future::ready(0i32);
        join!(ready)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 48);

    let fut = async {
        let ready1 = future::ready(0i32);
        let ready2 = future::ready(0i32);
        join!(ready1, ready2)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 72);
}

#[test]
fn try_join_size() {
    let fut = async {
        let ready = future::ready(Ok::<i32, i32>(0));
        try_join!(ready)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 48);

    let fut = async {
        let ready1 = future::ready(Ok::<i32, i32>(0));
        let ready2 = future::ready(Ok::<i32, i32>(0));
        try_join!(ready1, ready2)
    };
    assert_eq!(::std::mem::size_of_val(&fut), 80);
}
<|endoftext|>