<commit_before>//! Implements `StdTimeManager`.

use std::sync::RwLock;
use std::time::{SystemTime, Duration};
use std::cmp::min;
use board::*;
use depth::*;
use value::*;
use search_executor::*;
use hash_table::Variation;
use search_node::SearchNode;
use time_manager::{TimeManager, RemainingTime};
use uci::{SetOption, OptionDescription};

/// Implements the `TimeManager` trait.
pub struct StdTimeManager {
    started_at: SystemTime,
    depth: Depth,
    value: Value,
    data_points: Vec<(f64, f64)>,
    hard_limit: f64,
    allotted_time: f64,
    must_play: bool,
}

impl<T> TimeManager<T> for StdTimeManager
    where T: SearchExecutor<ReportData = Vec<Variation>>
{
    fn new(position: &T::SearchNode, time: &RemainingTime) -> StdTimeManager {
        // Get our remaining time and time increment (in milliseconds).
        let (t, inc) = if position.board().to_move == WHITE {
            (time.white_millis as f64, time.winc_millis as f64)
        } else {
            (time.black_millis as f64, time.binc_millis as f64)
        };

        // Get the number of moves until the next time control, or if
        // not available, guess the number of moves to the end of the
        // game.
        let n = time.movestogo.unwrap_or(40) as f64;
        debug_assert!(n >= 1.0);

        // Calculate the total time we have.
        let time_heap = t + inc * (n - 1.0);

        // Set a hard limit for the time we will spend on this
        // move. Thinking longer that that would be reckless.
        let hard_limit = (t / n.sqrt() + inc).min(t - 1000.0);

        StdTimeManager {
            started_at: SystemTime::now(),
            depth: 0,
            value: VALUE_UNKNOWN,
            data_points: Vec::with_capacity(32),
            hard_limit: if position.legal_moves().len() > 1 {
                hard_limit
            } else {
                // When there is only one legal move, the engine is
                // allowed to think just a fraction of a second in
                // order to find a good ponder move.
                hard_limit.min(500.0)
            },
            allotted_time: if *PONDERING_IS_ALLOWED.read().unwrap() {
                // Statistically, the move we ponder will be played in
                // 50% of the cases. Therefore, in principal we should
                // add half of opponent's thinking time to our time
                // heap. In reality we do not know how opponent's time
                // will be spend, so we speculatively increase our
                // time heap by 50%.
                1.5 * time_heap / n
            } else {
                time_heap / n
            },
            must_play: false,
        }
    }

    #[allow(unused_variables)]
    fn must_play(&mut self,
                 search_executor: &mut T,
                 report: Option<&SearchReport<Vec<Variation>>>)
                 -> bool {
        if !self.must_play {
            let mut is_finished = false;
            if let Some(r) = report {
                if r.depth > self.depth {
                    self.depth = r.depth;
                    let (target_depth, t_next) = self.target_depth(r);
                    let t_pessimistic = t_next * AVG_SLOPE.read().unwrap().exp().sqrt();
                    let msg = format!("TARGET_DEPTH={}", target_depth);
                    search_executor.send_message(msg.as_str());
                    is_finished = r.depth >= target_depth || t_pessimistic > self.hard_limit
                }
            }
            self.must_play = is_finished || elapsed_millis(&self.started_at) > self.hard_limit;
        }
        self.must_play
    }
}

impl SetOption for StdTimeManager {
    fn options() -> Vec<(String, OptionDescription)> {
        vec![("Ponder".to_string(), OptionDescription::Check { default: false })]
    }

    fn set_option(name: &str, value: &str) {
        if name == "Ponder" {
            match value {
                "true" => *PONDERING_IS_ALLOWED.write().unwrap() = true,
                "false" => *PONDERING_IS_ALLOWED.write().unwrap() = false,
                _ => (),
            }
        }
    }
}

impl StdTimeManager {
    /// Guesses what target depth we will be able to reach, and how
    /// much time (milliseconds) it will take for the next search
    /// depth to complete.
    fn target_depth(&mut self, report: &SearchReport<Vec<Variation>>) -> (Depth, f64) {
        let t = elapsed_millis(&self.started_at);

        // Ignore the first 1-2 depths.
        if t < 0.001 || report.searched_nodes < 100 {
            return (DEPTH_MAX, t);
        }

        // Add (x, y) to the list of data points.
        let x = report.depth as f64;
        let y = t.ln();
        self.data_points.push((x, y));

        // Do a linear extrapolation based on the last `M` data points.
        const M: usize = 5;
        let y_max = self.allotted_time.max(0.001).ln();
        let x_max;
        let t_next;
        match self.data_points.len() {
            n if n >= M => {
                let last_m = &self.data_points[n - M..];
                let (slope, intercept) = linear_regression(last_m);
                debug_assert!(slope >= 0.001);
                if n == M {
                    let mut s = AVG_SLOPE.write().unwrap();
                    *s = (*s * 2.0 + slope) / 3.0;
                }
                x_max = (y_max - intercept) / slope;
                t_next = (slope * (x + 1.0) + intercept).exp();
            }
            _ => {
                // There are not enough data points yet -- use `AVG_SLOPE`.
                let s = AVG_SLOPE.read().unwrap();
                x_max = x + (y_max - y) / *s;
                t_next = t * s.exp();
            }
        };

        // Set the target depth as close as possible to `x_max`.
        let mut target_depth = x_max.round()
                                    .min(DEPTH_MAX as f64)
                                    .max(DEPTH_MIN as f64) as Depth;

        // Search one ply deeper if the target depth is reached, but
        // position's evaluation has changed a lot.
        //
        // TODO: `25` must be bound to pawn's value.
        if target_depth == report.depth &&
           (self.value != VALUE_UNKNOWN && report.value != VALUE_UNKNOWN) &&
           (self.value as isize - report.value as isize).abs() >= 25 {
            target_depth = min(target_depth + 1, DEPTH_MAX);
        }
        self.value = report.value;

        (target_depth, t_next)
    }
}

lazy_static! {
    static ref PONDERING_IS_ALLOWED: RwLock<bool> = RwLock::new(false);
    static ref AVG_SLOPE: RwLock<f64> = RwLock::new(0.7);
}

/// Calculates elapsed milliseconds since a given time.
fn elapsed_millis(since: &SystemTime) -> f64 {
    let d = since.elapsed().unwrap_or(Duration::from_millis(0));
    (1000 * d.as_secs()) as f64 + (d.subsec_nanos() / 1_000_000) as f64
}

/// Calculates a regression line that approximates `points`.
fn linear_regression(points: &[(f64, f64)]) -> (f64, f64) {
    debug_assert!(points.len() > 1);
    let sum_x = points.iter().fold(0.0, |acc, &p| acc + p.0);
    let sum_y = points.iter().fold(0.0, |acc, &p| acc + p.1);
    let sum_xx = points.iter().fold(0.0, |acc, &p| acc + p.0 * p.0);
    let sum_xy = points.iter().fold(0.0, |acc, &p| acc + p.0 * p.1);
    let n = points.len() as f64;
    let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
    let intercept = (sum_y - slope * sum_x) / n;
    (slope.max(0.001), intercept)
}

#[cfg(test)]
mod tests {
    #[test]
    fn linear_regression() {
        use super::linear_regression;
        let points = vec![(21.0, 1.0), (22.0, 2.0), (23.0, 3.0), (24.0, 4.0)];
        let x = 25.0;
        let (slope, intercept) = linear_regression(&points);
        let y = slope * x + intercept;
        assert!(4.99 < y && y < 5.01);
    }
}
<commit_msg>search one ply further only if losing<commit_after>//! Implements `StdTimeManager`.

use std::sync::RwLock;
use std::time::{SystemTime, Duration};
use std::cmp::min;
use board::*;
use depth::*;
use value::*;
use search_executor::*;
use hash_table::Variation;
use search_node::SearchNode;
use time_manager::{TimeManager, RemainingTime};
use uci::{SetOption, OptionDescription};

/// Implements the `TimeManager` trait.
pub struct StdTimeManager {
    started_at: SystemTime,
    depth: Depth,
    value: Value,
    data_points: Vec<(f64, f64)>,
    hard_limit: f64,
    allotted_time: f64,
    must_play: bool,
}

impl<T> TimeManager<T> for StdTimeManager
    where T: SearchExecutor<ReportData = Vec<Variation>>
{
    fn new(position: &T::SearchNode, time: &RemainingTime) -> StdTimeManager {
        // Get our remaining time and time increment (in milliseconds).
        let (t, inc) = if position.board().to_move == WHITE {
            (time.white_millis as f64, time.winc_millis as f64)
        } else {
            (time.black_millis as f64, time.binc_millis as f64)
        };

        // Get the number of moves until the next time control, or if
        // not available, guess the number of moves to the end of the
        // game.
        let n = time.movestogo.unwrap_or(40) as f64;
        debug_assert!(n >= 1.0);

        // Calculate the total time we have.
        let time_heap = t + inc * (n - 1.0);

        // Set a hard limit for the time we will spend on this
        // move. Thinking longer that that would be reckless.
        let hard_limit = (t / n.sqrt() + inc).min(t - 1000.0);

        StdTimeManager {
            started_at: SystemTime::now(),
            depth: 0,
            value: VALUE_UNKNOWN,
            data_points: Vec::with_capacity(32),
            hard_limit: if position.legal_moves().len() > 1 {
                hard_limit
            } else {
                // When there is only one legal move, the engine is
                // allowed to think just a fraction of a second in
                // order to find a good ponder move.
                hard_limit.min(500.0)
            },
            allotted_time: if *PONDERING_IS_ALLOWED.read().unwrap() {
                // Statistically, the move we ponder will be played in
                // 50% of the cases. Therefore, in principal we should
                // add half of opponent's thinking time to our time
                // heap. In reality we do not know how opponent's time
                // will be spend, so we speculatively increase our
                // time heap by 50%.
                1.5 * time_heap / n
            } else {
                time_heap / n
            },
            must_play: false,
        }
    }

    #[allow(unused_variables)]
    fn must_play(&mut self,
                 search_executor: &mut T,
                 report: Option<&SearchReport<Vec<Variation>>>)
                 -> bool {
        if !self.must_play {
            let mut is_finished = false;
            if let Some(r) = report {
                if r.depth > self.depth {
                    self.depth = r.depth;
                    let (target_depth, t_next) = self.target_depth(r);
                    let t_pessimistic = t_next * AVG_SLOPE.read().unwrap().exp().sqrt();
                    let msg = format!("TARGET_DEPTH={}", target_depth);
                    search_executor.send_message(msg.as_str());
                    is_finished = r.depth >= target_depth || t_pessimistic > self.hard_limit
                }
            }
            self.must_play = is_finished || elapsed_millis(&self.started_at) > self.hard_limit;
        }
        self.must_play
    }
}

