<commit_before>#[macro_use]
extern crate stdweb;

#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use std::cell::RefCell;
use std::rc::Rc;

use stdweb::unstable::TryInto;
use stdweb::web::{
    IEventTarget,
    IElement,
    IHtmlElement,
    INode,
    IParentNode,
    HtmlElement,
    Element,
    document,
    window
};

use stdweb::web::event::{
    IEvent,
    IKeyboardEvent,
    DoubleClickEvent,
    ClickEvent,
    KeyPressEvent,
    ChangeEvent,
    BlurEvent,
    HashChangeEvent
};

use stdweb::web::html_element::InputElement;

// Shamelessly stolen from webplatform's TodoMVC example.
macro_rules! enclose {
    ( ($( $x:ident ),*) $y:expr ) => {
        {
            $(let $x = $x.clone();)*
            $y
        }
    };
}

#[derive(Clone, Serialize, Deserialize)]
struct Todo {
    title: String,
    completed: bool
}

#[derive(Serialize, Deserialize)]
struct State {
    todo_list: Vec< Todo >
}

impl State {
    fn new() -> Self {
        State {
            todo_list: Vec::new()
        }
    }
}

type StateRef = Rc< RefCell< State > >;

fn start_editing( state: &StateRef, index: usize, li: &HtmlElement, label: &HtmlElement ) {
    li.class_list().add( "editing" ).unwrap();

    let edit: InputElement = document().create_element( "input" ).unwrap().try_into().unwrap();
    edit.class_list().add( "edit" ).unwrap();
    edit.set_value( label.inner_text() );
    edit.add_event_listener( enclose!( (edit) move |event: KeyPressEvent| {
        if event.key() == "Enter" {
            edit.blur();
        }
    }));

    edit.add_event_listener( enclose!( (state, li, edit) move |_: BlurEvent| {
        li.class_list().remove( "editing" ).unwrap();
        li.remove_child( &edit ).unwrap();
        state.borrow_mut().todo_list[ index ].title = edit.value().into_string().unwrap();
        update_dom( &state );
    }));

    li.append_child( &edit );
    edit.focus();
}

fn create_entry( state: &StateRef, index: usize, text: &str ) -> HtmlElement {
    let li: HtmlElement = document().create_element( "li" ).unwrap().try_into().unwrap();
    let div = document().create_element( "div" ).unwrap();
    let checkbox: InputElement = document().create_element( "input" ).unwrap().try_into().unwrap();
    let label: HtmlElement = document().create_element( "label" ).unwrap().try_into().unwrap();
    let button = document().create_element( "button" ).unwrap();

    div.class_list().add( "view" ).unwrap();

    checkbox.class_list().add( "toggle" ).unwrap();
    checkbox.set_kind( "checkbox" );
    checkbox.add_event_listener( enclose!( (state, checkbox) move |_: ChangeEvent| {
        let checked: bool = js!( return @{&checkbox}.checked; ).try_into().unwrap();
        state.borrow_mut().todo_list[ index ].completed = checked;
        update_dom( &state );
    }));

    label.append_child( &document().create_text_node( text ) );
    label.add_event_listener( enclose!( (state, li, label) move |_: DoubleClickEvent| {
        start_editing( &state, index, &li, &label );
    }));

    button.class_list().add( "destroy" ).unwrap();
    button.add_event_listener( enclose!( (state) move |_: ClickEvent| {
        state.borrow_mut().todo_list.remove( index );
        update_dom( &state );
    }));

    li.append_child( &div );
    div.append_child( &checkbox );
    div.append_child( &label );
    div.append_child( &button );

    li
}

fn update_dom( state: &StateRef ) {
    // Ideally you'd use some kind of DOM diffing here;
    // since it's supposed to be a simple example we opt
    // for the nuclear option and just rebuild everything
    // from scratch.

    fn only_active( todo: &Todo ) -> bool { todo.completed == false }
    fn only_completed( todo: &Todo ) -> bool { todo.completed == true }
    fn all( _: &Todo ) -> bool { true }

    // See which filter we're supposed to use based on the URL.
    let hash = document().location().unwrap().hash().unwrap();
    let filter = match hash.as_str() {
        "#/active" => only_active,
        "#/completed" => only_completed,
        _ => all
    };

    let filter_anchor_selector = match hash.as_str() {
        "#/active" | "#/completed" => hash.as_str(),
        _ => "#/"
    };

    // Select the filter "button".
    let filter_anchors = document().query_selector_all( ".filters a" ).unwrap();
    for anchor in &filter_anchors {
        let anchor: Element = anchor.try_into().unwrap();
        anchor.class_list().remove( "selected" ).unwrap();
    }

    let filter_anchor_selector = format!( ".filters a[href='{}']", filter_anchor_selector );
    let selected_anchor: Element = document().query_selector( filter_anchor_selector.as_str() ).unwrap().unwrap().try_into().unwrap();
    selected_anchor.class_list().add( "selected" ).unwrap();

    // Clear previous entries in the list.
    let list = document().query_selector( ".todo-list" ).unwrap().unwrap();
    while let Some( child ) = list.first_child() {
        list.remove_child( &child ).unwrap();
    }

    // Fill out the list.
    let state_borrow = state.borrow();
    for (index, todo) in state_borrow.todo_list.iter().enumerate().filter( |&(_, todo)| filter( todo ) ) {
        let entry_node = create_entry( state, index, todo.title.as_str() );
        if todo.completed {
            entry_node.class_list().add( "completed" ).unwrap();
            let checkbox = entry_node.query_selector( "input[type='checkbox']" ).unwrap();
            js!( @{checkbox}.checked = true; );
        }
        list.append_child( &entry_node );
    }

    // Display the amount of active TODOs lefs.
    let items_left = state_borrow.todo_list.iter().filter( |todo| {
        todo.completed == false
    }).count();

    let counter_display = document().query_selector( ".todo-count" ).unwrap().unwrap();
    if items_left == 1 {
        counter_display.set_text_content( "1 item left" );
    } else {
        counter_display.set_text_content( format!( "{} items left", items_left ).as_str() );
    }

    // Hide the list if we don't have any TODOs.
    let main = document().query_selector( ".main" ).unwrap();
    if state_borrow.todo_list.is_empty() {
        js!( @{main}.style = "display: none;" );
    } else {
        js!( @{main}.style = "display: block;" );
    }

    // Save the state into local storage.
    let state_json = serde_json::to_string( &*state_borrow ).unwrap();
    window().local_storage().insert( "state", state_json.as_str() ).unwrap();
}

fn main() {
    stdweb::initialize();

    let state = window().local_storage().get( "state" ).and_then( |state_json| {
        serde_json::from_str( state_json.as_str() ).ok()
    }).unwrap_or_else( State::new );
    let state = Rc::new( RefCell::new( state ) );

    let title_entry: InputElement = document().query_selector( ".new-todo" ).unwrap().unwrap().try_into().unwrap();
    title_entry.add_event_listener( enclose!( (state, title_entry) move |event: KeyPressEvent| {
        if event.key() == "Enter" {
            event.prevent_default();

            let title: String = title_entry.value().try_into().unwrap();
            if title.is_empty() == false {
                state.borrow_mut().todo_list.push( Todo {
                    title: title,
                    completed: false
                });

                title_entry.set_value( "" );
                update_dom( &state );
            }
        }
    }));

    let clear_completed = document().query_selector( ".clear-completed" ).unwrap().unwrap();
    clear_completed.add_event_listener( enclose!( (state) move |_: ClickEvent| {
        state.borrow_mut().todo_list.retain( |todo| todo.completed == false );
        update_dom( &state );
    }));

    window().add_event_listener( enclose!( (state) move |_: HashChangeEvent| {
        update_dom( &state );
    }));

    update_dom( &state );
    stdweb::event_loop();
}
<commit_msg>Fix `todomvc` example<commit_after>#[macro_use]
extern crate stdweb;

#[macro_use]
extern crate serde_derive;
extern crate serde_json;

use std::cell::RefCell;
use std::rc::Rc;

use stdweb::unstable::TryInto;
use stdweb::web::{
    IEventTarget,
    IElement,
    IHtmlElement,
    INode,
    IParentNode,
    HtmlElement,
    Element,
    document,
    window
};

use stdweb::web::event::{
    IEvent,
    IKeyboardEvent,
    DoubleClickEvent,
    ClickEvent,
    KeyPressEvent,
    ChangeEvent,
    BlurEvent,
    HashChangeEvent
};

use stdweb::web::html_element::InputElement;

// Shamelessly stolen from webplatform's TodoMVC example.
macro_rules! enclose {
    ( ($( $x:ident ),*) $y:expr ) => {
        {
            $(let $x = $x.clone();)*
            $y
        }
    };
}

#[derive(Clone, Serialize, Deserialize)]
struct Todo {
    title: String,
    completed: bool
}

#[derive(Serialize, Deserialize)]
struct State {
    todo_list: Vec< Todo >
}

impl State {
    fn new() -> Self {
        State {
            todo_list: Vec::new()
        }
    }
}

type StateRef = Rc< RefCell< State > >;

fn start_editing( state: &StateRef, index: usize, li: &HtmlElement, label: &HtmlElement ) {
    li.class_list().add( "editing" ).unwrap();

    let edit: InputElement = document().create_element( "input" ).unwrap().try_into().unwrap();
    edit.class_list().add( "edit" ).unwrap();
    edit.set_value( label.inner_text() );
    edit.add_event_listener( enclose!( (edit) move |event: KeyPressEvent| {
        if event.key() == "Enter" {
            edit.blur();
        }
    }));

    edit.add_event_listener( enclose!( (state, li, edit) move |_: BlurEvent| {
        li.class_list().remove( "editing" ).unwrap();
        li.remove_child( &edit ).unwrap();
        state.borrow_mut().todo_list[ index ].title = edit.value().into_string().unwrap();
        update_dom( &state );
    }));

    li.append_child( &edit );
    edit.focus();
}

