<commit_before>use std::time::Duration;
use std::collections::HashMap;

use telebot;
use telebot::functions::*;
use tokio_core::reactor::{Interval, Handle, Timeout};
use futures::{self, Future, Stream, IntoFuture};
use tokio_curl::Session;
use regex::Regex;

use data;
use feed;
use utlis::{Escape, EscapeUrl, send_multiple_messages, format_and_split_msgs, to_chinese_error_msg,
            truncate_message, log_error, TELEGRAM_MAX_MSG_LEN};

// 5 minute
const FREQUENCY_SECOND: u64 = 300;

lazy_static!{
    // it's different from `feed::HOST`, so maybe need a better name?
    static ref HOST: Regex = Regex::new(r"^(?:https?://)?([^/]+)").unwrap();
}

pub fn spawn_fetcher(bot: telebot::RcBot, db: data::Database, handle: Handle) {
    handle.clone().spawn(Interval::new(Duration::from_secs(FREQUENCY_SECOND), &handle)
                             .expect("failed to start feed loop")
                             .map_err(|e| error!("feed loop error: {}", e))
                             .for_each(move |_| {
        let feeds = db.get_all_feeds();
        let grouped_feeds = grouping_by_host(feeds);
        let group_iter = futures::stream::iter(grouped_feeds.into_iter().map(|x| Ok(x)));
        let handle2 = handle.clone();
        let bot = bot.clone();
        let db = db.clone();
        let group_fetcher = group_iter.for_each(move |feeds| {
            let session = Session::new(handle2.clone());
            for feed in feeds {
                let fetcher = fetch_feed_updates(bot.clone(), db.clone(), session.clone(), feed);
                handle2.spawn(fetcher);
            }
            Timeout::new(Duration::from_secs(1), &handle2)
                .expect("failed to start sleep")
                .map_err(|e| error!("feed loop sleep error: {}", e))
        });
        handle.spawn(group_fetcher);
        Ok(())
    }))
}

fn grouping_by_host(feeds: Vec<data::Feed>) -> Vec<Vec<data::Feed>> {
    let mut result = HashMap::new();
    for feed in feeds {
        let host = get_host(&feed.link).to_owned();
        let group = result.entry(host).or_insert_with(|| Vec::new());
        group.push(feed);
    }
    result.into_iter().map(|(_, v)| v).collect()
}

fn get_host(url: &str) -> &str {
    HOST.captures(url).map_or(url, |r| r.get(0).unwrap().as_str())
}

fn shoud_unsubscribe_for_user(tg_err_msg: &str) -> bool {
    tg_err_msg.contains("Forbidden") || tg_err_msg.contains("chat not found") ||
    tg_err_msg.contains("group chat was migrated to a supergroup chat")
}

fn fetch_feed_updates<'a>(bot: telebot::RcBot,
                          db: data::Database,
                          session: Session,
                          feed: data::Feed)
                          -> impl Future<Item = (), Error = ()> + 'a {
    let bot_ = bot.clone();
    let db_ = db.clone();
    let feed_ = feed.clone();
    feed::fetch_feed(session, feed.link.to_owned())
        .map(move |rss| (bot_, db_, rss, feed_))
        .or_else(move |e| {
            if db.inc_error_count(&feed.link) > 1440 {
                    Err((bot, db, feed))
                } else {
                    Ok(())
                }
                .into_future()
                .or_else(|(bot, db, feed)| {
                    // 1440 * 5 minute = 5 days
                    db.reset_error_count(&feed.link);
                    let err_msg = to_chinese_error_msg(e);
                    let mut msgs = Vec::with_capacity(feed.subscribers.len());
                    for &subscriber in &feed.subscribers {
                        let m = bot.message(subscriber,
                                            format!("《{}》\
                                                     已经连续 5 天拉取出错 ({}),\
                                                     可能已经关闭, 请取消订阅",
                                                    EscapeUrl(&feed.link),
                                                    Escape(&feed.title),
                                                    Escape(&err_msg)))
                            .parse_mode("HTML")
                            .disable_web_page_preview(true)
                            .send();
                        let feed_link = feed.link.clone();
                        let db = db.clone();
                        let bot = bot.clone();
                        let r = m.or_else(move |e| {
                            match e {
                                    telebot::error::Error::Telegram(ref s)
                                        if shoud_unsubscribe_for_user(s) => {
                                            Err((bot, db, s.to_owned(), subscriber, feed_link))
                                        }
                                    _ => {
                                        warn!("failed to send error to {}, {:?}", subscriber, e);
                                        Ok(())
                                    }
                                }
                                .into_future()
                                .or_else(|(bot, db, s, subscriber, feed_link)| {
                                    if let Err(e) = db.unsubscribe(subscriber, &feed_link) {
                                        log_error(&e);
                                    }
                                    bot.message(subscriber,
                                                 format!("无法修复的错误 ({}), 自动退订",
                                                         s))
                                        .send()
                                        .then(|_| Err(()))
                                })
                                .and_then(|_| Err(()))
                        });
                        // if not use Box, rustc will panic
                        msgs.push(Box::new(r) as Box<Future<Item = _, Error = _>>);
                    }
                    futures::future::join_all(msgs).then(|_| Err(()))
                })
                .and_then(|_| Err(()))
        })
        .and_then(|(bot, db, rss, feed)| {
            if rss.title != feed.title {
                db.update_title(&feed.link, &rss.title);
            }
            let updates = db.update(&feed.link, rss.items);
            if updates.is_empty() {
                futures::future::err(())
            } else {
                futures::future::ok((bot, db, feed, rss.title, rss.link, updates))
            }
        })
        .and_then(|(bot, db, feed, rss_title, rss_link, updates)| {
            let msgs = format_and_split_msgs(format!("{}", rss_title), &updates, |item| {
                let title = item.title
                    .as_ref()
                    .map(|s| s.as_str())
                    .unwrap_or_else(|| &rss_title);
                let link = item.link
                    .as_ref()
                    .map(|s| s.as_str())
                    .unwrap_or_else(|| &rss_link);
                format!("{}",
                        EscapeUrl(link),
                        Escape(&truncate_message(title, TELEGRAM_MAX_MSG_LEN - 500)))
            });

            let mut msg_futures = Vec::with_capacity(feed.subscribers.len());
            for &subscriber in &feed.subscribers {
                let feed_link = feed.link.clone();
                let db = db.clone();
                let bot = bot.clone();
                let r = send_multiple_messages(&bot, subscriber, msgs.clone())
                    .or_else(move |e| {
                        match e {
                                telebot::error::Error::Telegram(ref s)
                                if shoud_unsubscribe_for_user(s) => {
                                Err((bot, db, s.to_owned(), subscriber, feed_link))
                            }
                                _ => {
                                    warn!("failed to send updates to {}, {:?}", subscriber, e);
                                    Ok(())
                                }
                            }
                            .into_future()
                            .or_else(|(bot, db, s, subscriber, feed_link)| {
                                if let Err(e) = db.unsubscribe(subscriber, &feed_link) {
                                    log_error(&e);
                                }
                                bot.message(subscriber,
                                             format!("无法修复的错误 ({}), 自动退订", s))
                                    .send()
                                    .then(|_| Err(()))
                            })
                    });
                msg_futures.push(Box::new(r) as Box<Future<Item = _, Error = _>>);
            }
            futures::future::join_all(msg_futures).then(|_| Ok(()))
        })
}
<commit_msg>better connection reuse<commit_after>use std::time::Duration;
use std::collections::HashMap;

use telebot;
use telebot::functions::*;
use tokio_core::reactor::{Interval, Handle, Timeout};
use futures::{self, Future, Stream, IntoFuture};
use tokio_curl::Session;
use regex::Regex;

use data;
use feed;
use utlis::{Escape, EscapeUrl, send_multiple_messages, format_and_split_msgs, to_chinese_error_msg,
            truncate_message, log_error, TELEGRAM_MAX_MSG_LEN};

// 5 minute
const FREQUENCY_SECOND: u64 = 300;

lazy_static!{
    // it's different from `feed::HOST`, so maybe need a better name?
    static ref HOST: Regex = Regex::new(r"^(?:https?://)?([^/]+)").unwrap();
}

pub fn spawn_fetcher(bot: telebot::RcBot, db: data::Database, handle: Handle) {
    handle.clone().spawn(Interval::new(Duration::from_secs(FREQUENCY_SECOND), &handle)
                             .expect("failed to start feed loop")
                             .map_err(|e| error!("feed loop error: {}", e))
                             .for_each(move |_| {
        let feeds = db.get_all_feeds();
        let grouped_feeds = grouping_by_host(feeds);
        let handle2 = handle.clone();
        let bot = bot.clone();
        let db = db.clone();
        let fetcher = futures::stream::iter(grouped_feeds.into_iter().map(|x| Ok(x)))
            .for_each(move |group| {
                let session = Session::new(handle2.clone());
                let bot = bot.clone();
                let db = db.clone();
                let group_fetcher = futures::stream::iter(group.into_iter().map(|x| Ok(x)))
                    .for_each(move |feed| {
                                  fetch_feed_updates(bot.clone(), db.clone(), session.clone(), feed)
                              });
                handle2.spawn(group_fetcher);
                Timeout::new(Duration::from_secs(1), &handle2)
                    .expect("failed to start sleep")
                    .map_err(|e| error!("feed loop sleep error: {}", e))
            });
        handle.spawn(fetcher);
        Ok(())
    }))
}

fn grouping_by_host(feeds: Vec<data::Feed>) -> Vec<Vec<data::Feed>> {
    let mut result = HashMap::new();
    for feed in feeds {
        let host = get_host(&feed.link).to_owned();
        let group = result.entry(host).or_insert_with(|| Vec::new());
        group.push(feed);
    }
    result.into_iter().map(|(_, v)| v).collect()
}

fn get_host(url: &str) -> &str {
    HOST.captures(url).map_or(url, |r| r.get(0).unwrap().as_str())
}

fn shoud_unsubscribe_for_user(tg_err_msg: &str) -> bool {
    tg_err_msg.contains("Forbidden") || tg_err_msg.contains("chat not found") ||
    tg_err_msg.contains("group chat was migrated to a supergroup chat")
}