impl SetOption for StdTimeManager {
    fn options() -> Vec<(String, OptionDescription)> {
        vec![("Ponder".to_string(), OptionDescription::Check { default: false })]
    }

    fn set_option(name: &str, value: &str) {
        if name == "Ponder" {
            match value {
                "true" => *PONDERING_IS_ALLOWED.write().unwrap() = true,
                "false" => *PONDERING_IS_ALLOWED.write().unwrap() = false,
                _ => (),
            }
        }
    }
}

impl StdTimeManager {
    /// Guesses what target depth we will be able to reach, and how
    /// much time (milliseconds) it will take for the next search
    /// depth to complete.
    fn target_depth(&mut self, report: &SearchReport<Vec<Variation>>) -> (Depth, f64) {
        let t = elapsed_millis(&self.started_at);

        // Ignore the first 1-2 depths.
        if t < 0.001 || report.searched_nodes < 100 {
            return (DEPTH_MAX, t);
        }

        // Add (x, y) to the list of data points.
        let x = report.depth as f64;
        let y = t.ln();
        self.data_points.push((x, y));

        // Do a linear extrapolation based on the last `M` data points.
        const M: usize = 5;
        let y_max = self.allotted_time.max(0.001).ln();
        let x_max;
        let t_next;
        match self.data_points.len() {
            n if n >= M => {
                let last_m = &self.data_points[n - M..];
                let (slope, intercept) = linear_regression(last_m);
                debug_assert!(slope >= 0.001);
                if n == M {
                    let mut s = AVG_SLOPE.write().unwrap();
                    *s = (*s * 2.0 + slope) / 3.0;
                }
                x_max = (y_max - intercept) / slope;
                t_next = (slope * (x + 1.0) + intercept).exp();
            }
            _ => {
                // There are not enough data points yet -- use `AVG_SLOPE`.
                let s = AVG_SLOPE.read().unwrap();
                x_max = x + (y_max - y) / *s;
                t_next = t * s.exp();
            }
        };

        // Set the target depth as close as possible to `x_max`.
        let mut target_depth = x_max.round()
                                    .min(DEPTH_MAX as f64)
                                    .max(DEPTH_MIN as f64) as Depth;

        // Search one ply deeper if the target depth is reached, but
        // position's evaluation worsened a lot.
        //
        // TODO: `25` must be bound to pawn's value.
        if target_depth == report.depth &&
           (self.value != VALUE_UNKNOWN && report.value != VALUE_UNKNOWN) &&
           (self.value as isize - report.value as isize >= 25) {
            target_depth = min(target_depth + 1, DEPTH_MAX);
        }
        self.value = report.value;

        (target_depth, t_next)
    }
}

lazy_static! {
    static ref PONDERING_IS_ALLOWED: RwLock<bool> = RwLock::new(false);
    static ref AVG_SLOPE: RwLock<f64> = RwLock::new(0.7);
}

/// Calculates elapsed milliseconds since a given time.
fn elapsed_millis(since: &SystemTime) -> f64 {
    let d = since.elapsed().unwrap_or(Duration::from_millis(0));
    (1000 * d.as_secs()) as f64 + (d.subsec_nanos() / 1_000_000) as f64
}

/// Calculates a regression line that approximates `points`.
fn linear_regression(points: &[(f64, f64)]) -> (f64, f64) {
    debug_assert!(points.len() > 1);
    let sum_x = points.iter().fold(0.0, |acc, &p| acc + p.0);
    let sum_y = points.iter().fold(0.0, |acc, &p| acc + p.1);
    let sum_xx = points.iter().fold(0.0, |acc, &p| acc + p.0 * p.0);
    let sum_xy = points.iter().fold(0.0, |acc, &p| acc + p.0 * p.1);
    let n = points.len() as f64;
    let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
    let intercept = (sum_y - slope * sum_x) / n;
    (slope.max(0.001), intercept)
}

#[cfg(test)]
mod tests {
    #[test]
    fn linear_regression() {
        use super::linear_regression;
        let points = vec![(21.0, 1.0), (22.0, 2.0), (23.0, 3.0), (24.0, 4.0)];
        let x = 25.0;
        let (slope, intercept) = linear_regression(&points);
        let y = slope * x + intercept;
        assert!(4.99 < y && y < 5.01);
    }
}
<|endoftext|>


<commit_before>//! The sendmail transport sends the email using the local sendmail command.
//!
//! ## Sync example
//!
//! ```rust
//! use lettre::{Message, Envelope, Transport, SendmailTransport};
//!
//! let email = Message::builder()
//!     .from("NoBody <nobody@domain.tld>".parse().unwrap())
//!     .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//!     .to("Hei <hei@domain.tld>".parse().unwrap())
//!     .subject("Happy new year")
//!     .body("Be happy!")
//!     .unwrap();
//!
//! let sender = SendmailTransport::new();
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! ```
//!
//! ## Async tokio 0.2 example
//!
//! ```rust
//! # #[cfg(feature = "tokio02")]
//! # async fn run() {
//! use lettre::{Message, Envelope, Tokio02Transport, SendmailTransport};
//!
//! let email = Message::builder()
//!     .from("NoBody <nobody@domain.tld>".parse().unwrap())
//!     .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//!     .to("Hei <hei@domain.tld>".parse().unwrap())
//!     .subject("Happy new year")
//!     .body("Be happy!")
//!     .unwrap();
//!
//! let sender = SendmailTransport::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # }
//! ```
//!
//! ## Async async-std 1.x example
//!
//!```rust
//! # #[cfg(feature = "async-std1")]
//! # async fn run() {
//! use lettre::{Message, Envelope, AsyncStd1Transport, SendmailTransport};
//!
//! let email = Message::builder()
//!     .from("NoBody <nobody@domain.tld>".parse().unwrap())
//!     .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//!     .to("Hei <hei@domain.tld>".parse().unwrap())
//!     .subject("Happy new year")
//!     .body("Be happy!")
//!     .unwrap();
//!
//! let sender = SendmailTransport::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # }
//! ```

pub use self::error::Error;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
use crate::{Envelope, Transport};
#[cfg(any(feature = "async-std1", feature = "tokio02"))]
use async_trait::async_trait;
use std::{
    convert::AsRef,
    ffi::OsString,
    io::prelude::*,
    process::{Command, Stdio},
};

mod error;

const DEFAUT_SENDMAIL: &str = "/usr/sbin/sendmail";

/// Sends an email using the `sendmail` command
#[derive(Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SendmailTransport {
    command: OsString,
}

impl SendmailTransport {
    /// Creates a new transport with the default `/usr/sbin/sendmail` command
    pub fn new() -> SendmailTransport {
        SendmailTransport {
            command: DEFAUT_SENDMAIL.into(),
        }
    }

    /// Creates a new transport to the given sendmail command
    pub fn new_with_command<S: Into<OsString>>(command: S) -> SendmailTransport {
        SendmailTransport {
            command: command.into(),
        }
    }

    fn command(&self, envelope: &Envelope) -> Command {
        let mut c = Command::new(&self.command);
        c.arg("-i")
            .arg("-f")
            .arg(envelope.from().map(|f| f.as_ref()).unwrap_or("\"\""))
            .args(envelope.to())
            .stdin(Stdio::piped())
            .stdout(Stdio::piped());
        c
    }

    #[cfg(feature = "tokio02")]
    fn tokio02_command(&self, envelope: &Envelope) -> tokio02_crate::process::Command {
        use tokio02_crate::process::Command;

        let mut c = Command::new(&self.command);
        c.kill_on_drop(true);
        c.arg("-i")
            .arg("-f")
            .arg(envelope.from().map(|f| f.as_ref()).unwrap_or("\"\""))
            .args(envelope.to())
            .stdin(Stdio::piped())
            .stdout(Stdio::piped());
        c
    }
}

impl Transport for SendmailTransport {
    type Ok = ();
    type Error = Error;

    fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
        // Spawn the sendmail command
        let mut process = self.command(envelope).spawn()?;

        process.stdin.as_mut().unwrap().write_all(email)?;
        let output = process.wait_with_output()?;

        if output.status.success() {
            Ok(())
        } else {
            Err(error::Error::Client(String::from_utf8(output.stderr)?))
        }
    }
}

#[cfg(feature = "async-std1")]
#[async_trait]
impl AsyncStd1Transport for SendmailTransport {
    type Ok = ();
    type Error = Error;

    async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
        let mut command = self.command(envelope);
        let email = email.to_vec();

        // TODO: Convert to real async, once async-std has a process implementation.
        let output = async_std::task::spawn_blocking(move || {
            // Spawn the sendmail command
            let mut process = command.spawn()?;

            process.stdin.as_mut().unwrap().write_all(&email)?;
            process.wait_with_output()
        })
        .await?;