fn create_entry( state: &StateRef, index: usize, text: &str ) -> HtmlElement {
    let li: HtmlElement = document().create_element( "li" ).unwrap().try_into().unwrap();
    let div = document().create_element( "div" ).unwrap();
    let checkbox: InputElement = document().create_element( "input" ).unwrap().try_into().unwrap();
    let label: HtmlElement = document().create_element( "label" ).unwrap().try_into().unwrap();
    let button = document().create_element( "button" ).unwrap();

    div.class_list().add( "view" ).unwrap();

    checkbox.class_list().add( "toggle" ).unwrap();
    checkbox.set_kind( "checkbox" );
    checkbox.add_event_listener( enclose!( (state, checkbox) move |_: ChangeEvent| {
        let checked: bool = js!( return @{&checkbox}.checked; ).try_into().unwrap();
        state.borrow_mut().todo_list[ index ].completed = checked;
        update_dom( &state );
    }));

    label.append_child( &document().create_text_node( text ) );
    label.add_event_listener( enclose!( (state, li, label) move |_: DoubleClickEvent| {
        start_editing( &state, index, &li, &label );
    }));

    button.class_list().add( "destroy" ).unwrap();
    button.add_event_listener( enclose!( (state) move |_: ClickEvent| {
        state.borrow_mut().todo_list.remove( index );
        update_dom( &state );
    }));

    li.append_child( &div );
    div.append_child( &checkbox );
    div.append_child( &label );
    div.append_child( &button );

    li
}

fn update_dom( state: &StateRef ) {
    // Ideally you'd use some kind of DOM diffing here;
    // since it's supposed to be a simple example we opt
    // for the nuclear option and just rebuild everything
    // from scratch.

    fn only_active( todo: &Todo ) -> bool { todo.completed == false }
    fn only_completed( todo: &Todo ) -> bool { todo.completed == true }
    fn all( _: &Todo ) -> bool { true }

    // See which filter we're supposed to use based on the URL.
    let hash = document().location().unwrap().hash().unwrap();
    let filter = match hash.as_str() {
        "#/active" => only_active,
        "#/completed" => only_completed,
        _ => all
    };

    let filter_anchor_selector = match hash.as_str() {
        "#/active" | "#/completed" => hash.as_str(),
        _ => "#/"
    };

    // Select the filter "button".
    let filter_anchors = document().query_selector_all( ".filters a" ).unwrap();
    for anchor in &filter_anchors {
        let anchor: Element = anchor.try_into().unwrap();
        anchor.class_list().remove( "selected" ).unwrap();
    }

    let filter_anchor_selector = format!( ".filters a[href='{}']", filter_anchor_selector );
    let selected_anchor: Element = document().query_selector( filter_anchor_selector.as_str() ).unwrap().unwrap();
    selected_anchor.class_list().add( "selected" ).unwrap();

    // Clear previous entries in the list.
    let list = document().query_selector( ".todo-list" ).unwrap().unwrap();
    while let Some( child ) = list.first_child() {
        list.remove_child( &child ).unwrap();
    }

    // Fill out the list.
    let state_borrow = state.borrow();
    for (index, todo) in state_borrow.todo_list.iter().enumerate().filter( |&(_, todo)| filter( todo ) ) {
        let entry_node = create_entry( state, index, todo.title.as_str() );
        if todo.completed {
            entry_node.class_list().add( "completed" ).unwrap();
            let checkbox = entry_node.query_selector( "input[type='checkbox']" ).unwrap();
            js!( @{checkbox}.checked = true; );
        }
        list.append_child( &entry_node );
    }

    // Display the amount of active TODOs lefs.
    let items_left = state_borrow.todo_list.iter().filter( |todo| {
        todo.completed == false
    }).count();

    let counter_display = document().query_selector( ".todo-count" ).unwrap().unwrap();
    if items_left == 1 {
        counter_display.set_text_content( "1 item left" );
    } else {
        counter_display.set_text_content( format!( "{} items left", items_left ).as_str() );
    }

    // Hide the list if we don't have any TODOs.
    let main = document().query_selector( ".main" ).unwrap();
    if state_borrow.todo_list.is_empty() {
        js!( @{main}.style = "display: none;" );
    } else {
        js!( @{main}.style = "display: block;" );
    }

    // Save the state into local storage.
    let state_json = serde_json::to_string( &*state_borrow ).unwrap();
    window().local_storage().insert( "state", state_json.as_str() ).unwrap();
}

fn main() {
    stdweb::initialize();

    let state = window().local_storage().get( "state" ).and_then( |state_json| {
        serde_json::from_str( state_json.as_str() ).ok()
    }).unwrap_or_else( State::new );
    let state = Rc::new( RefCell::new( state ) );

    let title_entry: InputElement = document().query_selector( ".new-todo" ).unwrap().unwrap().try_into().unwrap();
    title_entry.add_event_listener( enclose!( (state, title_entry) move |event: KeyPressEvent| {
        if event.key() == "Enter" {
            event.prevent_default();

            let title: String = title_entry.value().try_into().unwrap();
            if title.is_empty() == false {
                state.borrow_mut().todo_list.push( Todo {
                    title: title,
                    completed: false
                });

                title_entry.set_value( "" );
                update_dom( &state );
            }
        }
    }));

    let clear_completed = document().query_selector( ".clear-completed" ).unwrap().unwrap();
    clear_completed.add_event_listener( enclose!( (state) move |_: ClickEvent| {
        state.borrow_mut().todo_list.retain( |todo| todo.completed == false );
        update_dom( &state );
    }));

    window().add_event_listener( enclose!( (state) move |_: HashChangeEvent| {
        update_dom( &state );
    }));

    update_dom( &state );
    stdweb::event_loop();
}
<|endoftext|>


<commit_before>use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Mul, Sub};

#[macro_use]
extern crate try_opt;

extern crate num_bigint;
use num_bigint::BigInt;
extern crate num_traits;
use num_traits::pow;

/// Type implementing arbitrary-precision decimal arithmetic
#[derive(Debug, Eq, Clone)]
pub struct Decimal {
    digits: BigInt,
    decimal_index: usize,
}

impl Decimal {
    fn new(digits: BigInt, decimal_index: usize) -> Decimal {
        let mut value = Decimal {
            digits,
            decimal_index,
        };
        value.reduce();
        value
    }

    pub fn try_from(mut input: &str) -> Option<Decimal> {
        // clear extraneous whitespace
        input = input.trim();

        // don't bother to trim extraneous zeroes
        // leave it to users to manage their own memory

        // now build a representation of the number to parse
        let mut digits = String::with_capacity(input.len());
        let mut decimal_index = None;
        for ch in input.chars() {
            match ch {
                '0'..='9' | '-' | '+' => {
                    digits.push(ch);
                    if let Some(idx) = decimal_index.as_mut() {
                        *idx += 1;
                    }
                }
                '.' => {
                    if decimal_index.is_some() {
                        return None;
                    }
                    decimal_index = Some(0)
                }
                _ => return None,
            }
        }
        Some(Decimal::new(
            try_opt!(digits.parse::<BigInt>().ok()),
            match decimal_index {
                Some(idx) => idx,
                None => 0,
            },
        ))
    }

    /// Add precision to the less-precise value until precisions match
    ///
    /// Precision, in this case, is defined as the decimal index.
    fn equalize_precision(mut one: &mut Decimal, mut two: &mut Decimal) {
        fn expand(lower_precision: &mut Decimal, higher_precision: &Decimal) {
            let precision_difference =
                (higher_precision.decimal_index - lower_precision.decimal_index) as usize;

            lower_precision.digits =
                &lower_precision.digits * pow(BigInt::from(10_usize), precision_difference);
            lower_precision.decimal_index += precision_difference;
        }
        match one.decimal_index.cmp(&two.decimal_index) {
            std::cmp::Ordering::Equal => {}
            std::cmp::Ordering::Less => expand(&mut one, &two),
            std::cmp::Ordering::Greater => expand(&mut two, &one),
        }
        assert_eq!(one.decimal_index, two.decimal_index);
    }

    /// Eliminate extraneous trailing zeroes
    ///
    /// This reduces the decimal index, so that the raw values are easier to parse
    fn reduce(&mut self) {
        let extra_zeroes = self
            .digits
            .to_string() // produce a decimal representation
            .chars()
            .rev() // trailing values
            .take(self.decimal_index) // not counting past the decimal point
            .take_while(|&c| c == '0') // counting only `0` digits
            .count();
        self.digits = &self.digits / pow(BigInt::from(10_usize), extra_zeroes);
        self.decimal_index -= extra_zeroes;
    }
}