fn fetch_feed_updates<'a>(bot: telebot::RcBot,
                          db: data::Database,
                          session: Session,
                          feed: data::Feed)
                          -> impl Future<Item = (), Error = ()> + 'a {
    let bot_ = bot.clone();
    let db_ = db.clone();
    let feed_ = feed.clone();
    feed::fetch_feed(session, feed.link.to_owned())
        .map(move |rss| (bot_, db_, rss, feed_))
        .or_else(move |e| {
            if db.inc_error_count(&feed.link) > 1440 {
                    Err((bot, db, feed))
                } else {
                    Ok(())
                }
                .into_future()
                .or_else(|(bot, db, feed)| {
                    // 1440 * 5 minute = 5 days
                    db.reset_error_count(&feed.link);
                    let err_msg = to_chinese_error_msg(e);
                    let mut msgs = Vec::with_capacity(feed.subscribers.len());
                    for &subscriber in &feed.subscribers {
                        let m = bot.message(subscriber,
                                            format!("《{}》\
                                                     已经连续 5 天拉取出错 ({}),\
                                                     可能已经关闭, 请取消订阅",
                                                    EscapeUrl(&feed.link),
                                                    Escape(&feed.title),
                                                    Escape(&err_msg)))
                            .parse_mode("HTML")
                            .disable_web_page_preview(true)
                            .send();
                        let feed_link = feed.link.clone();
                        let db = db.clone();
                        let bot = bot.clone();
                        let r = m.or_else(move |e| {
                            match e {
                                    telebot::error::Error::Telegram(ref s)
                                        if shoud_unsubscribe_for_user(s) => {
                                            Err((bot, db, s.to_owned(), subscriber, feed_link))
                                        }
                                    _ => {
                                        warn!("failed to send error to {}, {:?}", subscriber, e);
                                        Ok(())
                                    }
                                }
                                .into_future()
                                .or_else(|(bot, db, s, subscriber, feed_link)| {
                                    if let Err(e) = db.unsubscribe(subscriber, &feed_link) {
                                        log_error(&e);
                                    }
                                    bot.message(subscriber,
                                                 format!("无法修复的错误 ({}), 自动退订",
                                                         s))
                                        .send()
                                        .then(|_| Err(()))
                                })
                                .and_then(|_| Err(()))
                        });
                        // if not use Box, rustc will panic
                        msgs.push(Box::new(r) as Box<Future<Item = _, Error = _>>);
                    }
                    futures::future::join_all(msgs).then(|_| Err(()))
                })
                .and_then(|_| Err(()))
        })
        .and_then(|(bot, db, rss, feed)| {
            if rss.title != feed.title {
                db.update_title(&feed.link, &rss.title);
            }
            let updates = db.update(&feed.link, rss.items);
            if updates.is_empty() {
                futures::future::err(())
            } else {
                futures::future::ok((bot, db, feed, rss.title, rss.link, updates))
            }
        })
        .and_then(|(bot, db, feed, rss_title, rss_link, updates)| {
            let msgs = format_and_split_msgs(format!("{}", rss_title), &updates, |item| {
                let title = item.title
                    .as_ref()
                    .map(|s| s.as_str())
                    .unwrap_or_else(|| &rss_title);
                let link = item.link
                    .as_ref()
                    .map(|s| s.as_str())
                    .unwrap_or_else(|| &rss_link);
                format!("{}",
                        EscapeUrl(link),
                        Escape(&truncate_message(title, TELEGRAM_MAX_MSG_LEN - 500)))
            });

            let mut msg_futures = Vec::with_capacity(feed.subscribers.len());
            for &subscriber in &feed.subscribers {
                let feed_link = feed.link.clone();
                let db = db.clone();
                let bot = bot.clone();
                let r = send_multiple_messages(&bot, subscriber, msgs.clone())
                    .or_else(move |e| {
                        match e {
                                telebot::error::Error::Telegram(ref s)
                                if shoud_unsubscribe_for_user(s) => {
                                Err((bot, db, s.to_owned(), subscriber, feed_link))
                            }
                                _ => {
                                    warn!("failed to send updates to {}, {:?}", subscriber, e);
                                    Ok(())
                                }
                            }
                            .into_future()
                            .or_else(|(bot, db, s, subscriber, feed_link)| {
                                if let Err(e) = db.unsubscribe(subscriber, &feed_link) {
                                    log_error(&e);
                                }
                                bot.message(subscriber,
                                             format!("无法修复的错误 ({}), 自动退订", s))
                                    .send()
                                    .then(|_| Err(()))
                            })
                    });
                msg_futures.push(Box::new(r) as Box<Future<Item = _, Error = _>>);
            }
            futures::future::join_all(msg_futures).then(|_| Ok(()))
        })
}
<|endoftext|>


<commit_before>use crate::prelude::*;

pub fn clif_intcast(
    fx: &mut FunctionCx<'_, '_, impl Backend>,
    val: Value,
    to: Type,
    signed: bool,
) -> Value {
    let from = fx.bcx.func.dfg.value_type(val);
    match (from, to) {
        // equal
        (_, _) if from == to => val,

        // extend
        (_, types::I128) => {
            let lo = if from == types::I64 {
                val
            } else if signed {
                fx.bcx.ins().sextend(types::I64, val)
            } else {
                fx.bcx.ins().uextend(types::I64, val)
            };
            let hi = if signed {
                fx.bcx.ins().sshr_imm(lo, 63)
            } else {
                fx.bcx.ins().iconst(types::I64, 0)
            };
            fx.bcx.ins().iconcat(lo, hi)
        }
        (_, _) if to.wider_or_equal(from) => {
            if signed {
                fx.bcx.ins().sextend(to, val)
            } else {
                fx.bcx.ins().uextend(to, val)
            }
        }

        // reduce
        (types::I128, _) => {
            let (lsb, _msb) = fx.bcx.ins().isplit(val);
            if to == types::I64 {
                lsb
            } else {
                fx.bcx.ins().ireduce(to, lsb)
            }
        }
        (_, _) => fx.bcx.ins().ireduce(to, val),
    }
}

pub fn clif_int_or_float_cast(
    fx: &mut FunctionCx<'_, '_, impl Backend>,
    from: Value,
    from_signed: bool,
    to_ty: Type,
    to_signed: bool,
) -> Value {
    let from_ty = fx.bcx.func.dfg.value_type(from);

    if from_ty.is_int() && to_ty.is_int() {
        // int-like -> int-like
        clif_intcast(
            fx,
            from,
            to_ty,
            from_signed, // FIXME is this correct?
        )
    } else if from_ty.is_int() && to_ty.is_float() {
        if from_ty == types::I128 {
            // _______ss__f_
            // __float  tisf: i128 -> f32
            // __float  tidf: i128 -> f64
            // __floatuntisf: u128 -> f32
            // __floatuntidf: u128 -> f64

            let name = format!(
                "__float{sign}ti{flt}f",
                sign = if from_signed { "" } else { "un" },
                flt = match to_ty {
                    types::F32 => "s",
                    types::F64 => "d",
                    _ => unreachable!("{:?}", to_ty),
                },
            );

            let from_rust_ty = if from_signed {
                fx.tcx.types.i128
            } else {
                fx.tcx.types.u128
            };

            let to_rust_ty = match to_ty {
                types::F32 => fx.tcx.types.f32,
                types::F64 => fx.tcx.types.f64,
                _ => unreachable!(),
            };

            return fx
                .easy_call(
                    &name,
                    &[CValue::by_val(from, fx.layout_of(from_rust_ty))],
                    to_rust_ty,
                )
                .load_scalar(fx);
        }

        // int-like -> float
        if from_signed {
            fx.bcx.ins().fcvt_from_sint(to_ty, from)
        } else {
            fx.bcx.ins().fcvt_from_uint(to_ty, from)
        }
    } else if from_ty.is_float() && to_ty.is_int() {
        if to_ty == types::I128 {
            // _____sssf___
            // __fix   sfti: f32 -> i128
            // __fix   dfti: f64 -> i128
            // __fixunssfti: f32 -> u128
            // __fixunsdfti: f64 -> u128

            let name = format!(
                "__fix{sign}{flt}fti",
                sign = if to_signed { "" } else { "uns" },
                flt = match from_ty {
                    types::F32 => "s",
                    types::F64 => "d",
                    _ => unreachable!("{:?}", to_ty),
                },
            );

            let from_rust_ty = match from_ty {
                types::F32 => fx.tcx.types.f32,
                types::F64 => fx.tcx.types.f64,
                _ => unreachable!(),
            };

            let to_rust_ty = if to_signed {
                fx.tcx.types.i128
            } else {
                fx.tcx.types.u128
            };

            return fx
                .easy_call(
                    &name,
                    &[CValue::by_val(from, fx.layout_of(from_rust_ty))],
                    to_rust_ty,
                )
                .load_scalar(fx);
        }

        // float -> int-like
        if to_ty == types::I8 || to_ty == types::I16 {
            // FIXME implement fcvt_to_*int_sat.i8/i16
            let val = if to_signed {
                fx.bcx.ins().fcvt_to_sint_sat(types::I32, from)
            } else {
                fx.bcx.ins().fcvt_to_uint_sat(types::I32, from)
            };
            let (min, max) = type_min_max_value(to_ty, to_signed);
            let min_val = fx.bcx.ins().iconst(types::I32, min);
            let max_val = fx.bcx.ins().iconst(types::I32, max);

            let val = if to_signed {
                let has_underflow = fx.bcx.ins().icmp_imm(IntCC::SignedLessThan, val, min);
                let has_overflow = fx.bcx.ins().icmp_imm(IntCC::SignedGreaterThan, val, max);
                let bottom_capped = fx.bcx.ins().select(has_underflow, min_val, val);
                fx.bcx.ins().select(has_overflow, max_val, bottom_capped)
            } else {
                let has_overflow = fx.bcx.ins().icmp_imm(IntCC::UnsignedGreaterThan, val, max);
                fx.bcx.ins().select(has_overflow, max_val, val)
            };
            fx.bcx.ins().ireduce(to_ty, val)
        } else {
            if to_signed {
                fx.bcx.ins().fcvt_to_sint_sat(to_ty, from)
            } else {
                fx.bcx.ins().fcvt_to_uint_sat(to_ty, from)
            }
        }
    } else if from_ty.is_float() && to_ty.is_float() {
        // float -> float
        match (from_ty, to_ty) {
            (types::F32, types::F64) => fx.bcx.ins().fpromote(types::F64, from),
            (types::F64, types::F32) => fx.bcx.ins().fdemote(types::F32, from),
            _ => from,
        }
    } else {
        unreachable!("cast value from {:?} to {:?}", from_ty, to_ty);
    }
}
<commit_msg>Remove FIXME<commit_after>use crate::prelude::*;

pub fn clif_intcast(
    fx: &mut FunctionCx<'_, '_, impl Backend>,
    val: Value,
    to: Type,
    signed: bool,
) -> Value {
    let from = fx.bcx.func.dfg.value_type(val);
    match (from, to) {
        // equal
        (_, _) if from == to => val,

        // extend
        (_, types::I128) => {
            let lo = if from == types::I64 {
                val
            } else if signed {
                fx.bcx.ins().sextend(types::I64, val)
            } else {
                fx.bcx.ins().uextend(types::I64, val)
            };
            let hi = if signed {
                fx.bcx.ins().sshr_imm(lo, 63)
            } else {
                fx.bcx.ins().iconst(types::I64, 0)
            };
            fx.bcx.ins().iconcat(lo, hi)
        }
        (_, _) if to.wider_or_equal(from) => {
            if signed {
                fx.bcx.ins().sextend(to, val)
            } else {
                fx.bcx.ins().uextend(to, val)
            }
        }

        // reduce
        (types::I128, _) => {
            let (lsb, _msb) = fx.bcx.ins().isplit(val);
            if to == types::I64 {
                lsb
            } else {
                fx.bcx.ins().ireduce(to, lsb)
            }
        }
        (_, _) => fx.bcx.ins().ireduce(to, val),
    }
}