        if output.status.success() {
            Ok(())
        } else {
            Err(Error::Client(String::from_utf8(output.stderr)?))
        }
    }
}

#[cfg(feature = "tokio02")]
#[async_trait]
impl Tokio02Transport for SendmailTransport {
    type Ok = ();
    type Error = Error;

    async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
        use tokio02_crate::io::AsyncWriteExt;

        let mut command = self.tokio02_command(envelope);

        // Spawn the sendmail command
        let mut process = command.spawn()?;

        process.stdin.as_mut().unwrap().write_all(&email).await?;
        let output = process.wait_with_output().await?;

        if output.status.success() {
            Ok(())
        } else {
            Err(Error::Client(String::from_utf8(output.stderr)?))
        }
    }
}
<commit_msg>fix: Default impl for SendmailTransport<commit_after>//! The sendmail transport sends the email using the local sendmail command.
//!
//! ## Sync example
//!
//! ```rust
//! use lettre::{Message, Envelope, Transport, SendmailTransport};
//!
//! let email = Message::builder()
//!     .from("NoBody <nobody@domain.tld>".parse().unwrap())
//!     .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//!     .to("Hei <hei@domain.tld>".parse().unwrap())
//!     .subject("Happy new year")
//!     .body("Be happy!")
//!     .unwrap();
//!
//! let sender = SendmailTransport::new();
//! let result = sender.send(&email);
//! assert!(result.is_ok());
//! ```
//!
//! ## Async tokio 0.2 example
//!
//! ```rust
//! # #[cfg(feature = "tokio02")]
//! # async fn run() {
//! use lettre::{Message, Envelope, Tokio02Transport, SendmailTransport};
//!
//! let email = Message::builder()
//!     .from("NoBody <nobody@domain.tld>".parse().unwrap())
//!     .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//!     .to("Hei <hei@domain.tld>".parse().unwrap())
//!     .subject("Happy new year")
//!     .body("Be happy!")
//!     .unwrap();
//!
//! let sender = SendmailTransport::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # }
//! ```
//!
//! ## Async async-std 1.x example
//!
//!```rust
//! # #[cfg(feature = "async-std1")]
//! # async fn run() {
//! use lettre::{Message, Envelope, AsyncStd1Transport, SendmailTransport};
//!
//! let email = Message::builder()
//!     .from("NoBody <nobody@domain.tld>".parse().unwrap())
//!     .reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
//!     .to("Hei <hei@domain.tld>".parse().unwrap())
//!     .subject("Happy new year")
//!     .body("Be happy!")
//!     .unwrap();
//!
//! let sender = SendmailTransport::new();
//! let result = sender.send(email).await;
//! assert!(result.is_ok());
//! # }
//! ```

pub use self::error::Error;
#[cfg(feature = "async-std1")]
use crate::AsyncStd1Transport;
#[cfg(feature = "tokio02")]
use crate::Tokio02Transport;
use crate::{Envelope, Transport};
#[cfg(any(feature = "async-std1", feature = "tokio02"))]
use async_trait::async_trait;
use std::{
    convert::AsRef,
    ffi::OsString,
    io::prelude::*,
    process::{Command, Stdio},
};

mod error;

const DEFAUT_SENDMAIL: &str = "/usr/sbin/sendmail";

/// Sends an email using the `sendmail` command
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SendmailTransport {
    command: OsString,
}

impl SendmailTransport {
    /// Creates a new transport with the default `/usr/sbin/sendmail` command
    pub fn new() -> SendmailTransport {
        SendmailTransport {
            command: DEFAUT_SENDMAIL.into(),
        }
    }

    /// Creates a new transport to the given sendmail command
    pub fn new_with_command<S: Into<OsString>>(command: S) -> SendmailTransport {
        SendmailTransport {
            command: command.into(),
        }
    }

    fn command(&self, envelope: &Envelope) -> Command {
        let mut c = Command::new(&self.command);
        c.arg("-i")
            .arg("-f")
            .arg(envelope.from().map(|f| f.as_ref()).unwrap_or("\"\""))
            .args(envelope.to())
            .stdin(Stdio::piped())
            .stdout(Stdio::piped());
        c
    }

    #[cfg(feature = "tokio02")]
    fn tokio02_command(&self, envelope: &Envelope) -> tokio02_crate::process::Command {
        use tokio02_crate::process::Command;

        let mut c = Command::new(&self.command);
        c.kill_on_drop(true);
        c.arg("-i")
            .arg("-f")
            .arg(envelope.from().map(|f| f.as_ref()).unwrap_or("\"\""))
            .args(envelope.to())
            .stdin(Stdio::piped())
            .stdout(Stdio::piped());
        c
    }
}

impl Default for SendmailTransport {
    fn default() -> Self {
        Self::new()
    }
}

impl Transport for SendmailTransport {
    type Ok = ();
    type Error = Error;

    fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
        // Spawn the sendmail command
        let mut process = self.command(envelope).spawn()?;

        process.stdin.as_mut().unwrap().write_all(email)?;
        let output = process.wait_with_output()?;

        if output.status.success() {
            Ok(())
        } else {
            Err(error::Error::Client(String::from_utf8(output.stderr)?))
        }
    }
}

#[cfg(feature = "async-std1")]
#[async_trait]
impl AsyncStd1Transport for SendmailTransport {
    type Ok = ();
    type Error = Error;

    async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
        let mut command = self.command(envelope);
        let email = email.to_vec();

        // TODO: Convert to real async, once async-std has a process implementation.
        let output = async_std::task::spawn_blocking(move || {
            // Spawn the sendmail command
            let mut process = command.spawn()?;

            process.stdin.as_mut().unwrap().write_all(&email)?;
            process.wait_with_output()
        })
        .await?;

        if output.status.success() {
            Ok(())
        } else {
            Err(Error::Client(String::from_utf8(output.stderr)?))
        }
    }
}

#[cfg(feature = "tokio02")]
#[async_trait]
impl Tokio02Transport for SendmailTransport {
    type Ok = ();
    type Error = Error;

    async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
        use tokio02_crate::io::AsyncWriteExt;

        let mut command = self.tokio02_command(envelope);

        // Spawn the sendmail command
        let mut process = command.spawn()?;

        process.stdin.as_mut().unwrap().write_all(&email).await?;
        let output = process.wait_with_output().await?;

        if output.status.success() {
            Ok(())
        } else {
            Err(Error::Client(String::from_utf8(output.stderr)?))
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2013-2015, The Rust-GNOME Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::fmt::{Error, Debug};
use std::ffi::CStr;

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Copy)]
pub enum Status {
    StatusSuccess = 0,

    StatusNoMemory,
    StatusInvalidRestore,
    StatusInvalidPopGroup,
    StatusNoCurrentPoint,
    StatusInvalidMatrix,
    StatusInvalidStatus,
    StatusNullPointer,
    StatusInvalidString,
    StatusInvalidPathData,
    StatusReadError,
    StatusWriteError,
    StatusSurfaceFinished,
    StatusSurfaceTypeMismatch,
    StatusPatternTypeMismatch,
    StatusInvalidContent,
    StatusInvalidFormat,
    StatusInvalidVisual,
    StatusFileNotFound,
    StatusInvalidDash,
    StatusInvalidDscComment,
    StatusInvalidIndex,
    StatusClipNotRepresentable,
    StatusTempFileError,
    StatusInvalidStride,
    StatusFontTypeMismatch,
    StatusUserFontImmutable,
    StatusUserFontError,
    StatusNegativeCount,
    StatusInvalidClusters,
    StatusInvalidSlant,
    StatusInvalidWeight,
    StatusInvalidSize,
    StatusUserFontNotImplemented,
    StatusDeviceTypeMismatch,
    StatusDeviceError,
    StatusInvalidMeshConstruction,
    StatusDeviceFinished,

    StatusLastStatus
}

impl Debug for Status {
    fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> Result<(), Error> {
        unsafe {
            let char_ptr = super::cairo_status_to_string(*self);
            let tmp = String::from_utf8_lossy(CStr::from_ptr(char_ptr).to_bytes()).into_owned();

            tmp.fmt(formatter)
        }
    }
}