macro_rules! auto_impl_decimal_ops {
    ($(#[$attr:meta])* $trait:ident, $func_name:ident, $digits_operation:expr, $index_operation:expr) => {
        impl $trait for Decimal {
            type Output = Self;
            $(#[$attr])*
            fn $func_name(mut self, mut rhs: Self) -> Self {
                Decimal::equalize_precision(&mut self, &mut rhs);
                Decimal::new(
                    $digits_operation(self.digits, rhs.digits),
                    $index_operation(self.decimal_index, rhs.decimal_index),
                )
            }
        }
    };
}

auto_impl_decimal_ops!(Add, add, |s, o| s + o, |s, _| s);
auto_impl_decimal_ops!(Sub, sub, |s, o| s - o, |s, _| s);
auto_impl_decimal_ops!(
    #[allow(clippy::suspicious_arithmetic_impl)]
    Mul,
    mul,
    |s, o| s * o,
    |s, o| s + o
);

macro_rules! auto_impl_decimal_cow {
    ($trait:ident, $func_name:ident, $digits_operation:expr, $return_type:ty) => {
        impl $trait for Decimal {
            fn $func_name(&self, other: &Self) -> $return_type {
                if self.decimal_index == other.decimal_index {
                    $digits_operation(&self.digits, &other.digits)
                } else {
                    // if we're here, the decimal indexes are unmatched.
                    // We have to compare equal terms.
                    // clone both sides so we can modify either of them as necessary.
                    // not efficient, but whatever.
                    let mut one = self.clone();
                    let mut two = other.clone();
                    Decimal::equalize_precision(&mut one, &mut two);
                    one.$func_name(&two)
                }
            }
        }
    };
}

auto_impl_decimal_cow!(PartialEq, eq, |a, b| a == b, bool);
auto_impl_decimal_cow!(Ord, cmp, |a: &BigInt, b: &BigInt| a.cmp(b), Ordering);

impl PartialOrd for Decimal {
    fn partial_cmp(&self, other: &Decimal) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl fmt::Display for Decimal {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // get a representation of the pure digits,
        // left-padded with zeroes
        let digits = format!("{:0>width$}", self.digits, width = self.decimal_index);
        if self.decimal_index == digits.len() {
            write!(f, "0.{}", digits)
        } else if self.decimal_index == 0 {
            write!(f, "{}", digits)
        } else {
            let (before_index, after_index) = digits.split_at(digits.len() - self.decimal_index);
            write!(f, "{}.{}", before_index, after_index)
        }
    }
}

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

    #[test]
    fn test_display_temp() {
        for &test_str in &["0", "1", "20", "0.3", "0.04", "50.05", "66.0006", "0.007"] {
            println!(
                "Decimal representation of \"{}\": {}",
                test_str,
                Decimal::try_from(test_str).expect("This should always become a decimal")
            );
            assert_eq!(
                test_str,
                Decimal::try_from(test_str)
                    .expect("This should always become a decimal")
                    .to_string()
            )
        }
    }
}
<commit_msg>decimal: Use unwrap_or_default<commit_after>use std::cmp::Ordering;
use std::fmt;
use std::ops::{Add, Mul, Sub};

#[macro_use]
extern crate try_opt;

extern crate num_bigint;
use num_bigint::BigInt;
extern crate num_traits;
use num_traits::pow;

/// Type implementing arbitrary-precision decimal arithmetic
#[derive(Debug, Eq, Clone)]
pub struct Decimal {
    digits: BigInt,
    decimal_index: usize,
}

impl Decimal {
    fn new(digits: BigInt, decimal_index: usize) -> Decimal {
        let mut value = Decimal {
            digits,
            decimal_index,
        };
        value.reduce();
        value
    }

    pub fn try_from(mut input: &str) -> Option<Decimal> {
        // clear extraneous whitespace
        input = input.trim();

        // don't bother to trim extraneous zeroes
        // leave it to users to manage their own memory

        // now build a representation of the number to parse
        let mut digits = String::with_capacity(input.len());
        let mut decimal_index = None;
        for ch in input.chars() {
            match ch {
                '0'..='9' | '-' | '+' => {
                    digits.push(ch);
                    if let Some(idx) = decimal_index.as_mut() {
                        *idx += 1;
                    }
                }
                '.' => {
                    if decimal_index.is_some() {
                        return None;
                    }
                    decimal_index = Some(0)
                }
                _ => return None,
            }
        }
        Some(Decimal::new(
            try_opt!(digits.parse::<BigInt>().ok()),
            decimal_index.unwrap_or_default(),
        ))
    }

    /// Add precision to the less-precise value until precisions match
    ///
    /// Precision, in this case, is defined as the decimal index.
    fn equalize_precision(mut one: &mut Decimal, mut two: &mut Decimal) {
        fn expand(lower_precision: &mut Decimal, higher_precision: &Decimal) {
            let precision_difference =
                (higher_precision.decimal_index - lower_precision.decimal_index) as usize;

            lower_precision.digits =
                &lower_precision.digits * pow(BigInt::from(10_usize), precision_difference);
            lower_precision.decimal_index += precision_difference;
        }
        match one.decimal_index.cmp(&two.decimal_index) {
            std::cmp::Ordering::Equal => {}
            std::cmp::Ordering::Less => expand(&mut one, &two),
            std::cmp::Ordering::Greater => expand(&mut two, &one),
        }
        assert_eq!(one.decimal_index, two.decimal_index);
    }

    /// Eliminate extraneous trailing zeroes
    ///
    /// This reduces the decimal index, so that the raw values are easier to parse
    fn reduce(&mut self) {
        let extra_zeroes = self
            .digits
            .to_string() // produce a decimal representation
            .chars()
            .rev() // trailing values
            .take(self.decimal_index) // not counting past the decimal point
            .take_while(|&c| c == '0') // counting only `0` digits
            .count();
        self.digits = &self.digits / pow(BigInt::from(10_usize), extra_zeroes);
        self.decimal_index -= extra_zeroes;
    }
}

macro_rules! auto_impl_decimal_ops {
    ($(#[$attr:meta])* $trait:ident, $func_name:ident, $digits_operation:expr, $index_operation:expr) => {
        impl $trait for Decimal {
            type Output = Self;
            $(#[$attr])*
            fn $func_name(mut self, mut rhs: Self) -> Self {
                Decimal::equalize_precision(&mut self, &mut rhs);
                Decimal::new(
                    $digits_operation(self.digits, rhs.digits),
                    $index_operation(self.decimal_index, rhs.decimal_index),
                )
            }
        }
    };
}

auto_impl_decimal_ops!(Add, add, |s, o| s + o, |s, _| s);
auto_impl_decimal_ops!(Sub, sub, |s, o| s - o, |s, _| s);
auto_impl_decimal_ops!(
    #[allow(clippy::suspicious_arithmetic_impl)]
    Mul,
    mul,
    |s, o| s * o,
    |s, o| s + o
);

macro_rules! auto_impl_decimal_cow {
    ($trait:ident, $func_name:ident, $digits_operation:expr, $return_type:ty) => {
        impl $trait for Decimal {
            fn $func_name(&self, other: &Self) -> $return_type {
                if self.decimal_index == other.decimal_index {
                    $digits_operation(&self.digits, &other.digits)
                } else {
                    // if we're here, the decimal indexes are unmatched.
                    // We have to compare equal terms.
                    // clone both sides so we can modify either of them as necessary.
                    // not efficient, but whatever.
                    let mut one = self.clone();
                    let mut two = other.clone();
                    Decimal::equalize_precision(&mut one, &mut two);
                    one.$func_name(&two)
                }
            }
        }
    };
}

auto_impl_decimal_cow!(PartialEq, eq, |a, b| a == b, bool);
auto_impl_decimal_cow!(Ord, cmp, |a: &BigInt, b: &BigInt| a.cmp(b), Ordering);

impl PartialOrd for Decimal {
    fn partial_cmp(&self, other: &Decimal) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl fmt::Display for Decimal {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // get a representation of the pure digits,
        // left-padded with zeroes
        let digits = format!("{:0>width$}", self.digits, width = self.decimal_index);
        if self.decimal_index == digits.len() {
            write!(f, "0.{}", digits)
        } else if self.decimal_index == 0 {
            write!(f, "{}", digits)
        } else {
            let (before_index, after_index) = digits.split_at(digits.len() - self.decimal_index);
            write!(f, "{}.{}", before_index, after_index)
        }
    }
}

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

    #[test]
    fn test_display_temp() {
        for &test_str in &["0", "1", "20", "0.3", "0.04", "50.05", "66.0006", "0.007"] {
            println!(
                "Decimal representation of \"{}\": {}",
                test_str,
                Decimal::try_from(test_str).expect("This should always become a decimal")
            );
            assert_eq!(
                test_str,
                Decimal::try_from(test_str)
                    .expect("This should always become a decimal")
                    .to_string()
            )
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2017 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use futures::{Future, Stream, Sink, IntoFuture};
use futures::future::Either;
use futures::sync::mpsc;
use futures::unsync;
use tokio_core::net::{TcpListener, TcpStream, TcpStreamNew};
use tokio_core::reactor::{Core, Handle};
use tokio_io::AsyncRead;
use tokio_retry::{Retry, Action};
use tokio_retry::strategy::{FixedInterval, jitter};

use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use std::collections::HashMap;
use std::rc::Rc;

use messages::{Any, Connect, RawMessage};
use node::{ExternalMessage, NodeTimeout};
use helpers::Milliseconds;

use super::EventHandler;
use super::error::{other_error, result_ok, forget_result, into_other, log_error};
use super::codec::MessagesCodec;
use super::EventsAggregator;

#[derive(Debug)]
pub enum NetworkEvent {
    MessageReceived(SocketAddr, RawMessage),
    PeerConnected(SocketAddr, Connect),
    PeerDisconnected(SocketAddr),
}

#[derive(Debug, Clone)]
pub enum NetworkRequest {
    SendMessage(SocketAddr, RawMessage),
    DisconnectWithPeer(SocketAddr),
    Shutdown,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct NetworkConfiguration {
    // TODO: think more about config parameters
    pub max_incoming_connections: usize,
    pub max_outgoing_connections: usize,
    pub tcp_nodelay: bool,
    pub tcp_keep_alive: Option<u64>,
    pub tcp_connect_retry_timeout: Milliseconds,
    pub tcp_connect_max_retries: u64,
}

impl Default for NetworkConfiguration {
    fn default() -> NetworkConfiguration {
        NetworkConfiguration {
            max_incoming_connections: 128,
            max_outgoing_connections: 128,
            tcp_keep_alive: None,
            tcp_nodelay: false,
            tcp_connect_retry_timeout: 15_000,
            tcp_connect_max_retries: 10,
        }
    }
}

#[derive(Debug)]
pub struct HandlerPart<H: EventHandler> {
    pub core: Core,
    pub handler: H,
    pub timeout_rx: mpsc::Receiver<NodeTimeout>,
    pub network_rx: mpsc::Receiver<NetworkEvent>,
    pub api_rx: mpsc::Receiver<ExternalMessage>,
}

#[derive(Debug)]
pub struct NetworkPart {
    pub listen_address: SocketAddr,
    pub network_config: NetworkConfiguration,
    pub network_requests: (mpsc::Sender<NetworkRequest>, mpsc::Receiver<NetworkRequest>),
    pub network_tx: mpsc::Sender<NetworkEvent>,
}

impl<H: EventHandler> HandlerPart<H> {
    pub fn run(self) -> Result<(), ()> {
        let mut core = self.core;
        let mut handler = self.handler;

        let events_handle = EventsAggregator::new(self.timeout_rx, self.network_rx, self.api_rx)
            .for_each(move |event| {
                handler.handle_event(event);
                Ok(())
            });
        core.run(events_handle)
    }
}

macro_rules! try_future_boxed {
    ($e:expr) =>(
        match $e {
            Ok(v) => v,
            Err(e) => {
                return Err(into_other(e)).into_future().boxed();
            }
        }
    )
}

impl NetworkPart {
    pub fn run(self) -> Result<(), io::Error> {
        let mut core = Core::new()?;
        // Cancelation token
        let (cancel_sender, cancel_handler) = unsync::mpsc::channel(1);
        // Outgoing connections handler
        let mut outgoing_connections: HashMap<SocketAddr, mpsc::Sender<RawMessage>> =
            HashMap::new();
        // Requests handler
        let handle = core.handle();
        let network_tx = self.network_tx.clone();
        let requests_tx = self.network_requests.0.clone();
        let network_config = self.network_config;
        let requests_handle = self.network_requests.1.for_each(move |request| {
            let cancel_sender = cancel_sender.clone();
            match request {
                NetworkRequest::SendMessage(peer, msg) => {
                    let conn_tx = if let Some(conn_tx) = outgoing_connections.get(&peer).cloned() {
                        conn_tx
                    } else {
                        // Register outgoing channel.
                        let (conn_tx, conn_rx) = mpsc::channel(10);
                        outgoing_connections.insert(peer, conn_tx.clone());
                        // Enable retry feature for outgoing connection.
                        let requests_tx = requests_tx.clone();
                        let timeout = network_config.tcp_connect_retry_timeout;
                        let max_tries = network_config.tcp_connect_max_retries as usize;
                        let connect_handle = Retry::spawn(
                            handle.clone(),
                            FixedInterval::from_millis(timeout).map(jitter).take(
                                max_tries,
                            ),
                            TcpStreamConnectAction {
                                handle: handle.clone(),
                                peer,
                            },
                        ).map_err(into_other)
                            .and_then(move |sock| {
                                try_future_boxed!(sock.set_nodelay(network_config.tcp_nodelay));
                                try_future_boxed!(sock.set_keepalive(
                                    network_config.tcp_keep_alive.map(Duration::from_millis),
                                ));

                                trace!("Established connection with peer={}", peer);

                                let stream = sock.framed(MessagesCodec);
                                let (sink, stream) = stream.split();

                                let writer = conn_rx
                                    .map_err(|_| other_error("Can't send data into socket"))
                                    .forward(sink);
                                let reader = stream.for_each(result_ok).map_err(into_other);

                                reader
                                    .select2(writer)
                                    .map_err(|_| other_error("Socket error"))
                                    .and_then(|res| match res {
                                        Either::A((_, _reader)) => Ok(()).into_future(),
                                        Either::B((_, _writer)) => Ok(()).into_future(),
                                    })
                                    .boxed()
                            })
                            .then(move |res| {
                                trace!("Connection with peer={} closed, reason={:?}", peer, res);
                                let request = NetworkRequest::DisconnectWithPeer(peer);
                                requests_tx
                                    .clone()
                                    .send(request)
                                    .map(forget_result)
                                    .map_err(into_other)
                            })
                            .map_err(log_error);
                        handle.spawn(connect_handle);
                        conn_tx
                    };

                    let send_handle = conn_tx.send(msg).map(forget_result).map_err(log_error);
                    handle.spawn(send_handle);
                }
                NetworkRequest::DisconnectWithPeer(peer) => {
                    outgoing_connections.remove(&peer);

                    let event = NetworkEvent::PeerDisconnected(peer);
                    let event_handle = network_tx.clone().send(event).map(forget_result).map_err(
                        log_error,
                    );
                    handle.spawn(event_handle);
                }
                // Immediately stop the event loop.
                NetworkRequest::Shutdown => {
                    cancel_sender
                        .clone()
                        .send(())
                        .map(forget_result)
                        .map_err(log_error)
                        .wait()?
                }
            }

            Ok(())
        });

        // Incoming connections limiter
        let incoming_connections_limit = network_config.max_incoming_connections;
        let incoming_connections_counter: Rc<()> = Rc::default();
        // Incoming connections handler
        let listener = TcpListener::bind(&self.listen_address, &core.handle())?;
        let network_tx = self.network_tx.clone();
        let handle = core.handle();
        let server = listener
            .incoming()
            .for_each(move |(sock, addr)| {
                // Increment weak counter
                let holder = Rc::downgrade(&incoming_connections_counter);
                // Check incoming connections count
                let connections_count = Rc::weak_count(&incoming_connections_counter);
                if connections_count > incoming_connections_limit {
                    warn!(
                        "Rejected incoming connection with peer={}, \
                        connections limit reached.",
                        addr
                    );
                    return Ok(());
                }
                trace!("Accepted incoming connection with peer={}", addr);
                let stream = sock.framed(MessagesCodec);
                let (_, stream) = stream.split();
                let network_tx = network_tx.clone();
                let connection_handler = stream
                    .into_future()
                    .map_err(|e| e.0)
                    .and_then(move |(raw, stream)| match raw.map(Any::from_raw) {
                        Some(Ok(Any::Connect(msg))) => Ok((msg, stream)),
                        Some(Ok(other)) => Err(other_error(&format!(
                            "First message is not Connect, got={:?}",
                            other
                        ))),
                        Some(Err(e)) => Err(into_other(e)),
                        None => Err(other_error("Incoming socket closed")),
                    })
                    .and_then(move |(connect, stream)| {
                        trace!("Received handshake message={:?}", connect);
                        let event = NetworkEvent::PeerConnected(addr, connect);
                        let stream = network_tx
                            .clone()
                            .send(event)
                            .map_err(into_other)
                            .and_then(move |_| Ok(stream))
                            .flatten_stream();

                        stream.for_each(move |raw| {
                            let event = NetworkEvent::MessageReceived(addr, raw);
                            network_tx.clone().send(event).map(forget_result).map_err(
                                into_other,
                            )
                        })
                    })
                    .map(|_| {
                        // Ensure that holder lives until the stream ends.
                        let _holder = holder;
                    })
                    .map_err(log_error);
                handle.spawn(connection_handler);
                Ok(())
            })
            .map_err(log_error);

        core.handle().spawn(server);
        core.handle().spawn(requests_handle);
        core.run(
            cancel_handler
                .into_future()
                .map(|_| trace!("Network thread shutdown"))
                .map_err(|_| other_error("An error during `Core` shutdown occured")),
        )
    }
}

// Tcp streams source for retry action.
struct TcpStreamConnectAction {
    handle: Handle,
    peer: SocketAddr,
}

impl Action for TcpStreamConnectAction {
    type Future = TcpStreamNew;
    type Item = <TcpStreamNew as Future>::Item;
    type Error = <TcpStreamNew as Future>::Error;

    fn run(&mut self) -> Self::Future {
        TcpStream::connect(&self.peer, &self.handle)
    }
}
<commit_msg>Use ConnectionPool instead event<commit_after>// Copyright 2017 The Exonum Team
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use futures::{Future, IntoFuture, Sink, Stream};
use futures::future::Either;
use futures::sync::mpsc;
use futures::unsync;
use tokio_core::net::{TcpListener, TcpStream, TcpStreamNew};
use tokio_core::reactor::{Core, Handle};
use tokio_io::AsyncRead;
use tokio_retry::{Action, Retry};
use tokio_retry::strategy::{jitter, FixedInterval};

use std::io;
use std::net::SocketAddr;
use std::time::Duration;
use std::collections::HashMap;
use std::rc::Rc;
use std::cell::RefCell;

use messages::{Any, Connect, RawMessage};
use node::{ExternalMessage, NodeTimeout};
use helpers::Milliseconds;

use super::EventHandler;
use super::error::{forget_result, into_other, log_error, other_error, result_ok};
use super::codec::MessagesCodec;
use super::EventsAggregator;

#[derive(Debug)]
pub enum NetworkEvent {
    MessageReceived(SocketAddr, RawMessage),
    PeerConnected(SocketAddr, Connect),
    PeerDisconnected(SocketAddr),
}

#[derive(Debug, Clone)]
pub enum NetworkRequest {
    SendMessage(SocketAddr, RawMessage),
    DisconnectWithPeer(SocketAddr),
    Shutdown,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct NetworkConfiguration {
    // TODO: think more about config parameters
    pub max_incoming_connections: usize,
    pub max_outgoing_connections: usize,
    pub tcp_nodelay: bool,
    pub tcp_keep_alive: Option<u64>,
    pub tcp_connect_retry_timeout: Milliseconds,
    pub tcp_connect_max_retries: u64,
}

impl Default for NetworkConfiguration {
    fn default() -> NetworkConfiguration {
        NetworkConfiguration {
            max_incoming_connections: 128,
            max_outgoing_connections: 128,
            tcp_keep_alive: None,
            tcp_nodelay: false,
            tcp_connect_retry_timeout: 15_000,
            tcp_connect_max_retries: 10,
        }
    }
}

#[derive(Debug)]
pub struct HandlerPart<H: EventHandler> {
    pub core: Core,
    pub handler: H,
    pub timeout_rx: mpsc::Receiver<NodeTimeout>,
    pub network_rx: mpsc::Receiver<NetworkEvent>,
    pub api_rx: mpsc::Receiver<ExternalMessage>,
}

#[derive(Debug)]
pub struct NetworkPart {
    pub listen_address: SocketAddr,
    pub network_config: NetworkConfiguration,
    pub network_requests: (mpsc::Sender<NetworkRequest>, mpsc::Receiver<NetworkRequest>),
    pub network_tx: mpsc::Sender<NetworkEvent>,
}

#[derive(Debug, Default, Clone)]
struct ConnectionsPool {
    inner: Rc<RefCell<HashMap<SocketAddr, mpsc::Sender<RawMessage>>>>,
}

impl ConnectionsPool {
    fn new() -> ConnectionsPool {
        ConnectionsPool::default()
    }

    fn insert(&self, peer: SocketAddr, sender: &mpsc::Sender<RawMessage>) {
        self.inner.borrow_mut().insert(peer, sender.clone());
    }

    fn remove(&self, peer: &SocketAddr) {
        self.inner.borrow_mut().remove(peer);
    }

    fn get(&self, peer: SocketAddr) -> Option<mpsc::Sender<RawMessage>> {
        self.inner.borrow_mut().get(&peer).cloned()
    }
}

impl<H: EventHandler> HandlerPart<H> {
    pub fn run(self) -> Result<(), ()> {
        let mut core = self.core;
        let mut handler = self.handler;

        let events_handle = EventsAggregator::new(self.timeout_rx, self.network_rx, self.api_rx)
            .for_each(move |event| {
                handler.handle_event(event);
                Ok(())
            });
        core.run(events_handle)
    }
}

macro_rules! try_future_boxed {
    ($e:expr) =>(
        match $e {
            Ok(v) => v,
            Err(e) => {
                return Err(into_other(e)).into_future().boxed();
            }
        }
    )
}

impl NetworkPart {
    pub fn run(self) -> Result<(), io::Error> {
        let network_config = self.network_config;
        // Cancelation token
        let (cancel_sender, cancel_handler) = unsync::mpsc::channel(1);
        // Outgoing connections
        let outgoing_connections = ConnectionsPool::new();
        // Requests handler
        let mut core = Core::new()?;
        let handle = core.handle();
        let network_tx = self.network_tx.clone();
        let requests_handle = self.network_requests.1.for_each(move |request| {
            let network_tx = network_tx.clone();
            let cancel_sender = cancel_sender.clone();
            let outgoing_connections = outgoing_connections.clone();
            match request {
                NetworkRequest::SendMessage(peer, msg) => {
                    let conn_tx = if let Some(conn_tx) = outgoing_connections.get(peer) {
                        conn_tx
                    } else {
                        // Register outgoing channel.
                        let (conn_tx, conn_rx) = mpsc::channel(10);
                        outgoing_connections.insert(peer, &conn_tx);
                        // Enable retry feature for outgoing connection.
                        let timeout = network_config.tcp_connect_retry_timeout;
                        let max_tries = network_config.tcp_connect_max_retries as usize;
                        let connect_handle = Retry::spawn(
                            handle.clone(),
                            FixedInterval::from_millis(timeout)
                                .map(jitter)
                                .take(max_tries),
                            TcpStreamConnectAction {
                                handle: handle.clone(),
                                peer,
                            },
                        ).map_err(into_other)
                            .and_then(move |sock| {
                                try_future_boxed!(sock.set_nodelay(network_config.tcp_nodelay));
                                try_future_boxed!(sock.set_keepalive(
                                    network_config.tcp_keep_alive.map(Duration::from_millis),
                                ));

                                trace!("Established connection with peer={}", peer);

                                let stream = sock.framed(MessagesCodec);
                                let (sink, stream) = stream.split();

                                let writer = conn_rx
                                    .map_err(|_| other_error("Can't send data into socket"))
                                    .forward(sink);
                                let reader = stream.for_each(result_ok).map_err(into_other);

                                reader
                                    .select2(writer)
                                    .map_err(|_| other_error("Socket error"))
                                    .and_then(|res| match res {
                                        Either::A((_, _reader)) => Ok(()).into_future(),
                                        Either::B((_, _writer)) => Ok(()).into_future(),
                                    })
                                    .boxed()
                            })
                            .then(move |res| {
                                trace!("Connection with peer={} closed, reason={:?}", peer, res);
                                outgoing_connections.clone().remove(&peer);
                                network_tx
                                    .clone()
                                    .send(NetworkEvent::PeerDisconnected(peer))
                                    .map(forget_result)
                                    .map_err(into_other)
                            })
                            .map_err(log_error);
                        handle.spawn(connect_handle);
                        conn_tx
                    };

                    let send_handle = conn_tx.send(msg).map(forget_result).map_err(log_error);
                    handle.spawn(send_handle);
                }
                NetworkRequest::DisconnectWithPeer(peer) => {
                    outgoing_connections.remove(&peer);
                    Self::send_event(&handle, &network_tx, NetworkEvent::PeerDisconnected(peer));
                }
                // Immediately stop the event loop.
                NetworkRequest::Shutdown => cancel_sender
                    .clone()
                    .send(())
                    .map(forget_result)
                    .map_err(log_error)
                    .wait()?,
            }

            Ok(())
        });

        // Incoming connections limiter
        let incoming_connections_limit = network_config.max_incoming_connections;
        let incoming_connections_counter: Rc<()> = Rc::default();
        // Incoming connections handler
        let listener = TcpListener::bind(&self.listen_address, &core.handle())?;
        let network_tx = self.network_tx.clone();
        let handle = core.handle();
        let server = listener
            .incoming()
            .for_each(move |(sock, addr)| {
                // Increment reference counter
                let holder = Rc::downgrade(&incoming_connections_counter);
                // Check incoming connections count
                let connections_count = Rc::weak_count(&incoming_connections_counter);
                if connections_count > incoming_connections_limit {
                    warn!(
                        "Rejected incoming connection with peer={}, \
                         connections limit reached.",
                        addr
                    );
                    return Ok(());
                }
                trace!("Accepted incoming connection with peer={}", addr);
                let stream = sock.framed(MessagesCodec);
                let (_, stream) = stream.split();
                let network_tx = network_tx.clone();
                let connection_handler = stream
                    .into_future()
                    .map_err(|e| e.0)
                    .and_then(move |(raw, stream)| match raw.map(Any::from_raw) {
                        Some(Ok(Any::Connect(msg))) => Ok((msg, stream)),
                        Some(Ok(other)) => Err(other_error(
                            &format!("First message is not Connect, got={:?}", other),
                        )),
                        Some(Err(e)) => Err(into_other(e)),
                        None => Err(other_error("Incoming socket closed")),
                    })
                    .and_then(move |(connect, stream)| {
                        trace!("Received handshake message={:?}", connect);
                        let event = NetworkEvent::PeerConnected(addr, connect);
                        let stream = network_tx
                            .clone()
                            .send(event)
                            .map_err(into_other)
                            .and_then(move |_| Ok(stream))
                            .flatten_stream();

                        stream.for_each(move |raw| {
                            let event = NetworkEvent::MessageReceived(addr, raw);
                            network_tx
                                .clone()
                                .send(event)
                                .map(forget_result)
                                .map_err(into_other)
                        })
                    })
                    .map(|_| {
                        // Ensure that holder lives until the stream ends.
                        let _holder = holder;
                    })
                    .map_err(log_error);
                handle.spawn(connection_handler);
                Ok(())
            })
            .map_err(log_error);

        core.handle().spawn(server);
        core.handle().spawn(requests_handle);
        core.run(
            cancel_handler
                .into_future()
                .map(|_| trace!("Network thread shutdown"))
                .map_err(|_| other_error("An error during `Core` shutdown occured")),
        )
    }

    fn send_event(handle: &Handle, network_tx: &mpsc::Sender<NetworkEvent>, event: NetworkEvent) {
        handle.spawn(
            network_tx
                .clone()
                .send(event)
                .map(forget_result)
                .map_err(log_error),
        );
    }
}

// Tcp streams source for retry action.
struct TcpStreamConnectAction {
    handle: Handle,
    peer: SocketAddr,
}

impl Action for TcpStreamConnectAction {
    type Future = TcpStreamNew;
    type Item = <TcpStreamNew as Future>::Item;
    type Error = <TcpStreamNew as Future>::Error;

    fn run(&mut self) -> Self::Future {
        TcpStream::connect(&self.peer, &self.handle)
    }
}
<|endoftext|>


<commit_before>use std::env;

fn main() {
    let target = env::var("TARGET").expect("TARGET was not set");
    if target.contains("linux") {
        if target.contains("android") {
            println!("cargo:rustc-link-lib=dl");
            println!("cargo:rustc-link-lib=log");
            println!("cargo:rustc-link-lib=gcc");
        } else if !target.contains("musl") {
            println!("cargo:rustc-link-lib=dl");
            println!("cargo:rustc-link-lib=rt");
            println!("cargo:rustc-link-lib=pthread");
        }
    } else if target.contains("freebsd") {
        println!("cargo:rustc-link-lib=execinfo");
        println!("cargo:rustc-link-lib=pthread");
    } else if target.contains("netbsd") {
        println!("cargo:rustc-link-lib=pthread");
        println!("cargo:rustc-link-lib=rt");
    } else if target.contains("dragonfly") || target.contains("openbsd") {
        println!("cargo:rustc-link-lib=pthread");
    } else if target.contains("solaris") {
        println!("cargo:rustc-link-lib=socket");
        println!("cargo:rustc-link-lib=posix4");
        println!("cargo:rustc-link-lib=pthread");
        println!("cargo:rustc-link-lib=resolv");
    } else if target.contains("apple-darwin") {
        println!("cargo:rustc-link-lib=System");

        // res_init and friends require -lresolv on macOS/iOS.
        // See #41582 and http://blog.achernya.com/2013/03/os-x-has-silly-libsystem.html
        println!("cargo:rustc-link-lib=resolv");
    } else if target.contains("apple-ios") {
        println!("cargo:rustc-link-lib=System");
        println!("cargo:rustc-link-lib=objc");
        println!("cargo:rustc-link-lib=framework=Security");
        println!("cargo:rustc-link-lib=framework=Foundation");
        println!("cargo:rustc-link-lib=resolv");
    } else if target.contains("uwp") {
        println!("cargo:rustc-link-lib=ws2_32");
        // For BCryptGenRandom
        println!("cargo:rustc-link-lib=bcrypt");
    } else if target.contains("windows") {
        println!("cargo:rustc-link-lib=advapi32");
        println!("cargo:rustc-link-lib=ws2_32");
        println!("cargo:rustc-link-lib=userenv");
    } else if target.contains("fuchsia") {
        println!("cargo:rustc-link-lib=zircon");
        println!("cargo:rustc-link-lib=fdio");
    } else if target.contains("cloudabi") {
        if cfg!(feature = "backtrace") {
            println!("cargo:rustc-link-lib=unwind");
        }
        println!("cargo:rustc-link-lib=c");
        println!("cargo:rustc-link-lib=compiler_rt");
    } else if target.contains("hermit") {
        println!("cargo:rustc-link-lib=hermit");
    }
}
<commit_msg>remove dependency from libhermit<commit_after>use std::env;

fn main() {
    let target = env::var("TARGET").expect("TARGET was not set");
    if target.contains("linux") {
        if target.contains("android") {
            println!("cargo:rustc-link-lib=dl");
            println!("cargo:rustc-link-lib=log");
            println!("cargo:rustc-link-lib=gcc");
        } else if !target.contains("musl") {
            println!("cargo:rustc-link-lib=dl");
            println!("cargo:rustc-link-lib=rt");
            println!("cargo:rustc-link-lib=pthread");
        }
    } else if target.contains("freebsd") {
        println!("cargo:rustc-link-lib=execinfo");
        println!("cargo:rustc-link-lib=pthread");
    } else if target.contains("netbsd") {
        println!("cargo:rustc-link-lib=pthread");
        println!("cargo:rustc-link-lib=rt");
    } else if target.contains("dragonfly") || target.contains("openbsd") {
        println!("cargo:rustc-link-lib=pthread");
    } else if target.contains("solaris") {
        println!("cargo:rustc-link-lib=socket");
        println!("cargo:rustc-link-lib=posix4");
        println!("cargo:rustc-link-lib=pthread");
        println!("cargo:rustc-link-lib=resolv");
    } else if target.contains("apple-darwin") {
        println!("cargo:rustc-link-lib=System");

        // res_init and friends require -lresolv on macOS/iOS.
        // See #41582 and http://blog.achernya.com/2013/03/os-x-has-silly-libsystem.html
        println!("cargo:rustc-link-lib=resolv");
    } else if target.contains("apple-ios") {
        println!("cargo:rustc-link-lib=System");
        println!("cargo:rustc-link-lib=objc");
        println!("cargo:rustc-link-lib=framework=Security");
        println!("cargo:rustc-link-lib=framework=Foundation");
        println!("cargo:rustc-link-lib=resolv");
    } else if target.contains("uwp") {
        println!("cargo:rustc-link-lib=ws2_32");
        // For BCryptGenRandom
        println!("cargo:rustc-link-lib=bcrypt");
    } else if target.contains("windows") {
        println!("cargo:rustc-link-lib=advapi32");
        println!("cargo:rustc-link-lib=ws2_32");
        println!("cargo:rustc-link-lib=userenv");
    } else if target.contains("fuchsia") {
        println!("cargo:rustc-link-lib=zircon");
        println!("cargo:rustc-link-lib=fdio");
    } else if target.contains("cloudabi") {
        if cfg!(feature = "backtrace") {
            println!("cargo:rustc-link-lib=unwind");
        }
        println!("cargo:rustc-link-lib=c");
        println!("cargo:rustc-link-lib=compiler_rt");
    }
}
<|endoftext|>


<commit_before>use std::cell::RefCell;
use std::collections::HashMap;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::Write;
use std::path::PathBuf;
use std::rc::Rc;
use time::Tm;

use libtiny_ui::*;

#[macro_use]
extern crate log;

#[derive(Clone)]
pub struct Logger {
    inner: Rc<RefCell<LoggerInner>>,
}

#[derive(Debug)]
pub enum LoggerInitError {
    CouldNotCreateDir { dir_path: PathBuf, err: io::Error },
}

impl Logger {
    pub fn new(
        log_dir: PathBuf,
        report_err: Box<dyn Fn(String)>,
    ) -> Result<Logger, LoggerInitError> {
        Ok(Logger {
            inner: Rc::new(RefCell::new(LoggerInner::new(log_dir, report_err)?)),
        })
    }
}

macro_rules! delegate {
    ( $name:ident ( $( $x:ident: $t:ty, )* ) ) => {
        fn $name(&self, $($x: $t,)*) {
            self.inner.borrow_mut().$name( $( $x, )* )
        }
    }
}

impl UI for Logger {
    fn draw(&self) {}
    delegate!(new_server_tab(serv: &str, alias: Option<String>,));
    delegate!(close_server_tab(serv: &str,));
    delegate!(new_chan_tab(serv: &str, chan: &str,));
    delegate!(close_chan_tab(serv: &str, chan: &str,));
    delegate!(close_user_tab(serv: &str, nick: &str,));
    delegate!(add_client_msg(msg: &str, target: &MsgTarget,));
    delegate!(add_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_err_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_client_err_msg(msg: &str, target: &MsgTarget,));
    delegate!(clear_nicks(serv: &str,));
    delegate!(set_nick(serv: &str, nick: &str,));
    delegate!(add_privmsg(
        sender: &str,
        msg: &str,
        ts: Tm,
        target: &MsgTarget,
        highlight: bool,
        is_action: bool,
    ));
    delegate!(add_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(remove_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(rename_nick(
        old_nick: &str,
        new_nick: &str,
        ts: Tm,
        target: &MsgTarget,
    ));
    delegate!(set_topic(topic: &str, ts: Tm, serv: &str, chan: &str,));
    delegate!(set_tab_style(style: TabStyle, target: &MsgTarget,));

    // TODO: Maybe just return true?
    fn user_tab_exists(&self, _serv: &str, _nick: &str) -> bool {
        false
    }
}

struct LoggerInner {
    /// Log file directory
    log_dir: PathBuf,

    /// Maps server names to their fds
    servers: HashMap<String, ServerLogs>,

    /// Callback used when reporting errors
    report_err: Box<dyn Fn(String)>,
}

struct ServerLogs {
    fd: File,
    chans: HashMap<String, File>,
    users: HashMap<String, File>,
}

fn print_header(fd: &mut File) -> io::Result<()> {
    writeln!(fd)?;
    writeln!(
        fd,
        "*** Logging started at {}",
        time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).unwrap()
    )?;
    writeln!(fd)
}

macro_rules! report_io_err {
    ( $f:expr, $e:expr ) => {
        match $e {
            Err(err) => {
                debug!("{:?}", err);
                $f(format!("{:?}", err));
                return;
            }
            Ok(ok) => ok,
        }
    };
}

// '/' is valid in channel names but we can't use it in file names, so we replace it with '-'.
// According to RFC 2812 nick names can't contain '/', but we still use this in nicks just to be
// safe. Other special characters mentioned in the RFC ("[]\`^{|}") can be used in file names so we
// don't replace those.
fn replace_forward_slash(path: &str) -> String {
    path.replace('/', "-")
}

fn try_open_log_file(path: &PathBuf, report_err: &dyn Fn(String)) -> Option<File> {
    match OpenOptions::new().create(true).append(true).open(path) {
        Ok(fd) => Some(fd),
        Err(err) => {
            report_err(format!("Couldn't open file {:?}: {}", path, err));
            None
        }
    }
}

impl LoggerInner {
    fn new(
        log_dir: PathBuf,
        report_err: Box<dyn Fn(String)>,
    ) -> Result<LoggerInner, LoggerInitError> {
        if let Err(err) = fs::create_dir(&log_dir) {
            if err.kind() != io::ErrorKind::AlreadyExists {
                return Err(LoggerInitError::CouldNotCreateDir {
                    dir_path: log_dir,
                    err,
                });
            }
        }

        Ok(LoggerInner {
            log_dir,
            servers: HashMap::new(),
            report_err,
        })
    }

    fn new_server_tab(&mut self, serv: &str, _alias: Option<String>) {
        let mut path = self.log_dir.clone();
        path.push(&format!("{}.txt", serv));
        debug!("Trying to open log file: {:?}", path);
        if let Some(mut fd) = try_open_log_file(&path, &*self.report_err) {
            report_io_err!(self.report_err, print_header(&mut fd));
            self.servers.insert(
                serv.to_string(),
                ServerLogs {
                    fd,
                    chans: HashMap::new(),
                    users: HashMap::new(),
                },
            );
        }
    }

    fn close_server_tab(&mut self, serv: &str) {
        self.servers.remove(serv);
    }

    fn new_chan_tab(&mut self, serv: &str, chan: &str) {
        match self.servers.get_mut(serv) {
            None => {
                debug!("new_chan_tab: can't find server: {}", serv);
            }
            Some(server) => {
                let mut path = self.log_dir.clone();
                path.push(&format!("{}_{}.txt", serv, replace_forward_slash(chan)));
                debug!("Trying to open log file: {:?}", path);
                if let Some(mut fd) = try_open_log_file(&path, &*self.report_err) {
                    report_io_err!(self.report_err, print_header(&mut fd));
                    server.chans.insert(chan.to_string(), fd);
                }
            }
        }
    }

    fn close_chan_tab(&mut self, serv: &str, chan: &str) {
        match self.servers.get_mut(serv) {
            None => {
                debug!("close_chan_tab: can't find server: {}", serv);
            }
            Some(server) => {
                server.chans.remove(chan);
            }
        }
    }

    fn close_user_tab(&mut self, serv: &str, nick: &str) {
        match self.servers.get_mut(serv) {
            None => {
                debug!("close_user_tab: can't find server: {}", serv);
            }
            Some(server) => {
                server.users.remove(nick);
            }
        }
    }

    fn add_client_msg(&mut self, msg: &str, target: &MsgTarget) {
        let now = now();
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(report_err, writeln!(fd, "[{}] [client] {}", now, msg));
        });
    }

    fn add_msg(&mut self, msg: &str, ts: Tm, target: &MsgTarget) {
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(report_err, writeln!(fd, "[{}] {}", strf(&ts), msg));
        });
    }

    fn add_err_msg(&self, _msg: &str, _ts: Tm, _target: &MsgTarget) {
        // IRC error messages are ignored
    }

    fn add_client_err_msg(&self, _msg: &str, _target: &MsgTarget) {
        // Ditto with client error messages
    }

    fn clear_nicks(&self, _serv: &str) {
        // Nothing to do here
    }

    fn set_nick(&self, _serv: &str, _nick: &str) {
        // Ditto
    }

    fn add_privmsg(
        &mut self,
        sender: &str,
        msg: &str,
        ts: Tm,
        target: &MsgTarget,
        _highlight: bool,
        is_action: bool,
    ) {
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            let io_ret = if is_action {
                writeln!(fd, "[{}] {} {}", strf(&ts), sender, msg)
            } else {
                writeln!(fd, "[{}] {}: {}", strf(&ts), sender, msg)
            };
            report_io_err!(report_err, io_ret);
        });
    }

    fn add_nick(&mut self, nick: &str, ts: Option<Tm>, target: &MsgTarget) {
        if let Some(_ts) = ts {
            // This method is only called when a user joins a chan
            self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
                report_io_err!(
                    report_err,
                    writeln!(fd, "[{}] {} joined the channel.", now(), nick)
                );
            });
        }
    }

    fn remove_nick(&mut self, nick: &str, ts: Option<Tm>, target: &MsgTarget) {
        if let Some(_ts) = ts {
            // TODO: Did the user leave a channel or the server? Currently we can't tell.
            self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
                report_io_err!(report_err, writeln!(fd, "[{}] {} left.", now(), nick));
            });
        }
    }

    fn rename_nick(&mut self, old_nick: &str, new_nick: &str, ts: Tm, target: &MsgTarget) {
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(
                report_err,
                writeln!(
                    fd,
                    "[{}] {} is now known as {}.",
                    strf(&ts),
                    old_nick,
                    new_nick
                )
            );
        });
    }

    fn set_topic(&mut self, topic: &str, ts: Tm, serv: &str, chan: &str) {
        let target = MsgTarget::Chan { serv, chan };
        self.apply_to_target(&target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(
                report_err,
                writeln!(fd, "[{}] Channel topic: {}.", strf(&ts), topic)
            );
        });
    }

    fn set_tab_style(&self, _: TabStyle, _: &MsgTarget) {
        // Nothing to do here
    }

    fn apply_to_target(&mut self, target: &MsgTarget, f: impl Fn(&mut File, &dyn Fn(String))) {
        match *target {
            MsgTarget::Server { serv } => match self.servers.get_mut(serv) {
                None => {
                    debug!("Can't find server: {}", serv);
                }
                Some(ServerLogs { ref mut fd, .. }) => {
                    f(fd, &*self.report_err);
                }
            },
            MsgTarget::Chan { serv, chan } => match self.servers.get_mut(serv) {
                None => {
                    debug!("Can't find server: {}", serv);
                }
                Some(ServerLogs { ref mut chans, .. }) => match chans.get_mut(chan) {
                    None => {
                        debug!("Can't find chan {} in server {}", chan, serv);
                    }
                    Some(fd) => {
                        f(fd, &*self.report_err);
                    }
                },
            },
            MsgTarget::User { serv, nick } => {
                match self.servers.get_mut(serv) {
                    None => {
                        debug!("Can't find server: {}", serv);
                    }
                    Some(ServerLogs { ref mut users, .. }) => {
                        match users.get_mut(nick) {
                            Some(fd) => {
                                f(fd, &*self.report_err);
                            }
                            None => {
                                // We don't have a `new_user_tab` trait method so user log files
                                // are created here
                                let mut path = self.log_dir.clone();
                                path.push(&format!("{}_{}.txt", serv, replace_forward_slash(nick)));
                                debug!("Trying to open log file: {:?}", path);
                                if let Some(mut fd) = try_open_log_file(&path, &*self.report_err) {
                                    report_io_err!(self.report_err, print_header(&mut fd));
                                    f(&mut fd, &*self.report_err);
                                    users.insert(nick.to_owned(), fd);
                                }
                            }
                        }
                    }
                }
            }
            MsgTarget::AllServTabs { serv } => match self.servers.get_mut(serv) {
                None => {
                    debug!("Can't find server: {}", serv);
                }
                Some(ServerLogs {
                    ref mut fd,
                    ref mut chans,
                    ref mut users,
                    ..
                }) => {
                    f(fd, &*self.report_err);
                    for (_, fd) in chans.iter_mut() {
                        f(fd, &*self.report_err);
                    }
                    for (_, fd) in users.iter_mut() {
                        f(fd, &*self.report_err);
                    }
                }
            },
            MsgTarget::CurrentTab => {
                // Probably a cmd error; these are ignored
            }
        }
    }
}