pub fn clif_int_or_float_cast(
    fx: &mut FunctionCx<'_, '_, impl Backend>,
    from: Value,
    from_signed: bool,
    to_ty: Type,
    to_signed: bool,
) -> Value {
    let from_ty = fx.bcx.func.dfg.value_type(from);

    if from_ty.is_int() && to_ty.is_int() {
        // int-like -> int-like
        clif_intcast(
            fx,
            from,
            to_ty,
            // This is correct as either from_signed == to_signed (=> this is trivially correct)
            // Or from_clif_ty == to_clif_ty, which means this is a no-op.
            from_signed,
        )
    } else if from_ty.is_int() && to_ty.is_float() {
        if from_ty == types::I128 {
            // _______ss__f_
            // __float  tisf: i128 -> f32
            // __float  tidf: i128 -> f64
            // __floatuntisf: u128 -> f32
            // __floatuntidf: u128 -> f64

            let name = format!(
                "__float{sign}ti{flt}f",
                sign = if from_signed { "" } else { "un" },
                flt = match to_ty {
                    types::F32 => "s",
                    types::F64 => "d",
                    _ => unreachable!("{:?}", to_ty),
                },
            );

            let from_rust_ty = if from_signed {
                fx.tcx.types.i128
            } else {
                fx.tcx.types.u128
            };

            let to_rust_ty = match to_ty {
                types::F32 => fx.tcx.types.f32,
                types::F64 => fx.tcx.types.f64,
                _ => unreachable!(),
            };

            return fx
                .easy_call(
                    &name,
                    &[CValue::by_val(from, fx.layout_of(from_rust_ty))],
                    to_rust_ty,
                )
                .load_scalar(fx);
        }

        // int-like -> float
        if from_signed {
            fx.bcx.ins().fcvt_from_sint(to_ty, from)
        } else {
            fx.bcx.ins().fcvt_from_uint(to_ty, from)
        }
    } else if from_ty.is_float() && to_ty.is_int() {
        if to_ty == types::I128 {
            // _____sssf___
            // __fix   sfti: f32 -> i128
            // __fix   dfti: f64 -> i128
            // __fixunssfti: f32 -> u128
            // __fixunsdfti: f64 -> u128

            let name = format!(
                "__fix{sign}{flt}fti",
                sign = if to_signed { "" } else { "uns" },
                flt = match from_ty {
                    types::F32 => "s",
                    types::F64 => "d",
                    _ => unreachable!("{:?}", to_ty),
                },
            );

            let from_rust_ty = match from_ty {
                types::F32 => fx.tcx.types.f32,
                types::F64 => fx.tcx.types.f64,
                _ => unreachable!(),
            };

            let to_rust_ty = if to_signed {
                fx.tcx.types.i128
            } else {
                fx.tcx.types.u128
            };

            return fx
                .easy_call(
                    &name,
                    &[CValue::by_val(from, fx.layout_of(from_rust_ty))],
                    to_rust_ty,
                )
                .load_scalar(fx);
        }

        // float -> int-like
        if to_ty == types::I8 || to_ty == types::I16 {
            // FIXME implement fcvt_to_*int_sat.i8/i16
            let val = if to_signed {
                fx.bcx.ins().fcvt_to_sint_sat(types::I32, from)
            } else {
                fx.bcx.ins().fcvt_to_uint_sat(types::I32, from)
            };
            let (min, max) = type_min_max_value(to_ty, to_signed);
            let min_val = fx.bcx.ins().iconst(types::I32, min);
            let max_val = fx.bcx.ins().iconst(types::I32, max);

            let val = if to_signed {
                let has_underflow = fx.bcx.ins().icmp_imm(IntCC::SignedLessThan, val, min);
                let has_overflow = fx.bcx.ins().icmp_imm(IntCC::SignedGreaterThan, val, max);
                let bottom_capped = fx.bcx.ins().select(has_underflow, min_val, val);
                fx.bcx.ins().select(has_overflow, max_val, bottom_capped)
            } else {
                let has_overflow = fx.bcx.ins().icmp_imm(IntCC::UnsignedGreaterThan, val, max);
                fx.bcx.ins().select(has_overflow, max_val, val)
            };
            fx.bcx.ins().ireduce(to_ty, val)
        } else {
            if to_signed {
                fx.bcx.ins().fcvt_to_sint_sat(to_ty, from)
            } else {
                fx.bcx.ins().fcvt_to_uint_sat(to_ty, from)
            }
        }
    } else if from_ty.is_float() && to_ty.is_float() {
        // float -> float
        match (from_ty, to_ty) {
            (types::F32, types::F64) => fx.bcx.ins().fpromote(types::F64, from),
            (types::F64, types::F32) => fx.bcx.ins().fdemote(types::F32, from),
            _ => from,
        }
    } else {
        unreachable!("cast value from {:?} to {:?}", from_ty, to_ty);
    }
}
<|endoftext|>


<commit_before>use std::collections::{HashMap, HashSet};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Duration;

#[cfg(feature = "tls")]
use crate::exop_impl::StartTLS;
use crate::ldap::Ldap;
use crate::protocol::{ItemSender, LdapCodec, LdapOp, MaybeControls, ResultSender};
use crate::result::{LdapError, Result};
use crate::search::SearchItem;
use crate::RequestId;

use lber::structures::{Null, Tag};

#[cfg(feature = "tls")]
use futures_util::future::TryFutureExt;
use futures_util::sink::SinkExt;
#[cfg(feature = "tls")]
use native_tls::TlsConnector;
#[cfg(unix)]
use percent_encoding::percent_decode;
use tokio::io::{self, AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
#[cfg(unix)]
use tokio::net::UnixStream;
use tokio::stream::StreamExt;
use tokio::sync::mpsc;
#[cfg(feature = "tls")]
use tokio::sync::oneshot;
#[cfg(feature = "tls")]
use tokio::time;
use tokio_tls::{TlsConnector as TokioTlsConnector, TlsStream};
use tokio_util::codec::{Decoder, Framed};
use url::{self, Url};

#[derive(Debug)]
enum ConnType {
    Tcp(TcpStream),
    #[cfg(feature = "tls")]
    Tls(TlsStream<TcpStream>),
    #[cfg(unix)]
    Unix(UnixStream),
}

impl AsyncRead for ConnType {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context,
        buf: &mut [u8],
    ) -> Poll<io::Result<usize>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_read(cx, buf),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_read(cx, buf),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_read(cx, buf),
        }
    }
}

impl AsyncWrite for ConnType {
    fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_write(cx, buf),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_write(cx, buf),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_write(cx, buf),
        }
    }

    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_flush(cx),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_flush(cx),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_flush(cx),
        }
    }

    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_shutdown(cx),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_shutdown(cx),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_shutdown(cx),
        }
    }
}

/// Additional settings for an LDAP connection.
///
/// The structure is opaque for better extensibility. An instance with
/// default values is constructed by [`new()`](#method.new), and all
/// available settings can be replaced through a builder-like interface,
/// by calling the appropriate functions.
#[derive(Clone, Default)]
pub struct LdapConnSettings {
    conn_timeout: Option<Duration>,
    #[cfg(feature = "tls")]
    connector: Option<TlsConnector>,
    #[cfg(feature = "tls")]
    starttls: bool,
    #[cfg(feature = "tls")]
    no_tls_verify: bool,
}

impl LdapConnSettings {
    /// Create an instance of the structure with default settings.
    pub fn new() -> LdapConnSettings {
        LdapConnSettings {
            ..Default::default()
        }
    }

    /// Set the connection timeout. If a connetion to the server can't
    /// be established before the timeout expires, an error will be
    /// returned to the user. Defaults to `None`, meaning an infinite
    /// timeout.
    pub fn set_conn_timeout(mut self, timeout: Duration) -> Self {
        self.conn_timeout = Some(timeout);
        self
    }

    #[cfg(feature = "tls")]
    /// Set a custom TLS connector, which enables setting various options
    /// when establishing a secure connection. See the documentation for
    /// [native_tls](https://docs.rs/native-tls/0.1.4/native_tls/).
    /// Defaults to `None`, which will use a connector with default
    /// settings.
    pub fn set_connector(mut self, connector: TlsConnector) -> Self {
        self.connector = Some(connector);
        self
    }

    #[cfg(feature = "tls")]
    /// If `true`, use the StartTLS extended operation to establish a
    /// secure connection. Defaults to `false`.
    pub fn set_starttls(mut self, starttls: bool) -> Self {
        self.starttls = starttls;
        self
    }

    #[cfg(feature = "tls")]
    /// The `starttls` settings indicates whether the StartTLS extended
    /// operation will be used to establish a secure connection.
    pub fn starttls(&self) -> bool {
        self.starttls
    }

    #[cfg(not(feature = "tls"))]
    /// Always `false` when no TLS support is compiled in.
    pub fn starttls(&self) -> bool {
        false
    }

    #[cfg(feature = "tls")]
    /// If `true`, try to establish a TLS connection without hostname
    /// verification. Defaults to `false`.
    ///
    /// The connection can still fail if the server certificate is
    /// considered invalid for other reasons (e.g., chain of trust or
    /// expiration date.) Depending on the platform, using a
    /// custom connector with backend-specific options _and_ setting
    /// this option to `true` may enable connections to servers with
    /// invalid certificates. One tested combination is OpenSSL with
    /// a connector for which `SSL_VERIFY_NONE` has been set.
    pub fn set_no_tls_verify(mut self, no_tls_verify: bool) -> Self {
        self.no_tls_verify = no_tls_verify;
        self
    }
}

enum LoopMode {
    #[allow(dead_code)]
    SingleOp,
    Continuous,
}

pub struct LdapConnAsync {
    msgmap: Arc<Mutex<(i32, HashSet<i32>)>>,
    resultmap: HashMap<i32, ResultSender>,
    searchmap: HashMap<i32, ItemSender>,
    rx: mpsc::UnboundedReceiver<(RequestId, LdapOp, Tag, MaybeControls, ResultSender)>,
    stream: Framed<ConnType, LdapCodec>,
}

#[macro_export]
macro_rules! drive {
    ($conn:expr) => {
        tokio::spawn(async move {
            if let Err(e) = $conn.drive().await {
                $crate::log::warn!("LDAP connection error: {}", e);
            }
        });
    };
}

impl LdapConnAsync {
    pub async fn with_settings(mut settings: LdapConnSettings, url: &str) -> Result<(Self, Ldap)> {
        if url.starts_with("ldapi://") {
            Ok(LdapConnAsync::new_unix(url, settings).await?)
        } else {
            let timeout = settings.conn_timeout.take();
            let conn_future = LdapConnAsync::new_tcp(url, settings);
            Ok(if let Some(timeout) = timeout {
                time::timeout(timeout, conn_future).await?
            } else {
                conn_future.await
            }?)
        }
    }

    pub async fn new(url: &str) -> Result<(Self, Ldap)> {
        Self::with_settings(LdapConnSettings::new(), url).await
    }

