<commit_before>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0
use crate::{
    counters::{self, FAILED_LABEL, SUCCEEDED_LABEL},
    logging::*,
    peer_manager::{PeerManagerError, TransportNotification},
    transport::Connection,
};
use anyhow::format_err;
use channel::{self};
use diem_config::network_id::NetworkContext;
use diem_logger::prelude::*;
use diem_time_service::{TimeService, TimeServiceTrait};
use diem_types::{network_address::NetworkAddress, PeerId};
use futures::{
    channel::oneshot,
    future::{BoxFuture, FutureExt},
    io::{AsyncRead, AsyncWrite},
    sink::SinkExt,
    stream::{Fuse, FuturesUnordered, StreamExt},
};
use netcore::transport::{ConnectionOrigin, Transport};
use short_hex_str::AsShortHexStr;
use std::{sync::Arc, time::Instant};

#[derive(Debug)]
pub enum TransportRequest {
    DialPeer(
        PeerId,
        NetworkAddress,
        oneshot::Sender<Result<(), PeerManagerError>>,
    ),
}

/// Responsible for listening for new incoming connections
pub struct TransportHandler<TTransport, TSocket>
where
    TTransport: Transport,
    TSocket: AsyncRead + AsyncWrite,
{
    network_context: Arc<NetworkContext>,
    time_service: TimeService,
    /// [`Transport`] that is used to establish connections
    transport: TTransport,
    listener: Fuse<TTransport::Listener>,
    transport_reqs_rx: channel::Receiver<TransportRequest>,
    transport_notifs_tx: channel::Sender<TransportNotification<TSocket>>,
}

impl<TTransport, TSocket> TransportHandler<TTransport, TSocket>
where
    TTransport: Transport<Output = Connection<TSocket>>,
    TTransport::Listener: 'static,
    TTransport::Inbound: 'static,
    TTransport::Outbound: 'static,
    TSocket: AsyncRead + AsyncWrite + 'static,
{
    pub fn new(
        network_context: Arc<NetworkContext>,
        time_service: TimeService,
        transport: TTransport,
        listen_addr: NetworkAddress,
        transport_reqs_rx: channel::Receiver<TransportRequest>,
        transport_notifs_tx: channel::Sender<TransportNotification<TSocket>>,
    ) -> (Self, NetworkAddress) {
        let (listener, listen_addr) = transport
            .listen_on(listen_addr)
            .expect("Transport listen on fails");
        debug!(
            NetworkSchema::new(&network_context),
            listen_address = listen_addr,
            "{} listening on '{}'",
            network_context,
            listen_addr
        );
        (
            Self {
                network_context,
                time_service,
                transport,
                listener: listener.fuse(),
                transport_reqs_rx,
                transport_notifs_tx,
            },
            listen_addr,
        )
    }

    pub async fn listen(mut self) {
        let mut pending_inbound_connections = FuturesUnordered::new();
        let mut pending_outbound_connections = FuturesUnordered::new();

        debug!(
            NetworkSchema::new(&self.network_context),
            "{} Incoming connections listener Task started", self.network_context
        );

        loop {
            futures::select! {
                dial_request = self.transport_reqs_rx.select_next_some() => {
                    if let Some(fut) = self.dial_peer(dial_request) {
                        pending_outbound_connections.push(fut);
                    }
                },
                incoming_connection = self.listener.select_next_some() => {
                    match incoming_connection {
                        Ok((upgrade, addr)) => {
                            debug!(
                                NetworkSchema::new(&self.network_context)
                                    .network_address(&addr),
                                "{} Incoming connection from {}",
                                self.network_context,
                                addr
                            );

                            counters::pending_connection_upgrades(
                                &self.network_context,
                                ConnectionOrigin::Inbound,
                            )
                            .inc();

                            let start_time = self.time_service.now();
                            pending_inbound_connections.push(upgrade.map(move |out| (out, addr, start_time)));
                        }
                        Err(e) => {
                            info!(
                                NetworkSchema::new(&self.network_context),
                                error = %e,
                                "{} Incoming connection error {}",
                                self.network_context,
                                e
                            );
                        }
                    }
                },
                (upgrade, addr, peer_id, start_time, response_tx) = pending_outbound_connections.select_next_some() => {
                    self.handle_completed_outbound_upgrade(upgrade, addr, peer_id, start_time, response_tx).await;
                },
                (upgrade, addr, start_time) = pending_inbound_connections.select_next_some() => {
                    self.handle_completed_inbound_upgrade(upgrade, addr, start_time).await;
                },
                complete => break,
            }
        }

        warn!(
            NetworkSchema::new(&self.network_context),
            "{} Incoming connections listener Task ended", self.network_context
        );
    }

    fn dial_peer(
        &self,
        dial_peer_request: TransportRequest,
    ) -> Option<
        BoxFuture<
            'static,
            (
                Result<Connection<TSocket>, TTransport::Error>,
                NetworkAddress,
                PeerId,
                Instant,
                oneshot::Sender<Result<(), PeerManagerError>>,
            ),
        >,
    > {
        match dial_peer_request {
            TransportRequest::DialPeer(peer_id, addr, response_tx) => {
                match self.transport.dial(peer_id, addr.clone()) {
                    Ok(upgrade) => {
                        counters::pending_connection_upgrades(
                            &self.network_context,
                            ConnectionOrigin::Outbound,
                        )
                        .inc();

                        let start_time = self.time_service.now();
                        Some(
                            upgrade
                                .map(move |out| (out, addr, peer_id, start_time, response_tx))
                                .boxed(),
                        )
                    }
                    Err(error) => {
                        if let Err(send_err) =
                            response_tx.send(Err(PeerManagerError::from_transport_error(error)))
                        {
                            info!(
                                NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                                "{} Failed to notify clients of TransportError for Peer {}: {:?}",
                                self.network_context,
                                peer_id.short_str(),
                                send_err
                            );
                        }
                        None
                    }
                }
            }
        }
    }

    async fn handle_completed_outbound_upgrade(
        &mut self,
        upgrade: Result<Connection<TSocket>, TTransport::Error>,
        addr: NetworkAddress,
        peer_id: PeerId,
        start_time: Instant,
        response_tx: oneshot::Sender<Result<(), PeerManagerError>>,
    ) {
        counters::pending_connection_upgrades(&self.network_context, ConnectionOrigin::Outbound)
            .dec();

        // Ensure that the connection matches the expected `PeerId`
        let elapsed_time = (self.time_service.now() - start_time).as_secs_f64();
        let upgrade = match upgrade {
            Ok(connection) => {
                let dialed_peer_id = connection.metadata.remote_peer_id;
                if dialed_peer_id == peer_id {
                    Ok(connection)
                } else {
                    Err(PeerManagerError::from_transport_error(format_err!(
                        "Dialed PeerId '{}' differs from expected PeerId '{}'",
                        dialed_peer_id.short_str(),
                        peer_id.short_str()
                    )))
                }
            }
            Err(err) => Err(PeerManagerError::from_transport_error(err)),
        };

        let response = match upgrade {
            Ok(connection) => {
                self.send_connection_to_peer_manager(connection, &addr, elapsed_time)
                    .await;
                Ok(())
            }
            Err(err) => {
                error!(
                    NetworkSchema::new(&self.network_context)
                        .remote_peer(&peer_id)
                        .network_address(&addr),
                    error = %err,
                    "{} Outbound connection failed for peer {} at {}: {}",
                    self.network_context,
                    peer_id.short_str(),
                    addr,
                    err
                );

                counters::connection_upgrade_time(
                    &self.network_context,
                    ConnectionOrigin::Outbound,
                    FAILED_LABEL,
                )
                .observe(elapsed_time);

                Err(err)
            }
        };

        if let Err(send_err) = response_tx.send(response) {
            warn!(
                NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                "{} Failed to notify PeerManager of OutboundConnection upgrade result for Peer {}: {:?}",
                self.network_context,
                peer_id.short_str(),
                send_err
            );
        }
    }

    async fn handle_completed_inbound_upgrade(
        &mut self,
        upgrade: Result<Connection<TSocket>, TTransport::Error>,
        addr: NetworkAddress,
        start_time: Instant,
    ) {
        counters::pending_connection_upgrades(&self.network_context, ConnectionOrigin::Inbound)
            .dec();

        let elapsed_time = (self.time_service.now() - start_time).as_secs_f64();
        match upgrade {
            Ok(connection) => {
                self.send_connection_to_peer_manager(connection, &addr, elapsed_time)
                    .await;
            }
            Err(err) => {
                warn!(
                    NetworkSchema::new(&self.network_context)
                        .network_address(&addr),
                    error = %err,
                    "{} Inbound connection from {} failed to upgrade after {:.3} secs: {}",
                    self.network_context,
                    addr,
                    elapsed_time,
                    err,
                );

                counters::connection_upgrade_time(
                    &self.network_context,
                    ConnectionOrigin::Inbound,
                    FAILED_LABEL,
                )
                .observe(elapsed_time);
            }
        }
    }

    /// Send a newly completed connection to `PeerManager`
    async fn send_connection_to_peer_manager(
        &mut self,
        connection: Connection<TSocket>,
        addr: &NetworkAddress,
        elapsed_time: f64,
    ) {
        let metadata = connection.metadata.clone();
        debug!(
            NetworkSchema::new(&self.network_context)
                .connection_metadata_with_address(&metadata)
                .network_address(addr),
            "{} {} connection from {} at {} successfully upgraded after {:.3} secs",
            self.network_context,
            metadata.origin,
            metadata.remote_peer_id.short_str(),
            metadata.addr,
            elapsed_time,
        );

        counters::connection_upgrade_time(&self.network_context, metadata.origin, SUCCEEDED_LABEL)
            .observe(elapsed_time);

        // Send the new connection to PeerManager
        let event = TransportNotification::NewConnection(connection);
        if let Err(err) = self.transport_notifs_tx.send(event).await {
            error!(
                NetworkSchema::new(&self.network_context)
                    .connection_metadata_with_address(&metadata),
                error = %err,
                "Failed to notify PeerManager of new connection"
            );
        }
    }
}
<commit_msg>[network] Make parity in inbound & outbound transport requests<commit_after>// Copyright (c) The Diem Core Contributors
// SPDX-License-Identifier: Apache-2.0
use crate::{
    counters::{self, FAILED_LABEL, SUCCEEDED_LABEL},
    logging::*,
    peer_manager::{PeerManagerError, TransportNotification},
    transport::Connection,
};
use anyhow::format_err;
use channel::{self};
use diem_config::network_id::NetworkContext;
use diem_logger::prelude::*;
use diem_time_service::{TimeService, TimeServiceTrait};
use diem_types::{network_address::NetworkAddress, PeerId};
use futures::{
    channel::oneshot,
    future::{BoxFuture, FutureExt},
    io::{AsyncRead, AsyncWrite},
    sink::SinkExt,
    stream::{Fuse, FuturesUnordered, StreamExt},
};
use netcore::transport::{ConnectionOrigin, Transport};
use short_hex_str::AsShortHexStr;
use std::{sync::Arc, time::Instant};

#[derive(Debug)]
pub enum TransportRequest {
    DialPeer(
        PeerId,
        NetworkAddress,
        oneshot::Sender<Result<(), PeerManagerError>>,
    ),
}