fn now() -> String {
    time::strftime("%H:%M:%S", &time::now()).unwrap()
}

fn strf(tm: &Tm) -> String {
    time::strftime("%H:%M:%S", tm).unwrap()
}
<commit_msg>logger: Create parent directories of log dir<commit_after>use std::cell::RefCell;
use std::collections::HashMap;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io;
use std::io::Write;
use std::path::PathBuf;
use std::rc::Rc;
use time::Tm;

use libtiny_ui::*;

#[macro_use]
extern crate log;

#[derive(Clone)]
pub struct Logger {
    inner: Rc<RefCell<LoggerInner>>,
}

#[derive(Debug)]
pub enum LoggerInitError {
    CouldNotCreateDir { dir_path: PathBuf, err: io::Error },
}

impl Logger {
    pub fn new(
        log_dir: PathBuf,
        report_err: Box<dyn Fn(String)>,
    ) -> Result<Logger, LoggerInitError> {
        Ok(Logger {
            inner: Rc::new(RefCell::new(LoggerInner::new(log_dir, report_err)?)),
        })
    }
}

macro_rules! delegate {
    ( $name:ident ( $( $x:ident: $t:ty, )* ) ) => {
        fn $name(&self, $($x: $t,)*) {
            self.inner.borrow_mut().$name( $( $x, )* )
        }
    }
}