    #[cfg(unix)]
    async fn new_unix(url: &str, _settings: LdapConnSettings) -> Result<(Self, Ldap)> {
        let path = url.split('/').nth(2).unwrap();
        if path.is_empty() {
            return Err(LdapError::EmptyUnixPath);
        }
        if path.contains(':') {
            return Err(LdapError::PortInUnixPath);
        }
        let dec_path = percent_decode(path.as_bytes()).decode_utf8_lossy();
        let stream = UnixStream::connect(dec_path.as_ref()).await?;
        Ok(Self::conn_pair(ConnType::Unix(stream)))
    }

    #[cfg(not(unix))]
    async fn new_unix(_url: &str, _settings: LdapConnSettings) -> Result<(Self, Ldap)> {
        unimplemented!("no Unix domain sockets on non-Unix platforms");
    }

    #[allow(unused_mut)]
    async fn new_tcp(url: &str, mut settings: LdapConnSettings) -> Result<(Self, Ldap)> {
        let url = Url::parse(url)?;
        let mut port = 389;
        let scheme = match url.scheme() {
            s @ "ldap" => {
                if settings.starttls() {
                    "starttls"
                } else {
                    s
                }
            }
            #[cfg(feature = "tls")]
            s @ "ldaps" => {
                settings = settings.set_starttls(false);
                port = 636;
                s
            }
            s => return Err(LdapError::UnknownScheme(String::from(s))),
        };
        if let Some(url_port) = url.port() {
            port = url_port;
        }
        let (_hostname, host_port) = match url.host_str() {
            Some(h) if h != "" => (h, format!("{}:{}", h, port)),
            Some(h) if h == "" => ("localhost", format!("localhost:{}", port)),
            _ => panic!("unexpected None from url.host_str()"),
        };
        let stream = TcpStream::connect(host_port.as_str()).await?;
        let (mut conn, mut ldap) = Self::conn_pair(ConnType::Tcp(stream));
        match scheme {
            "ldap" => (),
            #[cfg(feature = "tls")]
            s @ "ldaps" | s @ "starttls" => {
                let connector = match settings.connector {
                    Some(connector) => connector,
                    None => {
                        let mut builder = TlsConnector::builder();
                        if settings.no_tls_verify {
                            builder.danger_accept_invalid_certs(true);
                        }
                        builder.build().expect("connector")
                    }
                };
                if s == "starttls" {
                    let (tx, rx) = oneshot::channel();
                    tokio::spawn(async move {
                        conn.single_op(tx).await;
                    });
                    let res = tokio::try_join!(
                        rx.map_err(|e| LdapError::from(e)),
                        ldap.extended(StartTLS)
                    );
                    match res {
                        Ok((conn_res, res)) => {
                            conn = conn_res?;
                            res.success()?;
                        }
                        Err(e) => return Err(e),
                    }
                }
                let parts = conn.stream.into_parts();
                let tls_stream = if let ConnType::Tcp(stream) = parts.io {
                    TokioTlsConnector::from(connector)
                        .connect(_hostname, stream)
                        .await?
                } else {
                    panic!("underlying stream not TCP");
                };
                conn.stream = parts.codec.framed(ConnType::Tls(tls_stream));
            }
            _ => unimplemented!(),
        }
        Ok((conn, ldap))
    }

    fn conn_pair(ctype: ConnType) -> (Self, Ldap) {
        let codec = LdapCodec;
        let (tx, rx) = mpsc::unbounded_channel();
        let conn = LdapConnAsync {
            msgmap: Arc::new(Mutex::new((0, HashSet::new()))),
            resultmap: HashMap::new(),
            searchmap: HashMap::new(),
            rx: rx,
            stream: codec.framed(ctype),
        };
        let ldap = Ldap {
            msgmap: conn.msgmap.clone(),
            tx: tx,
            last_id: 0,
            timeout: None,
            controls: None,
            search_opts: None,
        };
        (conn, ldap)
    }

    pub async fn drive(self) -> Result<()> {
        self.turn(LoopMode::Continuous).await.map(|_| ())
    }

    #[cfg(feature = "tls")]
    pub(crate) async fn single_op(self, tx: oneshot::Sender<Result<Self>>) {
        if let Err(_) = tx.send(self.turn(LoopMode::SingleOp).await) {
            warn!("single op send error");
        }
    }

    async fn turn(mut self, mode: LoopMode) -> Result<Self> {
        loop {
            tokio::select! {
                op_tuple = self.rx.recv() => {
                    if let Some((id, op, tag, controls, tx)) = op_tuple {
                        if let &LdapOp::Search(ref search_tx) = &op {
                            self.searchmap.insert(id, search_tx.clone());
                        }
                        if let Err(e) = self.stream.send((id, tag, controls)).await {
                            warn!("socket send error: {}", e);
                            return Err(LdapError::from(e));
                        } else {
                            match op {
                                LdapOp::Single => {
                                    self.resultmap.insert(id, tx);
                                    continue;
                                },
                                LdapOp::Search(_) => (),
                                LdapOp::Abandon(msgid) => {
                                    let mut msgmap = self.msgmap.lock().expect("msgmap mutex (abandon)");
                                    msgmap.1.remove(&id);
                                    self.resultmap.remove(&msgid);
                                    self.searchmap.remove(&msgid);
                                },
                                LdapOp::Unbind => {
                                    if let Err(e) = self.stream.close().await {
                                        warn!("socket shutdown error: {}", e);
                                        return Err(LdapError::from(e));
                                    }
                                },
                            }
                            if let Err(e) = tx.send((Tag::Null(Null { ..Default::default() }), vec![])) {
                                warn!("ldap null result send error: {:?}", e);
                            }
                        }
                    }
                },
                resp = self.stream.next() => {
                    let (id, (tag, controls)) = match resp {
                        None => break,
                        Some(Err(e)) => {
                            warn!("socket receive error: {}", e);
                            return Err(LdapError::from(e));
                        },
                        Some(Ok(resp)) => resp,
                    };
                    if let Some(tx) = self.searchmap.get(&id) {
                        let protoop = if let Tag::StructureTag(protoop) = tag {
                            protoop
                        } else {
                            panic!("unmatched tag structure: {:?}", tag);
                        };
                        let (item, mut remove) = match protoop.id {
                            4 | 25 => (SearchItem::Entry(protoop), false),
                            5 => (SearchItem::Done(Tag::StructureTag(protoop).into(), controls), true),
                            19 => (SearchItem::Referral(protoop), false),
                            _ => panic!("unrecognized op id: {}", protoop.id),
                        };
                        if let Err(e) = tx.send(item) {
                            warn!("ldap search item send error, op={}: {:?}", id, e);
                            remove = true;
                        }
                        if remove {
                            self.searchmap.remove(&id);
                        }
                    } else if let Some(tx) = self.resultmap.remove(&id) {
                        let mut msgmap = self.msgmap.lock().expect("msgmap mutex (stream rx)");
                        msgmap.1.remove(&id);
                        if let Err(e) = tx.send((tag, controls)) {
                            warn!("ldap result send error: {:?}", e);
                        }
                    } else {
                        warn!("unmatched id: {}", id);
                    }
                },
            };
            if let LoopMode::SingleOp = mode {
                break;
            }
        }
        Ok(self)
    }
}
<commit_msg>Fix misplaced conditional use<commit_after>use std::collections::{HashMap, HashSet};
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use std::time::Duration;

#[cfg(feature = "tls")]
use crate::exop_impl::StartTLS;
use crate::ldap::Ldap;
use crate::protocol::{ItemSender, LdapCodec, LdapOp, MaybeControls, ResultSender};
use crate::result::{LdapError, Result};
use crate::search::SearchItem;
use crate::RequestId;

use lber::structures::{Null, Tag};

#[cfg(feature = "tls")]
use futures_util::future::TryFutureExt;
use futures_util::sink::SinkExt;
#[cfg(feature = "tls")]
use native_tls::TlsConnector;
#[cfg(unix)]
use percent_encoding::percent_decode;
use tokio::io::{self, AsyncRead, AsyncWrite};
use tokio::net::TcpStream;
#[cfg(unix)]
use tokio::net::UnixStream;
use tokio::stream::StreamExt;
use tokio::sync::mpsc;
#[cfg(feature = "tls")]
use tokio::sync::oneshot;
use tokio::time;
#[cfg(feature = "tls")]
use tokio_tls::{TlsConnector as TokioTlsConnector, TlsStream};
use tokio_util::codec::{Decoder, Framed};
use url::{self, Url};

#[derive(Debug)]
enum ConnType {
    Tcp(TcpStream),
    #[cfg(feature = "tls")]
    Tls(TlsStream<TcpStream>),
    #[cfg(unix)]
    Unix(UnixStream),
}

impl AsyncRead for ConnType {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context,
        buf: &mut [u8],
    ) -> Poll<io::Result<usize>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_read(cx, buf),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_read(cx, buf),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_read(cx, buf),
        }
    }
}

impl AsyncWrite for ConnType {
    fn poll_write(self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll<io::Result<usize>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_write(cx, buf),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_write(cx, buf),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_write(cx, buf),
        }
    }

    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_flush(cx),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_flush(cx),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_flush(cx),
        }
    }

    fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context) -> Poll<io::Result<()>> {
        match self.get_mut() {
            ConnType::Tcp(ts) => Pin::new(ts).poll_shutdown(cx),
            #[cfg(feature = "tls")]
            ConnType::Tls(tls) => Pin::new(tls).poll_shutdown(cx),
            #[cfg(unix)]
            ConnType::Unix(us) => Pin::new(us).poll_shutdown(cx),
        }
    }
}

/// Additional settings for an LDAP connection.
///
/// The structure is opaque for better extensibility. An instance with
/// default values is constructed by [`new()`](#method.new), and all
/// available settings can be replaced through a builder-like interface,
/// by calling the appropriate functions.
#[derive(Clone, Default)]
pub struct LdapConnSettings {
    conn_timeout: Option<Duration>,
    #[cfg(feature = "tls")]
    connector: Option<TlsConnector>,
    #[cfg(feature = "tls")]
    starttls: bool,
    #[cfg(feature = "tls")]
    no_tls_verify: bool,
}

impl LdapConnSettings {
    /// Create an instance of the structure with default settings.
    pub fn new() -> LdapConnSettings {
        LdapConnSettings {
            ..Default::default()
        }
    }

    /// Set the connection timeout. If a connetion to the server can't
    /// be established before the timeout expires, an error will be
    /// returned to the user. Defaults to `None`, meaning an infinite
    /// timeout.
    pub fn set_conn_timeout(mut self, timeout: Duration) -> Self {
        self.conn_timeout = Some(timeout);
        self
    }

    #[cfg(feature = "tls")]
    /// Set a custom TLS connector, which enables setting various options
    /// when establishing a secure connection. See the documentation for
    /// [native_tls](https://docs.rs/native-tls/0.1.4/native_tls/).
    /// Defaults to `None`, which will use a connector with default
    /// settings.
    pub fn set_connector(mut self, connector: TlsConnector) -> Self {
        self.connector = Some(connector);
        self
    }