impl Status {
    pub fn ensure_valid(&self) {
        if *self != Status::StatusSuccess {
            panic!("Cairo error {:?}", *self)
        }
    }
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Antialias {
    AntialiasDefault,

    /* method */
    AntialiasNone,
    AntialiasGray,
    AntialiasSubpixel,

    /* hints */
    AntialiasFast,
    AntialiasGood,
    AntialiasBest
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FillRule {
    FillRuleWinding,
    FillRuleEvenOdd
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineCap {
    LineCapButt,
    LineCapRound,
    LineCapSquare
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineJoin {
    LineJoinMiter,
    LineJoinRound,
    LineJoinBevel
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Operator {
    OperatorClear,

    OperatorSource,
    OperatorOver,
    OperatorIn,
    OperatorOut,
    OperatorAtop,

    OperatorDest,
    OperatorDestOver,
    OperatorDestIn,
    OperatorDestOut,
    OperatorDestAtop,

    OperatorXor,
    OperatorAdd,
    OperatorSaturate,

    OperatorMultiply,
    OperatorScreen,
    OperatorOverlay,
    OperatorDarken,
    OperatorLighten,
    OperatorColorDodge,
    OperatorColorBurn,
    OperatorHardLight,
    OperatorSoftLight,
    OperatorDifference,
    OperatorExclusion,
    OperatorHslHue,
    OperatorHslSaturation,
    OperatorHslColor,
    OperatorHslLuminosity
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PathDataType {
    PathMoveTo,
    PathLineTo,
    PathCurveTo,
    PathClosePath
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Content {
    ContentColor      = 0x1000,
    ContentAlpha      = 0x2000,
    ContentColorAlpha = 0x3000
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Extend {
    ExtendNone,
    ExtendRepeat,
    ExtendReflect,
    ExtendPad
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Filter {
    FilterFast,
    FilterGood,
    FilterBest,
    FilterNearest,
    FilterBilinear,
    FilterGaussian
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PatternType {
    PatternTypeSolid,
    PatternTypeSurface,
    PatternTypeLinearGradient,
    PatternTypeRadialGradient,
    #[cfg(feature = "cairo_1_12")]
    PatternTypeMesh,
    #[cfg(feature = "cairo_1_12")]
    PatternTypeRasterSource
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontSlant {
    FontSlantNormal,
    FontSlantItalic,
    FontSlantOblique
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontWeight {
    FontWeightNormal,
    FontWeightBold
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum TextClusterFlags {
    TextClusterFlagNone     = 0x00000000,
    TextClusterFlagBackward = 0x00000001
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontType {
    FontTypeToy,
    FontTypeFt,
    FontTypeWin32,
    FontTypeQuartz,
    FontTypeUser
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum SubpixelOrder {
    SubpixelOrderDefault,
    SubpixelOrderRgb,
    SubpixelOrderBgr,
    SubpixelOrderVrgb,
    SubpixelOrderVbgr
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintStyle {
    HintStyleDefault,
    HintStyleNone,
    HintStyleSlight,
    HintStyleMedium,
    HintStyleFull
}

#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintMetrics {
    HintMetricsDefault,
    HintMetricsOff,
    HintMetricsOn
}
<commit_msg>Add enums documentation<commit_after>// Copyright 2013-2015, The Rust-GNOME Project Developers.
// See the COPYRIGHT file at the top-level directory of this distribution.
// Licensed under the MIT license, see the LICENSE file or <http://opensource.org/licenses/MIT>

use std::fmt::{Error, Debug};
use std::ffi::CStr;

/// Status is used to indicate errors that can occur when using Cairo. In some cases it is
/// returned directly by functions, but when using `Context`, the last error, if any, is
/// stored in the context and can be retrieved with `Context::status()`.
///
/// New entries may be added in future versions. Use `Context::status_to_string()` to get a
/// human-readable representation of an error message.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Copy)]
pub enum Status {
    /// no error has occurred (Since 1.0)
    Success = 0,

    /// out of memory (Since 1.0)
    NoMemory,
    /// `Context::restore()` called without matching `Context::save()` (Since 1.0)
    InvalidRestore,
    /// no saved group to pop, i.e. `Context::pop_group()` without matching
    /// `Context::push_group()` (Since 1.0)
    InvalidPopGroup,
    /// no current point defined (Since 1.0)
    NoCurrentPoint,
    /// invalid matrix (not invertible) (Since 1.0)
    InvalidMatrix,
    /// invalid value for an input `Status` (Since 1.0)
    InvalidStatus,
    /// NULL pointer (Since 1.0)
    NullPointer,
    /// input string not valid UTF-8 (Since 1.0)
    InvalidString,
    /// input path data not valid (Since 1.0)
    InvalidPathData,
    /// error while reading from input stream (Since 1.0)
    ReadError,
    /// error while writing to output stream (Since 1.0)
    WriteError,
    /// target surface has been finished (Since 1.0)
    SurfaceFinished,
    /// the surface type is not appropriate for the operation (Since 1.0)
    SurfaceTypeMismatch,
    /// the pattern type is not appropriate for the operation (Since 1.0)
    PatternTypeMismatch,
    /// invalid value for an input `Content` (Since 1.0)
    InvalidContent,
    /// invalid value for an input `Format` (Since 1.0)
    InvalidFormat,
    /// invalid value for an input Visual* (Since 1.0)
    InvalidVisual,
    /// file not found (Since 1.0)
    FileNotFound,
    /// invalid value for a dash setting (Since 1.0)
    InvalidDash,
    /// invalid value for a DSC comment (Since 1.2)
    InvalidDscComment,
    /// invalid index passed to getter (Since 1.4)
    InvalidIndex,
    /// clip region not representable in desired format (Since 1.4)
    ClipNotRepresentable,
    /// error creating or writing to a temporary file (Since 1.6)
    TempFileError,
    /// invalid value for stride (Since 1.6)
    InvalidStride,
    /// the font type is not appropriate for the operation (Since 1.8)
    FontTypeMismatch,
    /// the user-font is immutable (Since 1.8)
    UserFontImmutable,
    /// error occurred in a user-font callback function (Since 1.8)
    UserFontError,
    /// negative number used where it is not allowed (Since 1.8)
    NegativeCount,
    /// input clusters do not represent the accompanying text and glyph array (Since 1.8)
    InvalidClusters,
    /// invalid value for an input `FontSlant` (Since 1.8)
    InvalidSlant,
    /// invalid value for an input `FontWeight` (Since 1.8)
    InvalidWeight,
    /// invalid value (typically too big) for the size of the input (surface, pattern,
    /// etc.) (Since 1.10)
    InvalidSize,
    /// user-font method not implemented (Since 1.10)
    UserFontNotImplemented,
    /// the device type is not appropriate for the operation (Since 1.10)
    DeviceTypeMismatch,
    /// an operation to the device caused an unspecified error (Since 1.10)
    DeviceError,
    /// a mesh pattern construction operation was used outside of a
    /// `Context::mesh_pattern_begin_patch()`/`Context::mesh_pattern_end_patch()`
    /// pair (Since 1.12)
    InvalidMeshConstruction,
    /// target device has been finished (Since 1.12)
    DeviceFinished,
    // CAIRO_MIME_TYPE_JBIG2_GLOBAL_ID has been used on at least one image but no
    // image provided `JBig2Global` (Since 1.14)
    // JBig2GlobalMissing,
    /// this is a special value indicating the number of status values defined in this
    /// enumeration. When using this value, note that the version of cairo at run-time
    /// may have additional status values defined than the value of this symbol at
    /// compile-time. (Since 1.10)
    LastStatus
}

impl Debug for Status {
    fn fmt(&self, formatter: &mut ::std::fmt::Formatter) -> Result<(), Error> {
        unsafe {
            let char_ptr = super::cairo_status_to_string(*self);
            let tmp = String::from_utf8_lossy(CStr::from_ptr(char_ptr).to_bytes()).into_owned();

            tmp.fmt(formatter)
        }
    }
}

impl Status {
    pub fn ensure_valid(&self) {
        if *self != Status::StatusSuccess {
            panic!("Cairo error {:?}", *self)
        }
    }
}

/// Specifies the type of antialiasing to do when rendering text or shapes.
///
/// As it is not necessarily clear from the above what advantages a particular antialias method
/// provides, since 1.12, there is also a set of hints:
/// `Fast`: Allow the backend to degrade raster quality for speed.
/// `Good`: A balance between speed and quality.
/// `Best`: A high-fidelity, but potentially slow, raster mode.
///
/// These make no guarantee on how the backend will perform its rasterisation (if it even
/// rasterises!), nor that they have any differing effect other than to enable some form of
/// antialiasing. In the case of glyph rendering, `Fast` and `Good` will be mapped to `Gray`, with
/// `Best` being equivalent to `Subpixel`.
///
/// The interpretation of `Default` is left entirely up to the backend, typically this will be
/// similar to `Good`.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Antialias {
    /// Use the default antialiasing for the subsystem and target device, since 1.0
    Default,

    /* method */
    /// Use a bilevel alpha mask, since 1.0
    None,
    /// Perform single-color antialiasing (using shades of gray for black text on a white
    /// background, for example), since 1.0
    Gray,
    /// Perform antialiasing by taking advantage of the order of subpixel elements on devices
    /// such as LCD panels, since 1.0
    Subpixel,

    /* hints */
    /// Hint that the backend should perform some antialiasing but prefer speed over quality,
    /// since 1.12
    Fast,
    /// The backend should balance quality against performance, since 1.12
    Good,
    /// Hint that the backend should render at the highest quality, sacrificing speed if
    /// necessary, since 1.12
    Best
}

/// `FillRule` is used to select how paths are filled. For both fill rules, whether or not
/// a point is included in the fill is determined by taking a ray from that point to infinity
/// and looking at intersections with the path. The ray can be in any direction, as long as
/// it doesn't pass through the end point of a segment or have a tricky intersection such as
/// intersecting tangent to the path. (Note that filling is not actually implemented in this
/// way. This is just a description of the rule that is applied.)
///
/// The default fill rule is `Winding`.
///
/// New entries may be added in future versions.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FillRule {
    /// If the path crosses the ray from left-to-right, counts +1. If the path crosses the ray
    /// from right to left, counts -1. (Left and right are determined from the perspective of
    /// looking along the ray from the starting point.) If the total count is non-zero, the point
    /// will be filled. (Since 1.0)
    Winding,
    /// Counts the total number of intersections, without regard to the orientation of the contour.
    /// If the total number of intersections is odd, the point will be filled. (Since 1.0)
    EvenOdd
}

/// Specifies how to render the endpoints of the path when stroking.
///
/// The default line cap style is `Butt`.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineCap {
    /// start(stop) the line exactly at the start(end) point (Since 1.0)
    Butt,
    /// use a round ending, the center of the circle is the end point (Since 1.0)
    Round,
    /// use squared ending, the center of the square is the end point (Since 1.0)
    Square
}

/// Specifies how to render the junction of two lines when stroking.
///
/// The default line join style is `Miter`.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum LineJoin {
    /// use a sharp (angled) corner, see `Context::set_miter_limit()` (Since 1.0)
    Miter,
    /// use a rounded join, the center of the circle is the joint point (Since 1.0)
    Round,
    /// use a cut-off join, the join is cut off at half the line width from the
    /// joint point (Since 1.0)
    nBevel
}

/// `Operator` is used to set the compositing operator for all cairo drawing operations.
///
/// The default operator is `Over`.
///
/// The operators marked as unbounded modify their destination even outside of the mask layer
/// (that is, their effect is not bound by the mask layer). However, their effect can still be
/// limited by way of clipping.
///
/// To keep things simple, the operator descriptions here document the behavior for when both
/// source and destination are either fully transparent or fully opaque. The actual implementation
/// works for translucent layers too. For a more detailed explanation of the effects of each
/// operator, including the mathematical definitions, see http://cairographics.org/operators/.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Operator {
    /// clear destination layer (bounded) (Since 1.0)
    Clear,

    /// replace destination layer (bounded) (Since 1.0)
    Source,
    /// draw source layer on top of destination layer (bounded) (Since 1.0)
    Over,
    /// draw source where there was destination content (unbounded) (Since 1.0)
    In,
    /// draw source where there was no destination content (unbounded) (Since 1.0)
    Out,
    /// draw source on top of destination content and only there (Since 1.0)
    Atop,

    /// ignore the source (Since 1.0)
    Dest,
    /// draw destination on top of source (Since 1.0)
    DestOver,
    /// leave destination only where there was source content (unbounded) (Since 1.0)
    DestIn,
    /// leave destination only where there was no source content (Since 1.0)
    DestOut,
    /// leave destination on top of source content and only there (unbounded) (Since 1.0)
    DestAtop,

    /// source and destination are shown where there is only one of them (Since 1.0)
    Xor,
    /// source and destination layers are accumulated (Since 1.0)
    Add,
    /// like over, but assuming source and dest are disjoint geometries (Since 1.0)
    Saturate,

    /// source and destination layers are multiplied. This causes the result to be at
    /// least as dark as the darker inputs. (Since 1.10)
    Multiply,
    /// source and destination are complemented and multiplied. This causes the result
    /// to be at least as light as the lighter inputs. (Since 1.10)
    Screen,
    /// multiplies or screens, depending on the lightness of the destination color.
    /// (Since 1.10)
    Overlay,
    /// replaces the destination with the source if it is darker, otherwise keeps the
    /// source. (Since 1.10)
    Darken,
    /// replaces the destination with the source if it is lighter, otherwise keeps the
    /// source. (Since 1.10)
    Lighten,
    /// brightens the destination color to reflect the source color. (Since 1.10)
    ColorDodge,
    /// darkens the destination color to reflect the source color. (Since 1.10)
    ColorBurn,
    /// Multiplies or screens, dependent on source color. (Since 1.10)
    HardLight,
    /// Darkens or lightens, dependent on source color. (Since 1.10)
    SoftLight,
    /// Takes the difference of the source and destination color. (Since 1.10)
    Difference,
    /// Produces an effect similar to difference, but with lower contrast. (Since 1.10)
    Exclusion,
    /// Creates a color with the hue of the source and the saturation and luminosity of
    /// the target. (Since 1.10)
    HslHue,
    /// Creates a color with the saturation of the source and the hue and luminosity of
    /// the target. Painting with this mode onto a gray area produces no change. (Since 1.10)
    HslSaturation,
    /// Creates a color with the hue and saturation of the source and the luminosity of the
    /// target. This preserves the gray levels of the target and is useful for coloring
    /// monochrome images or tinting color images. (Since 1.10)
    HslColor,
    /// Creates a color with the luminosity of the source and the hue and saturation of the
    /// target. This produces an inverse effect to `HslColor`. (Since 1.10)
    HslLuminosity
}

/// `PathData` is used to describe the type of one portion of a path when represented as a `Path`.
/// See `PathData` for details.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PathDataType {
    /// A move-to operation, since 1.0
    MoveTo,
    /// A line-to operation, since 1.0
    LineTo,
    /// A curve-to operation, since 1.0
    CurveTo,
    /// A close-path operation, since 1.0
    ClosePath
}

/// `Content` is used to describe the content that a surface will contain, whether color
/// information, alpha information (translucence vs. opacity), or both.
///
/// Note: The large values here are designed to keep `Content` values distinct from `Format`
/// values so that the implementation can detect the error if users confuse the two types.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Content {
    /// The surface will hold color content only. (Since 1.0)
    Color      = 0x1000,
    /// The surface will hold alpha content only. (Since 1.0)
    Alpha      = 0x2000,
    /// The surface will hold color and alpha content. (Since 1.0)
    ColorAlpha = 0x3000
}

/// `Extend` is used to describe how pattern color/alpha will be determined for areas
/// "outside" the pattern's natural area, (for example, outside the surface bounds or
/// outside the gradient geometry).
///
/// Mesh patterns are not affected by the extend mode.
///
/// The default extend mode is `None` for surface patterns and `Pad` for gradient
/// patterns.
///
/// New entries may be added in future versions.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Extend {
    /// pixels outside of the source pattern are fully transparent (Since 1.0)
    None,
    /// the pattern is tiled by repeating (Since 1.0)
    Repeat,
    /// the pattern is tiled by reflecting at the edges (Since 1.0; but only implemented
    /// for surface patterns since 1.6)
    Reflect,
    /// pixels outside of the pattern copy the closest pixel from the source (Since 1.2;
    /// but only implemented for surface patterns since 1.6)
    Pad
}

/// `Filter` is used to indicate what filtering should be applied when reading pixel values
/// from patterns. See `Pattern::set_filter()` for indicating the desired filter to be used
/// with a particular pattern.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum Filter {
    /// A high-performance filter, with quality similar to `Nearest` (Since 1.0)
    Fast,
    /// A reasonable-performance filter, with quality similar to `Bilinear` (Since 1.0)
    Good,
    /// The highest-quality available, performance may not be suitable for interactive
    /// use. (Since 1.0)
    Best,
    /// Nearest-neighbor filtering (Since 1.0)
    Nearest,
    /// Linear interpolation in two dimensions (Since 1.0)
    Bilinear,
    /// This filter value is currently unimplemented, and should not be used in current
    /// code. (Since 1.0)
    Gaussian
}

/// `PatternType` is used to describe the type of a given pattern.
///
/// The type of a pattern is determined by the function used to create it. The
/// `Pattern::create_rgb()` and `Pattern::create_rgba()` functions create `Solid` patterns.
/// The remaining cairo_pattern_create functions map to pattern types in obvious ways.
///
/// The pattern type can be queried with `Pattern::get_type()`.
///
/// Most `Pattern` functions can be called with a pattern of any type, (though trying to
/// change the extend or filter for a solid pattern will have no effect). A notable exception
/// is `Pattern::add_color_stop_rgb()` and `Pattern::add_color_stop_rgba()` which must only be
/// called with gradient patterns (either `Linear` or `Radial`). Otherwise the pattern will be
/// shutdown and put into an error state.
///
/// New entries may be added in future versions.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum PatternType {
    /// The pattern is a solid (uniform) color. It may be opaque or translucent, since 1.2.
    Solid,
    /// The pattern is a based on a surface (an image), since 1.2.
    Surface,
    /// The pattern is a linear gradient, since 1.2.
    Linear,
    /// The pattern is a radial gradient, since 1.2.
    Radial,
    /// The pattern is a mesh, since 1.12.
    #[cfg(feature = "cairo_1_12")]
    Mesh,
    /// The pattern is a user pattern providing raster data, since 1.12.
    #[cfg(feature = "cairo_1_12")]
    RasterSource
}

/// Specifies variants of a font face based on their slant.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontSlant {
    /// Upright font style, since 1.0
    Normal,
    /// Italic font style, since 1.0
    Italic,
    /// Oblique font style, since 1.0
    Oblique
}

/// Specifies variants of a font face based on their weight.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontWeight {
    /// Normal font weight, since 1.0
    Normal,
    /// Bold font weight, since 1.0
    Bold
}

/// Specifies properties of a text cluster mapping.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum TextClusterFlags {
    None     = 0x00000000,
    /// The clusters in the cluster array map to glyphs in the glyph array from end
    /// to start. (Since 1.8)
    Backward = 0x00000001
}

/// `FontType` is used to describe the type of a given font face or scaled font. The
/// font types are also known as "font backends" within cairo.
///
/// The type of a font face is determined by the function used to create it, which will
/// generally be of the form `Context::type_font_face_create()`. The font face type can
/// be queried with `Context::font_face_get_type()`.
///
/// The various cairo_font_face_t functions can be used with a font face of any type.
///
/// The type of a scaled font is determined by the type of the font face passed to
/// `Context::scaled_font_create()`. The scaled font type can be queried with
/// `Context::scaled_font_get_type()`.
///
/// The various `ScaledFont` functions can be used with scaled fonts of any type, but some
/// font backends also provide type-specific functions that must only be called with a
/// scaled font of the appropriate type. These functions have names that begin with
/// `Context::type_scaled_font()` such as `Context::ft_scaled_font_lock_face()`.
///
/// The behavior of calling a type-specific function with a scaled font of the wrong type
/// is undefined.
///
/// New entries may be added in future versions.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum FontType {
    /// The font was created using cairo's toy font api (Since: 1.2)
    FontTypeToy,
    /// The font is of type FreeType (Since: 1.2)
    FontTypeFt,
    /// The font is of type Win32 (Since: 1.2)
    FontTypeWin32,
    /// The font is of type Quartz (Since: 1.6, in 1.2 and 1.4 it was named
    /// CAIRO_FONT_TYPE_ATSUI)
    FontTypeQuartz,
    /// The font was create using cairo's user font api (Since: 1.8)
    FontTypeUser
}

/// The subpixel order specifies the order of color elements within each pixel on the display
/// device when rendering with an antialiasing mode of `Antialias::Subpixel`.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum SubpixelOrder {
    /// Use the default subpixel order for for the target device, since 1.0
    Default,
    /// Subpixel elements are arranged horizontally with red at the left, since 1.0
    Rgb,
    /// Subpixel elements are arranged horizontally with blue at the left, since 1.0
    Bgr,
    /// Subpixel elements are arranged vertically with red at the top, since 1.0
    Vrgb,
    /// Subpixel elements are arranged vertically with blue at the top, since 1.0
    Vbgr
}