impl UI for Logger {
    fn draw(&self) {}
    delegate!(new_server_tab(serv: &str, alias: Option<String>,));
    delegate!(close_server_tab(serv: &str,));
    delegate!(new_chan_tab(serv: &str, chan: &str,));
    delegate!(close_chan_tab(serv: &str, chan: &str,));
    delegate!(close_user_tab(serv: &str, nick: &str,));
    delegate!(add_client_msg(msg: &str, target: &MsgTarget,));
    delegate!(add_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_err_msg(msg: &str, ts: Tm, target: &MsgTarget,));
    delegate!(add_client_err_msg(msg: &str, target: &MsgTarget,));
    delegate!(clear_nicks(serv: &str,));
    delegate!(set_nick(serv: &str, nick: &str,));
    delegate!(add_privmsg(
        sender: &str,
        msg: &str,
        ts: Tm,
        target: &MsgTarget,
        highlight: bool,
        is_action: bool,
    ));
    delegate!(add_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(remove_nick(nick: &str, ts: Option<Tm>, target: &MsgTarget,));
    delegate!(rename_nick(
        old_nick: &str,
        new_nick: &str,
        ts: Tm,
        target: &MsgTarget,
    ));
    delegate!(set_topic(topic: &str, ts: Tm, serv: &str, chan: &str,));
    delegate!(set_tab_style(style: TabStyle, target: &MsgTarget,));

    // TODO: Maybe just return true?
    fn user_tab_exists(&self, _serv: &str, _nick: &str) -> bool {
        false
    }
}

struct LoggerInner {
    /// Log file directory
    log_dir: PathBuf,