    #[cfg(feature = "tls")]
    /// If `true`, use the StartTLS extended operation to establish a
    /// secure connection. Defaults to `false`.
    pub fn set_starttls(mut self, starttls: bool) -> Self {
        self.starttls = starttls;
        self
    }

    #[cfg(feature = "tls")]
    /// The `starttls` settings indicates whether the StartTLS extended
    /// operation will be used to establish a secure connection.
    pub fn starttls(&self) -> bool {
        self.starttls
    }

    #[cfg(not(feature = "tls"))]
    /// Always `false` when no TLS support is compiled in.
    pub fn starttls(&self) -> bool {
        false
    }

    #[cfg(feature = "tls")]
    /// If `true`, try to establish a TLS connection without hostname
    /// verification. Defaults to `false`.
    ///
    /// The connection can still fail if the server certificate is
    /// considered invalid for other reasons (e.g., chain of trust or
    /// expiration date.) Depending on the platform, using a
    /// custom connector with backend-specific options _and_ setting
    /// this option to `true` may enable connections to servers with
    /// invalid certificates. One tested combination is OpenSSL with
    /// a connector for which `SSL_VERIFY_NONE` has been set.
    pub fn set_no_tls_verify(mut self, no_tls_verify: bool) -> Self {
        self.no_tls_verify = no_tls_verify;
        self
    }
}

enum LoopMode {
    #[allow(dead_code)]
    SingleOp,
    Continuous,
}

pub struct LdapConnAsync {
    msgmap: Arc<Mutex<(i32, HashSet<i32>)>>,
    resultmap: HashMap<i32, ResultSender>,
    searchmap: HashMap<i32, ItemSender>,
    rx: mpsc::UnboundedReceiver<(RequestId, LdapOp, Tag, MaybeControls, ResultSender)>,
    stream: Framed<ConnType, LdapCodec>,
}

#[macro_export]
macro_rules! drive {
    ($conn:expr) => {
        tokio::spawn(async move {
            if let Err(e) = $conn.drive().await {
                $crate::log::warn!("LDAP connection error: {}", e);
            }
        });
    };
}

impl LdapConnAsync {
    pub async fn with_settings(mut settings: LdapConnSettings, url: &str) -> Result<(Self, Ldap)> {
        if url.starts_with("ldapi://") {
            Ok(LdapConnAsync::new_unix(url, settings).await?)
        } else {
            let timeout = settings.conn_timeout.take();
            let conn_future = LdapConnAsync::new_tcp(url, settings);
            Ok(if let Some(timeout) = timeout {
                time::timeout(timeout, conn_future).await?
            } else {
                conn_future.await
            }?)
        }
    }

    pub async fn new(url: &str) -> Result<(Self, Ldap)> {
        Self::with_settings(LdapConnSettings::new(), url).await
    }

    #[cfg(unix)]
    async fn new_unix(url: &str, _settings: LdapConnSettings) -> Result<(Self, Ldap)> {
        let path = url.split('/').nth(2).unwrap();
        if path.is_empty() {
            return Err(LdapError::EmptyUnixPath);
        }
        if path.contains(':') {
            return Err(LdapError::PortInUnixPath);
        }
        let dec_path = percent_decode(path.as_bytes()).decode_utf8_lossy();
        let stream = UnixStream::connect(dec_path.as_ref()).await?;
        Ok(Self::conn_pair(ConnType::Unix(stream)))
    }

    #[cfg(not(unix))]
    async fn new_unix(_url: &str, _settings: LdapConnSettings) -> Result<(Self, Ldap)> {
        unimplemented!("no Unix domain sockets on non-Unix platforms");
    }

    #[allow(unused_mut)]
    async fn new_tcp(url: &str, mut settings: LdapConnSettings) -> Result<(Self, Ldap)> {
        let url = Url::parse(url)?;
        let mut port = 389;
        let scheme = match url.scheme() {
            s @ "ldap" => {
                if settings.starttls() {
                    "starttls"
                } else {
                    s
                }
            }
            #[cfg(feature = "tls")]
            s @ "ldaps" => {
                settings = settings.set_starttls(false);
                port = 636;
                s
            }
            s => return Err(LdapError::UnknownScheme(String::from(s))),
        };
        if let Some(url_port) = url.port() {
            port = url_port;
        }
        let (_hostname, host_port) = match url.host_str() {
            Some(h) if h != "" => (h, format!("{}:{}", h, port)),
            Some(h) if h == "" => ("localhost", format!("localhost:{}", port)),
            _ => panic!("unexpected None from url.host_str()"),
        };
        let stream = TcpStream::connect(host_port.as_str()).await?;
        let (mut conn, mut ldap) = Self::conn_pair(ConnType::Tcp(stream));
        match scheme {
            "ldap" => (),
            #[cfg(feature = "tls")]
            s @ "ldaps" | s @ "starttls" => {
                let connector = match settings.connector {
                    Some(connector) => connector,
                    None => {
                        let mut builder = TlsConnector::builder();
                        if settings.no_tls_verify {
                            builder.danger_accept_invalid_certs(true);
                        }
                        builder.build().expect("connector")
                    }
                };
                if s == "starttls" {
                    let (tx, rx) = oneshot::channel();
                    tokio::spawn(async move {
                        conn.single_op(tx).await;
                    });
                    let res = tokio::try_join!(
                        rx.map_err(|e| LdapError::from(e)),
                        ldap.extended(StartTLS)
                    );
                    match res {
                        Ok((conn_res, res)) => {
                            conn = conn_res?;
                            res.success()?;
                        }
                        Err(e) => return Err(e),
                    }
                }
                let parts = conn.stream.into_parts();
                let tls_stream = if let ConnType::Tcp(stream) = parts.io {
                    TokioTlsConnector::from(connector)
                        .connect(_hostname, stream)
                        .await?
                } else {
                    panic!("underlying stream not TCP");
                };
                conn.stream = parts.codec.framed(ConnType::Tls(tls_stream));
            }
            _ => unimplemented!(),
        }
        Ok((conn, ldap))
    }

    fn conn_pair(ctype: ConnType) -> (Self, Ldap) {
        let codec = LdapCodec;
        let (tx, rx) = mpsc::unbounded_channel();
        let conn = LdapConnAsync {
            msgmap: Arc::new(Mutex::new((0, HashSet::new()))),
            resultmap: HashMap::new(),
            searchmap: HashMap::new(),
            rx: rx,
            stream: codec.framed(ctype),
        };
        let ldap = Ldap {
            msgmap: conn.msgmap.clone(),
            tx: tx,
            last_id: 0,
            timeout: None,
            controls: None,
            search_opts: None,
        };
        (conn, ldap)
    }

    pub async fn drive(self) -> Result<()> {
        self.turn(LoopMode::Continuous).await.map(|_| ())
    }

    #[cfg(feature = "tls")]
    pub(crate) async fn single_op(self, tx: oneshot::Sender<Result<Self>>) {
        if let Err(_) = tx.send(self.turn(LoopMode::SingleOp).await) {
            warn!("single op send error");
        }
    }