/// Specifies the type of hinting to do on font outlines. Hinting is the process of fitting
/// outlines to the pixel grid in order to improve the appearance of the result. Since hinting
/// outlines involves distorting them, it also reduces the faithfulness to the original outline
/// shapes. Not all of the outline hinting styles are supported by all font backends.
///
/// New entries may be added in future versions.
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintStyle {
    /// Use the default hint style for font backend and target device, since 1.0
    Default,
    /// Do not hint outlines, since 1.0
    None,
    /// Hint outlines slightly to improve contrast while retaining good fidelity to the
    /// original shapes, since 1.0
    Slight,
    /// Hint outlines with medium strength giving a compromise between fidelity to the
    /// original shapes and contrast, since 1.0
    Medium,
    /// Hint outlines to maximize contrast, since 1.0
    Full
}

/// Specifies whether to hint font metrics; hinting font metrics means quantizing them so
/// that they are integer values in device space. Doing this improves the consistency of
/// letter and line spacing, however it also means that text will be laid out differently
/// at different zoom factors
#[repr(C)]
#[derive(Clone, PartialEq, PartialOrd, Debug, Copy)]
pub enum HintMetrics {
    /// Hint metrics in the default manner for the font backend and target device, since 1.0
    Default,
    /// Do not hint font metrics, since 1.0
    Off,
    /// Hint font metrics, since 1.0
    On
}
<|endoftext|>