/// Responsible for listening for new incoming connections and making outbound connections
pub struct TransportHandler<TTransport, TSocket>
where
    TTransport: Transport,
    TSocket: AsyncRead + AsyncWrite,
{
    network_context: Arc<NetworkContext>,
    time_service: TimeService,
    /// [`Transport`] that is used to establish connections
    transport: TTransport,
    listener: Fuse<TTransport::Listener>,
    transport_reqs_rx: channel::Receiver<TransportRequest>,
    transport_notifs_tx: channel::Sender<TransportNotification<TSocket>>,
}

impl<TTransport, TSocket> TransportHandler<TTransport, TSocket>
where
    TTransport: Transport<Output = Connection<TSocket>>,
    TTransport::Listener: 'static,
    TTransport::Inbound: 'static,
    TTransport::Outbound: 'static,
    TSocket: AsyncRead + AsyncWrite + 'static,
{
    pub fn new(
        network_context: Arc<NetworkContext>,
        time_service: TimeService,
        transport: TTransport,
        listen_addr: NetworkAddress,
        transport_reqs_rx: channel::Receiver<TransportRequest>,
        transport_notifs_tx: channel::Sender<TransportNotification<TSocket>>,
    ) -> (Self, NetworkAddress) {
        let (listener, listen_addr) = transport
            .listen_on(listen_addr)
            .expect("Transport listen on fails");
        debug!(
            NetworkSchema::new(&network_context),
            listen_address = listen_addr,
            "{} listening on '{}'",
            network_context,
            listen_addr
        );
        (
            Self {
                network_context,
                time_service,
                transport,
                listener: listener.fuse(),
                transport_reqs_rx,
                transport_notifs_tx,
            },
            listen_addr,
        )
    }

    pub async fn listen(mut self) {
        let mut pending_inbound_connections = FuturesUnordered::new();
        let mut pending_outbound_connections = FuturesUnordered::new();

        debug!(
            NetworkSchema::new(&self.network_context),
            "{} Incoming connections listener Task started", self.network_context
        );

        loop {
            futures::select! {
                dial_request = self.transport_reqs_rx.select_next_some() => {
                    if let Some(fut) = self.dial_peer(dial_request) {
                        pending_outbound_connections.push(fut);
                    }
                },
                inbound_connection = self.listener.select_next_some() => {
                    if let Some(fut) = self.upgrade_inbound_connection(inbound_connection) {
                        pending_inbound_connections.push(fut);
                    }
                },
                (upgrade, addr, peer_id, start_time, response_tx) = pending_outbound_connections.select_next_some() => {
                    self.handle_completed_outbound_upgrade(upgrade, addr, peer_id, start_time, response_tx).await;
                },
                (upgrade, addr, start_time) = pending_inbound_connections.select_next_some() => {
                    self.handle_completed_inbound_upgrade(upgrade, addr, start_time).await;
                },
                complete => break,
            }
        }

        warn!(
            NetworkSchema::new(&self.network_context),
            "{} Incoming connections listener Task ended", self.network_context
        );
    }

    /// Make an inbound request upgrade future e.g. Noise handshakes
    fn upgrade_inbound_connection(
        &self,
        incoming_connection: Result<(TTransport::Inbound, NetworkAddress), TTransport::Error>,
    ) -> Option<
        BoxFuture<
            'static,
            (
                Result<Connection<TSocket>, TTransport::Error>,
                NetworkAddress,
                Instant,
            ),
        >,
    > {
        match incoming_connection {
            Ok((upgrade, addr)) => {
                debug!(
                    NetworkSchema::new(&self.network_context).network_address(&addr),
                    "{} Incoming connection from {}", self.network_context, addr
                );

                counters::pending_connection_upgrades(
                    &self.network_context,
                    ConnectionOrigin::Inbound,
                )
                .inc();

                let start_time = self.time_service.now();
                Some(upgrade.map(move |out| (out, addr, start_time)).boxed())
            }
            Err(e) => {
                info!(
                    NetworkSchema::new(&self.network_context),
                    error = %e,
                    "{} Incoming connection error {}",
                    self.network_context,
                    e
                );
                None
            }
        }
    }

    /// Make an outbound request upgrade future e.g. Noise handshakes
    fn dial_peer(
        &self,
        dial_peer_request: TransportRequest,
    ) -> Option<
        BoxFuture<
            'static,
            (
                Result<Connection<TSocket>, TTransport::Error>,
                NetworkAddress,
                PeerId,
                Instant,
                oneshot::Sender<Result<(), PeerManagerError>>,
            ),
        >,
    > {
        match dial_peer_request {
            TransportRequest::DialPeer(peer_id, addr, response_tx) => {
                match self.transport.dial(peer_id, addr.clone()) {
                    Ok(upgrade) => {
                        counters::pending_connection_upgrades(
                            &self.network_context,
                            ConnectionOrigin::Outbound,
                        )
                        .inc();

                        let start_time = self.time_service.now();
                        Some(
                            upgrade
                                .map(move |out| (out, addr, peer_id, start_time, response_tx))
                                .boxed(),
                        )
                    }
                    Err(error) => {
                        if let Err(send_err) =
                            response_tx.send(Err(PeerManagerError::from_transport_error(error)))
                        {
                            info!(
                                NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                                "{} Failed to notify clients of TransportError for Peer {}: {:?}",
                                self.network_context,
                                peer_id.short_str(),
                                send_err
                            );
                        }
                        None
                    }
                }
            }
        }
    }

    /// Notifies `PeerManager` of a completed or failed outbound connection
    async fn handle_completed_outbound_upgrade(
        &mut self,
        upgrade: Result<Connection<TSocket>, TTransport::Error>,
        addr: NetworkAddress,
        peer_id: PeerId,
        start_time: Instant,
        response_tx: oneshot::Sender<Result<(), PeerManagerError>>,
    ) {
        counters::pending_connection_upgrades(&self.network_context, ConnectionOrigin::Outbound)
            .dec();

        // Ensure that the connection matches the expected `PeerId`
        let elapsed_time = (self.time_service.now() - start_time).as_secs_f64();
        let upgrade = match upgrade {
            Ok(connection) => {
                let dialed_peer_id = connection.metadata.remote_peer_id;
                if dialed_peer_id == peer_id {
                    Ok(connection)
                } else {
                    Err(PeerManagerError::from_transport_error(format_err!(
                        "Dialed PeerId '{}' differs from expected PeerId '{}'",
                        dialed_peer_id.short_str(),
                        peer_id.short_str()
                    )))
                }
            }
            Err(err) => Err(PeerManagerError::from_transport_error(err)),
        };

        let response = match upgrade {
            Ok(connection) => {
                self.send_connection_to_peer_manager(connection, &addr, elapsed_time)
                    .await;
                Ok(())
            }
            Err(err) => {
                error!(
                    NetworkSchema::new(&self.network_context)
                        .remote_peer(&peer_id)
                        .network_address(&addr),
                    error = %err,
                    "{} Outbound connection failed for peer {} at {}: {}",
                    self.network_context,
                    peer_id.short_str(),
                    addr,
                    err
                );

                counters::connection_upgrade_time(
                    &self.network_context,
                    ConnectionOrigin::Outbound,
                    FAILED_LABEL,
                )
                .observe(elapsed_time);

                Err(err)
            }
        };

        if let Err(send_err) = response_tx.send(response) {
            warn!(
                NetworkSchema::new(&self.network_context).remote_peer(&peer_id),
                "{} Failed to notify PeerManager of OutboundConnection upgrade result for Peer {}: {:?}",
                self.network_context,
                peer_id.short_str(),
                send_err
            );
        }
    }

    /// Notifies `PeerManager` of a completed or failed inbound connection
    async fn handle_completed_inbound_upgrade(
        &mut self,
        upgrade: Result<Connection<TSocket>, TTransport::Error>,
        addr: NetworkAddress,
        start_time: Instant,
    ) {
        counters::pending_connection_upgrades(&self.network_context, ConnectionOrigin::Inbound)
            .dec();

        let elapsed_time = (self.time_service.now() - start_time).as_secs_f64();
        match upgrade {
            Ok(connection) => {
                self.send_connection_to_peer_manager(connection, &addr, elapsed_time)
                    .await;
            }
            Err(err) => {
                warn!(
                    NetworkSchema::new(&self.network_context)
                        .network_address(&addr),
                    error = %err,
                    "{} Inbound connection from {} failed to upgrade after {:.3} secs: {}",
                    self.network_context,
                    addr,
                    elapsed_time,
                    err,
                );

                counters::connection_upgrade_time(
                    &self.network_context,
                    ConnectionOrigin::Inbound,
                    FAILED_LABEL,
                )
                .observe(elapsed_time);
            }
        }
    }

    /// Send a newly completed connection to `PeerManager`
    async fn send_connection_to_peer_manager(
        &mut self,
        connection: Connection<TSocket>,
        addr: &NetworkAddress,
        elapsed_time: f64,
    ) {
        let metadata = connection.metadata.clone();
        debug!(
            NetworkSchema::new(&self.network_context)
                .connection_metadata_with_address(&metadata)
                .network_address(addr),
            "{} {} connection from {} at {} successfully upgraded after {:.3} secs",
            self.network_context,
            metadata.origin,
            metadata.remote_peer_id.short_str(),
            metadata.addr,
            elapsed_time,
        );

        counters::connection_upgrade_time(&self.network_context, metadata.origin, SUCCEEDED_LABEL)
            .observe(elapsed_time);

        // Send the new connection to PeerManager
        let event = TransportNotification::NewConnection(connection);
        if let Err(err) = self.transport_notifs_tx.send(event).await {
            error!(
                NetworkSchema::new(&self.network_context)
                    .connection_metadata_with_address(&metadata),
                error = %err,
                "Failed to notify PeerManager of new connection"
            );
        }
    }
}
<|endoftext|>


<commit_before>extern crate philipshue;
extern crate regex;

use std::env;
use std::time::Duration;
use regex::Regex;