    /// Maps server names to their fds
    servers: HashMap<String, ServerLogs>,

    /// Callback used when reporting errors
    report_err: Box<dyn Fn(String)>,
}

struct ServerLogs {
    fd: File,
    chans: HashMap<String, File>,
    users: HashMap<String, File>,
}

fn print_header(fd: &mut File) -> io::Result<()> {
    writeln!(fd)?;
    writeln!(
        fd,
        "*** Logging started at {}",
        time::strftime("%Y-%m-%d %H:%M:%S", &time::now()).unwrap()
    )?;
    writeln!(fd)
}

macro_rules! report_io_err {
    ( $f:expr, $e:expr ) => {
        match $e {
            Err(err) => {
                debug!("{:?}", err);
                $f(format!("{:?}", err));
                return;
            }
            Ok(ok) => ok,
        }
    };
}

// '/' is valid in channel names but we can't use it in file names, so we replace it with '-'.
// According to RFC 2812 nick names can't contain '/', but we still use this in nicks just to be
// safe. Other special characters mentioned in the RFC ("[]\`^{|}") can be used in file names so we
// don't replace those.
fn replace_forward_slash(path: &str) -> String {
    path.replace('/', "-")
}

fn try_open_log_file(path: &PathBuf, report_err: &dyn Fn(String)) -> Option<File> {
    match OpenOptions::new().create(true).append(true).open(path) {
        Ok(fd) => Some(fd),
        Err(err) => {
            report_err(format!("Couldn't open file {:?}: {}", path, err));
            None
        }
    }
}

impl LoggerInner {
    fn new(
        log_dir: PathBuf,
        report_err: Box<dyn Fn(String)>,
    ) -> Result<LoggerInner, LoggerInitError> {
        if let Err(err) = fs::create_dir_all(&log_dir) {
            if err.kind() != io::ErrorKind::AlreadyExists {
                return Err(LoggerInitError::CouldNotCreateDir {
                    dir_path: log_dir,
                    err,
                });
            }
        }

        Ok(LoggerInner {
            log_dir,
            servers: HashMap::new(),
            report_err,
        })
    }