<commit_before>mod component;
mod selection;
mod state;
mod widget_util;

use self::{
    selection::Selection,
    state::{InputState, Reactive, ReactiveState, UIEvents, UIState},
};
use crate::key::Key;
use crate::Args;
use crate::{file::SerializedFile, remote::RemoteLogin, try_opt_r, user::Users};
use anime::remote::ScoreParser;
use anyhow::{anyhow, Context, Result};
use component::prompt::command::Command;
use component::prompt::command::InputResult;
use component::prompt::COMMAND_KEY;
use component::series_list::SeriesList;
use component::Component;
use component::{main_panel::MainPanel, prompt::command::CommandPrompt};
use crossterm::{event::KeyCode, terminal};
use state::{SharedState, UIErrorKind, UIEvent};
use std::{io, sync::Arc};
use tokio::sync::Notify;
use tui::{
    backend::CrosstermBackend,
    layout::{Constraint, Direction, Layout},
    Terminal,
};

pub async fn run(args: &Args) -> Result<()> {
    let terminal = init_terminal().context("failed to init backend")?;

    let mut ui = UI::init(&args, terminal)
        .await
        .context("failed to init UI")?;

    let result = ui.run().await;
    ui.exit()?;

    result
}

fn init_terminal() -> Result<CrosstermTerminal> {
    terminal::enable_raw_mode().context("failed to enable raw mode")?;

    let stdout = io::stdout();
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend).context("terminal creation failed")?;

    terminal.clear().context("failed to clear terminal")?;

    terminal
        .hide_cursor()
        .context("failed to hide mouse cursor")?;

    Ok(terminal)
}

type CrosstermTerminal = Terminal<CrosstermBackend<io::Stdout>>;

struct UI {
    events: UIEvents,
    terminal: CrosstermTerminal,
    state: SharedState,
    dirty_state_notify: Arc<Notify>,
    panels: Panels,
}

impl UI {
    async fn init(args: &Args, terminal: CrosstermTerminal) -> Result<UI> {
        let events = UIEvents::new().context("UI events init")?;

        let mut state = UIState::init().context("UI state init")?;

        state
            .select_initial_series(args)
            .context("selecting initial series")?;

        let dirty_state_notify = Arc::new(Notify::const_new());
        let shared_state = SharedState::new(Reactive::new(state, Arc::clone(&dirty_state_notify)));

        let panels = Panels::init(&shared_state);

        if !args.offline {
            if let Some((user, token)) = Users::load_or_create()?.take_last_used_user() {
                shared_state.login_to_remote_async(RemoteLogin::AniList(user.username, token));
            }
        }

        Ok(Self {
            events,
            terminal,
            state: shared_state,
            dirty_state_notify,
            panels,
        })
    }

    async fn run(&mut self) -> Result<()> {
        {
            let mut state = self.state.lock();

            if let Err(err) = self.panels.draw(state.get_mut(), &mut self.terminal) {
                return Err(err);
            }
        }

        loop {
            match self.next_cycle().await {
                CycleResult::Ok => (),
                CycleResult::Exit => break Ok(()),
                CycleResult::Error(err) => return Err(err),
            }
        }
    }

    async fn next_cycle(&mut self) -> CycleResult {
        let event = match self.events.next(&self.dirty_state_notify).await {
            Ok(Some(event)) => event,
            Ok(None) => return CycleResult::Ok,
            Err(UIErrorKind::ExitRequest) => return CycleResult::Exit,
            Err(UIErrorKind::Other(err)) => return CycleResult::Error(err),
        };

        let mut state = self.state.lock();

        let result = match event {
            UIEvent::Key(key) => self.panels.process_key(key, &mut state).await,
            UIEvent::StateChange | UIEvent::Resize => CycleResult::Ok,
        };

        if let Err(err) = self.panels.draw(state.get_mut(), &mut self.terminal) {
            return CycleResult::Error(err);
        }

        result
    }

    pub fn exit(mut self) -> Result<()> {
        self.terminal.clear().ok();
        terminal::disable_raw_mode().map_err(Into::into)
    }
}

pub enum CycleResult {
    Ok,
    Exit,
    Error(anyhow::Error),
}

struct Panels {
    command_prompt: CommandPrompt,
    main_panel: MainPanel,
    state: SharedState,
}

impl Panels {
    fn init(state: &SharedState) -> Self {
        Self {
            command_prompt: CommandPrompt::new(),
            main_panel: MainPanel::new(state.clone()),
            state: state.clone(),
        }
    }

    async fn process_key(&mut self, key: Key, state: &mut ReactiveState) -> CycleResult {
        macro_rules! capture {
            ($result:expr) => {
                match $result {
                    Ok(value) => value,
                    Err(err) => {
                        state.get_mut().log.push_error(&err);
                        return CycleResult::Ok;
                    }
                }
            };
        }

        macro_rules! process_key {
            ($component:ident) => {
                capture!(self.$component.process_key(key, state.get_mut()))
            };
        }

        match state.input_state {
            InputState::Idle => match *key {
                KeyCode::Char('q') => return CycleResult::Exit,
                _ if key == state.config.tui.keys.play_next_episode => {
                    capture!(state.get_mut().play_next_series_episode(&self.state).await)
                }
                KeyCode::Char('a') => {
                    capture!(self.main_panel.switch_to_add_series(state.get_mut()))
                }
                KeyCode::Char('e') => {
                    capture!(self.main_panel.switch_to_update_series(state.get_mut()))
                }
                KeyCode::Char('D') => {
                    capture!(self.main_panel.switch_to_delete_series(state.get_mut()))
                }
                KeyCode::Char('u') => self.main_panel.switch_to_user_panel(state.get_mut()),
                KeyCode::Char('s') => {
                    capture!(self.main_panel.switch_to_split_series(state.get_mut()))
                }
                KeyCode::Char(COMMAND_KEY) => {
                    state.get_mut().input_state = InputState::EnteringCommand
                }
                _ => SeriesList::process_key(key, state.get_mut()),
            },
            InputState::Locked => (),
            InputState::FocusedOnMainPanel => process_key!(main_panel),
            InputState::EnteringCommand => {
                let state = state.get_mut();
                let result = self.command_prompt.process_key(key, state);

                if !matches!(result, Ok(InputResult::Continue)) {
                    self.command_prompt.reset();
                    state.input_state.reset();
                }

                match capture!(result) {
                    InputResult::Command(cmd) => {
                        capture!(Self::process_command(cmd, state))
                    }
                    InputResult::Done | InputResult::Continue => (),
                }
            }
        }

        CycleResult::Ok
    }

    fn draw(&mut self, state: &UIState, terminal: &mut CrosstermTerminal) -> Result<()> {
        terminal
            .draw(|mut frame| {
                let horiz_splitter = Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints([Constraint::Min(20), Constraint::Percentage(70)].as_ref())
                    .split(frame.size());

                SeriesList::draw(state, horiz_splitter[0], &mut frame);

                // Series info panel vertical splitter
                let info_panel_splitter = Layout::default()
                    .direction(Direction::Vertical)
                    .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
                    .split(horiz_splitter[1]);

                self.main_panel
                    .draw(state, info_panel_splitter[0], &mut frame);

                match state.input_state {
                    InputState::EnteringCommand => {
                        self.command_prompt.draw(info_panel_splitter[1], frame)
                    }
                    _ => state.log.draw(info_panel_splitter[1], frame),
                }
            })
            .map_err(Into::into)
    }