use philipshue::hue::LightCommand;
use philipshue::bridge::{discover, Bridge};

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 4 {
        println!("usage : {:?} <username> <light_id>,<light_id>,... on|off|[bri]:[hue]:[sat]|[ct]MK:[bri]|[w]K:[bri]|[RR][GG][BB]:[bri]",
                 args[0]);
        return;
    }
    let bridge = Bridge::new(discover().unwrap().pop().unwrap().into_ip(), &*args[1]);
    let ref input_lights: Vec<usize> = args[2].split(",").map(|s| s.parse::<usize>().unwrap()).collect();
    let ref command = args[3];
    let re_triplet = Regex::new("([0-9]{0,3}):([0-9]{0,5}):([0-9]{0,3})").unwrap();
    let re_mired = Regex::new("([0-9]{0,4})MK:([0-9]{0,5})").unwrap();
    let re_kelvin = Regex::new("([0-9]{4,4})K:([0-9]{0,5})").unwrap();
    let re_rrggbb = Regex::new("([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})").unwrap();

    let mut light_command = LightCommand::default();

    let parsed = match &command[..] {
        "on" => light_command.on(),
        "off" => light_command.off(),
        _ if re_triplet.is_match(&command) => {
            let caps = re_triplet.captures(&command).unwrap();

            light_command.bri = caps.at(1).and_then(|s| s.parse::<u8>().ok());
            light_command.hue = caps.at(2).and_then(|s| s.parse::<u16>().ok());
            light_command.sat = caps.at(3).and_then(|s| s.parse::<u8>().ok());
            light_command
        }
        _ if re_mired.is_match(&command) => {
            let caps = re_mired.captures(&command).unwrap();

            light_command.ct = caps.at(1).and_then(|s| s.parse::<u16>().ok());
            light_command.bri = caps.at(2).and_then(|s| s.parse::<u8>().ok());
            light_command.sat = Some(254);
            light_command
        }
        _ if re_kelvin.is_match(&command) => {
            let caps = re_kelvin.captures(&command).unwrap();

            light_command.ct = caps.at(1).and_then(|s| s.parse::<u32>().ok().map(|k| (1000000u32 / k) as u16));
            light_command.bri = caps.at(2).and_then(|s| s.parse::<u8>().ok());
            light_command.sat = Some(254);
            light_command
        }
        _ if re_rrggbb.is_match(&command) => {
            let caps = re_rrggbb.captures(&command).unwrap();

            let rgb: Vec<u8> = [caps.at(1), caps.at(2), caps.at(3)].iter().map(|s| u8::from_str_radix(s.unwrap(), 16).unwrap()).collect();
            let (hue, sat, bri) = rgb_to_hsv(rgb[0], rgb[1], rgb[2]);
            light_command.with_hue(hue).with_sat(sat).with_bri(bri)
        }
        _ => panic!("can not understand command {:?}", command),
    };

    for &id in input_lights.into_iter() {
        match bridge.set_light_state(id, &parsed){
            Ok(resps) => for resp in resps.into_iter(){
                if let Some(success) = resp.success{
                    println!("Success: {:?}", success)
                }else if let Some(err) = resp.error{
                    println!("Error: {:?}", err);
                }
            },
            Err(e) => println!("Error happened trying to send request:\n\t{:?}", e)
        }
        std::thread::sleep(Duration::from_millis(50))
    }
}

fn rgb_to_hsv(r: u8, g: u8, b: u8) -> (u16, u8, u8) {
    let r = r as f64 / 255f64;
    let g = g as f64 / 255f64;
    let b = b as f64 / 255f64;
    let max = r.max(g.max(b));
    let min = r.min(g.min(b));

    if max == min {
        (0, 0, (max * 255.) as u8)
    } else {
        let d = max - min;
        let s = d / max;
        let h = if max == r {
            (g - b) / d + (if g < b { 6f64 } else { 0f64 })
        } else if max == g {
            (b - r) / d + 2f64
        } else {
            (r - g) / d + 4f64
        };
        ((65535. * h / 6.) as u16, (s * 255.) as u8, (max * 255.) as u8)
    }
}
<commit_msg>Rework set_light_state example<commit_after>extern crate philipshue;
extern crate regex;

use std::env;
use std::time::Duration;
use std::num::ParseIntError;

use philipshue::hue::LightCommand;
use philipshue::bridge::{discover, Bridge};

fn main(){
    match run(){
        Ok(()) => (),
        Err(_) => println!("Invalid number!")
    }
}

fn run() -> Result<(), ParseIntError> {
    let args: Vec<String> = env::args().collect();
    if args.len() < 4 {
        println!("Usage: {} <username> <light_id>,<light_id>,... on|off|bri <bri>|hue <hue>|sat <sat>|rgb <r> <g> <b>|hsv <hue> <sat> <bri>|mired <ct> <bri>|kelvin <temp> <bri>",
                 args[0]);
        return Ok(());
    }
    let bridge = Bridge::new(discover().unwrap().pop().unwrap().into_ip(), &*args[1]);
    let ref input_lights: Vec<usize> = args[2].split(",").fold(Ok(Vec::new()), |v, s| v.and_then(|mut v| s.parse::<usize>().map(|n| v.push(n)).map(|_| v)))?;

    let cmd = LightCommand::default();

    let cmd = match &*args[3]{
        "on" => cmd.on(),
        "off" => cmd.off(),
        "bri" => cmd.with_bri(args[4].parse()?),
        "hue" => cmd.with_hue(args[4].parse()?),
        "sat" => cmd.with_sat(args[4].parse()?),
        "hsv" => cmd
            .with_hue(args[4].parse()?)
            .with_sat(args[5].parse()?)
            .with_bri(args[6].parse()?),
        "rgb" => {
            let (hue, sat, bri) = rgb_to_hsv(args[4].parse()?,
                args[5].parse()?, args[6].parse()?);
            cmd.with_hue(hue).with_sat(sat).with_bri(bri)
        },
        "mired" => cmd
            .with_ct(args[4].parse()?)
            .with_bri(args[5].parse()?)
            .with_sat(254),
        "kelvin" => cmd
            .with_ct((1000000u32 / args[4].parse::<u32>()?) as u16)
            .with_bri(args[5].parse()?)
            .with_sat(254),
        _ => panic!("Invalid command!")
    };

    for &id in input_lights.into_iter() {
        match bridge.set_light_state(id, &cmd){
            Ok(resps) => for resp in resps.into_iter(){
                if let Some(success) = resp.success{
                    println!("Success: {:?}", success)
                }else if let Some(err) = resp.error{
                    println!("Error: {:?}", err);
                }
            },
            Err(e) => println!("Error happened trying to send request:\n\t{:?}", e)
        }
        std::thread::sleep(Duration::from_millis(50))
    }

    Ok(())
}

fn rgb_to_hsv(r: u8, g: u8, b: u8) -> (u16, u8, u8) {
    let r = r as f64 / 255f64;
    let g = g as f64 / 255f64;
    let b = b as f64 / 255f64;
    let max = r.max(g.max(b));
    let min = r.min(g.min(b));

    if max == min {
        (0, 0, (max * 255.) as u8)
    } else {
        let d = max - min;
        let s = d / max;
        let h = if max == r {
            (g - b) / d + (if g < b { 6f64 } else { 0f64 })
        } else if max == g {
            (b - r) / d + 2f64
        } else {
            (r - g) / d + 4f64
        };
        ((65535. * h / 6.) as u16, (s * 255.) as u8, (max * 255.) as u8)
    }
}
<|endoftext|>


<commit_before>// Copyright 2015-2017 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! The Resolver is responsible for performing recursive queries to lookup domain names.
//!
//! This is a 100% in process DNS resolver. It *does not* use the Host OS' resolver. If what is desired is to use the Host OS' resolver, generally in the system's libc, then the `std::net::ToSocketAddrs` variant over `&str` is what should be used. As of the initial release, `trust-dns-resolver` it does not currently support search paths or ndot recursive lookups. It only supports FQDN, where the name must be specified with the final dot, e.g. `www.example.com.`. This limitation will be removed in future releases.
//!
//! Unlike the `trust-dns` client, this tries to provide a simpler interface to perform DNS queries. For update options, i.e. Dynamic DNS, the `trust-dns` crate must be used directly.
//!
//! There are two types for performing DNS queries, `Resolver` and `ResolverFuture`. `Resolver` is the easiest to work with, it is a wrapper around `ResolverFuture`. `ResolverFuture` is a `Tokio` based async resolver, and can be used inside any `Tokio` based system.
//!
//! *Notes on current limitations*: Some standard system options and configurations are not yet supported. Recursion is not yet built, i.e. CNAME chains will not be fully resolved if an address is not returned, though many upstream resolvers will perform this making it a none blocking issue.
//!
//! This as best as possible attempts to abide by the the DNS RFCs, please file issues at https://github.com/bluejekyll/trust-dns .
//!
//! # Usage
//!
//! ## Declare dependency
//!
//! ```toml
//! [dependency]
//! trust-dns-resolver = "^0.1"
//! ```
//!
//! ## Extern the crate for usage in the library
//!
//! ```rust
//! extern crate trust_dns_resolver;
//! ```
//!
//! ## Using the Synchronous Resolver
//!
//! This uses the default configuration, which sets the [Google Public DNS](https://developers.google.com/speed/public-dns/) as the upstream resolvers. Please see their [privacy statement](https://developers.google.com/speed/public-dns/privacy) for important information about what they track, many ISP's track similar information in DNS.
//!
//! ```rust
//! use std::net::*;
//! use trust_dns_resolver::Resolver;
//! use trust_dns_resolver::config::*;
//!
//! // Construct a new Resolver with default configuration options
//! let mut resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
//!
//! // Lookup the IP addresses associated with a name.
//! // The final dot forces this to be an FQDN, otherwise the lookup rules as specified
//! //  in `ResolverOpts` will take effect
//! let mut response = resolver.lookup_ip("www.example.com.").unwrap();
//!
//! // There can be many addresses associated with the name,
//! //  this can return IPv4 and/or IPv6 addresses
//! let address = response.iter().next().expect("no addresses returned!");
//! if address.is_ipv4() {
//!     assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)));
//! } else {
//!     assert_eq!(address, IpAddr::V6(Ipv6Addr::new(0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946)));
//! }
//! ```
//!
//! ## Using the host system config
//!
//! On Unix systems, the `/etc/resolv.conf` can be used for configuration. Not all options specified in the host systems `resolv.conf` are applicable or compatible with this software. In addition there may be additional options supported which the host system does not. Example:
//!
//! ```rust,no_run
//! # use std::net::*;
//! # use trust_dns_resolver::Resolver;
//! // Use the host OS'es `/etc/resolv.conf`
//! # #[cfg(unix)]
//! let mut resolver = Resolver::from_system_conf().unwrap();
//! let mut response = resolver.lookup_ip("www.example.com.").unwrap();
//! ```
//!
//! ## Using the Tokio/Async Resolver
//!
//! For more advanced asynchronous usage, the ResolverFuture is integrated with Tokio. In fact, the ResolverFuture is used by the synchronous Resolver for all lookups.
//!
//! ```rust
//! # extern crate futures;
//! # extern crate tokio_core;
//! # extern crate trust_dns_resolver;
//! # fn main() {
//! use std::net::*;
//! use tokio_core::reactor::Core;
//! use trust_dns_resolver::ResolverFuture;
//! use trust_dns_resolver::config::*;
//!
//! // We need a Tokio reactor::Core to run the resolver
//! //  this is responsible for running all Future tasks and registering interest in IO channels
//! let mut io_loop = Core::new().unwrap();
//!
//! // Construct a new Resolver with default configuration options
//! let mut resolver = ResolverFuture::new(ResolverConfig::default(), ResolverOpts::default(), &io_loop.handle());
//!
//! // Lookup the IP addresses associated with a name.
//! // This returns a future that will lookup the IP addresses, it must be run in the Core to
//! //  to get the actual result.
//! let lookup_future = resolver.lookup_ip("www.example.com.");
//!
//! // Run the lookup until it resolves or errors
//! let mut response = io_loop.run(lookup_future).unwrap();
//!
//! // There can be many addresses associated with the name,
//! //  this can return IPv4 and/or IPv6 addresses
//! let address = response.iter().next().expect("no addresses returned!");
//! if address.is_ipv4() {
//!     assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)));
//! } else {
//!     assert_eq!(address, IpAddr::V6(Ipv6Addr::new(0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946)));
//! }
//! # }
//! ```
//!
//! Generally after a lookup in an asynchornous context, there would probably be a connection made to a server, for example:
//!
//! ```c
//! let result = io_loop.run(lookup_future.and_then(|ips| {
//!                                  let ip = ips.next().unwrap();
//!                                  TcpStream::connect()
//!                              }).and_then(|conn| /* do something with the connection... */)
//!                          ).unwrap();
//! ```
//!
//! It's beyond the scope of these examples to show how to deal with connection failures and looping etc. But if you wanted to say try a different address from the result set after a connection failure, it will be necessary to create a type that implements the `Future` trait. Inside the `Future::poll` method would be the place to implement a loop over the different IP addresses.