    async fn turn(mut self, mode: LoopMode) -> Result<Self> {
        loop {
            tokio::select! {
                op_tuple = self.rx.recv() => {
                    if let Some((id, op, tag, controls, tx)) = op_tuple {
                        if let &LdapOp::Search(ref search_tx) = &op {
                            self.searchmap.insert(id, search_tx.clone());
                        }
                        if let Err(e) = self.stream.send((id, tag, controls)).await {
                            warn!("socket send error: {}", e);
                            return Err(LdapError::from(e));
                        } else {
                            match op {
                                LdapOp::Single => {
                                    self.resultmap.insert(id, tx);
                                    continue;
                                },
                                LdapOp::Search(_) => (),
                                LdapOp::Abandon(msgid) => {
                                    let mut msgmap = self.msgmap.lock().expect("msgmap mutex (abandon)");
                                    msgmap.1.remove(&id);
                                    self.resultmap.remove(&msgid);
                                    self.searchmap.remove(&msgid);
                                },
                                LdapOp::Unbind => {
                                    if let Err(e) = self.stream.close().await {
                                        warn!("socket shutdown error: {}", e);
                                        return Err(LdapError::from(e));
                                    }
                                },
                            }
                            if let Err(e) = tx.send((Tag::Null(Null { ..Default::default() }), vec![])) {
                                warn!("ldap null result send error: {:?}", e);
                            }
                        }
                    }
                },
                resp = self.stream.next() => {
                    let (id, (tag, controls)) = match resp {
                        None => break,
                        Some(Err(e)) => {
                            warn!("socket receive error: {}", e);
                            return Err(LdapError::from(e));
                        },
                        Some(Ok(resp)) => resp,
                    };
                    if let Some(tx) = self.searchmap.get(&id) {
                        let protoop = if let Tag::StructureTag(protoop) = tag {
                            protoop
                        } else {
                            panic!("unmatched tag structure: {:?}", tag);
                        };
                        let (item, mut remove) = match protoop.id {
                            4 | 25 => (SearchItem::Entry(protoop), false),
                            5 => (SearchItem::Done(Tag::StructureTag(protoop).into(), controls), true),
                            19 => (SearchItem::Referral(protoop), false),
                            _ => panic!("unrecognized op id: {}", protoop.id),
                        };
                        if let Err(e) = tx.send(item) {
                            warn!("ldap search item send error, op={}: {:?}", id, e);
                            remove = true;
                        }
                        if remove {
                            self.searchmap.remove(&id);
                        }
                    } else if let Some(tx) = self.resultmap.remove(&id) {
                        let mut msgmap = self.msgmap.lock().expect("msgmap mutex (stream rx)");
                        msgmap.1.remove(&id);
                        if let Err(e) = tx.send((tag, controls)) {
                            warn!("ldap result send error: {:?}", e);
                        }
                    } else {
                        warn!("unmatched id: {}", id);
                    }
                },
            };
            if let LoopMode::SingleOp = mode {
                break;
            }
        }
        Ok(self)
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]

#[macro_use] extern crate bitflags;

extern crate fsevent_sys as fsevent;
extern crate libc;

use fsevent::core_foundation as cf;
use fsevent::fsevent as fs;

use std::ptr;
use std::slice;
use std::slice::from_raw_parts_mut;
use std::str::from_utf8;
use std::ffi::CStr;
use std::convert::AsRef;

use std::sync::mpsc::{Sender};

pub const NULL: cf::CFRef = cf::NULL;

pub struct FsEvent {
  paths: cf::CFMutableArrayRef,
  since_when: fs::FSEventStreamEventId,
  latency: cf::CFTimeInterval,
  flags: fs::FSEventStreamCreateFlags,
  sender: Sender<Event>,
}

#[derive(Debug)]
pub struct Event {
  pub event_id: u64,
  pub flag: StreamFlags,
  pub path: String,
}

pub type FsEventCallback = fn(Vec<Event>);

// Synchronize with
// /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Headers/FSEvents.h
bitflags! {
  #[repr(C)]
  pub struct StreamFlags: u32 {
    const NONE = 0x00000000;
    const MUST_SCAN_SUBDIRS = 0x00000001;
    const USER_DROPPED = 0x00000002;
    const KERNEL_DROPPED = 0x00000004;
    const IDS_WRAPPED = 0x00000008;
    const HISTORY_DONE = 0x00000010;
    const ROOT_CHANGED = 0x00000020;
    const MOUNT = 0x00000040;
    const UNMOUNT = 0x00000080;
    const ITEM_CREATED = 0x00000100;
    const ITEM_REMOVED = 0x00000200;
    const INOTE_META_MOD = 0x00000400;
    const ITEM_RENAMED = 0x00000800;
    const ITEM_MODIFIED = 0x00001000;
    const FINDER_INFO_MOD = 0x00002000;
    const ITEM_CHANGE_OWNER = 0x00004000;
    const ITEM_XATTR_MOD = 0x00008000;
    const IS_FILE = 0x00010000;
    const IS_DIR = 0x00020000;
    const IS_SYMLIMK = 0x00040000;
    const OWN_EVENT = 0x00080000;
    const IS_HARDLINK = 0x00100000;
    const IS_LAST_HARDLINK = 0x00200000;
    const ITEM_CLONED = 0x400000;
  }
}

impl std::fmt::Display for StreamFlags {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    if self.contains(StreamFlags::MUST_SCAN_SUBDIRS) {
      let _d = write!(f, "MUST_SCAN_SUBDIRS ");
    }
    if self.contains(StreamFlags::USER_DROPPED) {
      let _d = write!(f, "USER_DROPPED ");
    }
    if self.contains(StreamFlags::KERNEL_DROPPED) {
      let _d = write!(f, "KERNEL_DROPPED ");
    }
    if self.contains(StreamFlags::IDS_WRAPPED) {
      let _d = write!(f, "IDS_WRAPPED ");
    }
    if self.contains(StreamFlags::HISTORY_DONE) {
      let _d = write!(f, "HISTORY_DONE ");
    }
    if self.contains(StreamFlags::ROOT_CHANGED) {
      let _d = write!(f, "ROOT_CHANGED ");
    }
    if self.contains(StreamFlags::MOUNT) {
      let _d = write!(f, "MOUNT ");
    }
    if self.contains(StreamFlags::UNMOUNT) {
      let _d = write!(f, "UNMOUNT ");
    }
    if self.contains(StreamFlags::ITEM_CREATED) {
      let _d = write!(f, "ITEM_CREATED ");
    }
    if self.contains(StreamFlags::ITEM_REMOVED) {
      let _d = write!(f, "ITEM_REMOVED ");
    }
    if self.contains(StreamFlags::INOTE_META_MOD) {
      let _d = write!(f, "INOTE_META_MOD ");
    }
    if self.contains(StreamFlags::ITEM_RENAMED) {
      let _d = write!(f, "ITEM_RENAMED ");
    }
    if self.contains(StreamFlags::ITEM_MODIFIED) {
      let _d = write!(f, "ITEM_MODIFIED ");
    }
    if self.contains(StreamFlags::FINDER_INFO_MOD) {
      let _d = write!(f, "FINDER_INFO_MOD ");
    }
    if self.contains(StreamFlags::ITEM_CHANGE_OWNER) {
      let _d = write!(f, "ITEM_CHANGE_OWNER ");
    }
    if self.contains(StreamFlags::ITEM_XATTR_MOD) {
      let _d = write!(f, "ITEM_XATTR_MOD ");
    }
    if self.contains(StreamFlags::IS_FILE) {
      let _d = write!(f, "IS_FILE ");
    }
    if self.contains(StreamFlags::IS_DIR) {
      let _d = write!(f, "IS_DIR ");
    }
    if self.contains(StreamFlags::IS_SYMLIMK) {
      let _d = write!(f, "IS_SYMLIMK ");
    }
    if self.contains(StreamFlags::OWN_EVENT) {
      let _d = write!(f, "OWN_EVENT ");
    }
    if self.contains(StreamFlags::IS_LAST_HARDLINK) {
      let _d = write!(f, "IS_LAST_HARDLINK ");
    }
    if self.contains(StreamFlags::IS_HARDLINK) {
      let _d = write!(f, "IS_HARDLINK ");
    }
    if self.contains(StreamFlags::ITEM_CLONED) {
      let
      _d = write!(f, "ITEM_CLONED ");
    }
    write!(f, "")
  }
}

pub fn is_api_available() -> (bool, String) {
  let ma = cf::system_version_major();
  let mi = cf::system_version_minor();

  if ma == 10 && mi < 5 {
    (false, "This version of OSX does not support the FSEvent library, cannot proceed".to_string())
  } else {
    (true, "ok".to_string())
  }
}

fn default_stream_context(info: *const FsEvent) -> fs::FSEventStreamContext {
  let ptr = info as *mut libc::c_void;
  fs::FSEventStreamContext {
    version: 0,
    info: ptr,
    retain: cf::NULL,
    copy_description: cf::NULL
  }
}

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub struct Error {
    msg: String,
}

impl std::error::Error for Error {
    fn description(&self) -> &str {
        &self.msg
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.msg.fmt(f)
    }
}

impl FsEvent {
  pub fn new(sender: Sender<Event>) -> FsEvent {
    let fsevent: FsEvent;

    unsafe {
      fsevent = FsEvent{
        paths: cf::CFArrayCreateMutable(cf::kCFAllocatorDefault, 0, &cf::kCFTypeArrayCallBacks),
        since_when: fs::kFSEventStreamEventIdSinceNow,
        latency: 0.0,
        flags: fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer,
        sender: sender,
      };
    }
    fsevent
  }

  // https://github.com/thibaudgg/rb-fsevent/blob/master/ext/fsevent_watch/main.c
  pub fn append_path(&self,source: &str) -> Result<()> {
    unsafe {
      let mut err = ptr::null_mut();
      let cf_path = cf::str_path_to_cfstring_ref(source, &mut err);
      if err != ptr::null_mut() {
          let cf_str = cf::CFCopyDescription(err as cf::CFRef);
          let mut buf = [0; 1024];
          cf::CFStringGetCString(cf_str, buf.as_mut_ptr(), buf.len() as cf::CFIndex, cf::kCFStringEncodingUTF8);
          Err(Error { msg: CStr::from_ptr(buf.as_ptr()).to_str().unwrap_or("Unknown error").to_string() })
      } else {
          cf::CFArrayAppendValue(self.paths, cf_path);
          cf::CFRelease(cf_path);
          Ok(())
      }
    }
  }
  pub fn observe(&self) {
    let stream_context = default_stream_context(self);

    let cb = callback as *mut _;

    unsafe {
      let stream = fs::FSEventStreamCreate(cf::kCFAllocatorDefault,
       cb,
       &stream_context,
       self.paths,
       self.since_when,
       self.latency,
       self.flags);

      // fs::FSEventStreamShow(stream);

      fs::FSEventStreamScheduleWithRunLoop(stream,
        cf::CFRunLoopGetCurrent(),
        cf::kCFRunLoopDefaultMode);

      fs::FSEventStreamStart(stream);
      cf::CFRunLoopRun();

      fs::FSEventStreamFlushSync(stream);
      fs::FSEventStreamStop(stream);

    }
  }
}

#[allow(unused_variables)]
pub fn callback(
    stream_ref: fs::FSEventStreamRef,
    info: *mut libc::c_void,
    num_events: libc::size_t,      // size_t numEvents
    event_paths: *const *const libc::c_char, // void *eventPaths
    event_flags: *mut libc::c_void, // const FSEventStreamEventFlags eventFlags[]
    event_ids: *mut libc::c_void,  // const FSEventStreamEventId eventIds[]
  ) {
  let num = num_events as usize;
  let e_ptr = event_flags as *mut u32;
  let i_ptr = event_ids as *mut u64;
  let fs_event = info as *mut FsEvent;

  unsafe {
    let paths: &[*const libc::c_char] = std::mem::transmute(slice::from_raw_parts(event_paths, num));
    let flags = from_raw_parts_mut(e_ptr, num);
    let ids = from_raw_parts_mut(i_ptr, num);

    for p in 0..num {
      let i = CStr::from_ptr(paths[p]).to_bytes();
      let path = from_utf8(i).ok().expect("Invalid UTF8 string.");
      let flag: StreamFlags = StreamFlags::from_bits(flags[p] as u32)
      .expect(format!("Unable to decode StreamFlags: {} for {}", flags[p] as u32, path).as_ref());
      // println!("{}: {}", ids[p], flag);

      let event = Event{event_id: ids[p], flag: flag, path: path.to_string()};
      let _s = (*fs_event).sender.send(event);
    }
  }
}
<commit_msg>clippy: Use is_null, not != ptr::null_mut().<commit_after>#![cfg_attr(feature = "cargo-clippy", allow(unreadable_literal))]

#[macro_use] extern crate bitflags;

extern crate fsevent_sys as fsevent;
extern crate libc;

use fsevent::core_foundation as cf;
use fsevent::fsevent as fs;

use std::ptr;
use std::slice;
use std::slice::from_raw_parts_mut;
use std::str::from_utf8;
use std::ffi::CStr;
use std::convert::AsRef;

use std::sync::mpsc::{Sender};

pub const NULL: cf::CFRef = cf::NULL;

pub struct FsEvent {
  paths: cf::CFMutableArrayRef,
  since_when: fs::FSEventStreamEventId,
  latency: cf::CFTimeInterval,
  flags: fs::FSEventStreamCreateFlags,
  sender: Sender<Event>,
}

#[derive(Debug)]
pub struct Event {
  pub event_id: u64,
  pub flag: StreamFlags,
  pub path: String,
}

pub type FsEventCallback = fn(Vec<Event>);

// Synchronize with
// /System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/Headers/FSEvents.h
bitflags! {
  #[repr(C)]
  pub struct StreamFlags: u32 {
    const NONE = 0x00000000;
    const MUST_SCAN_SUBDIRS = 0x00000001;
    const USER_DROPPED = 0x00000002;
    const KERNEL_DROPPED = 0x00000004;
    const IDS_WRAPPED = 0x00000008;
    const HISTORY_DONE = 0x00000010;
    const ROOT_CHANGED = 0x00000020;
    const MOUNT = 0x00000040;
    const UNMOUNT = 0x00000080;
    const ITEM_CREATED = 0x00000100;
    const ITEM_REMOVED = 0x00000200;
    const INOTE_META_MOD = 0x00000400;
    const ITEM_RENAMED = 0x00000800;
    const ITEM_MODIFIED = 0x00001000;
    const FINDER_INFO_MOD = 0x00002000;
    const ITEM_CHANGE_OWNER = 0x00004000;
    const ITEM_XATTR_MOD = 0x00008000;
    const IS_FILE = 0x00010000;
    const IS_DIR = 0x00020000;
    const IS_SYMLIMK = 0x00040000;
    const OWN_EVENT = 0x00080000;
    const IS_HARDLINK = 0x00100000;
    const IS_LAST_HARDLINK = 0x00200000;
    const ITEM_CLONED = 0x400000;
  }
}