    fn process_command(command: Command, state: &mut UIState) -> Result<()> {
        let remote = &mut state.remote;
        let config = &state.config;
        let db = &state.db;

        match command {
            Command::PlayerArgs(args) => {
                let series = try_opt_r!(state.series.valid_selection_mut());

                series.data.config.player_args = args.into();
                series.save(db)?;
                Ok(())
            }
            Command::Progress(direction) => {
                use component::prompt::command::ProgressDirection;

                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                match direction {
                    ProgressDirection::Forwards => series.episode_completed(remote, config, db),
                    ProgressDirection::Backwards => series.episode_regressed(remote, config, db),
                }
            }
            cmd @ Command::SyncFromRemote | cmd @ Command::SyncToRemote => {
                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                match cmd {
                    Command::SyncFromRemote => series.data.force_sync_from_remote(remote)?,
                    Command::SyncToRemote => series.data.entry.force_sync_to_remote(remote)?,
                    _ => unreachable!(),
                }

                series.save(db)?;
                Ok(())
            }
            Command::Score(raw_score) => {
                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                let score = match remote.parse_score(&raw_score) {
                    Some(score) if score == 0 => None,
                    Some(score) => Some(score),
                    None => return Err(anyhow!("invalid score")),
                };

                series.data.entry.set_score(score.map(i16::from));
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

                Ok(())
            }
            Command::Status(status) => {
                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                series.data.entry.set_status(status, config);
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

                Ok(())
            }
        }
    }
}
<commit_msg>anup: Only call Reactive::get_mut() once when processing update cycles<commit_after>mod component;
mod selection;
mod state;
mod widget_util;

use self::{
    selection::Selection,
    state::{InputState, Reactive, UIEvents, UIState},
};
use crate::key::Key;
use crate::Args;
use crate::{file::SerializedFile, remote::RemoteLogin, try_opt_r, user::Users};
use anime::remote::ScoreParser;
use anyhow::{anyhow, Context, Result};
use component::prompt::command::Command;
use component::prompt::command::InputResult;
use component::prompt::COMMAND_KEY;
use component::series_list::SeriesList;
use component::Component;
use component::{main_panel::MainPanel, prompt::command::CommandPrompt};
use crossterm::{event::KeyCode, terminal};
use state::{SharedState, UIErrorKind, UIEvent};
use std::{io, sync::Arc};
use tokio::sync::Notify;
use tui::{
    backend::CrosstermBackend,
    layout::{Constraint, Direction, Layout},
    Terminal,
};

pub async fn run(args: &Args) -> Result<()> {
    let terminal = init_terminal().context("failed to init backend")?;

    let mut ui = UI::init(&args, terminal)
        .await
        .context("failed to init UI")?;

    let result = ui.run().await;
    ui.exit()?;

    result
}

fn init_terminal() -> Result<CrosstermTerminal> {
    terminal::enable_raw_mode().context("failed to enable raw mode")?;

    let stdout = io::stdout();
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend).context("terminal creation failed")?;

    terminal.clear().context("failed to clear terminal")?;

    terminal
        .hide_cursor()
        .context("failed to hide mouse cursor")?;

    Ok(terminal)
}

type CrosstermTerminal = Terminal<CrosstermBackend<io::Stdout>>;

struct UI {
    events: UIEvents,
    terminal: CrosstermTerminal,
    state: SharedState,
    dirty_state_notify: Arc<Notify>,
    panels: Panels,
}

impl UI {
    async fn init(args: &Args, terminal: CrosstermTerminal) -> Result<UI> {
        let events = UIEvents::new().context("UI events init")?;

        let mut state = UIState::init().context("UI state init")?;

        state
            .select_initial_series(args)
            .context("selecting initial series")?;

        let dirty_state_notify = Arc::new(Notify::const_new());
        let shared_state = SharedState::new(Reactive::new(state, Arc::clone(&dirty_state_notify)));

        let panels = Panels::init(&shared_state);

        if !args.offline {
            if let Some((user, token)) = Users::load_or_create()?.take_last_used_user() {
                shared_state.login_to_remote_async(RemoteLogin::AniList(user.username, token));
            }
        }

        Ok(Self {
            events,
            terminal,
            state: shared_state,
            dirty_state_notify,
            panels,
        })
    }

    async fn run(&mut self) -> Result<()> {
        {
            let mut state = self.state.lock();

            if let Err(err) = self.panels.draw(state.get_mut(), &mut self.terminal) {
                return Err(err);
            }
        }

        loop {
            match self.next_cycle().await {
                CycleResult::Ok => (),
                CycleResult::Exit => break Ok(()),
                CycleResult::Error(err) => return Err(err),
            }
        }
    }

    async fn next_cycle(&mut self) -> CycleResult {
        let event = match self.events.next(&self.dirty_state_notify).await {
            Ok(Some(event)) => event,
            Ok(None) => return CycleResult::Ok,
            Err(UIErrorKind::ExitRequest) => return CycleResult::Exit,
            Err(UIErrorKind::Other(err)) => return CycleResult::Error(err),
        };

        let mut state = self.state.lock();
        let state = state.get_mut();

        let result = match event {
            UIEvent::Key(key) => self.panels.process_key(key, state).await,
            UIEvent::StateChange | UIEvent::Resize => CycleResult::Ok,
        };

        if let Err(err) = self.panels.draw(state, &mut self.terminal) {
            return CycleResult::Error(err);
        }

        result
    }

    pub fn exit(mut self) -> Result<()> {
        self.terminal.clear().ok();
        terminal::disable_raw_mode().map_err(Into::into)
    }
}

pub enum CycleResult {
    Ok,
    Exit,
    Error(anyhow::Error),
}

struct Panels {
    command_prompt: CommandPrompt,
    main_panel: MainPanel,
    state: SharedState,
}

impl Panels {
    fn init(state: &SharedState) -> Self {
        Self {
            command_prompt: CommandPrompt::new(),
            main_panel: MainPanel::new(state.clone()),
            state: state.clone(),
        }
    }

    async fn process_key(&mut self, key: Key, state: &mut UIState) -> CycleResult {
        macro_rules! capture {
            ($result:expr) => {
                match $result {
                    Ok(value) => value,
                    Err(err) => {
                        state.log.push_error(&err);
                        return CycleResult::Ok;
                    }
                }
            };
        }

        macro_rules! process_key {
            ($component:ident) => {
                capture!(self.$component.process_key(key, state))
            };
        }

        match state.input_state {
            InputState::Idle => match *key {
                KeyCode::Char('q') => return CycleResult::Exit,
                _ if key == state.config.tui.keys.play_next_episode => {
                    capture!(state.play_next_series_episode(&self.state).await)
                }
                KeyCode::Char('a') => {
                    capture!(self.main_panel.switch_to_add_series(state))
                }
                KeyCode::Char('e') => {
                    capture!(self.main_panel.switch_to_update_series(state))
                }
                KeyCode::Char('D') => {
                    capture!(self.main_panel.switch_to_delete_series(state))
                }
                KeyCode::Char('u') => self.main_panel.switch_to_user_panel(state),
                KeyCode::Char('s') => {
                    capture!(self.main_panel.switch_to_split_series(state))
                }
                KeyCode::Char(COMMAND_KEY) => state.input_state = InputState::EnteringCommand,
                _ => SeriesList::process_key(key, state),
            },
            InputState::Locked => (),
            InputState::FocusedOnMainPanel => process_key!(main_panel),
            InputState::EnteringCommand => {
                let result = self.command_prompt.process_key(key, state);

                if !matches!(result, Ok(InputResult::Continue)) {
                    self.command_prompt.reset();
                    state.input_state.reset();
                }

                match capture!(result) {
                    InputResult::Command(cmd) => {
                        capture!(Self::process_command(cmd, state))
                    }
                    InputResult::Done | InputResult::Continue => (),
                }
            }
        }

        CycleResult::Ok
    }

    fn draw(&mut self, state: &UIState, terminal: &mut CrosstermTerminal) -> Result<()> {
        terminal
            .draw(|mut frame| {
                let horiz_splitter = Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints([Constraint::Min(20), Constraint::Percentage(70)].as_ref())
                    .split(frame.size());

                SeriesList::draw(state, horiz_splitter[0], &mut frame);

                // Series info panel vertical splitter
                let info_panel_splitter = Layout::default()
                    .direction(Direction::Vertical)
                    .constraints([Constraint::Percentage(80), Constraint::Percentage(20)].as_ref())
                    .split(horiz_splitter[1]);

                self.main_panel
                    .draw(state, info_panel_splitter[0], &mut frame);

                match state.input_state {
                    InputState::EnteringCommand => {
                        self.command_prompt.draw(info_panel_splitter[1], frame)
                    }
                    _ => state.log.draw(info_panel_splitter[1], frame),
                }
            })
            .map_err(Into::into)
    }

    fn process_command(command: Command, state: &mut UIState) -> Result<()> {
        let remote = &mut state.remote;
        let config = &state.config;
        let db = &state.db;

        match command {
            Command::PlayerArgs(args) => {
                let series = try_opt_r!(state.series.valid_selection_mut());

                series.data.config.player_args = args.into();
                series.save(db)?;
                Ok(())
            }
            Command::Progress(direction) => {
                use component::prompt::command::ProgressDirection;

                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                match direction {
                    ProgressDirection::Forwards => series.episode_completed(remote, config, db),
                    ProgressDirection::Backwards => series.episode_regressed(remote, config, db),
                }
            }
            cmd @ Command::SyncFromRemote | cmd @ Command::SyncToRemote => {
                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                match cmd {
                    Command::SyncFromRemote => series.data.force_sync_from_remote(remote)?,
                    Command::SyncToRemote => series.data.entry.force_sync_to_remote(remote)?,
                    _ => unreachable!(),
                }

                series.save(db)?;
                Ok(())
            }
            Command::Score(raw_score) => {
                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                let score = match remote.parse_score(&raw_score) {
                    Some(score) if score == 0 => None,
                    Some(score) => Some(score),
                    None => return Err(anyhow!("invalid score")),
                };

                series.data.entry.set_score(score.map(i16::from));
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

                Ok(())
            }
            Command::Status(status) => {
                let series = try_opt_r!(state.series.valid_selection_mut());
                let remote = remote.get_logged_in()?;

                series.data.entry.set_status(status, config);
                series.data.entry.sync_to_remote(remote)?;
                series.save(db)?;

                Ok(())
            }
        }
    }
}
<|endoftext|>