#![deny(missing_docs)]

extern crate futures;
extern crate lalrpop_util;
#[macro_use]
extern crate log;
extern crate lru_cache;
extern crate tokio_core;
extern crate trust_dns;

pub mod config;
pub mod lookup_ip;
pub mod lookup;
mod lookup_state;
mod name_server_pool;
mod resolver;
pub mod system_conf;
mod resolver_future;

pub use resolver::Resolver;
pub use resolver_future::ResolverFuture;

/// returns a version as specified in Cargo.toml
pub fn version() -> &'static str {
    env!("CARGO_PKG_VERSION")
}
<commit_msg>fix windows take v<commit_after>// Copyright 2015-2017 Benjamin Fry <benjaminfry@me.com>
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.

//! The Resolver is responsible for performing recursive queries to lookup domain names.
//!
//! This is a 100% in process DNS resolver. It *does not* use the Host OS' resolver. If what is desired is to use the Host OS' resolver, generally in the system's libc, then the `std::net::ToSocketAddrs` variant over `&str` is what should be used. As of the initial release, `trust-dns-resolver` it does not currently support search paths or ndot recursive lookups. It only supports FQDN, where the name must be specified with the final dot, e.g. `www.example.com.`. This limitation will be removed in future releases.
//!
//! Unlike the `trust-dns` client, this tries to provide a simpler interface to perform DNS queries. For update options, i.e. Dynamic DNS, the `trust-dns` crate must be used directly.
//!
//! There are two types for performing DNS queries, `Resolver` and `ResolverFuture`. `Resolver` is the easiest to work with, it is a wrapper around `ResolverFuture`. `ResolverFuture` is a `Tokio` based async resolver, and can be used inside any `Tokio` based system.
//!
//! *Notes on current limitations*: Some standard system options and configurations are not yet supported. Recursion is not yet built, i.e. CNAME chains will not be fully resolved if an address is not returned, though many upstream resolvers will perform this making it a none blocking issue.
//!
//! This as best as possible attempts to abide by the the DNS RFCs, please file issues at https://github.com/bluejekyll/trust-dns .
//!
//! # Usage
//!
//! ## Declare dependency
//!
//! ```toml
//! [dependency]
//! trust-dns-resolver = "^0.1"
//! ```
//!
//! ## Extern the crate for usage in the library
//!
//! ```rust
//! extern crate trust_dns_resolver;
//! ```
//!
//! ## Using the Synchronous Resolver
//!
//! This uses the default configuration, which sets the [Google Public DNS](https://developers.google.com/speed/public-dns/) as the upstream resolvers. Please see their [privacy statement](https://developers.google.com/speed/public-dns/privacy) for important information about what they track, many ISP's track similar information in DNS.
//!
//! ```rust
//! use std::net::*;
//! use trust_dns_resolver::Resolver;
//! use trust_dns_resolver::config::*;
//!
//! // Construct a new Resolver with default configuration options
//! let mut resolver = Resolver::new(ResolverConfig::default(), ResolverOpts::default()).unwrap();
//!
//! // Lookup the IP addresses associated with a name.
//! // The final dot forces this to be an FQDN, otherwise the lookup rules as specified
//! //  in `ResolverOpts` will take effect
//! let mut response = resolver.lookup_ip("www.example.com.").unwrap();
//!
//! // There can be many addresses associated with the name,
//! //  this can return IPv4 and/or IPv6 addresses
//! let address = response.iter().next().expect("no addresses returned!");
//! if address.is_ipv4() {
//!     assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)));
//! } else {
//!     assert_eq!(address, IpAddr::V6(Ipv6Addr::new(0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946)));
//! }
//! ```
//!
//! ## Using the host system config
//!
//! On Unix systems, the `/etc/resolv.conf` can be used for configuration. Not all options specified in the host systems `resolv.conf` are applicable or compatible with this software. In addition there may be additional options supported which the host system does not. Example:
//!
//! ```rust,no_run
//! # use std::net::*;
//! # use trust_dns_resolver::Resolver;
//! // Use the host OS'es `/etc/resolv.conf`
//! # #[cfg(unix)]
//! let mut resolver = Resolver::from_system_conf().unwrap();
//! # #[cfg(unix)]
//! let mut response = resolver.lookup_ip("www.example.com.").unwrap();
//! ```
//!
//! ## Using the Tokio/Async Resolver
//!
//! For more advanced asynchronous usage, the ResolverFuture is integrated with Tokio. In fact, the ResolverFuture is used by the synchronous Resolver for all lookups.
//!
//! ```rust
//! # extern crate futures;
//! # extern crate tokio_core;
//! # extern crate trust_dns_resolver;
//! # fn main() {
//! use std::net::*;
//! use tokio_core::reactor::Core;
//! use trust_dns_resolver::ResolverFuture;
//! use trust_dns_resolver::config::*;
//!
//! // We need a Tokio reactor::Core to run the resolver
//! //  this is responsible for running all Future tasks and registering interest in IO channels
//! let mut io_loop = Core::new().unwrap();
//!
//! // Construct a new Resolver with default configuration options
//! let mut resolver = ResolverFuture::new(ResolverConfig::default(), ResolverOpts::default(), &io_loop.handle());
//!
//! // Lookup the IP addresses associated with a name.
//! // This returns a future that will lookup the IP addresses, it must be run in the Core to
//! //  to get the actual result.
//! let lookup_future = resolver.lookup_ip("www.example.com.");
//!
//! // Run the lookup until it resolves or errors
//! let mut response = io_loop.run(lookup_future).unwrap();
//!
//! // There can be many addresses associated with the name,
//! //  this can return IPv4 and/or IPv6 addresses
//! let address = response.iter().next().expect("no addresses returned!");
//! if address.is_ipv4() {
//!     assert_eq!(address, IpAddr::V4(Ipv4Addr::new(93, 184, 216, 34)));
//! } else {
//!     assert_eq!(address, IpAddr::V6(Ipv6Addr::new(0x2606, 0x2800, 0x220, 0x1, 0x248, 0x1893, 0x25c8, 0x1946)));
//! }
//! # }
//! ```
//!
//! Generally after a lookup in an asynchornous context, there would probably be a connection made to a server, for example:
//!
//! ```c
//! let result = io_loop.run(lookup_future.and_then(|ips| {
//!                                  let ip = ips.next().unwrap();
//!                                  TcpStream::connect()
//!                              }).and_then(|conn| /* do something with the connection... */)
//!                          ).unwrap();
//! ```
//!
//! It's beyond the scope of these examples to show how to deal with connection failures and looping etc. But if you wanted to say try a different address from the result set after a connection failure, it will be necessary to create a type that implements the `Future` trait. Inside the `Future::poll` method would be the place to implement a loop over the different IP addresses.

#![deny(missing_docs)]

extern crate futures;
extern crate lalrpop_util;
#[macro_use]
extern crate log;
extern crate lru_cache;
extern crate tokio_core;
extern crate trust_dns;

pub mod config;
pub mod lookup_ip;
pub mod lookup;
mod lookup_state;
mod name_server_pool;
mod resolver;
pub mod system_conf;
mod resolver_future;

pub use resolver::Resolver;
pub use resolver_future::ResolverFuture;

/// returns a version as specified in Cargo.toml
pub fn version() -> &'static str {
    env!("CARGO_PKG_VERSION")
}
<|endoftext|>


<commit_before>//! Easy file downloading
#![deny(rust_2018_idioms)]

use std::path::Path;
use url::Url;

mod errors;
pub use crate::errors::*;

#[derive(Debug, Copy, Clone)]
pub enum Backend {
    Curl,
    Reqwest(TlsBackend),
}

#[derive(Debug, Copy, Clone)]
pub enum TlsBackend {
    Rustls,
    Default,
}

#[derive(Debug, Copy, Clone)]
pub enum Event<'a> {
    ResumingPartialDownload,
    /// Received the Content-Length of the to-be downloaded data.
    DownloadContentLengthReceived(u64),
    /// Received some data.
    DownloadDataReceived(&'a [u8]),
}