impl std::fmt::Display for StreamFlags {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    if self.contains(StreamFlags::MUST_SCAN_SUBDIRS) {
      let _d = write!(f, "MUST_SCAN_SUBDIRS ");
    }
    if self.contains(StreamFlags::USER_DROPPED) {
      let _d = write!(f, "USER_DROPPED ");
    }
    if self.contains(StreamFlags::KERNEL_DROPPED) {
      let _d = write!(f, "KERNEL_DROPPED ");
    }
    if self.contains(StreamFlags::IDS_WRAPPED) {
      let _d = write!(f, "IDS_WRAPPED ");
    }
    if self.contains(StreamFlags::HISTORY_DONE) {
      let _d = write!(f, "HISTORY_DONE ");
    }
    if self.contains(StreamFlags::ROOT_CHANGED) {
      let _d = write!(f, "ROOT_CHANGED ");
    }
    if self.contains(StreamFlags::MOUNT) {
      let _d = write!(f, "MOUNT ");
    }
    if self.contains(StreamFlags::UNMOUNT) {
      let _d = write!(f, "UNMOUNT ");
    }
    if self.contains(StreamFlags::ITEM_CREATED) {
      let _d = write!(f, "ITEM_CREATED ");
    }
    if self.contains(StreamFlags::ITEM_REMOVED) {
      let _d = write!(f, "ITEM_REMOVED ");
    }
    if self.contains(StreamFlags::INOTE_META_MOD) {
      let _d = write!(f, "INOTE_META_MOD ");
    }
    if self.contains(StreamFlags::ITEM_RENAMED) {
      let _d = write!(f, "ITEM_RENAMED ");
    }
    if self.contains(StreamFlags::ITEM_MODIFIED) {
      let _d = write!(f, "ITEM_MODIFIED ");
    }
    if self.contains(StreamFlags::FINDER_INFO_MOD) {
      let _d = write!(f, "FINDER_INFO_MOD ");
    }
    if self.contains(StreamFlags::ITEM_CHANGE_OWNER) {
      let _d = write!(f, "ITEM_CHANGE_OWNER ");
    }
    if self.contains(StreamFlags::ITEM_XATTR_MOD) {
      let _d = write!(f, "ITEM_XATTR_MOD ");
    }
    if self.contains(StreamFlags::IS_FILE) {
      let _d = write!(f, "IS_FILE ");
    }
    if self.contains(StreamFlags::IS_DIR) {
      let _d = write!(f, "IS_DIR ");
    }
    if self.contains(StreamFlags::IS_SYMLIMK) {
      let _d = write!(f, "IS_SYMLIMK ");
    }
    if self.contains(StreamFlags::OWN_EVENT) {
      let _d = write!(f, "OWN_EVENT ");
    }
    if self.contains(StreamFlags::IS_LAST_HARDLINK) {
      let _d = write!(f, "IS_LAST_HARDLINK ");
    }
    if self.contains(StreamFlags::IS_HARDLINK) {
      let _d = write!(f, "IS_HARDLINK ");
    }
    if self.contains(StreamFlags::ITEM_CLONED) {
      let
      _d = write!(f, "ITEM_CLONED ");
    }
    write!(f, "")
  }
}

pub fn is_api_available() -> (bool, String) {
  let ma = cf::system_version_major();
  let mi = cf::system_version_minor();

  if ma == 10 && mi < 5 {
    (false, "This version of OSX does not support the FSEvent library, cannot proceed".to_string())
  } else {
    (true, "ok".to_string())
  }
}

fn default_stream_context(info: *const FsEvent) -> fs::FSEventStreamContext {
  let ptr = info as *mut libc::c_void;
  fs::FSEventStreamContext {
    version: 0,
    info: ptr,
    retain: cf::NULL,
    copy_description: cf::NULL
  }
}

pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub struct Error {
    msg: String,
}

impl std::error::Error for Error {
    fn description(&self) -> &str {
        &self.msg
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        self.msg.fmt(f)
    }
}

impl FsEvent {
  pub fn new(sender: Sender<Event>) -> FsEvent {
    let fsevent: FsEvent;

    unsafe {
      fsevent = FsEvent{
        paths: cf::CFArrayCreateMutable(cf::kCFAllocatorDefault, 0, &cf::kCFTypeArrayCallBacks),
        since_when: fs::kFSEventStreamEventIdSinceNow,
        latency: 0.0,
        flags: fs::kFSEventStreamCreateFlagFileEvents | fs::kFSEventStreamCreateFlagNoDefer,
        sender: sender,
      };
    }
    fsevent
  }

  // https://github.com/thibaudgg/rb-fsevent/blob/master/ext/fsevent_watch/main.c
  pub fn append_path(&self,source: &str) -> Result<()> {
    unsafe {
      let mut err = ptr::null_mut();
      let cf_path = cf::str_path_to_cfstring_ref(source, &mut err);
      if !err.is_null() {
          let cf_str = cf::CFCopyDescription(err as cf::CFRef);
          let mut buf = [0; 1024];
          cf::CFStringGetCString(cf_str, buf.as_mut_ptr(), buf.len() as cf::CFIndex, cf::kCFStringEncodingUTF8);
          Err(Error { msg: CStr::from_ptr(buf.as_ptr()).to_str().unwrap_or("Unknown error").to_string() })
      } else {
          cf::CFArrayAppendValue(self.paths, cf_path);
          cf::CFRelease(cf_path);
          Ok(())
      }
    }
  }
  pub fn observe(&self) {
    let stream_context = default_stream_context(self);

    let cb = callback as *mut _;

    unsafe {
      let stream = fs::FSEventStreamCreate(cf::kCFAllocatorDefault,
       cb,
       &stream_context,
       self.paths,
       self.since_when,
       self.latency,
       self.flags);

      // fs::FSEventStreamShow(stream);

      fs::FSEventStreamScheduleWithRunLoop(stream,
        cf::CFRunLoopGetCurrent(),
        cf::kCFRunLoopDefaultMode);

      fs::FSEventStreamStart(stream);
      cf::CFRunLoopRun();

      fs::FSEventStreamFlushSync(stream);
      fs::FSEventStreamStop(stream);

    }
  }
}

#[allow(unused_variables)]
pub fn callback(
    stream_ref: fs::FSEventStreamRef,
    info: *mut libc::c_void,
    num_events: libc::size_t,      // size_t numEvents
    event_paths: *const *const libc::c_char, // void *eventPaths
    event_flags: *mut libc::c_void, // const FSEventStreamEventFlags eventFlags[]
    event_ids: *mut libc::c_void,  // const FSEventStreamEventId eventIds[]
  ) {
  let num = num_events as usize;
  let e_ptr = event_flags as *mut u32;
  let i_ptr = event_ids as *mut u64;
  let fs_event = info as *mut FsEvent;

  unsafe {
    let paths: &[*const libc::c_char] = std::mem::transmute(slice::from_raw_parts(event_paths, num));
    let flags = from_raw_parts_mut(e_ptr, num);
    let ids = from_raw_parts_mut(i_ptr, num);

    for p in 0..num {
      let i = CStr::from_ptr(paths[p]).to_bytes();
      let path = from_utf8(i).ok().expect("Invalid UTF8 string.");
      let flag: StreamFlags = StreamFlags::from_bits(flags[p] as u32)
      .expect(format!("Unable to decode StreamFlags: {} for {}", flags[p] as u32, path).as_ref());
      // println!("{}: {}", ids[p], flag);

      let event = Event{event_id: ids[p], flag: flag, path: path.to_string()};
      let _s = (*fs_event).sender.send(event);
    }
  }
}
<|endoftext|>


<commit_before>extern crate rusoto_mock;

use ::{CloudFront, CloudFrontClient, ListDistributionsRequest};

use rusoto_core::Region;
use self::rusoto_mock::*;