<commit_before><commit_msg>Move range exhaustiveness check to IntRange::intersection<commit_after><|endoftext|>


<commit_before>use crate::utils::{match_def_path, paths, span_lint_and_note};
use rustc_hir::def_id::DefId;
use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GeneratorInteriorTypeCause;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;

declare_clippy_lint! {
    /// **What it does:** Checks for calls to await while holding a
    /// non-async-aware MutexGuard.
    ///
    /// **Why is this bad?** The Mutex types found in std::sync and parking_lot
    /// are not designed to operate in an async context across await points.
    ///
    /// There are two potential solutions. One is to use an asynx-aware Mutex
    /// type. Many asynchronous foundation crates provide such a Mutex type. The
    /// other solution is to ensure the mutex is unlocked before calling await,
    /// either by introducing a scope or an explicit call to Drop::drop.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// use std::sync::Mutex;
    ///
    /// async fn foo(x: &Mutex<u32>) {
    ///   let guard = x.lock().unwrap();
    ///   *guard += 1;
    ///   bar.await;
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust,ignore
    /// use std::sync::Mutex;
    ///
    /// async fn foo(x: &Mutex<u32>) {
    ///   {
    ///     let guard = x.lock().unwrap();
    ///     *guard += 1;
    ///   }
    ///   bar.await;
    /// }
    /// ```
    pub AWAIT_HOLDING_LOCK,
    pedantic,
    "Inside an async function, holding a MutexGuard while calling await"
}

declare_clippy_lint! {
    /// **What it does:** Checks for calls to await while holding a
    /// `RefCell` `Ref` or `RefMut`.
    ///
    /// **Why is this bad?** `RefCell` refs only check for exclusive mutable access
    /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
    /// risks panics from a mutable ref shared while other refs are outstanding.
    ///
    /// **Known problems:** None.
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// use std::cell::RefCell;
    ///
    /// async fn foo(x: &RefCell<u32>) {
    ///   let mut y = x.borrow_mut();
    ///   *y += 1;
    ///   bar.await;
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust,ignore
    /// use std::cell::RefCell;
    ///
    /// async fn foo(x: &RefCell<u32>) {
    ///   {
    ///      let mut y = x.borrow_mut();
    ///      *y += 1;
    ///   }
    ///   bar.await;
    /// }
    /// ```
    pub AWAIT_HOLDING_REFCELL_REF,
    pedantic,
    "Inside an async function, holding a RefCell ref while calling await"
}

declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);

impl LateLintPass<'_> for AwaitHolding {
    fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
        use AsyncGeneratorKind::{Block, Closure, Fn};
        if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
            let body_id = BodyId {
                hir_id: body.value.hir_id,
            };
            let def_id = cx.tcx.hir().body_owner_def_id(body_id);
            let typeck_results = cx.tcx.typeck(def_id);
            check_interior_types(
                cx,
                &typeck_results.generator_interior_types.as_ref().skip_binder(),
                body.value.span,
            );
        }
    }
}

fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
    for ty_cause in ty_causes {
        if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
            if is_mutex_guard(cx, adt.did) {
                span_lint_and_note(
                    cx,
                    AWAIT_HOLDING_LOCK,
                    ty_cause.span,
                    "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.",
                    ty_cause.scope_span.or(Some(span)),
                    "these are all the await points this lock is held through",
                );
            }
            if is_refcell_ref(cx, adt.did) {
                span_lint_and_note(
                        cx,
                        AWAIT_HOLDING_REFCELL_REF,
                        ty_cause.span,
                        "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await.",
                        ty_cause.scope_span.or(Some(span)),
                        "these are all the await points this ref is held through",
                    );
            }
        }
    }
}

fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
    match_def_path(cx, def_id, &paths::MUTEX_GUARD)
        || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
        || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
        || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
        || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
        || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
}

fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
    match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
}
<commit_msg>Explicitly document false positives<commit_after>use crate::utils::{match_def_path, paths, span_lint_and_note};
use rustc_hir::def_id::DefId;
use rustc_hir::{AsyncGeneratorKind, Body, BodyId, GeneratorKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::GeneratorInteriorTypeCause;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;

declare_clippy_lint! {
    /// **What it does:** Checks for calls to await while holding a
    /// non-async-aware MutexGuard.
    ///
    /// **Why is this bad?** The Mutex types found in std::sync and parking_lot
    /// are not designed to operate in an async context across await points.
    ///
    /// There are two potential solutions. One is to use an asynx-aware Mutex
    /// type. Many asynchronous foundation crates provide such a Mutex type. The
    /// other solution is to ensure the mutex is unlocked before calling await,
    /// either by introducing a scope or an explicit call to Drop::drop.
    ///
    /// **Known problems:** Will report false positive for explicitly dropped guards ([#6446](https://github.com/rust-lang/rust-clippy/issues/6446)).
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// use std::sync::Mutex;
    ///
    /// async fn foo(x: &Mutex<u32>) {
    ///   let guard = x.lock().unwrap();
    ///   *guard += 1;
    ///   bar.await;
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust,ignore
    /// use std::sync::Mutex;
    ///
    /// async fn foo(x: &Mutex<u32>) {
    ///   {
    ///     let guard = x.lock().unwrap();
    ///     *guard += 1;
    ///   }
    ///   bar.await;
    /// }
    /// ```
    pub AWAIT_HOLDING_LOCK,
    pedantic,
    "Inside an async function, holding a MutexGuard while calling await"
}

declare_clippy_lint! {
    /// **What it does:** Checks for calls to await while holding a
    /// `RefCell` `Ref` or `RefMut`.
    ///
    /// **Why is this bad?** `RefCell` refs only check for exclusive mutable access
    /// at runtime. Holding onto a `RefCell` ref across an `await` suspension point
    /// risks panics from a mutable ref shared while other refs are outstanding.
    ///
    /// **Known problems:** Will report false positive for explicitly dropped refs ([#6353](https://github.com/rust-lang/rust-clippy/issues/6353)).
    ///
    /// **Example:**
    ///
    /// ```rust,ignore
    /// use std::cell::RefCell;
    ///
    /// async fn foo(x: &RefCell<u32>) {
    ///   let mut y = x.borrow_mut();
    ///   *y += 1;
    ///   bar.await;
    /// }
    /// ```
    ///
    /// Use instead:
    /// ```rust,ignore
    /// use std::cell::RefCell;
    ///
    /// async fn foo(x: &RefCell<u32>) {
    ///   {
    ///      let mut y = x.borrow_mut();
    ///      *y += 1;
    ///   }
    ///   bar.await;
    /// }
    /// ```
    pub AWAIT_HOLDING_REFCELL_REF,
    pedantic,
    "Inside an async function, holding a RefCell ref while calling await"
}

declare_lint_pass!(AwaitHolding => [AWAIT_HOLDING_LOCK, AWAIT_HOLDING_REFCELL_REF]);

impl LateLintPass<'_> for AwaitHolding {
    fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) {
        use AsyncGeneratorKind::{Block, Closure, Fn};
        if let Some(GeneratorKind::Async(Block | Closure | Fn)) = body.generator_kind {
            let body_id = BodyId {
                hir_id: body.value.hir_id,
            };
            let def_id = cx.tcx.hir().body_owner_def_id(body_id);
            let typeck_results = cx.tcx.typeck(def_id);
            check_interior_types(
                cx,
                &typeck_results.generator_interior_types.as_ref().skip_binder(),
                body.value.span,
            );
        }
    }
}

fn check_interior_types(cx: &LateContext<'_>, ty_causes: &[GeneratorInteriorTypeCause<'_>], span: Span) {
    for ty_cause in ty_causes {
        if let rustc_middle::ty::Adt(adt, _) = ty_cause.ty.kind() {
            if is_mutex_guard(cx, adt.did) {
                span_lint_and_note(
                    cx,
                    AWAIT_HOLDING_LOCK,
                    ty_cause.span,
                    "this MutexGuard is held across an 'await' point. Consider using an async-aware Mutex type or ensuring the MutexGuard is dropped before calling await.",
                    ty_cause.scope_span.or(Some(span)),
                    "these are all the await points this lock is held through",
                );
            }
            if is_refcell_ref(cx, adt.did) {
                span_lint_and_note(
                        cx,
                        AWAIT_HOLDING_REFCELL_REF,
                        ty_cause.span,
                        "this RefCell Ref is held across an 'await' point. Consider ensuring the Ref is dropped before calling await.",
                        ty_cause.scope_span.or(Some(span)),
                        "these are all the await points this ref is held through",
                    );
            }
        }
    }
}

fn is_mutex_guard(cx: &LateContext<'_>, def_id: DefId) -> bool {
    match_def_path(cx, def_id, &paths::MUTEX_GUARD)
        || match_def_path(cx, def_id, &paths::RWLOCK_READ_GUARD)
        || match_def_path(cx, def_id, &paths::RWLOCK_WRITE_GUARD)
        || match_def_path(cx, def_id, &paths::PARKING_LOT_MUTEX_GUARD)
        || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_READ_GUARD)
        || match_def_path(cx, def_id, &paths::PARKING_LOT_RWLOCK_WRITE_GUARD)
}

fn is_refcell_ref(cx: &LateContext<'_>, def_id: DefId) -> bool {
    match_def_path(cx, def_id, &paths::REFCELL_REF) || match_def_path(cx, def_id, &paths::REFCELL_REFMUT)
}
<|endoftext|>