fn download_with_backend(
    backend: Backend,
    url: &Url,
    resume_from: u64,
    callback: &dyn Fn(Event<'_>) -> Result<()>,
) -> Result<()> {
    match backend {
        Backend::Curl => curl::download(url, resume_from, callback),
        Backend::Reqwest(tls) => reqwest_be::download(url, resume_from, callback, tls),
    }
}

pub fn download_to_path_with_backend(
    backend: Backend,
    url: &Url,
    path: &Path,
    resume_from_partial: bool,
    callback: Option<&dyn Fn(Event<'_>) -> Result<()>>,
) -> Result<()> {
    use std::cell::RefCell;
    use std::fs::remove_file;
    use std::fs::OpenOptions;
    use std::io::{Read, Seek, SeekFrom, Write};

    || -> Result<()> {
        let (file, resume_from) = if resume_from_partial {
            let possible_partial = OpenOptions::new().read(true).open(&path);

            let downloaded_so_far = if let Ok(mut partial) = possible_partial {
                if let Some(cb) = callback {
                    cb(Event::ResumingPartialDownload)?;

                    let mut buf = vec![0; 32768];
                    let mut downloaded_so_far = 0;
                    loop {
                        let n = partial.read(&mut buf)?;
                        downloaded_so_far += n as u64;
                        if n == 0 {
                            break;
                        }
                        cb(Event::DownloadDataReceived(&buf[..n]))?;
                    }

                    downloaded_so_far
                } else {
                    let file_info = partial.metadata()?;
                    file_info.len()
                }
            } else {
                0
            };

            let mut possible_partial = OpenOptions::new()
                .write(true)
                .create(true)
                .open(&path)
                .chain_err(|| "error opening file for download")?;

            possible_partial.seek(SeekFrom::End(0))?;

            (possible_partial, downloaded_so_far)
        } else {
            (
                OpenOptions::new()
                    .write(true)
                    .create(true)
                    .open(&path)
                    .chain_err(|| "error creating file for download")?,
                0,
            )
        };

        let file = RefCell::new(file);

        download_with_backend(backend, url, resume_from, &|event| {
            if let Event::DownloadDataReceived(data) = event {
                file.borrow_mut()
                    .write_all(data)
                    .chain_err(|| "unable to write download to disk")?;
            }
            match callback {
                Some(cb) => cb(event),
                None => Ok(()),
            }
        })?;

        file.borrow_mut()
            .sync_data()
            .chain_err(|| "unable to sync download to disk")?;

        Ok(())
    }()
    .map_err(|e| {
        // TODO: We currently clear up the cached download on any error, should we restrict it to a subset?
        remove_file(path)
            .chain_err(|| "cleaning up cached downloads")
            .unwrap();
        e
    })
}

/// Download via libcurl; encrypt with the native (or OpenSSl) TLS
/// stack via libcurl
#[cfg(feature = "curl-backend")]
pub mod curl {
    use super::Event;
    use crate::errors::*;
    use curl::easy::Easy;
    use std::cell::RefCell;
    use std::str;
    use std::time::Duration;
    use url::Url;

    pub fn download(
        url: &Url,
        resume_from: u64,
        callback: &dyn Fn(Event<'_>) -> Result<()>,
    ) -> Result<()> {
        // Fetch either a cached libcurl handle (which will preserve open
        // connections) or create a new one if it isn't listed.
        //
        // Once we've acquired it, reset the lifetime from 'static to our local
        // scope.
        thread_local!(static EASY: RefCell<Easy> = RefCell::new(Easy::new()));
        EASY.with(|handle| {
            let mut handle = handle.borrow_mut();

            handle
                .url(&url.to_string())
                .chain_err(|| "failed to set url")?;
            handle
                .follow_location(true)
                .chain_err(|| "failed to set follow redirects")?;

            if resume_from > 0 {
                handle
                    .resume_from(resume_from)
                    .chain_err(|| "setting the range header for download resumption")?;
            } else {
                // an error here indicates that the range header isn't supported by underlying curl,
                // so there's nothing to "clear" - safe to ignore this error.
                let _ = handle.resume_from(0);
            }

            // Take at most 30s to connect
            handle
                .connect_timeout(Duration::new(30, 0))
                .chain_err(|| "failed to set connect timeout")?;

            {
                let cberr = RefCell::new(None);
                let mut transfer = handle.transfer();

                // Data callback for libcurl which is called with data that's
                // downloaded. We just feed it into our hasher and also write it out
                // to disk.
                transfer
                    .write_function(|data| match callback(Event::DownloadDataReceived(data)) {
                        Ok(()) => Ok(data.len()),
                        Err(e) => {
                            *cberr.borrow_mut() = Some(e);
                            Ok(0)
                        }
                    })
                    .chain_err(|| "failed to set write")?;

                // Listen for headers and parse out a `Content-Length` (case-insensitive) if it
                // comes so we know how much we're downloading.
                transfer
                    .header_function(|header| {
                        if let Ok(data) = str::from_utf8(header) {
                            let prefix = "content-length: ";
                            if data.to_ascii_lowercase().starts_with(prefix) {
                                if let Ok(s) = data[prefix.len()..].trim().parse::<u64>() {
                                    let msg = Event::DownloadContentLengthReceived(s + resume_from);
                                    match callback(msg) {
                                        Ok(()) => (),
                                        Err(e) => {
                                            *cberr.borrow_mut() = Some(e);
                                            return false;
                                        }
                                    }
                                }
                            }
                        }
                        true
                    })
                    .chain_err(|| "failed to set header")?;

                // If an error happens check to see if we had a filesystem error up
                // in `cberr`, but we always want to punt it up.
                transfer.perform().or_else(|e| {
                    // If the original error was generated by one of our
                    // callbacks, return it.
                    match cberr.borrow_mut().take() {
                        Some(cberr) => Err(cberr),
                        None => {
                            // Otherwise, return the error from curl
                            if e.is_file_couldnt_read_file() {
                                Err(e).chain_err(|| ErrorKind::FileNotFound)
                            } else {
                                Err(e).chain_err(|| "error during download")
                            }
                        }
                    }
                })?;
            }

            // If we didn't get a 20x or 0 ("OK" for files) then return an error
            let code = handle
                .response_code()
                .chain_err(|| "failed to get response code")?;
            match code {
                0 | 200..=299 => {}
                _ => {
                    return Err(ErrorKind::HttpStatus(code).into());
                }
            };

            Ok(())
        })
    }
}

#[cfg(feature = "reqwest-backend")]
pub mod reqwest_be {
    use super::Event;
    use super::TlsBackend;
    use crate::errors::*;
    use lazy_static::lazy_static;
    use reqwest::blocking::{Client, ClientBuilder, Response};
    use reqwest::{header, Proxy};
    use std::io;
    use std::time::Duration;
    use url::Url;

    pub fn download(
        url: &Url,
        resume_from: u64,
        callback: &dyn Fn(Event<'_>) -> Result<()>,
        tls: TlsBackend,
    ) -> Result<()> {
        // Short-circuit reqwest for the "file:" URL scheme
        if download_from_file_url(url, resume_from, callback)? {
            return Ok(());
        }

        let mut res =
            request(url, resume_from, tls).chain_err(|| "failed to make network request")?;

        if !res.status().is_success() {
            let code: u16 = res.status().into();
            return Err(ErrorKind::HttpStatus(u32::from(code)).into());
        }

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

        if let Some(len) = res.headers().get(header::CONTENT_LENGTH) {
            // TODO possible issues during unwrap?
            let len = len.to_str().unwrap().parse::<u64>().unwrap() + resume_from;
            callback(Event::DownloadContentLengthReceived(len))?;
        }

        loop {
            let bytes_read =
                io::Read::read(&mut res, &mut buffer).chain_err(|| "error reading from socket")?;

            if bytes_read != 0 {
                callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?;
            } else {
                return Ok(());
            }
        }
    }

    fn client_generic() -> ClientBuilder {
        Client::builder()
            .gzip(false)
            .proxy(Proxy::custom(env_proxy))
            .timeout(Duration::from_secs(30))
    }
    #[cfg(feature = "reqwest-rustls-tls")]
    lazy_static! {
        static ref CLIENT_RUSTLS_TLS: Client = {
            let catcher = || {
                client_generic().use_rustls_tls()
                    .build()
            };

            // woah, an unwrap?!
            // It's OK. This is the same as what is happening in curl.
            //
            // The curl::Easy::new() internally assert!s that the initialized
            // Easy is not null. Inside reqwest, the errors here would be from
            // the TLS library returning a null pointer as well.
            catcher().unwrap()
        };
    }
    #[cfg(feature = "reqwest-default-tls")]
    lazy_static! {
        static ref CLIENT_DEFAULT_TLS: Client = {
            let catcher = || {
                client_generic()
                    .build()
            };

            // woah, an unwrap?!
            // It's OK. This is the same as what is happening in curl.
            //
            // The curl::Easy::new() internally assert!s that the initialized
            // Easy is not null. Inside reqwest, the errors here would be from
            // the TLS library returning a null pointer as well.
            catcher().unwrap()
        };
    }

    fn env_proxy(url: &Url) -> Option<Url> {
        env_proxy::for_url(url).to_url()
    }

    fn request(url: &Url, resume_from: u64, backend: TlsBackend) -> Result<Response> {
        let client: &Client = match backend {
            #[cfg(feature = "reqwest-rustls-tls")]
            TlsBackend::Rustls => &CLIENT_RUSTLS_TLS,
            #[cfg(not(feature = "reqwest-rustls-tls"))]
            TlsBackend::Rustls => {
                return Err(ErrorKind::BackendUnavailable("reqwest rustls").into());
            }
            #[cfg(feature = "reqwest-default-tls")]
            TlsBackend::Default => &CLIENT_DEFAULT_TLS,
            #[cfg(not(feature = "reqwest-default-tls"))]
            TlsBackend::Default => {
                return Err(ErrorKind::BackendUnavailable("reqwest default TLS").into());
            }
        };
        let mut req = client.get(url.as_str());

        if resume_from != 0 {
            req = req.header(header::RANGE, format!("bytes={}-", resume_from));
        }

        Ok(req.send()?)
    }

    fn download_from_file_url(
        url: &Url,
        resume_from: u64,
        callback: &dyn Fn(Event<'_>) -> Result<()>,
    ) -> Result<bool> {
        use std::fs;

        // The file scheme is mostly for use by tests to mock the dist server
        if url.scheme() == "file" {
            let src = url
                .to_file_path()
                .map_err(|_| Error::from(format!("bogus file url: '{}'", url)))?;
            if !src.is_file() {
                // Because some of rustup's logic depends on checking
                // the error when a downloaded file doesn't exist, make
                // the file case return the same error value as the
                // network case.
                return Err(ErrorKind::FileNotFound.into());
            }

            let mut f = fs::File::open(src).chain_err(|| "unable to open downloaded file")?;
            io::Seek::seek(&mut f, io::SeekFrom::Start(resume_from))?;

            let mut buffer = vec![0u8; 0x10000];
            loop {
                let bytes_read = io::Read::read(&mut f, &mut buffer)
                    .chain_err(|| "unable to read downloaded file")?;
                if bytes_read == 0 {
                    break;
                }
                callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?;
            }

            Ok(true)
        } else {
            Ok(false)
        }
    }
}

#[cfg(not(feature = "curl-backend"))]
pub mod curl {

    use super::Event;
    use crate::errors::*;
    use url::Url;

    pub fn download(
        _url: &Url,
        _resume_from: u64,
        _callback: &dyn Fn(Event<'_>) -> Result<()>,
    ) -> Result<()> {
        Err(ErrorKind::BackendUnavailable("curl").into())
    }
}

#[cfg(not(feature = "reqwest-backend"))]
pub mod reqwest_be {

    use super::Event;
    use crate::errors::*;
    use url::Url;

    pub fn download(
        _url: &Url,
        _resume_from: u64,
        _callback: &dyn Fn(Event<'_>) -> Result<()>,
    ) -> Result<()> {
        Err(ErrorKind::BackendUnavailable("reqwest").into())
    }
}
<commit_msg>download: Fix up snafu when reqwest backend is disabled<commit_after>//! Easy file downloading
#![deny(rust_2018_idioms)]

use std::path::Path;
use url::Url;

mod errors;
pub use crate::errors::*;

#[derive(Debug, Copy, Clone)]
pub enum Backend {
    Curl,
    Reqwest(TlsBackend),
}

#[derive(Debug, Copy, Clone)]
pub enum TlsBackend {
    Rustls,
    Default,
}

#[derive(Debug, Copy, Clone)]
pub enum Event<'a> {
    ResumingPartialDownload,
    /// Received the Content-Length of the to-be downloaded data.
    DownloadContentLengthReceived(u64),
    /// Received some data.
    DownloadDataReceived(&'a [u8]),
}

fn download_with_backend(
    backend: Backend,
    url: &Url,
    resume_from: u64,
    callback: &dyn Fn(Event<'_>) -> Result<()>,
) -> Result<()> {
    match backend {
        Backend::Curl => curl::download(url, resume_from, callback),
        Backend::Reqwest(tls) => reqwest_be::download(url, resume_from, callback, tls),
    }
}

pub fn download_to_path_with_backend(
    backend: Backend,
    url: &Url,
    path: &Path,
    resume_from_partial: bool,
    callback: Option<&dyn Fn(Event<'_>) -> Result<()>>,
) -> Result<()> {
    use std::cell::RefCell;
    use std::fs::remove_file;
    use std::fs::OpenOptions;
    use std::io::{Read, Seek, SeekFrom, Write};

    || -> Result<()> {
        let (file, resume_from) = if resume_from_partial {
            let possible_partial = OpenOptions::new().read(true).open(&path);

            let downloaded_so_far = if let Ok(mut partial) = possible_partial {
                if let Some(cb) = callback {
                    cb(Event::ResumingPartialDownload)?;

                    let mut buf = vec![0; 32768];
                    let mut downloaded_so_far = 0;
                    loop {
                        let n = partial.read(&mut buf)?;
                        downloaded_so_far += n as u64;
                        if n == 0 {
                            break;
                        }
                        cb(Event::DownloadDataReceived(&buf[..n]))?;
                    }

                    downloaded_so_far
                } else {
                    let file_info = partial.metadata()?;
                    file_info.len()
                }
            } else {
                0
            };

            let mut possible_partial = OpenOptions::new()
                .write(true)
                .create(true)
                .open(&path)
                .chain_err(|| "error opening file for download")?;

            possible_partial.seek(SeekFrom::End(0))?;

            (possible_partial, downloaded_so_far)
        } else {
            (
                OpenOptions::new()
                    .write(true)
                    .create(true)
                    .open(&path)
                    .chain_err(|| "error creating file for download")?,
                0,
            )
        };

        let file = RefCell::new(file);

        download_with_backend(backend, url, resume_from, &|event| {
            if let Event::DownloadDataReceived(data) = event {
                file.borrow_mut()
                    .write_all(data)
                    .chain_err(|| "unable to write download to disk")?;
            }
            match callback {
                Some(cb) => cb(event),
                None => Ok(()),
            }
        })?;

        file.borrow_mut()
            .sync_data()
            .chain_err(|| "unable to sync download to disk")?;

        Ok(())
    }()
    .map_err(|e| {
        // TODO: We currently clear up the cached download on any error, should we restrict it to a subset?
        remove_file(path)
            .chain_err(|| "cleaning up cached downloads")
            .unwrap();
        e
    })
}

/// Download via libcurl; encrypt with the native (or OpenSSl) TLS
/// stack via libcurl
#[cfg(feature = "curl-backend")]
pub mod curl {
    use super::Event;
    use crate::errors::*;
    use curl::easy::Easy;
    use std::cell::RefCell;
    use std::str;
    use std::time::Duration;
    use url::Url;

    pub fn download(
        url: &Url,
        resume_from: u64,
        callback: &dyn Fn(Event<'_>) -> Result<()>,
    ) -> Result<()> {
        // Fetch either a cached libcurl handle (which will preserve open
        // connections) or create a new one if it isn't listed.
        //
        // Once we've acquired it, reset the lifetime from 'static to our local
        // scope.
        thread_local!(static EASY: RefCell<Easy> = RefCell::new(Easy::new()));
        EASY.with(|handle| {
            let mut handle = handle.borrow_mut();

            handle
                .url(&url.to_string())
                .chain_err(|| "failed to set url")?;
            handle
                .follow_location(true)
                .chain_err(|| "failed to set follow redirects")?;

            if resume_from > 0 {
                handle
                    .resume_from(resume_from)
                    .chain_err(|| "setting the range header for download resumption")?;
            } else {
                // an error here indicates that the range header isn't supported by underlying curl,
                // so there's nothing to "clear" - safe to ignore this error.
                let _ = handle.resume_from(0);
            }

            // Take at most 30s to connect
            handle
                .connect_timeout(Duration::new(30, 0))
                .chain_err(|| "failed to set connect timeout")?;

            {
                let cberr = RefCell::new(None);
                let mut transfer = handle.transfer();

                // Data callback for libcurl which is called with data that's
                // downloaded. We just feed it into our hasher and also write it out
                // to disk.
                transfer
                    .write_function(|data| match callback(Event::DownloadDataReceived(data)) {
                        Ok(()) => Ok(data.len()),
                        Err(e) => {
                            *cberr.borrow_mut() = Some(e);
                            Ok(0)
                        }
                    })
                    .chain_err(|| "failed to set write")?;

                // Listen for headers and parse out a `Content-Length` (case-insensitive) if it
                // comes so we know how much we're downloading.
                transfer
                    .header_function(|header| {
                        if let Ok(data) = str::from_utf8(header) {
                            let prefix = "content-length: ";
                            if data.to_ascii_lowercase().starts_with(prefix) {
                                if let Ok(s) = data[prefix.len()..].trim().parse::<u64>() {
                                    let msg = Event::DownloadContentLengthReceived(s + resume_from);
                                    match callback(msg) {
                                        Ok(()) => (),
                                        Err(e) => {
                                            *cberr.borrow_mut() = Some(e);
                                            return false;
                                        }
                                    }
                                }
                            }
                        }
                        true
                    })
                    .chain_err(|| "failed to set header")?;

                // If an error happens check to see if we had a filesystem error up
                // in `cberr`, but we always want to punt it up.
                transfer.perform().or_else(|e| {
                    // If the original error was generated by one of our
                    // callbacks, return it.
                    match cberr.borrow_mut().take() {
                        Some(cberr) => Err(cberr),
                        None => {
                            // Otherwise, return the error from curl
                            if e.is_file_couldnt_read_file() {
                                Err(e).chain_err(|| ErrorKind::FileNotFound)
                            } else {
                                Err(e).chain_err(|| "error during download")
                            }
                        }
                    }
                })?;
            }

            // If we didn't get a 20x or 0 ("OK" for files) then return an error
            let code = handle
                .response_code()
                .chain_err(|| "failed to get response code")?;
            match code {
                0 | 200..=299 => {}
                _ => {
                    return Err(ErrorKind::HttpStatus(code).into());
                }
            };

            Ok(())
        })
    }
}

#[cfg(feature = "reqwest-backend")]
pub mod reqwest_be {
    use super::Event;
    use super::TlsBackend;
    use crate::errors::*;
    use lazy_static::lazy_static;
    use reqwest::blocking::{Client, ClientBuilder, Response};
    use reqwest::{header, Proxy};
    use std::io;
    use std::time::Duration;
    use url::Url;

    pub fn download(
        url: &Url,
        resume_from: u64,
        callback: &dyn Fn(Event<'_>) -> Result<()>,
        tls: TlsBackend,
    ) -> Result<()> {
        // Short-circuit reqwest for the "file:" URL scheme
        if download_from_file_url(url, resume_from, callback)? {
            return Ok(());
        }

        let mut res =
            request(url, resume_from, tls).chain_err(|| "failed to make network request")?;

        if !res.status().is_success() {
            let code: u16 = res.status().into();
            return Err(ErrorKind::HttpStatus(u32::from(code)).into());
        }

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

        if let Some(len) = res.headers().get(header::CONTENT_LENGTH) {
            // TODO possible issues during unwrap?
            let len = len.to_str().unwrap().parse::<u64>().unwrap() + resume_from;
            callback(Event::DownloadContentLengthReceived(len))?;
        }

        loop {
            let bytes_read =
                io::Read::read(&mut res, &mut buffer).chain_err(|| "error reading from socket")?;

            if bytes_read != 0 {
                callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?;
            } else {
                return Ok(());
            }
        }
    }

    fn client_generic() -> ClientBuilder {
        Client::builder()
            .gzip(false)
            .proxy(Proxy::custom(env_proxy))
            .timeout(Duration::from_secs(30))
    }
    #[cfg(feature = "reqwest-rustls-tls")]
    lazy_static! {
        static ref CLIENT_RUSTLS_TLS: Client = {
            let catcher = || {
                client_generic().use_rustls_tls()
                    .build()
            };

            // woah, an unwrap?!
            // It's OK. This is the same as what is happening in curl.
            //
            // The curl::Easy::new() internally assert!s that the initialized
            // Easy is not null. Inside reqwest, the errors here would be from
            // the TLS library returning a null pointer as well.
            catcher().unwrap()
        };
    }
    #[cfg(feature = "reqwest-default-tls")]
    lazy_static! {
        static ref CLIENT_DEFAULT_TLS: Client = {
            let catcher = || {
                client_generic()
                    .build()
            };

            // woah, an unwrap?!
            // It's OK. This is the same as what is happening in curl.
            //
            // The curl::Easy::new() internally assert!s that the initialized
            // Easy is not null. Inside reqwest, the errors here would be from
            // the TLS library returning a null pointer as well.
            catcher().unwrap()
        };
    }

    fn env_proxy(url: &Url) -> Option<Url> {
        env_proxy::for_url(url).to_url()
    }

    fn request(url: &Url, resume_from: u64, backend: TlsBackend) -> Result<Response> {
        let client: &Client = match backend {
            #[cfg(feature = "reqwest-rustls-tls")]
            TlsBackend::Rustls => &CLIENT_RUSTLS_TLS,
            #[cfg(not(feature = "reqwest-rustls-tls"))]
            TlsBackend::Rustls => {
                return Err(ErrorKind::BackendUnavailable("reqwest rustls").into());
            }
            #[cfg(feature = "reqwest-default-tls")]
            TlsBackend::Default => &CLIENT_DEFAULT_TLS,
            #[cfg(not(feature = "reqwest-default-tls"))]
            TlsBackend::Default => {
                return Err(ErrorKind::BackendUnavailable("reqwest default TLS").into());
            }
        };
        let mut req = client.get(url.as_str());

        if resume_from != 0 {
            req = req.header(header::RANGE, format!("bytes={}-", resume_from));
        }

        Ok(req.send()?)
    }

    fn download_from_file_url(
        url: &Url,
        resume_from: u64,
        callback: &dyn Fn(Event<'_>) -> Result<()>,
    ) -> Result<bool> {
        use std::fs;

        // The file scheme is mostly for use by tests to mock the dist server
        if url.scheme() == "file" {
            let src = url
                .to_file_path()
                .map_err(|_| Error::from(format!("bogus file url: '{}'", url)))?;
            if !src.is_file() {
                // Because some of rustup's logic depends on checking
                // the error when a downloaded file doesn't exist, make
                // the file case return the same error value as the
                // network case.
                return Err(ErrorKind::FileNotFound.into());
            }

            let mut f = fs::File::open(src).chain_err(|| "unable to open downloaded file")?;
            io::Seek::seek(&mut f, io::SeekFrom::Start(resume_from))?;

            let mut buffer = vec![0u8; 0x10000];
            loop {
                let bytes_read = io::Read::read(&mut f, &mut buffer)
                    .chain_err(|| "unable to read downloaded file")?;
                if bytes_read == 0 {
                    break;
                }
                callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?;
            }

            Ok(true)
        } else {
            Ok(false)
        }
    }
}

#[cfg(not(feature = "curl-backend"))]
pub mod curl {

    use super::Event;
    use crate::errors::*;
    use url::Url;

    pub fn download(
        _url: &Url,
        _resume_from: u64,
        _callback: &dyn Fn(Event<'_>) -> Result<()>,
    ) -> Result<()> {
        Err(ErrorKind::BackendUnavailable("curl").into())
    }
}

#[cfg(not(feature = "reqwest-backend"))]
pub mod reqwest_be {

    use super::Event;
    use super::TlsBackend;
    use crate::errors::*;
    use url::Url;

    pub fn download(
        _url: &Url,
        _resume_from: u64,
        _callback: &dyn Fn(Event<'_>) -> Result<()>,
        _tls: TlsBackend,
    ) -> Result<()> {
        Err(ErrorKind::BackendUnavailable("reqwest").into())
    }
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;
use std::{cmp, str};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use spin::Mutex;
use syscall::{Error, EBADF, EINVAL, ENOENT, Result, Scheme, SEEK_CUR, SEEK_END, SEEK_SET};

use ahci::disk::Disk;

pub struct DiskScheme {
    disks: Box<[Arc<Mutex<Disk>>]>,
    handles: Mutex<BTreeMap<usize, (Arc<Mutex<Disk>>, usize)>>,
    next_id: AtomicUsize
}

impl DiskScheme {
    pub fn new(disks: Vec<Disk>) -> DiskScheme {
        let mut disk_arcs = vec![];
        for disk in disks {
            disk_arcs.push(Arc::new(Mutex::new(disk)));
        }

        DiskScheme {
            disks: disk_arcs.into_boxed_slice(),
            handles: Mutex::new(BTreeMap::new()),
            next_id: AtomicUsize::new(0)
        }
    }
}

impl Scheme for DiskScheme {
    fn open(&self, path: &[u8], _flags: usize) -> Result<usize> {
        let path_str = str::from_utf8(path).or(Err(Error::new(ENOENT)))?;

        let i = path_str.parse::<usize>().or(Err(Error::new(ENOENT)))?;

        if let Some(disk) = self.disks.get(i) {
            let id = self.next_id.fetch_add(1, Ordering::SeqCst);
            self.handles.lock().insert(id, (disk.clone(), 0));
            Ok(id)
        } else {
            Err(Error::new(ENOENT))
        }
    }

    fn read(&self, id: usize, buf: &mut [u8]) -> Result<usize> {
        let mut handles = self.handles.lock();
        let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;

        let mut disk = handle.0.lock();
        let count = disk.read((handle.1 as u64)/512, buf)?;
        handle.1 += count;
        Ok(count)
    }

    fn write(&self, id: usize, buf: &[u8]) -> Result<usize> {
        let mut handles = self.handles.lock();
        let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;

        let mut disk = handle.0.lock();
        let count = disk.write((handle.1 as u64)/512, buf)?;
        handle.1 += count;
        Ok(count)
    }

    fn seek(&self, id: usize, pos: usize, whence: usize) -> Result<usize> {
        let mut handles = self.handles.lock();
        let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;

        let len = handle.0.lock().size() as usize;
        handle.1 = match whence {
            SEEK_SET => cmp::min(len, pos),
            SEEK_CUR => cmp::max(0, cmp::min(len as isize, handle.1 as isize + pos as isize)) as usize,
            SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize,
            _ => return Err(Error::new(EINVAL))
        };

        Ok(handle.1)
    }

    fn close(&self, id: usize) -> Result<usize> {
        let mut handles = self.handles.lock();
        handles.remove(&id).ok_or(Error::new(EBADF)).and(Ok(0))
    }
}
<commit_msg>Add dup to ahci disk scheme<commit_after>use std::collections::BTreeMap;
use std::{cmp, str};
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use spin::Mutex;
use syscall::{Error, EBADF, EINVAL, ENOENT, Result, Scheme, SEEK_CUR, SEEK_END, SEEK_SET};

use ahci::disk::Disk;

pub struct DiskScheme {
    disks: Box<[Arc<Mutex<Disk>>]>,
    handles: Mutex<BTreeMap<usize, (Arc<Mutex<Disk>>, usize)>>,
    next_id: AtomicUsize
}

impl DiskScheme {
    pub fn new(disks: Vec<Disk>) -> DiskScheme {
        let mut disk_arcs = vec![];
        for disk in disks {
            disk_arcs.push(Arc::new(Mutex::new(disk)));
        }

        DiskScheme {
            disks: disk_arcs.into_boxed_slice(),
            handles: Mutex::new(BTreeMap::new()),
            next_id: AtomicUsize::new(0)
        }
    }
}

impl Scheme for DiskScheme {
    fn open(&self, path: &[u8], _flags: usize) -> Result<usize> {
        let path_str = str::from_utf8(path).or(Err(Error::new(ENOENT)))?;

        let i = path_str.parse::<usize>().or(Err(Error::new(ENOENT)))?;

        if let Some(disk) = self.disks.get(i) {
            let id = self.next_id.fetch_add(1, Ordering::SeqCst);
            self.handles.lock().insert(id, (disk.clone(), 0));
            Ok(id)
        } else {
            Err(Error::new(ENOENT))
        }
    }

    fn dup(&self, id: usize) -> Result<usize> {
        let mut handles = self.handles.lock();
        let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;

        let new_id = self.next_id.fetch_add(1, Ordering::SeqCst);
        self.handles.lock().insert(new_id, handle.clone());
        Ok(new_id)
    }

    fn read(&self, id: usize, buf: &mut [u8]) -> Result<usize> {
        let mut handles = self.handles.lock();
        let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;

        let mut disk = handle.0.lock();
        let count = disk.read((handle.1 as u64)/512, buf)?;
        handle.1 += count;
        Ok(count)
    }

    fn write(&self, id: usize, buf: &[u8]) -> Result<usize> {
        let mut handles = self.handles.lock();
        let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;

        let mut disk = handle.0.lock();
        let count = disk.write((handle.1 as u64)/512, buf)?;
        handle.1 += count;
        Ok(count)
    }

    fn seek(&self, id: usize, pos: usize, whence: usize) -> Result<usize> {
        let mut handles = self.handles.lock();
        let mut handle = handles.get_mut(&id).ok_or(Error::new(EBADF))?;

        let len = handle.0.lock().size() as usize;
        handle.1 = match whence {
            SEEK_SET => cmp::min(len, pos),
            SEEK_CUR => cmp::max(0, cmp::min(len as isize, handle.1 as isize + pos as isize)) as usize,
            SEEK_END => cmp::max(0, cmp::min(len as isize, len as isize + pos as isize)) as usize,
            _ => return Err(Error::new(EINVAL))
        };

        Ok(handle.1)
    }

    fn close(&self, id: usize) -> Result<usize> {
        let mut handles = self.handles.lock();
        handles.remove(&id).ok_or(Error::new(EBADF)).and(Ok(0))
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code)]

use linked_hash_map::LinkedHashMap;

use parser::ast::*;
use scope::scope::*;
use scope::symbols::*;

#[derive(Debug, Clone)]
pub struct Context {
    base_scope: Scope,
    scopes: LinkedHashMap<String, Scope>,
    symbol_maps: LinkedHashMap<String, Symbols>
}

impl Default for Context {
    fn default() -> Context {
        let symbols = Symbols::default();
        let base_scope = Scope::with_map_id(symbols.map_id());
        Context::new(base_scope, symbols)

    }
}

impl Context {
    pub fn new(base_scope: Scope, symbols: Symbols) -> Self {
        let mut ctx = Context {
            base_scope: base_scope,
            scopes: Default::default(),
            symbol_maps: Default::default()
        };

        ctx.add_symbol_map(symbols);
        ctx
    }

    pub fn scope(&mut self) -> Scope {
        if let Some(scope) = self.scopes.back().map(|s| s.1.clone()) {
            return scope;
        }

        self.base_scope.clone()
    }

    pub fn push_scope(&mut self, scope: Scope) {
        self.scopes.insert(scope.id().to_owned(), scope);
    }

    pub fn pop_scope(&mut self) {
        self.scopes.pop_back();
    }

    pub fn resolve_sym(&mut self, key: &str) -> Option<Symbol> {
        let scope = self.scope();
        let map_id = scope.map_id();

        let mut cur_map = self.symbol_maps.get(map_id);
        while let Some(map) = cur_map {
            if let Some(sym) = map.get_sym(key) {
                return Some(sym.to_owned());
            };

            cur_map = map.parent_map_id().and_then(|id| self.symbol_maps.get(id));
        };

        None
    }

    pub fn add_symbol_map(&mut self, map: Symbols) {
        self.symbol_maps.insert(map.map_id().to_owned(), map);
    }

    pub fn add_sym(&mut self, key: &str, sym: Symbol) {
        let map_id = self.scope().map_id().to_owned();
        if let Some(map) = self.symbol_maps.get_mut(&map_id) {
            map.add_sym(key, sym);
        };
    }
}

#[cfg(test)]
mod tests {
    use parser::ast::*;
    use scope::context::*;

    fn create_symbols(key: &str, sym: Symbol, parent_map_id: Option<&str>) -> Symbols {
        let mut symbols = Symbols::new(parent_map_id);
        symbols.add_sym(key, sym);
        symbols
    }

    fn add_scope_with_symbols(ctx: &mut Context, key: &str, sym: Symbol, parent_map_id: Option<&str>) -> String {
        let symbols = create_symbols(key, sym, parent_map_id);
        let map_id =  format!("{}", symbols.map_id());
        ctx.add_symbol_map(symbols);
        let scope = Scope::with_map_id(&map_id);
        ctx.push_scope(scope);
        map_id
    }

    #[test]
    pub fn test_context_symbol_maps_super1() {
        let mut context = Context::default();

        let map_id = add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz1"), None);
        let map_id = add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz2"), Some(&map_id));
        add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz3"), Some(&map_id));

        // We should resolve the symbol from the nearest scope where it is defined
        assert_eq!(context.resolve_sym("abc"), Some(Symbol::prop("xyz3")));
    }

    #[test]
    pub fn test_context_symbol_maps_super2() {
        let mut context = Context::default();

        let map_id = add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz1"), None);
        let map_id = add_scope_with_symbols(&mut context, "def", Symbol::prop("xyz2"), Some(&map_id));
        add_scope_with_symbols(&mut context, "ghi", Symbol::prop("xyz3"), Some(&map_id));

        // We should resolve the symbol from the nearest scope where it is defined
        assert_eq!(context.resolve_sym("abc"), Some(Symbol::prop("xyz1")));
    }

    #[derive(Debug, Clone, Default)]
    struct ContextTestDocument {
    }

    #[derive(Debug, Clone, Default)]
    struct ContextTestOutput {
        doc: ContextTestDocument
    }

    impl ContextTestOutput {
        pub fn write_1(&self, _: &str) {

        }
    }

    #[derive(Debug, Clone)]
    struct ContextTest {
        ctx: Context,
        output: ContextTestOutput
    }

    impl ContextTest {
        pub fn example_1(&mut self) {
            let map_id = add_scope_with_symbols(&mut self.ctx, "abc", Symbol::prop("xyz1"), None);
            let map_id = add_scope_with_symbols(&mut self.ctx, "def", Symbol::prop("xyz2"), Some(&map_id));
            add_scope_with_symbols(&mut self.ctx, "ghi", Symbol::prop("xyz3"), Some(&map_id));

            self.output.write_1("zyx");

            // We should resolve the symbol from the nearest scope where it is defined
            assert_eq!(self.ctx.resolve_sym("abc"), Some(Symbol::prop("xyz1")));
        }
    }

    #[test]
    pub fn test_context_1() {
        // let map_id = "m1";
        // let scope = Scope::with_map_id(map_id);
        // let context = Context::with_base_scope(scope);
        let ctx = Context::default();
        let mut context_test = ContextTest { ctx: ctx, output: Default::default() };
        context_test.example_1();
    }

    #[test]
    pub fn test_context_symbol_path_mixed1() {
        let mut ctx = Context::default();
        let mut scope = ctx.scope();

        let expr1 = ExprValue::Expr(ExprOp::Add, Box::new(ExprValue::LiteralNumber(1)), Box::new(ExprValue::LiteralNumber(2)));
        scope.append_path_expr(&expr1);
        scope.append_path_str("test");

        ctx.push_scope(scope);

        let expr = ctx.scope().join_path_as_expr(None);
        assert_eq!(expr, Some(ExprValue::Apply(ExprApplyOp::JoinString(None), Some(vec![
            Box::new(ExprValue::Expr(ExprOp::Add, Box::new(ExprValue::LiteralNumber(1)), Box::new(ExprValue::LiteralNumber(2)))),
            Box::new(ExprValue::LiteralString("test".to_owned()))
        ]))));
    }

    fn create_child_scope_with_symbols(ctx: &mut Context, key: &str, sym: Symbol) -> Scope {
        let parent_map_id = ctx.scope().map_id().to_owned();
        let symbol_path = ctx.scope().symbol_path().clone();
        let symbols = create_symbols(key, sym, Some(&parent_map_id));
        ctx.add_symbol_map(symbols);
        Scope::new(&parent_map_id, Some(symbol_path))
    }

    #[test]
    pub fn test_context_scope_nesting1() {
        let mut ctx = Context::default();

        // Lm
        {
            let mut s1 = create_child_scope_with_symbols(&mut ctx, "abc", Symbol::prop("xyz1"));
            s1.append_path_str("Lm");
            ctx.push_scope(s1);
        }

        // Lm.No
        {
            let mut s2 = create_child_scope_with_symbols(&mut ctx, "abc", Symbol::prop("xyz2"));
            s2.append_path_str("No");
            ctx.add_sym("def", Symbol::prop("def2"));
            ctx.push_scope(s2);
        }

        // Lm.No.Pq
        {
            let mut s3 = create_child_scope_with_symbols(&mut ctx, "abc", Symbol::prop("xyz3"));
            s3.append_path_str("Pq");
            ctx.push_scope(s3);
        }

        // The joined path (dynamic) should be a string join operation
        let expr = ctx.scope().join_path_as_expr(Some("."));
        assert_eq!(expr, Some(ExprValue::Apply(ExprApplyOp::JoinString(Some(".".to_owned())), Some(vec![
            Box::new(ExprValue::LiteralString("Lm".to_owned())),
            Box::new(ExprValue::LiteralString("No".to_owned())),
            Box::new(ExprValue::LiteralString("Pq".to_owned()))
        ]))));

        // We should resolve the symbol from the nearest scope where it is defined
        // assert_eq!(ctx.resolve_sym("abc"), Some(Symbol::prop("xyz1")));

        // We should resolve the symbol from the nearest scope where it is defined
        // assert_eq!(ctx.resolve_sym("def"), Some(Symbol::prop("def2")));
    }
}<commit_msg>Implement more helpers on Context, simplify test<commit_after>#![allow(dead_code)]

use linked_hash_map::LinkedHashMap;

use parser::ast::*;
use scope::scope::*;
use scope::symbols::*;

#[derive(Debug, Clone)]
pub struct Context {
    // base_scope: Scope,
    scopes: LinkedHashMap<String, Scope>,
    symbol_maps: LinkedHashMap<String, Symbols>
}

impl Default for Context {
    fn default() -> Context {
        let symbols = Symbols::default();
        let base_scope = Scope::with_map_id(symbols.map_id());
        Context::new(base_scope, symbols)

    }
}

impl Context {
    pub fn new(base_scope: Scope, symbols: Symbols) -> Self {
        let mut ctx = Context {
            // base_scope: base_scope,
            scopes: Default::default(),
            symbol_maps: Default::default()
        };

        ctx.push_scope(base_scope);
        ctx.add_symbol_map(symbols);
        ctx
    }

    fn scope_ref_mut(&mut self) -> Option<&mut Scope> {
        let scope_id = self.scopes.back().map(|s| s.1.id().to_owned());
        if let Some(scope_id) = scope_id {
            return self.scopes.get_mut(&scope_id);
        }
        None
    }

    fn scope_ref(&mut self) -> Option<&Scope> {
        let scope_id = self.scopes.back().map(|s| s.1.id().to_owned());
        if let Some(scope_id) = scope_id {
            return self.scopes.get(&scope_id);
        }
        None
    }

    pub fn scope(&mut self) -> Scope {
        self.scope_ref().unwrap().clone()
    }

    pub fn create_child_scope(&mut self) -> Scope {
        let parent_scope = self.scope();
        let parent_map_id = parent_scope.map_id().to_owned();
        let symbol_path = parent_scope.symbol_path().clone();

        let symbols = Symbols::new(Some(&parent_map_id));
        let map_id = symbols.map_id().to_owned();
        self.add_symbol_map(symbols);

        Scope::new(&map_id, Some(symbol_path))
    }

    pub fn push_scope(&mut self, scope: Scope) {
        self.scopes.insert(scope.id().to_owned(), scope);
    }

    pub fn pop_scope(&mut self) {
        self.scopes.pop_back();
    }

    pub fn push_child_scope(&mut self) {
        let scope = self.create_child_scope();
        self.push_scope(scope);
    }

    pub fn resolve_sym(&mut self, key: &str) -> Option<Symbol> {
        let scope = self.scope();
        let map_id = scope.map_id();

        let mut cur_map = self.symbol_maps.get(map_id);
        while let Some(map) = cur_map {
            if let Some(sym) = map.get_sym(key) {
                return Some(sym.to_owned());
            };

            cur_map = map.parent_map_id().and_then(|id| self.symbol_maps.get(id));
        };

        None
    }

    pub fn add_symbol_map(&mut self, map: Symbols) {
        self.symbol_maps.insert(map.map_id().to_owned(), map);
    }

    pub fn add_sym(&mut self, key: &str, sym: Symbol) {
        let map_id = self.scope().map_id().to_owned();
        if let Some(map) = self.symbol_maps.get_mut(&map_id) {
            map.add_sym(key, sym);
        };
    }

    pub fn append_path_expr(&mut self, expr: &ExprValue) {
        if let Some(scope) = self.scope_ref_mut() {
            scope.append_path_expr(expr);
        };
    }

    pub fn append_path_str(&mut self, s: &str) {
        if let Some(scope) = self.scope_ref_mut() {
            scope.append_path_str(s);
        };
    }

}

#[cfg(test)]
mod tests {
    use parser::ast::*;
    use scope::context::*;

    fn create_symbols(key: &str, sym: Symbol, parent_map_id: Option<&str>) -> Symbols {
        let mut symbols = Symbols::new(parent_map_id);
        symbols.add_sym(key, sym);
        symbols
    }

    fn add_scope_with_symbols(ctx: &mut Context, key: &str, sym: Symbol, parent_map_id: Option<&str>) -> String {
        let symbols = create_symbols(key, sym, parent_map_id);
        let map_id =  format!("{}", symbols.map_id());
        ctx.add_symbol_map(symbols);
        let scope = Scope::with_map_id(&map_id);
        ctx.push_scope(scope);
        map_id
    }

    #[test]
    pub fn test_context_symbol_maps_super1() {
        let mut context = Context::default();

        let map_id = add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz1"), None);
        let map_id = add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz2"), Some(&map_id));
        add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz3"), Some(&map_id));

        // We should resolve the symbol from the nearest scope where it is defined
        assert_eq!(context.resolve_sym("abc"), Some(Symbol::prop("xyz3")));
    }

    #[test]
    pub fn test_context_symbol_maps_super2() {
        let mut context = Context::default();

        let map_id = add_scope_with_symbols(&mut context, "abc", Symbol::prop("xyz1"), None);
        let map_id = add_scope_with_symbols(&mut context, "def", Symbol::prop("xyz2"), Some(&map_id));
        add_scope_with_symbols(&mut context, "ghi", Symbol::prop("xyz3"), Some(&map_id));

        // We should resolve the symbol from the nearest scope where it is defined
        assert_eq!(context.resolve_sym("abc"), Some(Symbol::prop("xyz1")));
    }

    #[derive(Debug, Clone, Default)]
    struct ContextTestDocument {
    }

    #[derive(Debug, Clone, Default)]
    struct ContextTestOutput {
        doc: ContextTestDocument
    }

    impl ContextTestOutput {
        pub fn write_1(&self, _: &str) {

        }
    }

    #[derive(Debug, Clone)]
    struct ContextTest {
        ctx: Context,
        output: ContextTestOutput
    }

    impl ContextTest {
        pub fn example_1(&mut self) {
            let map_id = add_scope_with_symbols(&mut self.ctx, "abc", Symbol::prop("xyz1"), None);
            let map_id = add_scope_with_symbols(&mut self.ctx, "def", Symbol::prop("xyz2"), Some(&map_id));
            add_scope_with_symbols(&mut self.ctx, "ghi", Symbol::prop("xyz3"), Some(&map_id));

            self.output.write_1("zyx");

            // We should resolve the symbol from the nearest scope where it is defined
            assert_eq!(self.ctx.resolve_sym("abc"), Some(Symbol::prop("xyz1")));
        }
    }

    #[test]
    pub fn test_context_1() {
        // let map_id = "m1";
        // let scope = Scope::with_map_id(map_id);
        // let context = Context::with_base_scope(scope);
        let ctx = Context::default();
        let mut context_test = ContextTest { ctx: ctx, output: Default::default() };
        context_test.example_1();
    }

    #[test]
    pub fn test_context_symbol_path_mixed1() {
        let mut ctx = Context::default();
        let mut scope = ctx.scope();

        let expr1 = ExprValue::Expr(ExprOp::Add, Box::new(ExprValue::LiteralNumber(1)), Box::new(ExprValue::LiteralNumber(2)));
        scope.append_path_expr(&expr1);
        scope.append_path_str("test");

        ctx.push_scope(scope);

        let expr = ctx.scope().join_path_as_expr(None);
        assert_eq!(expr, Some(ExprValue::Apply(ExprApplyOp::JoinString(None), Some(vec![
            Box::new(ExprValue::Expr(ExprOp::Add, Box::new(ExprValue::LiteralNumber(1)), Box::new(ExprValue::LiteralNumber(2)))),
            Box::new(ExprValue::LiteralString("test".to_owned()))
        ]))));
    }

    fn create_child_scope_with_symbols(ctx: &mut Context, key: &str, sym: Symbol) -> Scope {
        let parent_map_id = ctx.scope().map_id().to_owned();
        let symbol_path = ctx.scope().symbol_path().clone();
        let symbols = create_symbols(key, sym, Some(&parent_map_id));
        let map_id = symbols.map_id().to_owned();
        ctx.add_symbol_map(symbols);
        Scope::new(&map_id, Some(symbol_path))
    }

    #[test]
    pub fn test_context_scope_nesting1() {
        let mut ctx = Context::default();

        // Lm
        {
            ctx.push_child_scope();
            ctx.append_path_str("Lm");
            ctx.add_sym("abc", Symbol::prop("xyz3"));
        }

        // Lm.No
        {
            ctx.push_child_scope();
            ctx.append_path_str("No");
            ctx.add_sym("abc", Symbol::prop("xyz2"));
            ctx.add_sym("def", Symbol::prop("def2"));
        }

        // Lm.No.Pq
        {
            ctx.push_child_scope();
            ctx.append_path_str("Pq");
            ctx.add_sym("abc", Symbol::prop("xyz3"));
        }

        // The joined path (dynamic) should be a string join operation
        let expr = ctx.scope().join_path_as_expr(Some("."));
        assert_eq!(expr, Some(ExprValue::Apply(ExprApplyOp::JoinString(Some(".".to_owned())), Some(vec![
            Box::new(ExprValue::LiteralString("Lm".to_owned())),
            Box::new(ExprValue::LiteralString("No".to_owned())),
            Box::new(ExprValue::LiteralString("Pq".to_owned()))
        ]))));

        // We should resolve the symbol from the nearest scope where it is defined
        assert_eq!(ctx.resolve_sym("abc"), Some(Symbol::prop("xyz3")));

        // We should resolve the symbol from the nearest scope where it is defined
        assert_eq!(ctx.resolve_sym("def"), Some(Symbol::prop("def2")));
    }
}<|endoftext|>