#[test]
fn should_list_distributions() {
    // reproduced from botocore `cloudfront-list-distributions.xml`
    let mock = MockRequestDispatcher::with_status(200)
        .with_body(r#"<?xml version="1.0" encoding="UTF-8"?>
<DistributionList xmlns="http://cloudfront.amazonaws.com/doc/2014-05-31/">
   <Marker>RMPARXS293KSTG7</Marker>
   <NextMarker>EMLARXS9EXAMPLE</NextMarker>
   <MaxItems>2</MaxItems>
   <IsTruncated>true</IsTruncated>
   <Quantity>1</Quantity>
   <Items>
      <DistributionSummary>
         <Id>EDFDVBD6EXAMPLE</Id>
         <Status>Deployed</Status>
         <LastModifiedTime>2012-05-19T19:37:58Z</LastModifiedTime>
         <DomainName>d111111abcdef8.cloudfront.net</DomainName>
         <Aliases>
            <Quantity>1</Quantity>
            <Items>
               <CNAME>www.example.com</CNAME>
            </Items>
         </Aliases>
         <Origins>
            <Quantity>2</Quantity>
            <Items>
               <Origin>
                  <Id>example-Amazon S3-origin</Id>
                  <DomainName>myawsbucket.s3.amazonaws.com</DomainName>
                  <S3OriginConfig>
                     <OriginAccessIdentity>origin-access-identity/cloudfront/E74FTE3AEXAMPLE</OriginAccessIdentity>
                  </S3OriginConfig>
               </Origin>
               <Origin>
                  <Id>example-custom-origin</Id>
                  <DomainName>example.com</DomainName>
                  <CustomOriginConfig>
                     <HTTPPort>80</HTTPPort>
                     <HTTPSPort>443</HTTPSPort>
                     <OriginProtocolPolicy>match-viewer</OriginProtocolPolicy>
                  </CustomOriginConfig>
               </Origin>
            </Items>
         </Origins>
         <DefaultCacheBehavior>
            <TargetOriginId>example-Amazon S3-origin</TargetOriginId>
            <ForwardedValues>
               <QueryString>true</QueryString>
               <Cookies>
                  <Forward>whitelist</Forward>
                  <WhitelistedNames>
                     <Quantity>1</Quantity>
                     <Items>
                        <Name>example-cookie</Name>
                     </Items>
                  </WhitelistedNames>
               </Cookies>
            </ForwardedValues>
            <TrustedSigners>
               <Enabled>true</Enabled>
               <Quantity>3</Quantity>
               <Items>
                  <AwsAccountNumber>self</AwsAccountNumber>
                  <AwsAccountNumber>111122223333</AwsAccountNumber>
                  <AwsAccountNumber>444455556666</AwsAccountNumber>
               </Items>
            </TrustedSigners>
            <ViewerProtocolPolicy>https-only</ViewerProtocolPolicy>
            <MinTTL>0</MinTTL>
         </DefaultCacheBehavior>
         <CacheBehaviors>
            <Quantity>1</Quantity>
            <Items>
               <CacheBehavior>
                  <PathPattern>*.jpg</PathPattern>
                  <TargetOriginId>example-custom-origin</TargetOriginId>
                  <ForwardedValues>
                     <QueryString>false</QueryString>
                     <Cookies>
                        <Forward>all</Forward>
                     </Cookies>
                  </ForwardedValues>
                  <TrustedSigners>
                     <Enabled>true</Enabled>
                     <Quantity>2</Quantity>
                     <Items>
                        <AwsAccountNumber>self</AwsAccountNumber>
                        <AwsAccountNumber>111122223333</AwsAccountNumber>
                     </Items>
                  </TrustedSigners>
                  <ViewerProtocolPolicy>allow-all</ViewerProtocolPolicy>
                  <MinTTL>86400</MinTTL>
               </CacheBehavior>
            </Items>
         </CacheBehaviors>
         <Comment>example comment</Comment>
         <Logging>
            <Enabled>true</Enabled>
            <IncludeCookies>true</IncludeCookies>
            <Bucket>myawslogbucket.s3.amazonaws.com</Bucket>
            <Prefix>example.com.</Prefix>
         </Logging>
         <ViewerCertificate>
            <IAMCertificateId>AS1A2M3P4L5E67SIIXR3J</IAMCertificateId>
         </ViewerCertificate>
         <PriceClass>PriceClass_All</PriceClass>
         <Enabled>true</Enabled>
      </DistributionSummary>
   </Items>
</DistributionList>"#);

    let request = ListDistributionsRequest::default();

    let client = CloudFrontClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let result = client.list_distributions(request).sync().unwrap();
    assert!(&result.distribution_list.is_some());
    let parsed_result = result.distribution_list.unwrap();
    assert!(&parsed_result.items.is_some());
    assert_eq!(parsed_result.items.unwrap().len(), 1);
}
<commit_msg>Deeper testing of CloudFront unit test result.<commit_after>extern crate rusoto_mock;

use ::{CloudFront, CloudFrontClient, ListDistributionsRequest};

use rusoto_core::Region;
use self::rusoto_mock::*;

#[test]
fn should_list_distributions() {
    // reproduced from botocore `cloudfront-list-distributions.xml`
    let mock = MockRequestDispatcher::with_status(200)
        .with_body(r#"<?xml version="1.0" encoding="UTF-8"?>
<DistributionList xmlns="http://cloudfront.amazonaws.com/doc/2014-05-31/">
   <Marker>RMPARXS293KSTG7</Marker>
   <NextMarker>EMLARXS9EXAMPLE</NextMarker>
   <MaxItems>2</MaxItems>
   <IsTruncated>true</IsTruncated>
   <Quantity>1</Quantity>
   <Items>
      <DistributionSummary>
         <Id>EDFDVBD6EXAMPLE</Id>
         <Status>Deployed</Status>
         <LastModifiedTime>2012-05-19T19:37:58Z</LastModifiedTime>
         <DomainName>d111111abcdef8.cloudfront.net</DomainName>
         <Aliases>
            <Quantity>1</Quantity>
            <Items>
               <CNAME>www.example.com</CNAME>
            </Items>
         </Aliases>
         <Origins>
            <Quantity>2</Quantity>
            <Items>
               <Origin>
                  <Id>example-Amazon S3-origin</Id>
                  <DomainName>myawsbucket.s3.amazonaws.com</DomainName>
                  <S3OriginConfig>
                     <OriginAccessIdentity>origin-access-identity/cloudfront/E74FTE3AEXAMPLE</OriginAccessIdentity>
                  </S3OriginConfig>
               </Origin>
               <Origin>
                  <Id>example-custom-origin</Id>
                  <DomainName>example.com</DomainName>
                  <CustomOriginConfig>
                     <HTTPPort>80</HTTPPort>
                     <HTTPSPort>443</HTTPSPort>
                     <OriginProtocolPolicy>match-viewer</OriginProtocolPolicy>
                  </CustomOriginConfig>
               </Origin>
            </Items>
         </Origins>
         <DefaultCacheBehavior>
            <TargetOriginId>example-Amazon S3-origin</TargetOriginId>
            <ForwardedValues>
               <QueryString>true</QueryString>
               <Cookies>
                  <Forward>whitelist</Forward>
                  <WhitelistedNames>
                     <Quantity>1</Quantity>
                     <Items>
                        <Name>example-cookie</Name>
                     </Items>
                  </WhitelistedNames>
               </Cookies>
            </ForwardedValues>
            <TrustedSigners>
               <Enabled>true</Enabled>
               <Quantity>3</Quantity>
               <Items>
                  <AwsAccountNumber>self</AwsAccountNumber>
                  <AwsAccountNumber>111122223333</AwsAccountNumber>
                  <AwsAccountNumber>444455556666</AwsAccountNumber>
               </Items>
            </TrustedSigners>
            <ViewerProtocolPolicy>https-only</ViewerProtocolPolicy>
            <MinTTL>0</MinTTL>
         </DefaultCacheBehavior>
         <CacheBehaviors>
            <Quantity>1</Quantity>
            <Items>
               <CacheBehavior>
                  <PathPattern>*.jpg</PathPattern>
                  <TargetOriginId>example-custom-origin</TargetOriginId>
                  <ForwardedValues>
                     <QueryString>false</QueryString>
                     <Cookies>
                        <Forward>all</Forward>
                     </Cookies>
                  </ForwardedValues>
                  <TrustedSigners>
                     <Enabled>true</Enabled>
                     <Quantity>2</Quantity>
                     <Items>
                        <AwsAccountNumber>self</AwsAccountNumber>
                        <AwsAccountNumber>111122223333</AwsAccountNumber>
                     </Items>
                  </TrustedSigners>
                  <ViewerProtocolPolicy>allow-all</ViewerProtocolPolicy>
                  <MinTTL>86400</MinTTL>
               </CacheBehavior>
            </Items>
         </CacheBehaviors>
         <Comment>example comment</Comment>
         <Logging>
            <Enabled>true</Enabled>
            <IncludeCookies>true</IncludeCookies>
            <Bucket>myawslogbucket.s3.amazonaws.com</Bucket>
            <Prefix>example.com.</Prefix>
         </Logging>
         <ViewerCertificate>
            <IAMCertificateId>AS1A2M3P4L5E67SIIXR3J</IAMCertificateId>
         </ViewerCertificate>
         <PriceClass>PriceClass_All</PriceClass>
         <Enabled>true</Enabled>
      </DistributionSummary>
   </Items>
</DistributionList>"#);

    let request = ListDistributionsRequest::default();

    let client = CloudFrontClient::new_with(mock, MockCredentialsProvider, Region::UsEast1);
    let result = client.list_distributions(request).sync().unwrap();
    assert!(&result.distribution_list.is_some());
    let parsed_result = result.distribution_list.unwrap();
    assert!(&parsed_result.items.is_some());
    let items = parsed_result.items.unwrap();
    assert_eq!(items.len(), 1);
    let first_item = &items[0];
    assert_eq!(first_item.id, "EDFDVBD6EXAMPLE");
    assert_eq!(first_item.status, "Deployed");
    assert_eq!(first_item.domain_name, "d111111abcdef8.cloudfront.net");
    assert_eq!(first_item.origins.quantity, 2);
    match first_item.origins.items {
        Some(ref i) => assert_eq!(i[1].domain_name, "example.com"),
        None => panic!("Should have items"),
    }
}
<|endoftext|>


<commit_before>use std::str::FromStr;
use std::process::Command;
use serde_json;

use error::Error;
use error::Error::ErrCronFormat;
use scheduler::{Scheduler, SchedulerResult};

#[derive(Serialize, Deserialize, Debug)]
pub struct CronEntry {
    id: u32,
    cronId: u32,
    treeId: u64,
    treePath: String,

    intervals: String,
    command: String,
    description: String,
    daemon: bool,
    testRun: bool,
    timeoutSeconds: u32,
    autokill: bool,
    alarmEmail: bool,
    alarmSms: bool,
    alarmUsers: String,
    host: String,

    watch: bool,
    active: bool,
}

impl CronEntry {
    pub fn to_command(&self) -> Command {
        let fields: Vec<&str> = self.command.split_whitespace().collect();
        let mut cmd = Command::new(fields[0]);
        cmd.args(&fields[1..]);
        cmd
    }

    pub fn to_scheduler(&self) -> SchedulerResult {
        Scheduler::new(&self.intervals)
    }
}

impl FromStr for CronEntry {
    type Err = Error;

    fn from_str(s: &str) -> Result<CronEntry, Error> {
        serde_json::from_str::<CronEntry>(s).map_err(|e| ErrCronFormat(e.to_string()))
    }
}

#[test]
fn test_from_str() {
    let e = r#"{"intervals":"* * * * *","command":"date +%F_%T","description":"每分钟打印详细时间","daemon":false,"testRun":true,"timeoutSeconds":5,"autokill":true,"alarmEmail":false,"alarmSms":true,"alarmUsers":"gaobushuang","id":1,"cronId":1,"treeId":261,"treePath":"b2c.b2cop.build-ci.build-ci.cn-test","active":false,"updateTime":"2017-02-07T13:22:52+08:00","lastCode":0,"lastJob":0,"watch":true,"host":""}"#
        .parse::<CronEntry>().unwrap();
    println!("{:?}", e);
}
<commit_msg>feat(core): make all fields of CronEntry to be public<commit_after>use std::str::FromStr;
use std::process::Command;
use serde_json;

use error::Error;
use error::Error::ErrCronFormat;
use scheduler::{Scheduler, SchedulerResult};

#[derive(Serialize, Deserialize, Debug)]
pub struct CronEntry {
    pub id: u32,
    pub cronId: u32,
    pub treeId: u64,
    pub treePath: String,

    pub intervals: String,
    pub command: String,
    pub description: String,
    pub daemon: bool,
    pub testRun: bool,
    pub timeoutSeconds: u32,
    pub autokill: bool,
    pub alarmEmail: bool,
    pub alarmSms: bool,
    pub alarmUsers: String,
    pub host: String,

    pub watch: bool,
    pub active: bool,
}

impl CronEntry {
    pub fn to_command(&self) -> Command {
        let fields: Vec<&str> = self.command.split_whitespace().collect();
        let mut cmd = Command::new(fields[0]);
        cmd.args(&fields[1..]);
        cmd
    }

    pub fn to_scheduler(&self) -> SchedulerResult {
        Scheduler::new(&self.intervals)
    }
}

impl FromStr for CronEntry {
    type Err = Error;

    fn from_str(s: &str) -> Result<CronEntry, Error> {
        serde_json::from_str::<CronEntry>(s).map_err(|e| ErrCronFormat(e.to_string()))
    }
}

#[test]
fn test_from_str() {
    let e = r#"{"intervals":"* * * * *","command":"date +%F_%T","description":"每分钟打印详细时间","daemon":false,"testRun":true,"timeoutSeconds":5,"autokill":true,"alarmEmail":false,"alarmSms":true,"alarmUsers":"gaobushuang","id":1,"cronId":1,"treeId":261,"treePath":"b2c.b2cop.build-ci.build-ci.cn-test","active":false,"updateTime":"2017-02-07T13:22:52+08:00","lastCode":0,"lastJob":0,"watch":true,"host":""}"#
        .parse::<CronEntry>().unwrap();
    println!("{:?}", e);
}
<|endoftext|>