    fn new_server_tab(&mut self, serv: &str, _alias: Option<String>) {
        let mut path = self.log_dir.clone();
        path.push(&format!("{}.txt", serv));
        debug!("Trying to open log file: {:?}", path);
        if let Some(mut fd) = try_open_log_file(&path, &*self.report_err) {
            report_io_err!(self.report_err, print_header(&mut fd));
            self.servers.insert(
                serv.to_string(),
                ServerLogs {
                    fd,
                    chans: HashMap::new(),
                    users: HashMap::new(),
                },
            );
        }
    }

    fn close_server_tab(&mut self, serv: &str) {
        self.servers.remove(serv);
    }

    fn new_chan_tab(&mut self, serv: &str, chan: &str) {
        match self.servers.get_mut(serv) {
            None => {
                debug!("new_chan_tab: can't find server: {}", serv);
            }
            Some(server) => {
                let mut path = self.log_dir.clone();
                path.push(&format!("{}_{}.txt", serv, replace_forward_slash(chan)));
                debug!("Trying to open log file: {:?}", path);
                if let Some(mut fd) = try_open_log_file(&path, &*self.report_err) {
                    report_io_err!(self.report_err, print_header(&mut fd));
                    server.chans.insert(chan.to_string(), fd);
                }
            }
        }
    }

    fn close_chan_tab(&mut self, serv: &str, chan: &str) {
        match self.servers.get_mut(serv) {
            None => {
                debug!("close_chan_tab: can't find server: {}", serv);
            }
            Some(server) => {
                server.chans.remove(chan);
            }
        }
    }

    fn close_user_tab(&mut self, serv: &str, nick: &str) {
        match self.servers.get_mut(serv) {
            None => {
                debug!("close_user_tab: can't find server: {}", serv);
            }
            Some(server) => {
                server.users.remove(nick);
            }
        }
    }

    fn add_client_msg(&mut self, msg: &str, target: &MsgTarget) {
        let now = now();
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(report_err, writeln!(fd, "[{}] [client] {}", now, msg));
        });
    }

    fn add_msg(&mut self, msg: &str, ts: Tm, target: &MsgTarget) {
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(report_err, writeln!(fd, "[{}] {}", strf(&ts), msg));
        });
    }

    fn add_err_msg(&self, _msg: &str, _ts: Tm, _target: &MsgTarget) {
        // IRC error messages are ignored
    }

    fn add_client_err_msg(&self, _msg: &str, _target: &MsgTarget) {
        // Ditto with client error messages
    }

    fn clear_nicks(&self, _serv: &str) {
        // Nothing to do here
    }

    fn set_nick(&self, _serv: &str, _nick: &str) {
        // Ditto
    }

    fn add_privmsg(
        &mut self,
        sender: &str,
        msg: &str,
        ts: Tm,
        target: &MsgTarget,
        _highlight: bool,
        is_action: bool,
    ) {
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            let io_ret = if is_action {
                writeln!(fd, "[{}] {} {}", strf(&ts), sender, msg)
            } else {
                writeln!(fd, "[{}] {}: {}", strf(&ts), sender, msg)
            };
            report_io_err!(report_err, io_ret);
        });
    }

    fn add_nick(&mut self, nick: &str, ts: Option<Tm>, target: &MsgTarget) {
        if let Some(_ts) = ts {
            // This method is only called when a user joins a chan
            self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
                report_io_err!(
                    report_err,
                    writeln!(fd, "[{}] {} joined the channel.", now(), nick)
                );
            });
        }
    }

    fn remove_nick(&mut self, nick: &str, ts: Option<Tm>, target: &MsgTarget) {
        if let Some(_ts) = ts {
            // TODO: Did the user leave a channel or the server? Currently we can't tell.
            self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
                report_io_err!(report_err, writeln!(fd, "[{}] {} left.", now(), nick));
            });
        }
    }

    fn rename_nick(&mut self, old_nick: &str, new_nick: &str, ts: Tm, target: &MsgTarget) {
        self.apply_to_target(target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(
                report_err,
                writeln!(
                    fd,
                    "[{}] {} is now known as {}.",
                    strf(&ts),
                    old_nick,
                    new_nick
                )
            );
        });
    }

    fn set_topic(&mut self, topic: &str, ts: Tm, serv: &str, chan: &str) {
        let target = MsgTarget::Chan { serv, chan };
        self.apply_to_target(&target, |fd: &mut File, report_err: &dyn Fn(String)| {
            report_io_err!(
                report_err,
                writeln!(fd, "[{}] Channel topic: {}.", strf(&ts), topic)
            );
        });
    }

    fn set_tab_style(&self, _: TabStyle, _: &MsgTarget) {
        // Nothing to do here
    }

    fn apply_to_target(&mut self, target: &MsgTarget, f: impl Fn(&mut File, &dyn Fn(String))) {
        match *target {
            MsgTarget::Server { serv } => match self.servers.get_mut(serv) {
                None => {
                    debug!("Can't find server: {}", serv);
                }
                Some(ServerLogs { ref mut fd, .. }) => {
                    f(fd, &*self.report_err);
                }
            },
            MsgTarget::Chan { serv, chan } => match self.servers.get_mut(serv) {
                None => {
                    debug!("Can't find server: {}", serv);
                }
                Some(ServerLogs { ref mut chans, .. }) => match chans.get_mut(chan) {
                    None => {
                        debug!("Can't find chan {} in server {}", chan, serv);
                    }
                    Some(fd) => {
                        f(fd, &*self.report_err);
                    }
                },
            },
            MsgTarget::User { serv, nick } => {
                match self.servers.get_mut(serv) {
                    None => {
                        debug!("Can't find server: {}", serv);
                    }
                    Some(ServerLogs { ref mut users, .. }) => {
                        match users.get_mut(nick) {
                            Some(fd) => {
                                f(fd, &*self.report_err);
                            }
                            None => {
                                // We don't have a `new_user_tab` trait method so user log files
                                // are created here
                                let mut path = self.log_dir.clone();
                                path.push(&format!("{}_{}.txt", serv, replace_forward_slash(nick)));
                                debug!("Trying to open log file: {:?}", path);
                                if let Some(mut fd) = try_open_log_file(&path, &*self.report_err) {
                                    report_io_err!(self.report_err, print_header(&mut fd));
                                    f(&mut fd, &*self.report_err);
                                    users.insert(nick.to_owned(), fd);
                                }
                            }
                        }
                    }
                }
            }
            MsgTarget::AllServTabs { serv } => match self.servers.get_mut(serv) {
                None => {
                    debug!("Can't find server: {}", serv);
                }
                Some(ServerLogs {
                    ref mut fd,
                    ref mut chans,
                    ref mut users,
                    ..
                }) => {
                    f(fd, &*self.report_err);
                    for (_, fd) in chans.iter_mut() {
                        f(fd, &*self.report_err);
                    }
                    for (_, fd) in users.iter_mut() {
                        f(fd, &*self.report_err);
                    }
                }
            },
            MsgTarget::CurrentTab => {
                // Probably a cmd error; these are ignored
            }
        }
    }
}

fn now() -> String {
    time::strftime("%H:%M:%S", &time::now()).unwrap()
}

fn strf(tm: &Tm) -> String {
    time::strftime("%H:%M:%S", tm).unwrap()
}
<|endoftext|>