<commit_before>//! Hosts result from a configuration of the system hosts file

use std::collections::HashMap;
use std::io;
use std::path::Path;
#[cfg(any(unix, windows))]
use std::str::FromStr;
use std::sync::Arc;

use dns_lru;
use lookup::Lookup;
use proto::op::Query;
use proto::rr::{Name, RecordType};
#[cfg(any(unix, windows))]
use proto::rr::{RData, Record};

#[derive(Debug, Default)]
struct LookupType {
    /// represents the A record type
    a: Option<Lookup>,
    /// represents the AAAA record type
    aaaa: Option<Lookup>,
}

/// Configuration for the local hosts file
#[derive(Debug, Default)]
pub struct Hosts {
    /// Name -> RDatas map
    by_name: HashMap<Name, LookupType>,
}

impl Hosts {
    /// Creates a new configuration from the system hosts file,
    /// only works for Windows and Unix-like OSes,
    /// will return empty configuration on others
    pub fn new() -> Hosts {
        read_hosts_conf(hosts_path()).unwrap_or_default()
    }

    /// lookup_static_host looks up the addresses
    /// for the given host from the system hosts file.
    pub fn lookup_static_host(&self, query: &Query) -> Option<Lookup> {
        if !self.by_name.is_empty() {
            if let Some(val) = self.by_name.get(query.name()) {
                let result = match query.query_type() {
                    RecordType::A => val.a.clone(),
                    RecordType::AAAA => val.aaaa.clone(),
                    _ => None,
                };

                return result;
            }
        }
        None
    }

    /// Insert a new Lookup for the associated `Name` and `RecordType`
    pub fn insert(&mut self, name: Name, record_type: RecordType, lookup: Lookup) {
        assert!(record_type == RecordType::A || record_type == RecordType::AAAA);

        let lookup_type = self
            .by_name
            .entry(name.clone())
            .or_insert_with(LookupType::default);

        let new_lookup = {
            let mut old_lookup = match record_type {
                RecordType::A => lookup_type.a.get_or_insert_with(|| {
                    let query = Query::query(name.clone(), record_type);
                    Lookup::new_with_max_ttl(query, Arc::new(vec![]))
                }),
                RecordType::AAAA => lookup_type.aaaa.get_or_insert_with(|| {
                    let query = Query::query(name.clone(), record_type);
                    Lookup::new_with_max_ttl(query, Arc::new(vec![]))
                }),
                _ => {
                    warn!("unsupported IP type from Hosts file: {:#?}", record_type);
                    return;
                }
            };

            old_lookup.append(lookup)
        };

        // replace the appended version
        match record_type {
            RecordType::A => lookup_type.a = Some(new_lookup),
            RecordType::AAAA => lookup_type.aaaa = Some(new_lookup),
            _ => warn!("unsupported IP type from Hosts file"),
        }
    }
}

#[cfg(unix)]
fn hosts_path() -> &'static str {
    "/etc/hosts"
}

#[cfg(windows)]
fn hosts_path() -> std::path::PathBuf {
    let system_root =
        std::env::var_os("SystemRoot").expect("Environtment variable SystemRoot not found");
    let system_root = Path::new(&system_root);
    system_root.join("System32\\drivers\\etc\\hosts")
}

/// parse configuration from `path`
#[cfg(any(unix, windows))]
pub fn read_hosts_conf<P: AsRef<Path>>(path: P) -> io::Result<Hosts> {
    use std::fs::File;
    use std::io::{BufRead, BufReader};

    use proto::rr::domain::TryParseIp;

    let mut hosts = Hosts {
        by_name: HashMap::new(),
    };

    // lines in the file should have the form `addr host1 host2 host3 ...`
    // line starts with `#` will be regarded with comments and ignored,
    // also empty line also will be ignored,
    // if line only include `addr` without `host` will be ignored,
    // file will parsed to map in the form `Name -> LookUp`.
    let file = File::open(path)?;

    for line in BufReader::new(file).lines() {
        // Remove comments from the line
        let line = line
            .as_ref()
            .map(|line| line.split('#').next().unwrap().trim())
            .unwrap_or_default();
        if line.is_empty() {
            continue;
        }

        let fields: Vec<_> = line.split_whitespace().collect();
        if fields.len() < 2 {
            continue;
        }
        let addr = if let Some(a) = fields[0].try_parse_ip() {
            a
        } else {
            warn!("could not parse an IP from hosts file");
            continue;
        };

        for domain in fields.iter().skip(1).map(|domain| domain.to_lowercase()) {
            if let Ok(name) = Name::from_str(&domain) {
                let record = Record::from_rdata(name.clone(), dns_lru::MAX_TTL, addr.clone());

                match addr {
                    RData::A(..) => {
                        let query = Query::query(name.clone(), RecordType::A);
                        let lookup = Lookup::new_with_max_ttl(query, Arc::new(vec![record]));
                        hosts.insert(name.clone(), RecordType::A, lookup);
                    }
                    RData::AAAA(..) => {
                        let query = Query::query(name.clone(), RecordType::AAAA);
                        let lookup = Lookup::new_with_max_ttl(query, Arc::new(vec![record]));
                        hosts.insert(name.clone(), RecordType::AAAA, lookup);
                    }
                    _ => {
                        warn!("unsupported IP type from Hosts file: {:#?}", addr);
                        continue;
                    }
                };

                // TODO: insert reverse lookup as well.
            };
        }
    }

    Ok(hosts)
}

#[cfg(not(any(unix, windows)))]
pub fn read_hosts_conf<P: AsRef<Path>>(_path: P) -> io::Result<Hosts> {
    Err(io::Error::new(
        io::ErrorKind::Other,
        "Only Windows or Unix-like hosts file is supported".to_string(),
    ))
}

#[cfg(any(unix, windows))]
#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use std::net::{Ipv4Addr, Ipv6Addr};

    fn tests_dir() -> String {
        let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or_else(|_| ".".to_owned());
        format! {"{}/../resolver/tests", server_path}
    }

    #[test]
    fn test_read_hosts_conf() {
        let path = format!("{}/hosts", tests_dir());
        let hosts = read_hosts_conf(&path).unwrap();

        let name = Name::from_str("localhost").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();

        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))]);

        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::AAAA))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();

        assert_eq!(
            rdatas,
            vec![RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
        );

        let name = Name::from_str("broadcasthost").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(255, 255, 255, 255))]);

        let name = Name::from_str("example.com").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 102))]);

        let name = Name::from_str("a.example.com").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 111))]);

        let name = Name::from_str("b.example.com").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 111))]);
    }
}
<commit_msg>Minor rewording of doc comment.<commit_after>//! Hosts result from a configuration of the system hosts file

use std::collections::HashMap;
use std::io;
use std::path::Path;
#[cfg(any(unix, windows))]
use std::str::FromStr;
use std::sync::Arc;

use dns_lru;
use lookup::Lookup;
use proto::op::Query;
use proto::rr::{Name, RecordType};
#[cfg(any(unix, windows))]
use proto::rr::{RData, Record};

#[derive(Debug, Default)]
struct LookupType {
    /// represents the A record type
    a: Option<Lookup>,
    /// represents the AAAA record type
    aaaa: Option<Lookup>,
}

/// Configuration for the local hosts file
#[derive(Debug, Default)]
pub struct Hosts {
    /// Name -> RDatas map
    by_name: HashMap<Name, LookupType>,
}

impl Hosts {
    /// Creates a new configuration from the system hosts file,
    /// only works for Windows and Unix-like OSes,
    /// will return empty configuration on others
    pub fn new() -> Hosts {
        read_hosts_conf(hosts_path()).unwrap_or_default()
    }

    /// Look up the addresses for the given host from the system hosts file.
    pub fn lookup_static_host(&self, query: &Query) -> Option<Lookup> {
        if !self.by_name.is_empty() {
            if let Some(val) = self.by_name.get(query.name()) {
                let result = match query.query_type() {
                    RecordType::A => val.a.clone(),
                    RecordType::AAAA => val.aaaa.clone(),
                    _ => None,
                };

                return result;
            }
        }
        None
    }

    /// Insert a new Lookup for the associated `Name` and `RecordType`
    pub fn insert(&mut self, name: Name, record_type: RecordType, lookup: Lookup) {
        assert!(record_type == RecordType::A || record_type == RecordType::AAAA);

        let lookup_type = self
            .by_name
            .entry(name.clone())
            .or_insert_with(LookupType::default);

        let new_lookup = {
            let mut old_lookup = match record_type {
                RecordType::A => lookup_type.a.get_or_insert_with(|| {
                    let query = Query::query(name.clone(), record_type);
                    Lookup::new_with_max_ttl(query, Arc::new(vec![]))
                }),
                RecordType::AAAA => lookup_type.aaaa.get_or_insert_with(|| {
                    let query = Query::query(name.clone(), record_type);
                    Lookup::new_with_max_ttl(query, Arc::new(vec![]))
                }),
                _ => {
                    warn!("unsupported IP type from Hosts file: {:#?}", record_type);
                    return;
                }
            };

            old_lookup.append(lookup)
        };

        // replace the appended version
        match record_type {
            RecordType::A => lookup_type.a = Some(new_lookup),
            RecordType::AAAA => lookup_type.aaaa = Some(new_lookup),
            _ => warn!("unsupported IP type from Hosts file"),
        }
    }
}

#[cfg(unix)]
fn hosts_path() -> &'static str {
    "/etc/hosts"
}

#[cfg(windows)]
fn hosts_path() -> std::path::PathBuf {
    let system_root =
        std::env::var_os("SystemRoot").expect("Environtment variable SystemRoot not found");
    let system_root = Path::new(&system_root);
    system_root.join("System32\\drivers\\etc\\hosts")
}

/// parse configuration from `path`
#[cfg(any(unix, windows))]
pub fn read_hosts_conf<P: AsRef<Path>>(path: P) -> io::Result<Hosts> {
    use std::fs::File;
    use std::io::{BufRead, BufReader};

    use proto::rr::domain::TryParseIp;

    let mut hosts = Hosts {
        by_name: HashMap::new(),
    };

    // lines in the file should have the form `addr host1 host2 host3 ...`
    // line starts with `#` will be regarded with comments and ignored,
    // also empty line also will be ignored,
    // if line only include `addr` without `host` will be ignored,
    // file will parsed to map in the form `Name -> LookUp`.
    let file = File::open(path)?;

    for line in BufReader::new(file).lines() {
        // Remove comments from the line
        let line = line
            .as_ref()
            .map(|line| line.split('#').next().unwrap().trim())
            .unwrap_or_default();
        if line.is_empty() {
            continue;
        }

        let fields: Vec<_> = line.split_whitespace().collect();
        if fields.len() < 2 {
            continue;
        }
        let addr = if let Some(a) = fields[0].try_parse_ip() {
            a
        } else {
            warn!("could not parse an IP from hosts file");
            continue;
        };

        for domain in fields.iter().skip(1).map(|domain| domain.to_lowercase()) {
            if let Ok(name) = Name::from_str(&domain) {
                let record = Record::from_rdata(name.clone(), dns_lru::MAX_TTL, addr.clone());

                match addr {
                    RData::A(..) => {
                        let query = Query::query(name.clone(), RecordType::A);
                        let lookup = Lookup::new_with_max_ttl(query, Arc::new(vec![record]));
                        hosts.insert(name.clone(), RecordType::A, lookup);
                    }
                    RData::AAAA(..) => {
                        let query = Query::query(name.clone(), RecordType::AAAA);
                        let lookup = Lookup::new_with_max_ttl(query, Arc::new(vec![record]));
                        hosts.insert(name.clone(), RecordType::AAAA, lookup);
                    }
                    _ => {
                        warn!("unsupported IP type from Hosts file: {:#?}", addr);
                        continue;
                    }
                };

                // TODO: insert reverse lookup as well.
            };
        }
    }

    Ok(hosts)
}

#[cfg(not(any(unix, windows)))]
pub fn read_hosts_conf<P: AsRef<Path>>(_path: P) -> io::Result<Hosts> {
    Err(io::Error::new(
        io::ErrorKind::Other,
        "Only Windows or Unix-like hosts file is supported".to_string(),
    ))
}

#[cfg(any(unix, windows))]
#[cfg(test)]
mod tests {
    use super::*;
    use std::env;
    use std::net::{Ipv4Addr, Ipv6Addr};

    fn tests_dir() -> String {
        let server_path = env::var("TDNS_SERVER_SRC_ROOT").unwrap_or_else(|_| ".".to_owned());
        format! {"{}/../resolver/tests", server_path}
    }

    #[test]
    fn test_read_hosts_conf() {
        let path = format!("{}/hosts", tests_dir());
        let hosts = read_hosts_conf(&path).unwrap();

        let name = Name::from_str("localhost").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();

        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(127, 0, 0, 1))]);

        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::AAAA))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();

        assert_eq!(
            rdatas,
            vec![RData::AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))]
        );

        let name = Name::from_str("broadcasthost").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(255, 255, 255, 255))]);

        let name = Name::from_str("example.com").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 102))]);

        let name = Name::from_str("a.example.com").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 111))]);

        let name = Name::from_str("b.example.com").unwrap();
        let rdatas = hosts
            .lookup_static_host(&Query::query(name.clone(), RecordType::A))
            .unwrap()
            .iter()
            .map(ToOwned::to_owned)
            .collect::<Vec<RData>>();
        assert_eq!(rdatas, vec![RData::A(Ipv4Addr::new(10, 0, 1, 111))]);
    }
}
<|endoftext|>


<commit_before>//! Errors that can be sent from the homeserver.

use std::fmt::{self, Display, Formatter};

use ruma_api::{error::ResponseDeserializationError, EndpointError};
use ruma_identifiers::RoomVersionId;
use serde::{Deserialize, Serialize};
use serde_json::{from_slice as from_json_slice, to_vec as to_json_vec};
use strum::{AsRefStr, Display};

/// An enum for the error kind. Items may contain additional information.
#[derive(Debug, Clone, Serialize, Deserialize, AsRefStr, Display)]
#[serde(tag = "errcode")]
#[cfg_attr(test, derive(PartialEq))]
pub enum ErrorKind {
    /// M_FORBIDDEN
    #[serde(rename = "M_FORBIDDEN")]
    #[strum(to_string = "M_FORBIDDEN")]
    Forbidden,

    /// M_UNKNOWN_TOKEN
    #[serde(rename = "M_UNKNOWN_TOKEN")]
    #[strum(to_string = "M_UNKNOWN_TOKEN")]
    UnknownToken {
        /// If this is `true`, the client can acquire a new access token by specifying the device ID
        /// it is already using to the login API. For more information, see [the spec].
        ///
        /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#soft-logout
        #[serde(default, skip_serializing_if = "ruma_serde::is_default")]
        soft_logout: bool,
    },

    /// M_MISSING_TOKEN
    #[serde(rename = "M_MISSING_TOKEN")]
    #[strum(to_string = "M_MISSING_TOKEN")]
    MissingToken,

    /// M_BAD_JSON
    #[serde(rename = "M_BAD_JSON")]
    #[strum(to_string = "M_BAD_JSON")]
    BadJson,

    /// M_NOT_JSON
    #[serde(rename = "M_NOT_JSON")]
    #[strum(to_string = "M_NOT_JSON")]
    NotJson,

    /// M_NOT_FOUND
    #[serde(rename = "M_NOT_FOUND")]
    #[strum(to_string = "M_NOT_FOUND")]
    NotFound,

    /// M_LIMIT_EXCEEDED
    #[serde(rename = "M_LIMIT_EXCEEDED")]
    #[strum(to_string = "M_LIMIT_EXCEEDED")]
    LimitExceeded,

    /// M_UNKNOWN
    #[serde(rename = "M_UNKNOWN")]
    #[strum(to_string = "M_UNKNOWN")]
    Unknown,

    /// M_UNRECOGNIZED
    #[serde(rename = "M_UNRECOGNIZED")]
    #[strum(to_string = "M_UNRECOGNIZED")]
    Unrecognized,

    /// M_UNAUTHORIZED
    #[serde(rename = "M_UNAUTHORIZED")]
    #[strum(to_string = "M_UNAUTHORIZED")]
    Unauthorized,

    /// M_USER_DEACTIVATED
    #[serde(rename = "M_USER_DEACTIVATED")]
    #[strum(to_string = "M_USER_DEACTIVATED")]
    UserDeactivated,

    /// M_USER_IN_USE
    #[serde(rename = "M_USER_IN_USE")]
    #[strum(to_string = "M_USER_IN_USE")]
    UserInUse,

    /// M_INVALID_USERNAME
    #[serde(rename = "M_INVALID_USERNAME")]
    #[strum(to_string = "M_INVALID_USERNAME")]
    InvalidUsername,

    /// M_ROOM_IN_USE
    #[serde(rename = "M_ROOM_IN_USE")]
    #[strum(to_string = "M_ROOM_IN_USE")]
    RoomInUse,

    /// M_INVALID_ROOM_STATE
    #[serde(rename = "M_INVALID_ROOM_STATE")]
    #[strum(to_string = "M_INVALID_ROOM_STATE")]
    InvalidRoomState,

    /// M_THREEPID_IN_USE
    #[serde(rename = "M_THREEPID_IN_USE")]
    #[strum(to_string = "M_THREEPID_IN_USE")]
    ThreepidInUse,

    /// M_THREEPID_NOT_FOUND
    #[serde(rename = "M_THREEPID_NOT_FOUND")]
    #[strum(to_string = "M_THREEPID_NOT_FOUND")]
    ThreepidNotFound,

    /// M_THREEPID_AUTH_FAILED
    #[serde(rename = "M_THREEPID_AUTH_FAILED")]
    #[strum(to_string = "M_THREEPID_AUTH_FAILED")]
    ThreepidAuthFailed,

    /// M_THREEPID_DENIED
    #[serde(rename = "M_THREEPID_DENIED")]
    #[strum(to_string = "M_THREEPID_DENIED")]
    ThreepidDenied,

    /// M_SERVER_NOT_TRUSTED
    #[serde(rename = "M_SERVER_NOT_TRUSTED")]
    #[strum(to_string = "M_SERVER_NOT_TRUSTED")]
    ServerNotTrusted,

    /// M_UNSUPPORTED_ROOM_VERSION
    #[serde(rename = "M_UNSUPPORTED_ROOM_VERSION")]
    #[strum(to_string = "M_UNSUPPORTED_ROOM_VERSION")]
    UnsupportedRoomVersion,

    /// M_INCOMPATIBLE_ROOM_VERSION
    #[serde(rename = "M_INCOMPATIBLE_ROOM_VERSION")]
    #[strum(to_string = "M_INCOMPATIBLE_ROOM_VERSION")]
    IncompatibleRoomVersion {
        /// The room's version.
        room_version: RoomVersionId,
    },

    /// M_BAD_STATE
    #[serde(rename = "M_BAD_STATE")]
    #[strum(to_string = "M_BAD_STATE")]
    BadState,

    /// M_GUEST_ACCESS_FORBIDDEN
    #[serde(rename = "M_GUEST_ACCESS_FORBIDDEN")]
    #[strum(to_string = "M_GUEST_ACCESS_FORBIDDEN")]
    GuestAccessForbidden,

    /// M_CAPTCHA_NEEDED
    #[serde(rename = "M_CAPTCHA_NEEDED")]
    #[strum(to_string = "M_CAPTCHA_NEEDED")]
    CaptchaNeeded,

    /// M_CAPTCHA_INVALID
    #[serde(rename = "M_CAPTCHA_INVALID")]
    #[strum(to_string = "M_CAPTCHA_INVALID")]
    CaptchaInvalid,

    /// M_MISSING_PARAM
    #[serde(rename = "M_MISSING_PARAM")]
    #[strum(to_string = "M_MISSING_PARAM")]
    MissingParam,

    /// M_INVALID_PARAM
    #[serde(rename = "M_INVALID_PARAM")]
    #[strum(to_string = "M_INVALID_PARAM")]
    InvalidParam,

    /// M_TOO_LARGE
    #[serde(rename = "M_TOO_LARGE")]
    #[strum(to_string = "M_TOO_LARGE")]
    TooLarge,

    /// M_EXCLUSIVE
    #[serde(rename = "M_EXCLUSIVE")]
    #[strum(to_string = "M_EXCLUSIVE")]
    Exclusive,

    /// M_RESOURCE_LIMIT_EXCEEDED
    #[serde(rename = "M_RESOURCE_LIMIT_EXCEEDED")]
    #[strum(to_string = "M_RESOURCE_LIMIT_EXCEEDED")]
    ResourceLimitExceeded {
        /// A URI giving a contact method for the server administrator.
        admin_contact: String,
    },
}

/// A Matrix Error without a status code
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ErrorBody {
    /// A value which can be used to handle an error message
    #[serde(flatten)]
    pub kind: ErrorKind,

    /// A human-readable error message, usually a sentence explaining what went wrong.
    #[serde(rename = "error")]
    pub message: String,
}

/// A Matrix Error
#[derive(Debug, Clone)]
pub struct Error {
    /// A value which can be used to handle an error message
    pub kind: ErrorKind,

    /// A human-readable error message, usually a sentence explaining what went wrong.
    pub message: String,

    /// The http status code
    pub status_code: http::StatusCode,
}

impl EndpointError for Error {
    fn try_from_response(
        response: http::Response<Vec<u8>>,
    ) -> Result<Self, ResponseDeserializationError> {
        match from_json_slice::<ErrorBody>(response.body()) {
            Ok(error_body) => Ok(error_body.into_error(response.status())),
            Err(de_error) => Err(ResponseDeserializationError::new(de_error, response)),
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "[{} / {}] {}", self.status_code.as_u16(), self.kind, self.message)
    }
}

impl std::error::Error for Error {}

impl From<Error> for ErrorBody {
    fn from(error: Error) -> Self {
        Self { kind: error.kind, message: error.message }
    }
}

impl ErrorBody {
    /// Convert the ErrorBody into an Error by adding the http status code.
    pub fn into_error(self, status_code: http::StatusCode) -> Error {
        Error { kind: self.kind, message: self.message, status_code }
    }
}

impl From<Error> for http::Response<Vec<u8>> {
    fn from(error: Error) -> http::Response<Vec<u8>> {
        http::Response::builder()
            .header(http::header::CONTENT_TYPE, "application/json")
            .status(error.status_code)
            .body(to_json_vec(&ErrorBody::from(error)).unwrap())
            .unwrap()
    }
}
<commit_msg>client-api: Add ErrorKind::CannotLeaveServerNoticeRoom<commit_after>//! Errors that can be sent from the homeserver.

use std::fmt::{self, Display, Formatter};

use ruma_api::{error::ResponseDeserializationError, EndpointError};
use ruma_identifiers::RoomVersionId;
use serde::{Deserialize, Serialize};
use serde_json::{from_slice as from_json_slice, to_vec as to_json_vec};
use strum::{AsRefStr, Display};

/// An enum for the error kind. Items may contain additional information.
#[derive(Debug, Clone, Serialize, Deserialize, AsRefStr, Display)]
#[serde(tag = "errcode")]
#[cfg_attr(test, derive(PartialEq))]
pub enum ErrorKind {
    /// M_FORBIDDEN
    #[serde(rename = "M_FORBIDDEN")]
    #[strum(to_string = "M_FORBIDDEN")]
    Forbidden,

    /// M_UNKNOWN_TOKEN
    #[serde(rename = "M_UNKNOWN_TOKEN")]
    #[strum(to_string = "M_UNKNOWN_TOKEN")]
    UnknownToken {
        /// If this is `true`, the client can acquire a new access token by specifying the device ID
        /// it is already using to the login API. For more information, see [the spec].
        ///
        /// [the spec]: https://matrix.org/docs/spec/client_server/r0.6.1#soft-logout
        #[serde(default, skip_serializing_if = "ruma_serde::is_default")]
        soft_logout: bool,
    },

    /// M_MISSING_TOKEN
    #[serde(rename = "M_MISSING_TOKEN")]
    #[strum(to_string = "M_MISSING_TOKEN")]
    MissingToken,

    /// M_BAD_JSON
    #[serde(rename = "M_BAD_JSON")]
    #[strum(to_string = "M_BAD_JSON")]
    BadJson,

    /// M_NOT_JSON
    #[serde(rename = "M_NOT_JSON")]
    #[strum(to_string = "M_NOT_JSON")]
    NotJson,

    /// M_NOT_FOUND
    #[serde(rename = "M_NOT_FOUND")]
    #[strum(to_string = "M_NOT_FOUND")]
    NotFound,

    /// M_LIMIT_EXCEEDED
    #[serde(rename = "M_LIMIT_EXCEEDED")]
    #[strum(to_string = "M_LIMIT_EXCEEDED")]
    LimitExceeded,

    /// M_UNKNOWN
    #[serde(rename = "M_UNKNOWN")]
    #[strum(to_string = "M_UNKNOWN")]
    Unknown,

    /// M_UNRECOGNIZED
    #[serde(rename = "M_UNRECOGNIZED")]
    #[strum(to_string = "M_UNRECOGNIZED")]
    Unrecognized,

    /// M_UNAUTHORIZED
    #[serde(rename = "M_UNAUTHORIZED")]
    #[strum(to_string = "M_UNAUTHORIZED")]
    Unauthorized,

    /// M_USER_DEACTIVATED
    #[serde(rename = "M_USER_DEACTIVATED")]
    #[strum(to_string = "M_USER_DEACTIVATED")]
    UserDeactivated,

    /// M_USER_IN_USE
    #[serde(rename = "M_USER_IN_USE")]
    #[strum(to_string = "M_USER_IN_USE")]
    UserInUse,

    /// M_INVALID_USERNAME
    #[serde(rename = "M_INVALID_USERNAME")]
    #[strum(to_string = "M_INVALID_USERNAME")]
    InvalidUsername,

    /// M_ROOM_IN_USE
    #[serde(rename = "M_ROOM_IN_USE")]
    #[strum(to_string = "M_ROOM_IN_USE")]
    RoomInUse,

    /// M_INVALID_ROOM_STATE
    #[serde(rename = "M_INVALID_ROOM_STATE")]
    #[strum(to_string = "M_INVALID_ROOM_STATE")]
    InvalidRoomState,

    /// M_THREEPID_IN_USE
    #[serde(rename = "M_THREEPID_IN_USE")]
    #[strum(to_string = "M_THREEPID_IN_USE")]
    ThreepidInUse,

    /// M_THREEPID_NOT_FOUND
    #[serde(rename = "M_THREEPID_NOT_FOUND")]
    #[strum(to_string = "M_THREEPID_NOT_FOUND")]
    ThreepidNotFound,

    /// M_THREEPID_AUTH_FAILED
    #[serde(rename = "M_THREEPID_AUTH_FAILED")]
    #[strum(to_string = "M_THREEPID_AUTH_FAILED")]
    ThreepidAuthFailed,

    /// M_THREEPID_DENIED
    #[serde(rename = "M_THREEPID_DENIED")]
    #[strum(to_string = "M_THREEPID_DENIED")]
    ThreepidDenied,

    /// M_SERVER_NOT_TRUSTED
    #[serde(rename = "M_SERVER_NOT_TRUSTED")]
    #[strum(to_string = "M_SERVER_NOT_TRUSTED")]
    ServerNotTrusted,

    /// M_UNSUPPORTED_ROOM_VERSION
    #[serde(rename = "M_UNSUPPORTED_ROOM_VERSION")]
    #[strum(to_string = "M_UNSUPPORTED_ROOM_VERSION")]
    UnsupportedRoomVersion,

    /// M_INCOMPATIBLE_ROOM_VERSION
    #[serde(rename = "M_INCOMPATIBLE_ROOM_VERSION")]
    #[strum(to_string = "M_INCOMPATIBLE_ROOM_VERSION")]
    IncompatibleRoomVersion {
        /// The room's version.
        room_version: RoomVersionId,
    },

    /// M_BAD_STATE
    #[serde(rename = "M_BAD_STATE")]
    #[strum(to_string = "M_BAD_STATE")]
    BadState,

    /// M_GUEST_ACCESS_FORBIDDEN
    #[serde(rename = "M_GUEST_ACCESS_FORBIDDEN")]
    #[strum(to_string = "M_GUEST_ACCESS_FORBIDDEN")]
    GuestAccessForbidden,

    /// M_CAPTCHA_NEEDED
    #[serde(rename = "M_CAPTCHA_NEEDED")]
    #[strum(to_string = "M_CAPTCHA_NEEDED")]
    CaptchaNeeded,

    /// M_CAPTCHA_INVALID
    #[serde(rename = "M_CAPTCHA_INVALID")]
    #[strum(to_string = "M_CAPTCHA_INVALID")]
    CaptchaInvalid,

    /// M_MISSING_PARAM
    #[serde(rename = "M_MISSING_PARAM")]
    #[strum(to_string = "M_MISSING_PARAM")]
    MissingParam,

    /// M_INVALID_PARAM
    #[serde(rename = "M_INVALID_PARAM")]
    #[strum(to_string = "M_INVALID_PARAM")]
    InvalidParam,

    /// M_TOO_LARGE
    #[serde(rename = "M_TOO_LARGE")]
    #[strum(to_string = "M_TOO_LARGE")]
    TooLarge,

    /// M_EXCLUSIVE
    #[serde(rename = "M_EXCLUSIVE")]
    #[strum(to_string = "M_EXCLUSIVE")]
    Exclusive,

    /// M_RESOURCE_LIMIT_EXCEEDED
    #[serde(rename = "M_RESOURCE_LIMIT_EXCEEDED")]
    #[strum(to_string = "M_RESOURCE_LIMIT_EXCEEDED")]
    ResourceLimitExceeded {
        /// A URI giving a contact method for the server administrator.
        admin_contact: String,
    },

    /// M_CANNOT_LEAVE_SERVER_NOTICE_ROOM
    #[serde(rename = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM")]
    #[strum(to_string = "M_CANNOT_LEAVE_SERVER_NOTICE_ROOM")]
    CannotLeaveServerNoticeRoom,
}

/// A Matrix Error without a status code
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(test, derive(PartialEq))]
pub struct ErrorBody {
    /// A value which can be used to handle an error message
    #[serde(flatten)]
    pub kind: ErrorKind,

    /// A human-readable error message, usually a sentence explaining what went wrong.
    #[serde(rename = "error")]
    pub message: String,
}

/// A Matrix Error
#[derive(Debug, Clone)]
pub struct Error {
    /// A value which can be used to handle an error message
    pub kind: ErrorKind,

    /// A human-readable error message, usually a sentence explaining what went wrong.
    pub message: String,

    /// The http status code
    pub status_code: http::StatusCode,
}

impl EndpointError for Error {
    fn try_from_response(
        response: http::Response<Vec<u8>>,
    ) -> Result<Self, ResponseDeserializationError> {
        match from_json_slice::<ErrorBody>(response.body()) {
            Ok(error_body) => Ok(error_body.into_error(response.status())),
            Err(de_error) => Err(ResponseDeserializationError::new(de_error, response)),
        }
    }
}

impl Display for Error {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "[{} / {}] {}", self.status_code.as_u16(), self.kind, self.message)
    }
}

impl std::error::Error for Error {}

impl From<Error> for ErrorBody {
    fn from(error: Error) -> Self {
        Self { kind: error.kind, message: error.message }
    }
}

impl ErrorBody {
    /// Convert the ErrorBody into an Error by adding the http status code.
    pub fn into_error(self, status_code: http::StatusCode) -> Error {
        Error { kind: self.kind, message: self.message, status_code }
    }
}

impl From<Error> for http::Response<Vec<u8>> {
    fn from(error: Error) -> http::Response<Vec<u8>> {
        http::Response::builder()
            .header(http::header::CONTENT_TYPE, "application/json")
            .status(error.status_code)
            .body(to_json_vec(&ErrorBody::from(error)).unwrap())
            .unwrap()
    }
}
<|endoftext|>


<commit_before>//! Thread-local storage for JavaScript threads.
//!
//! At runtime, an instance of a Node.js addon can contain its own local storage,
//! which can then be shared and accessed as needed between Rust modules. This can
//! be useful for setting up long-lived state that needs to be shared between calls
//! of an addon's APIs.
//!
//! For example, an addon may wish to track the [thread ID][threadId] of each of its
//! instances:
//!
//! ```
//! # use neon::prelude::*;
//! # use neon::thread::LocalKey;
//! static THREAD_ID: LocalKey<u32> = LocalKey::new();
//!
//! pub fn thread_id<'cx, C: Context<'cx>>(cx: &mut C) -> NeonResult<u32> {
//!     THREAD_ID.get_or_try_init(cx, |cx| {
//!         let global = cx.global();
//!         let require: Handle<JsFunction> = global.get(cx, "require")?;
//!         let worker: Handle<JsObject> = require.call_with(cx)
//!             .arg(cx.string("node:worker_threads"))
//!             .apply(cx)?;
//!         let threadId: Handle<JsNumber> = worker.get(cx, "threadId")?;
//!         Ok(threadId.value(cx) as u32)
//!     }).cloned()
//! }
//! ```
//!
//! ### The Addon Lifecycle
//!
//! For some use cases, a single shared global constant stored in a `static` variable
//! might be sufficient:
//!
//! ```
//! static MY_CONSTANT: &'static str = "hello Neon";
//! ```
//!
//! This variable will be allocated when the addon is first loaded into the Node.js
//! process. This works fine for single-threaded applications, or global immutable
//! data.
//!
//! However, since the addition of [worker threads][workers] in Node v10,
//! modules can be instantiated multiple times in a single Node process. This means
//! that while the dynamically-loaded binary library (i.e., the Rust implementation of
//! the addon) is only loaded once in the running process, but its `main()` function
//! is executed multiple times with distinct module objects, once per application thread:
//!
//! ![The Node.js addon lifecycle, described in detail below.][lifecycle]
//!
//! This means that any thread-local data needs to be initialized separately for each
//! instance of the addon. This module provides a simple container type, [`LocalKey`](LocalKey),
//! for allocating and initializing thread-local data. (Technically, this data is stored in the
//! addon's [module instance][environment], which is equivalent to being thread-local.)
//!
//! A common example is when an addon defines a custom datatype, which cannot be used across
//! separate threads. The constructor for the datatype can be [rooted](crate::handle::Root) and
//! saved in thread-local storage:
//!
//! ```
//! # use neon::prelude::*;
//! # use neon::thread::LocalKey;
//! # fn initialize_my_datatype<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> { unimplemented!() }
//! static MY_CONSTRUCTOR: LocalKey<Root<JsFunction>> = LocalKey::new();
//!
//! pub fn my_constructor<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> {
//!     let constructor = MY_CONSTRUCTOR.get_or_try_init(cx, |cx| {
//!         let constructor: Handle<JsFunction> = initialize_my_datatype(cx)?;
//!         Ok(constructor.root(cx))
//!     })?;
//!     Ok(constructor.to_inner(cx))
//! }
//! ```
//!
//! Notice that if this code were implemented without a `LocalKey`, it would fail whenever
//! one thread stores an instance of the constructor and a different thread attempts to
//! access it with the call to [`to_inner()`](crate::handle::Root::to_inner).
//!
//! ### When to Use Thread-Local Storage
//!
//! Single-threaded applications don't generally need to worry about thread-local data.
//! There are two cases where Neon apps should consider storing static data in a
//! `LocalKey` storage cell:
//!
//! - **Multi-threaded applications:** If your Node application uses the `Worker`
//!   API, you'll want to store any static data that might get access from multiple
//!   threads in thread-local data.
//! - **Libraries:** If your addon is part of a library that could be used by multiple
//!   applications, you'll want to store static data in thread-local data in case the
//!   addon ends up instantiated by multiple threads in some future application.
//!
//! ### Why Not Use Standard TLS?
//!
//! Because the JavaScript engine may not tie JavaScript threads 1:1 to system threads,
//! it is recommended to use this module instead of the Rust standard thread-local storage
//! when tying data to a JavaScript thread.
//!
//! [environment]: https://nodejs.org/api/n-api.html#environment-life-cycle-apis
//! [lifecycle]: https://raw.githubusercontent.com/neon-bindings/neon/main/doc/lifecycle.png
//! [workers]: https://nodejs.org/api/worker_threads.html
//! [threadId]: https://nodejs.org/api/worker_threads.html#workerthreadid

use std::any::Any;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};

use once_cell::sync::OnceCell;

use crate::context::Context;
use crate::lifecycle::LocalCell;

static COUNTER: AtomicUsize = AtomicUsize::new(0);

fn next_id() -> usize {
    COUNTER.fetch_add(1, Ordering::SeqCst)
}

/// A JavaScript thread-local container that owns its contents, similar to
/// [`std::thread::LocalKey`](std::thread::LocalKey) but tied to a JavaScript thread rather
/// than a system thread.
///
/// ### Initialization and Destruction
///
/// Initialization is dynamically performed on the first call to one of the `init` methods
/// of `LocalKey`, and values that implement [`Drop`](std::ops::Drop) get destructed when
/// the JavaScript thread exits, i.e. when a worker thread terminates or the main thread
/// terminates on process exit.
#[derive(Default)]
pub struct LocalKey<T> {
    _type: PhantomData<T>,
    id: OnceCell<usize>,
}

impl<T> LocalKey<T> {
    /// Creates a new local value. This method is `const`, so it can be assigned to
    /// static variables.
    pub const fn new() -> Self {
        Self {
            _type: PhantomData,
            id: OnceCell::new(),
        }
    }

    fn id(&self) -> usize {
        *self.id.get_or_init(next_id)
    }
}

impl<T: Any + Send + 'static> LocalKey<T> {
    /// Gets the current value of the cell. Returns `None` if the cell has not
    /// yet been initialized.
    pub fn get<'cx, 'a, C>(&self, cx: &'a mut C) -> Option<&'cx T>
    where
        C: Context<'cx>,
    {
        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
        // id guarantees that the cell is only ever assigned instances of type T.
        let r: Option<&T> =
            LocalCell::get(cx, self.id()).map(|value| value.downcast_ref().unwrap());

        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
        // move or change for the duration of the context.
        unsafe { std::mem::transmute::<Option<&'a T>, Option<&'cx T>>(r) }
    }

    /// Gets the current value of the cell, initializing it with the result of
    /// calling `f` if it has not yet been initialized.
    pub fn get_or_init<'cx, 'a, C, F>(&self, cx: &'a mut C, f: F) -> &'cx T
    where
        C: Context<'cx>,
        F: FnOnce() -> T,
    {
        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
        // id guarantees that the cell is only ever assigned instances of type T.
        let r: &T = LocalCell::get_or_init(cx, self.id(), || Box::new(f()))
            .downcast_ref()
            .unwrap();

        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
        // move or change for the duration of the context.
        unsafe { std::mem::transmute::<&'a T, &'cx T>(r) }
    }

    /// Gets the current value of the cell, initializing it with the result of
    /// calling `f` if it has not yet been initialized. Returns `Err` if the
    /// callback triggers a JavaScript exception.
    ///
    /// # Panics
    ///
    /// During the execution of `f`, calling any methods on this `LocalKey` that
    /// attempt to initialize it will panic.
    pub fn get_or_try_init<'cx, 'a, C, E, F>(&self, cx: &'a mut C, f: F) -> Result<&'cx T, E>
    where
        C: Context<'cx>,
        F: FnOnce(&mut C) -> Result<T, E>,
    {
        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
        // id guarantees that the cell is only ever assigned instances of type T.
        let r: &T = LocalCell::get_or_try_init(cx, self.id(), |cx| Ok(Box::new(f(cx)?)))?
            .downcast_ref()
            .unwrap();

        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
        // move or change for the duration of the context.
        Ok(unsafe { std::mem::transmute::<&'a T, &'cx T>(r) })
    }
}

impl<T: Any + Send + Default + 'static> LocalKey<T> {
    /// Gets the current value of the cell, initializing it with the default value
    /// if it has not yet been initialized.
    pub fn get_or_init_default<'cx, 'a, C>(&self, cx: &'a mut C) -> &'cx T
    where
        C: Context<'cx>,
    {
        self.get_or_init(cx, Default::default)
    }
}
<commit_msg>Clarify doc text<commit_after>//! Thread-local storage for JavaScript threads.
//!
//! At runtime, an instance of a Node.js addon can contain its own local storage,
//! which can then be shared and accessed as needed from Rust in a Neon module. This can
//! be useful for setting up long-lived state that needs to be shared between calls
//! of an addon's APIs.
//!
//! For example, an addon may wish to track the [thread ID][threadId] of each of its
//! instances:
//!
//! ```
//! # use neon::prelude::*;
//! # use neon::thread::LocalKey;
//! static THREAD_ID: LocalKey<u32> = LocalKey::new();
//!
//! pub fn thread_id<'cx, C: Context<'cx>>(cx: &mut C) -> NeonResult<u32> {
//!     THREAD_ID.get_or_try_init(cx, |cx| {
//!         let global = cx.global();
//!         let require: Handle<JsFunction> = global.get(cx, "require")?;
//!         let worker: Handle<JsObject> = require.call_with(cx)
//!             .arg(cx.string("node:worker_threads"))
//!             .apply(cx)?;
//!         let threadId: Handle<JsNumber> = worker.get(cx, "threadId")?;
//!         Ok(threadId.value(cx) as u32)
//!     }).cloned()
//! }
//! ```
//!
//! ### The Addon Lifecycle
//!
//! For some use cases, a single shared global constant stored in a `static` variable
//! might be sufficient:
//!
//! ```
//! static MY_CONSTANT: &'static str = "hello Neon";
//! ```
//!
//! This variable will be allocated when the addon is first loaded into the Node.js
//! process. This works fine for single-threaded applications, or global immutable
//! data.
//!
//! However, since the addition of [worker threads][workers] in Node v10,
//! modules can be instantiated multiple times in a single Node process. This means
//! that while the dynamically-loaded binary library (i.e., the Rust implementation of
//! the addon) is only loaded once in the running process, but its `main()` function
//! is executed multiple times with distinct module objects, once per application thread:
//!
//! ![The Node.js addon lifecycle, described in detail below.][lifecycle]
//!
//! This means that any thread-local data needs to be initialized separately for each
//! instance of the addon. This module provides a simple container type, [`LocalKey`](LocalKey),
//! for allocating and initializing thread-local data. (Technically, this data is stored in the
//! addon's [module instance][environment], which is equivalent to being thread-local.)
//!
//! A common example is when an addon defines a custom datatype, which cannot be used across
//! separate threads. The constructor for the datatype can be [rooted](crate::handle::Root) and
//! saved in thread-local storage:
//!
//! ```
//! # use neon::prelude::*;
//! # use neon::thread::LocalKey;
//! # fn initialize_my_datatype<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> { unimplemented!() }
//! static MY_CONSTRUCTOR: LocalKey<Root<JsFunction>> = LocalKey::new();
//!
//! pub fn my_constructor<'cx, C: Context<'cx>>(cx: &mut C) -> JsResult<'cx, JsFunction> {
//!     let constructor = MY_CONSTRUCTOR.get_or_try_init(cx, |cx| {
//!         let constructor: Handle<JsFunction> = initialize_my_datatype(cx)?;
//!         Ok(constructor.root(cx))
//!     })?;
//!     Ok(constructor.to_inner(cx))
//! }
//! ```
//!
//! Notice that if this code were implemented without a `LocalKey`, it would fail whenever
//! one thread stores an instance of the constructor and a different thread attempts to
//! access it with the call to [`to_inner()`](crate::handle::Root::to_inner).
//!
//! ### When to Use Thread-Local Storage
//!
//! Single-threaded applications don't generally need to worry about thread-local data.
//! There are two cases where Neon apps should consider storing static data in a
//! `LocalKey` storage cell:
//!
//! - **Multi-threaded applications:** If your Node application uses the `Worker`
//!   API, you'll want to store any static data that might get access from multiple
//!   threads in thread-local data.
//! - **Libraries:** If your addon is part of a library that could be used by multiple
//!   applications, you'll want to store static data in thread-local data in case the
//!   addon ends up instantiated by multiple threads in some future application.
//!
//! ### Why Not Use Standard TLS?
//!
//! Because the JavaScript engine may not tie JavaScript threads 1:1 to system threads,
//! it is recommended to use this module instead of the Rust standard thread-local storage
//! when tying data to a JavaScript thread.
//!
//! [environment]: https://nodejs.org/api/n-api.html#environment-life-cycle-apis
//! [lifecycle]: https://raw.githubusercontent.com/neon-bindings/neon/main/doc/lifecycle.png
//! [workers]: https://nodejs.org/api/worker_threads.html
//! [threadId]: https://nodejs.org/api/worker_threads.html#workerthreadid

use std::any::Any;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};

use once_cell::sync::OnceCell;

use crate::context::Context;
use crate::lifecycle::LocalCell;

static COUNTER: AtomicUsize = AtomicUsize::new(0);

fn next_id() -> usize {
    COUNTER.fetch_add(1, Ordering::SeqCst)
}

/// A JavaScript thread-local container that owns its contents, similar to
/// [`std::thread::LocalKey`](std::thread::LocalKey) but tied to a JavaScript thread rather
/// than a system thread.
///
/// ### Initialization and Destruction
///
/// Initialization is dynamically performed on the first call to one of the `init` methods
/// of `LocalKey`, and values that implement [`Drop`](std::ops::Drop) get destructed when
/// the JavaScript thread exits, i.e. when a worker thread terminates or the main thread
/// terminates on process exit.
#[derive(Default)]
pub struct LocalKey<T> {
    _type: PhantomData<T>,
    id: OnceCell<usize>,
}

impl<T> LocalKey<T> {
    /// Creates a new local value. This method is `const`, so it can be assigned to
    /// static variables.
    pub const fn new() -> Self {
        Self {
            _type: PhantomData,
            id: OnceCell::new(),
        }
    }

    fn id(&self) -> usize {
        *self.id.get_or_init(next_id)
    }
}

impl<T: Any + Send + 'static> LocalKey<T> {
    /// Gets the current value of the cell. Returns `None` if the cell has not
    /// yet been initialized.
    pub fn get<'cx, 'a, C>(&self, cx: &'a mut C) -> Option<&'cx T>
    where
        C: Context<'cx>,
    {
        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
        // id guarantees that the cell is only ever assigned instances of type T.
        let r: Option<&T> =
            LocalCell::get(cx, self.id()).map(|value| value.downcast_ref().unwrap());

        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
        // move or change for the duration of the context.
        unsafe { std::mem::transmute::<Option<&'a T>, Option<&'cx T>>(r) }
    }

    /// Gets the current value of the cell, initializing it with the result of
    /// calling `f` if it has not yet been initialized.
    pub fn get_or_init<'cx, 'a, C, F>(&self, cx: &'a mut C, f: F) -> &'cx T
    where
        C: Context<'cx>,
        F: FnOnce() -> T,
    {
        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
        // id guarantees that the cell is only ever assigned instances of type T.
        let r: &T = LocalCell::get_or_init(cx, self.id(), || Box::new(f()))
            .downcast_ref()
            .unwrap();

        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
        // move or change for the duration of the context.
        unsafe { std::mem::transmute::<&'a T, &'cx T>(r) }
    }

    /// Gets the current value of the cell, initializing it with the result of
    /// calling `f` if it has not yet been initialized. Returns `Err` if the
    /// callback triggers a JavaScript exception.
    ///
    /// # Panics
    ///
    /// During the execution of `f`, calling any methods on this `LocalKey` that
    /// attempt to initialize it will panic.
    pub fn get_or_try_init<'cx, 'a, C, E, F>(&self, cx: &'a mut C, f: F) -> Result<&'cx T, E>
    where
        C: Context<'cx>,
        F: FnOnce(&mut C) -> Result<T, E>,
    {
        // Unwrap safety: The type bound LocalKey<T> and the fact that every LocalKey has a unique
        // id guarantees that the cell is only ever assigned instances of type T.
        let r: &T = LocalCell::get_or_try_init(cx, self.id(), |cx| Ok(Box::new(f(cx)?)))?
            .downcast_ref()
            .unwrap();

        // Safety: Since the Box is immutable and heap-allocated, it's guaranteed not to
        // move or change for the duration of the context.
        Ok(unsafe { std::mem::transmute::<&'a T, &'cx T>(r) })
    }
}

impl<T: Any + Send + Default + 'static> LocalKey<T> {
    /// Gets the current value of the cell, initializing it with the default value
    /// if it has not yet been initialized.
    pub fn get_or_init_default<'cx, 'a, C>(&self, cx: &'a mut C) -> &'cx T
    where
        C: Context<'cx>,
    {
        self.get_or_init(cx, Default::default)
    }
}
<|endoftext|>


<commit_before>use gfx::format::{Depth, Rgba8};
use gfx::handle::{DepthStencilView, RenderTargetView};
use gfx::state::{CullFace, FrontFace, RasterMethod, Rasterizer};
use gfx::traits::FactoryExt;
use gfx::{CommandBuffer, Device, Encoder, Factory, PipelineState, Primitive, Resources};
use glutin::{NotCurrent, PossiblyCurrent, WindowedContext};
use plexus::buffer::MeshBuffer;
use plexus::index::Flat3;

use crate::pipeline::{self, Data, Meta, Transform, Vertex};

const CLEAR_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];

pub trait SwapBuffers {
    fn swap_buffers(&mut self) -> Result<(), ()>;
}

impl SwapBuffers for WindowedContext<PossiblyCurrent> {
    fn swap_buffers(&mut self) -> Result<(), ()> {
        match WindowedContext::swap_buffers(self) {
            Ok(_) => Ok(()),
            Err(_) => Err(()),
        }
    }
}

pub trait UpdateFrameBufferView<R>
where
    R: Resources,
{
    fn update_frame_buffer_view(
        &self,
        color: &mut RenderTargetView<R, Rgba8>,
        depth: &mut DepthStencilView<R, Depth>,
    );
}

impl UpdateFrameBufferView<gfx_device_gl::Resources> for WindowedContext<PossiblyCurrent> {
    fn update_frame_buffer_view(
        &self,
        color: &mut RenderTargetView<gfx_device_gl::Resources, Rgba8>,
        depth: &mut DepthStencilView<gfx_device_gl::Resources, Depth>,
    ) {
        gfx_window_glutin::update_views(self, color, depth);
    }
}

pub trait Binding {
    type Window: SwapBuffers + UpdateFrameBufferView<Self::Resources>;
    type Resources: Resources;
    type Factory: Factory<Self::Resources>;
    type CommandBuffer: CommandBuffer<Self::Resources>;
    type Device: Device<Resources = Self::Resources, CommandBuffer = Self::CommandBuffer>;
}

pub enum GlutinBinding {}

impl Binding for GlutinBinding {
    type Window = WindowedContext<PossiblyCurrent>;
    type Resources = gfx_device_gl::Resources;
    type Factory = gfx_device_gl::Factory;
    type CommandBuffer = gfx_device_gl::CommandBuffer;
    type Device = gfx_device_gl::Device;
}

pub struct Renderer<R>
where
    R: Binding,
{
    pub window: R::Window,
    pub factory: R::Factory,
    device: R::Device,
    encoder: Encoder<R::Resources, R::CommandBuffer>,
    state: PipelineState<R::Resources, Meta>,
    data: Data<R::Resources>,
}

impl Renderer<GlutinBinding> {
    pub fn from_glutin_window(window: WindowedContext<NotCurrent>) -> Self {
        let (window, device, mut factory, color, depth) = gfx_window_glutin::init_existing(window);
        let encoder = factory.create_command_buffer().into();
        Renderer::new(window, factory, device, encoder, color, depth)
    }
}

impl<R> Renderer<R>
where
    R: Binding,
{
    fn new(
        window: R::Window,
        mut factory: R::Factory,
        device: R::Device,
        encoder: Encoder<R::Resources, R::CommandBuffer>,
        color: RenderTargetView<R::Resources, Rgba8>,
        depth: DepthStencilView<R::Resources, Depth>,
    ) -> Self {
        let shaders = factory
            .create_shader_set(
                include_bytes!("shader.v.glsl"),
                include_bytes!("shader.f.glsl"),
            )
            .unwrap();
        let state = factory
            .create_pipeline_state(
                &shaders,
                Primitive::TriangleList,
                Rasterizer {
                    method: RasterMethod::Fill,
                    front_face: FrontFace::CounterClockwise,
                    cull_face: CullFace::Back,
                    offset: None,
                    samples: None,
                },
                pipeline::new(),
            )
            .unwrap();
        let data = Data {
            buffer: factory.create_vertex_buffer(&[]),
            transform: factory.create_constant_buffer(1),
            viewpoint: [0.0; 4],
            camera: [[0.0; 4]; 4],
            model: [[0.0; 4]; 4],
            color,
            depth,
        };
        Renderer {
            window,
            factory,
            device,
            encoder,
            state,
            data,
        }
    }

    pub fn set_transform(&mut self, transform: &Transform) -> Result<(), ()> {
        self.data.camera = transform.camera;
        self.data.model = transform.model;
        match self
            .encoder
            .update_buffer(&self.data.transform, &[*transform], 0)
        {
            Ok(_) => Ok(()),
            Err(_) => Err(()),
        }
    }

    pub fn draw_mesh_buffer(&mut self, buffer: &MeshBuffer<Flat3<u32>, Vertex>) {
        let (buffer, slice) = self
            .factory
            .create_vertex_buffer_with_slice(buffer.as_vertex_slice(), buffer.as_index_slice());
        self.data.buffer = buffer;
        self.encoder.draw(&slice, &self.state, &self.data);
    }

    pub fn clear(&mut self) {
        self.encoder.clear(&self.data.color, CLEAR_COLOR);
        self.encoder.clear_depth(&self.data.depth, 1.0);
    }

    pub fn flush(&mut self) -> Result<(), ()> {
        self.encoder.flush(&mut self.device);
        match self.window.swap_buffers().and_then(|_| {
            self.device.cleanup();
            Ok(())
        }) {
            Ok(_) => Ok(()),
            Err(_) => Err(()),
        }
    }

    pub fn update_frame_buffer_view(&mut self) {
        self.window
            .update_frame_buffer_view(&mut self.data.color, &mut self.data.depth);
    }
}
<commit_msg>Replace `and_then` with `if`.<commit_after>use gfx::format::{Depth, Rgba8};
use gfx::handle::{DepthStencilView, RenderTargetView};
use gfx::state::{CullFace, FrontFace, RasterMethod, Rasterizer};
use gfx::traits::FactoryExt;
use gfx::{CommandBuffer, Device, Encoder, Factory, PipelineState, Primitive, Resources};
use glutin::{NotCurrent, PossiblyCurrent, WindowedContext};
use plexus::buffer::MeshBuffer;
use plexus::index::Flat3;

use crate::pipeline::{self, Data, Meta, Transform, Vertex};

const CLEAR_COLOR: [f32; 4] = [0.0, 0.0, 0.0, 1.0];

pub trait SwapBuffers {
    fn swap_buffers(&mut self) -> Result<(), ()>;
}

impl SwapBuffers for WindowedContext<PossiblyCurrent> {
    fn swap_buffers(&mut self) -> Result<(), ()> {
        match WindowedContext::swap_buffers(self) {
            Ok(_) => Ok(()),
            Err(_) => Err(()),
        }
    }
}

pub trait UpdateFrameBufferView<R>
where
    R: Resources,
{
    fn update_frame_buffer_view(
        &self,
        color: &mut RenderTargetView<R, Rgba8>,
        depth: &mut DepthStencilView<R, Depth>,
    );
}

impl UpdateFrameBufferView<gfx_device_gl::Resources> for WindowedContext<PossiblyCurrent> {
    fn update_frame_buffer_view(
        &self,
        color: &mut RenderTargetView<gfx_device_gl::Resources, Rgba8>,
        depth: &mut DepthStencilView<gfx_device_gl::Resources, Depth>,
    ) {
        gfx_window_glutin::update_views(self, color, depth);
    }
}

pub trait Binding {
    type Window: SwapBuffers + UpdateFrameBufferView<Self::Resources>;
    type Resources: Resources;
    type Factory: Factory<Self::Resources>;
    type CommandBuffer: CommandBuffer<Self::Resources>;
    type Device: Device<Resources = Self::Resources, CommandBuffer = Self::CommandBuffer>;
}

pub enum GlutinBinding {}

impl Binding for GlutinBinding {
    type Window = WindowedContext<PossiblyCurrent>;
    type Resources = gfx_device_gl::Resources;
    type Factory = gfx_device_gl::Factory;
    type CommandBuffer = gfx_device_gl::CommandBuffer;
    type Device = gfx_device_gl::Device;
}

pub struct Renderer<R>
where
    R: Binding,
{
    pub window: R::Window,
    pub factory: R::Factory,
    device: R::Device,
    encoder: Encoder<R::Resources, R::CommandBuffer>,
    state: PipelineState<R::Resources, Meta>,
    data: Data<R::Resources>,
}

impl Renderer<GlutinBinding> {
    pub fn from_glutin_window(window: WindowedContext<NotCurrent>) -> Self {
        let (window, device, mut factory, color, depth) = gfx_window_glutin::init_existing(window);
        let encoder = factory.create_command_buffer().into();
        Renderer::new(window, factory, device, encoder, color, depth)
    }
}

impl<R> Renderer<R>
where
    R: Binding,
{
    fn new(
        window: R::Window,
        mut factory: R::Factory,
        device: R::Device,
        encoder: Encoder<R::Resources, R::CommandBuffer>,
        color: RenderTargetView<R::Resources, Rgba8>,
        depth: DepthStencilView<R::Resources, Depth>,
    ) -> Self {
        let shaders = factory
            .create_shader_set(
                include_bytes!("shader.v.glsl"),
                include_bytes!("shader.f.glsl"),
            )
            .unwrap();
        let state = factory
            .create_pipeline_state(
                &shaders,
                Primitive::TriangleList,
                Rasterizer {
                    method: RasterMethod::Fill,
                    front_face: FrontFace::CounterClockwise,
                    cull_face: CullFace::Back,
                    offset: None,
                    samples: None,
                },
                pipeline::new(),
            )
            .unwrap();
        let data = Data {
            buffer: factory.create_vertex_buffer(&[]),
            transform: factory.create_constant_buffer(1),
            viewpoint: [0.0; 4],
            camera: [[0.0; 4]; 4],
            model: [[0.0; 4]; 4],
            color,
            depth,
        };
        Renderer {
            window,
            factory,
            device,
            encoder,
            state,
            data,
        }
    }

    pub fn set_transform(&mut self, transform: &Transform) -> Result<(), ()> {
        self.data.camera = transform.camera;
        self.data.model = transform.model;
        match self
            .encoder
            .update_buffer(&self.data.transform, &[*transform], 0)
        {
            Ok(_) => Ok(()),
            Err(_) => Err(()),
        }
    }

    pub fn draw_mesh_buffer(&mut self, buffer: &MeshBuffer<Flat3<u32>, Vertex>) {
        let (buffer, slice) = self
            .factory
            .create_vertex_buffer_with_slice(buffer.as_vertex_slice(), buffer.as_index_slice());
        self.data.buffer = buffer;
        self.encoder.draw(&slice, &self.state, &self.data);
    }

    pub fn clear(&mut self) {
        self.encoder.clear(&self.data.color, CLEAR_COLOR);
        self.encoder.clear_depth(&self.data.depth, 1.0);
    }

    pub fn flush(&mut self) -> Result<(), ()> {
        self.encoder.flush(&mut self.device);
        if self.window.swap_buffers().is_ok() {
            self.device.cleanup();
            Ok(())
        }
        else {
            Err(())
        }
    }

    pub fn update_frame_buffer_view(&mut self) {
        self.window
            .update_frame_buffer_view(&mut self.data.color, &mut self.data.depth);
    }
}
<|endoftext|>


<commit_before>/// The 'Material Design Icons' is the official icon set from Google.
/// The icons are designed under the material design guidelines.
/// Because the official repository is no longer maintained from Google,
/// source code is now maintained and hosted at:
/// https://github.com/material-icons/material-icons-font
/// Package-Info: package.json

// TL;DR
// https://stackoverflow.com/questions/11002820/why-should-we-include-ttf-eot-woff-svg-in-a-font-face
// ... woff2 gets drafted and accepted, which improves the compression
// leading to even smaller files, along with the ability to load a single font
// "in parts" so that a font that supports 20 scripts can be stored as "chunks"
// on disk instead, with browsers automatically able to load the font "in parts"
// as needed, rather than needing to transfer the entire font up front, further
// improving the typesetting experience.
// If you don't want to support IE 8 and lower, and iOS 4 and lower,
// and android 4.3 or earlier, then you can just use WOFF
// (and WOFF2, a more highly compressed WOFF, for the newest browsers that support it.)

// Reference to the material icon fonts.
pub const MATERIAL_ICONS_FONT_TTF: &[u8] = include_bytes!("MaterialIcons.ttf");
// legacy: Google version
pub const MATERIAL_ICONS_REGULAR_FONT_TTF: &[u8] = include_bytes!("MaterialIcons-Regular.ttf");

pub const MATERIAL_ICONS_BASELINE_FONT: &[u8] = include_bytes!("MaterialIcons-Baseline.woff2");
pub const MATERIAL_ICONS_OUTLINED_FONT: &[u8] = include_bytes!("MaterialIcons-Outlined.woff2");
pub const MATERIAL_ICONS_ROUND_FONT: &[u8] = include_bytes!("MaterialIcons-Round.woff2");
pub const MATERIAL_ICONS_SHARP_FONT: &[u8] = include_bytes!("MaterialIcons-Sharp.woff2");
pub const MATERIAL_ICONS_TWOTONE_FONT: &[u8] = include_bytes!("MaterialIcons-TwoTone.woff2");

// Reference to the roboto fonts.
pub const ROBOTO_REGULAR_FONT: &[u8] = include_bytes!("Roboto-Regular.ttf");
pub const ROBOTO_MEDIUM_FONT: &[u8] = include_bytes!("Roboto-Medium.ttf");

// font sizes
pub const FONT_SIZE_12: f64 = 12.0;

// icon sizes
pub const ICON_FONT_SIZE_12: f64 = 12.0;
<commit_msg>theme: fonts: make font name more explicit V2<commit_after>/// The 'Material Design Icons' is the official icon set from Google.
/// The icons are designed under the material design guidelines.
/// Because the official repository is no longer maintained from Google,
/// source code is now maintained and hosted at:
/// https://github.com/material-icons/material-icons-font
/// Package-Info: package.json

// TL;DR
// https://stackoverflow.com/questions/11002820/why-should-we-include-ttf-eot-woff-svg-in-a-font-face
// ... woff2 gets drafted and accepted, which improves the compression
// leading to even smaller files, along with the ability to load a single font
// "in parts" so that a font that supports 20 scripts can be stored as "chunks"
// on disk instead, with browsers automatically able to load the font "in parts"
// as needed, rather than needing to transfer the entire font up front, further
// improving the typesetting experience.
// If you don't want to support IE 8 and lower, and iOS 4 and lower,
// and android 4.3 or earlier, then you can just use WOFF
// (and WOFF2, a more highly compressed WOFF, for the newest browsers that support it.)

// Reference to the material icon fonts.
pub const MATERIAL_ICONS_FONT_TTF: &[u8] = include_bytes!("MaterialIcons.ttf");

pub const MATERIAL_ICONS_BASELINE_FONT: &[u8] = include_bytes!("MaterialIcons-Baseline.woff2");
pub const MATERIAL_ICONS_OUTLINED_FONT: &[u8] = include_bytes!("MaterialIcons-Outlined.woff2");
pub const MATERIAL_ICONS_ROUND_FONT: &[u8] = include_bytes!("MaterialIcons-Round.woff2");
pub const MATERIAL_ICONS_SHARP_FONT: &[u8] = include_bytes!("MaterialIcons-Sharp.woff2");
pub const MATERIAL_ICONS_TWOTONE_FONT: &[u8] = include_bytes!("MaterialIcons-TwoTone.woff2");

// Reference to the roboto fonts.
pub const ROBOTO_REGULAR_FONT: &[u8] = include_bytes!("Roboto-Regular.ttf");
pub const ROBOTO_MEDIUM_FONT: &[u8] = include_bytes!("Roboto-Medium.ttf");

// font sizes
pub const FONT_SIZE_12: f64 = 12.0;

// icon sizes
pub const ICON_FONT_SIZE_12: f64 = 12.0;
<|endoftext|>


<commit_before>use analysis::bounds::Bound;
use analysis::imports::Imports;
use analysis::ref_mode::RefMode;
use analysis::rust_type::*;
use analysis::signals;
use analysis::signatures::{Signature, Signatures};
use analysis::trampolines;
use config::{self, GObject, PropertyGenerateFlags};
use env::Env;
use library;
use nameutil;
use traits::*;
use version::Version;

#[derive(Debug)]
pub struct Property {
    pub name: String,
    pub var_name: String,
    pub typ: library::TypeId,
    pub is_get: bool,
    pub func_name: String,
    pub nullable: library::Nullable,
    pub get_out_ref_mode: RefMode,
    pub set_in_ref_mode: RefMode,
    pub version: Option<Version>,
    pub deprecated_version: Option<Version>,
    pub bound: Option<Bound>,
}

pub fn analyze(
    env: &Env,
    props: &[library::Property],
    type_tid: library::TypeId,
    generate_trait: bool,
    trampolines: &mut trampolines::Trampolines,
    obj: &GObject,
    imports: &mut Imports,
    signatures: &Signatures,
    deps: &[library::TypeId],
) -> (Vec<Property>, Vec<signals::Info>) {
    let mut properties = Vec::new();
    let mut notify_signals = Vec::new();

    for prop in props {
        let configured_properties = obj.properties.matched(&prop.name);
        if configured_properties.iter().any(|f| f.ignore) {
            continue;
        }

        if env.is_totally_deprecated(prop.deprecated_version) {
            continue;
        }

        let (getter, setter, notify_signal) = analyze_property(
            env,
            prop,
            type_tid,
            &configured_properties,
            generate_trait,
            trampolines,
            obj,
            imports,
            signatures,
            deps,
        );

        if let Some(notify_signal) = notify_signal {
            notify_signals.push(notify_signal);
        }

        if getter.is_none() && setter.is_none() {
            continue;
        }

        let type_string = rust_type(env, prop.typ);
        let used_type_string = used_rust_type(env, prop.typ);
        if let Some(prop) = getter {
            if let Ok(ref s) = used_type_string {
                imports.add_used_type(s, prop.version);
            }
            if type_string.is_ok() {
                imports.add("gobject_ffi", prop.version);
                imports.add("glib::Value", prop.version);
                imports.add("glib::StaticType", prop.version);
            }

            properties.push(prop);
        }
        if let Some(prop) = setter {
            if let Ok(ref s) = used_type_string {
                imports.add_used_type(s, prop.version);
            }
            if type_string.is_ok() {
                imports.add("gobject_ffi", prop.version);
                imports.add("glib::Value", prop.version);
            }

            if let Some(ref bound, ..) = prop.bound {
                if bound.bound_type.need_isa() {
                    imports.add("glib", prop.version);
                    imports.add("glib::object::IsA", prop.version);
                }
            }

            properties.push(prop);
        }
    }

    (properties, notify_signals)
}

fn analyze_property(
    env: &Env,
    prop: &library::Property,
    type_tid: library::TypeId,
    configured_properties: &[&config::properties::Property],
    generate_trait: bool,
    trampolines: &mut trampolines::Trampolines,
    obj: &GObject,
    imports: &mut Imports,
    signatures: &Signatures,
    deps: &[library::TypeId],
) -> (Option<Property>, Option<Property>, Option<signals::Info>) {
    let type_name = type_tid.full_name(&env.library);
    let name = prop.name.clone();

    let prop_version = configured_properties
        .iter()
        .filter_map(|f| f.version)
        .min()
        .or(prop.version)
        .or_else(|| Some(env.config.min_cfg_version));
    let generate = configured_properties
        .iter()
        .filter_map(|f| f.generate)
        .next();
    let generate_set = generate.is_some();
    let generate = generate.unwrap_or_else(PropertyGenerateFlags::all);

    let name_for_func = nameutil::signal_to_snake(&name);
    let var_name = nameutil::mangle_keywords(&*name_for_func).into_owned();
    let get_func_name = format!("get_property_{}", name_for_func);
    let set_func_name = format!("set_property_{}", name_for_func);
    let check_get_func_name = format!("get_{}", name_for_func);
    let check_set_func_name = format!("set_{}", name_for_func);

    let mut readable = prop.readable;
    let mut writable = if prop.construct_only {
        false
    } else {
        prop.writable
    };
    let mut notifable = !prop.construct_only;
    if generate_set && generate.contains(PropertyGenerateFlags::GET) && !readable {
        warn!(
            "Attempt to generate getter for notreadable property \"{}.{}\"",
            type_name, name
        );
    }
    if generate_set && generate.contains(PropertyGenerateFlags::SET) && !writable {
        warn!(
            "Attempt to generate setter for nonwritable property \"{}.{}\"",
            type_name, name
        );
    }
    readable &= generate.contains(PropertyGenerateFlags::GET);
    writable &= generate.contains(PropertyGenerateFlags::SET);
    if generate_set {
        notifable = generate.contains(PropertyGenerateFlags::NOTIFY);
    }

    if readable {
        let (has, version) = Signature::has_for_property(env, &check_get_func_name,
                                                         true, prop.typ, signatures, deps);
        if has && (env.is_totally_deprecated(version) || version <= prop_version) {
            readable = false;
        }
    }
    if writable {
        let (has, version) = Signature::has_for_property(env, &check_set_func_name,
                                                         false, prop.typ, signatures, deps);
        if has && (env.is_totally_deprecated(version) || version <= prop_version) {
            writable = false;
        }
    }

    let get_out_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::Return);
    let mut set_in_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::In);
    if set_in_ref_mode == RefMode::ByRefMut {
        set_in_ref_mode = RefMode::ByRef;
    }
    let nullable = library::Nullable(set_in_ref_mode.is_ref());
    let getter = if readable {
        Some(Property {
            name: name.clone(),
            var_name: String::new(),
            typ: prop.typ,
            is_get: true,
            func_name: get_func_name,
            nullable,
            get_out_ref_mode,
            set_in_ref_mode,
            version: prop_version,
            deprecated_version: prop.deprecated_version,
            bound: None,
        })
    } else {
        None
    };

    let setter = if writable {
        let bound = Bound::get_for_property_setter(env, &var_name, prop.typ, nullable);
        Some(Property {
            name: name.clone(),
            var_name,
            typ: prop.typ,
            is_get: false,
            func_name: set_func_name,
            nullable,
            get_out_ref_mode,
            set_in_ref_mode,
            version: prop_version,
            deprecated_version: prop.deprecated_version,
            bound,
        })
    } else {
        None
    };

    let notify_signal = if notifable {
        let mut used_types: Vec<String> = Vec::with_capacity(4);
        let trampoline_name = trampolines::analyze(
            env,
            &library::Signal {
                name: format!("notify::{}", name),
                parameters: Vec::new(),
                ret: library::Parameter {
                    name: "".into(),
                    typ: env
                        .library
                        .find_type(library::INTERNAL_NAMESPACE, "none")
                        .unwrap(),
                    c_type: "none".into(),
                    instance_parameter: false,
                    direction: library::ParameterDirection::Return,
                    transfer: library::Transfer::None,
                    caller_allocates: false,
                    nullable: library::Nullable(false),
                    allow_none: false,
                    array_length: None,
                    is_error: false,
                    doc: None,
                    scope: library::ParameterScope::None,
                    closure: None,
                    destroy: None,
                },
                is_action: false,
                version: prop_version,
                deprecated_version: prop.deprecated_version,
                doc: None,
                doc_deprecated: None,
            },
            type_tid,
            generate_trait,
            &[],
            trampolines,
            obj,
            &mut used_types,
            prop_version,
        );

        if trampoline_name.is_ok() {
            imports.add_used_types(&used_types, prop_version);
            if generate_trait {
                imports.add("glib", prop_version);
                imports.add("glib::object::Downcast", prop_version);
            }
            imports.add("glib::signal::connect", prop_version);
            imports.add("glib::signal::SignalHandlerId", prop_version);
            imports.add("std::mem::transmute", prop_version);
            imports.add("std::boxed::Box as Box_", prop_version);
            imports.add("glib_ffi", prop_version);

            Some(signals::Info {
                connect_name: format!("connect_property_{}_notify", name_for_func),
                signal_name: format!("notify::{}", name),
                trampoline_name,
                action_emit_name: None,
                version: prop_version,
                deprecated_version: prop.deprecated_version,
                doc_hidden: false,
            })
        } else {
            None
        }
    } else {
        None
    };

    (getter, setter, notify_signal)
}
<commit_msg>Fix missing glib<commit_after>use analysis::bounds::Bound;
use analysis::imports::Imports;
use analysis::ref_mode::RefMode;
use analysis::rust_type::*;
use analysis::signals;
use analysis::signatures::{Signature, Signatures};
use analysis::trampolines;
use config::{self, GObject, PropertyGenerateFlags};
use env::Env;
use library;
use nameutil;
use traits::*;
use version::Version;

#[derive(Debug)]
pub struct Property {
    pub name: String,
    pub var_name: String,
    pub typ: library::TypeId,
    pub is_get: bool,
    pub func_name: String,
    pub nullable: library::Nullable,
    pub get_out_ref_mode: RefMode,
    pub set_in_ref_mode: RefMode,
    pub version: Option<Version>,
    pub deprecated_version: Option<Version>,
    pub bound: Option<Bound>,
}

pub fn analyze(
    env: &Env,
    props: &[library::Property],
    type_tid: library::TypeId,
    generate_trait: bool,
    trampolines: &mut trampolines::Trampolines,
    obj: &GObject,
    imports: &mut Imports,
    signatures: &Signatures,
    deps: &[library::TypeId],
) -> (Vec<Property>, Vec<signals::Info>) {
    let mut properties = Vec::new();
    let mut notify_signals = Vec::new();

    for prop in props {
        let configured_properties = obj.properties.matched(&prop.name);
        if configured_properties.iter().any(|f| f.ignore) {
            continue;
        }

        if env.is_totally_deprecated(prop.deprecated_version) {
            continue;
        }

        let (getter, setter, notify_signal) = analyze_property(
            env,
            prop,
            type_tid,
            &configured_properties,
            generate_trait,
            trampolines,
            obj,
            imports,
            signatures,
            deps,
        );

        if let Some(notify_signal) = notify_signal {
            notify_signals.push(notify_signal);
        }

        if getter.is_none() && setter.is_none() {
            continue;
        }

        let type_string = rust_type(env, prop.typ);
        let used_type_string = used_rust_type(env, prop.typ);
        if let Some(prop) = getter {
            if let Ok(ref s) = used_type_string {
                imports.add_used_type(s, prop.version);
            }
            if type_string.is_ok() {
                imports.add("gobject_ffi", prop.version);
                imports.add("glib::Value", prop.version);
                imports.add("glib::StaticType", prop.version);
            }

            properties.push(prop);
        }
        if let Some(prop) = setter {
            if let Ok(ref s) = used_type_string {
                imports.add_used_type(s, prop.version);
            }
            if type_string.is_ok() {
                imports.add("gobject_ffi", prop.version);
                imports.add("glib::Value", prop.version);
            }

            if let Some(ref bound, ..) = prop.bound {
                imports.add("glib", prop.version);
                if bound.bound_type.need_isa() {
                    imports.add("glib::object::IsA", prop.version);
                }
            }

            properties.push(prop);
        }
    }

    (properties, notify_signals)
}

fn analyze_property(
    env: &Env,
    prop: &library::Property,
    type_tid: library::TypeId,
    configured_properties: &[&config::properties::Property],
    generate_trait: bool,
    trampolines: &mut trampolines::Trampolines,
    obj: &GObject,
    imports: &mut Imports,
    signatures: &Signatures,
    deps: &[library::TypeId],
) -> (Option<Property>, Option<Property>, Option<signals::Info>) {
    let type_name = type_tid.full_name(&env.library);
    let name = prop.name.clone();

    let prop_version = configured_properties
        .iter()
        .filter_map(|f| f.version)
        .min()
        .or(prop.version)
        .or_else(|| Some(env.config.min_cfg_version));
    let generate = configured_properties
        .iter()
        .filter_map(|f| f.generate)
        .next();
    let generate_set = generate.is_some();
    let generate = generate.unwrap_or_else(PropertyGenerateFlags::all);

    let name_for_func = nameutil::signal_to_snake(&name);
    let var_name = nameutil::mangle_keywords(&*name_for_func).into_owned();
    let get_func_name = format!("get_property_{}", name_for_func);
    let set_func_name = format!("set_property_{}", name_for_func);
    let check_get_func_name = format!("get_{}", name_for_func);
    let check_set_func_name = format!("set_{}", name_for_func);

    let mut readable = prop.readable;
    let mut writable = if prop.construct_only {
        false
    } else {
        prop.writable
    };
    let mut notifable = !prop.construct_only;
    if generate_set && generate.contains(PropertyGenerateFlags::GET) && !readable {
        warn!(
            "Attempt to generate getter for notreadable property \"{}.{}\"",
            type_name, name
        );
    }
    if generate_set && generate.contains(PropertyGenerateFlags::SET) && !writable {
        warn!(
            "Attempt to generate setter for nonwritable property \"{}.{}\"",
            type_name, name
        );
    }
    readable &= generate.contains(PropertyGenerateFlags::GET);
    writable &= generate.contains(PropertyGenerateFlags::SET);
    if generate_set {
        notifable = generate.contains(PropertyGenerateFlags::NOTIFY);
    }

    if readable {
        let (has, version) = Signature::has_for_property(env, &check_get_func_name,
                                                         true, prop.typ, signatures, deps);
        if has && (env.is_totally_deprecated(version) || version <= prop_version) {
            readable = false;
        }
    }
    if writable {
        let (has, version) = Signature::has_for_property(env, &check_set_func_name,
                                                         false, prop.typ, signatures, deps);
        if has && (env.is_totally_deprecated(version) || version <= prop_version) {
            writable = false;
        }
    }

    let get_out_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::Return);
    let mut set_in_ref_mode = RefMode::of(env, prop.typ, library::ParameterDirection::In);
    if set_in_ref_mode == RefMode::ByRefMut {
        set_in_ref_mode = RefMode::ByRef;
    }
    let nullable = library::Nullable(set_in_ref_mode.is_ref());
    let getter = if readable {
        Some(Property {
            name: name.clone(),
            var_name: String::new(),
            typ: prop.typ,
            is_get: true,
            func_name: get_func_name,
            nullable,
            get_out_ref_mode,
            set_in_ref_mode,
            version: prop_version,
            deprecated_version: prop.deprecated_version,
            bound: None,
        })
    } else {
        None
    };

    let setter = if writable {
        let bound = Bound::get_for_property_setter(env, &var_name, prop.typ, nullable);
        Some(Property {
            name: name.clone(),
            var_name,
            typ: prop.typ,
            is_get: false,
            func_name: set_func_name,
            nullable,
            get_out_ref_mode,
            set_in_ref_mode,
            version: prop_version,
            deprecated_version: prop.deprecated_version,
            bound,
        })
    } else {
        None
    };

    let notify_signal = if notifable {
        let mut used_types: Vec<String> = Vec::with_capacity(4);
        let trampoline_name = trampolines::analyze(
            env,
            &library::Signal {
                name: format!("notify::{}", name),
                parameters: Vec::new(),
                ret: library::Parameter {
                    name: "".into(),
                    typ: env
                        .library
                        .find_type(library::INTERNAL_NAMESPACE, "none")
                        .unwrap(),
                    c_type: "none".into(),
                    instance_parameter: false,
                    direction: library::ParameterDirection::Return,
                    transfer: library::Transfer::None,
                    caller_allocates: false,
                    nullable: library::Nullable(false),
                    allow_none: false,
                    array_length: None,
                    is_error: false,
                    doc: None,
                    scope: library::ParameterScope::None,
                    closure: None,
                    destroy: None,
                },
                is_action: false,
                version: prop_version,
                deprecated_version: prop.deprecated_version,
                doc: None,
                doc_deprecated: None,
            },
            type_tid,
            generate_trait,
            &[],
            trampolines,
            obj,
            &mut used_types,
            prop_version,
        );

        if trampoline_name.is_ok() {
            imports.add_used_types(&used_types, prop_version);
            if generate_trait {
                imports.add("glib", prop_version);
                imports.add("glib::object::Downcast", prop_version);
            }
            imports.add("glib::signal::connect", prop_version);
            imports.add("glib::signal::SignalHandlerId", prop_version);
            imports.add("std::mem::transmute", prop_version);
            imports.add("std::boxed::Box as Box_", prop_version);
            imports.add("glib_ffi", prop_version);

            Some(signals::Info {
                connect_name: format!("connect_property_{}_notify", name_for_func),
                signal_name: format!("notify::{}", name),
                trampoline_name,
                action_emit_name: None,
                version: prop_version,
                deprecated_version: prop.deprecated_version,
                doc_hidden: false,
            })
        } else {
            None
        }
    } else {
        None
    };

    (getter, setter, notify_signal)
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::{cell::RefCell, rc::Rc};

use dbus::Connection;

use crate::{
    dbus_api::{consts, util::prop_changed_dispatch},
    engine::{EngineEvent, EngineListener, MaybeDbusPath},
};

#[derive(Debug)]
pub struct EventHandler {
    dbus_conn: Rc<RefCell<Connection>>,
}

impl EventHandler {
    pub fn new(dbus_conn: Rc<RefCell<Connection>>) -> EventHandler {
        EventHandler { dbus_conn }
    }
}

impl EngineListener for EventHandler {
    fn notify(&self, event: &EngineEvent) {
        match *event {
            EngineEvent::BlockdevStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::BLOCKDEV_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::BLOCKDEV_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        error!(
                            "BlockdevStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
            EngineEvent::FilesystemRenamed {
                dbus_path,
                from,
                to,
            } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::FILESYSTEM_NAME_PROP,
                        to.to_string(),
                        &dbus_path,
                        consts::FILESYSTEM_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        error!(
                            "FilesystemRenamed: {} from: {} to: {} failed to send dbus update.",
                            dbus_path, from, to,
                        );
                    });
                }
            }
            EngineEvent::PoolExtendStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_EXTEND_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        error!(
                            "PoolExtendStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
            EngineEvent::PoolRenamed {
                dbus_path,
                from,
                to,
            } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_NAME_PROP,
                        to.to_string(),
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        error!(
                            "PoolRenamed: {} from: {} to: {} failed to send dbus update.",
                            dbus_path, from, to,
                        );
                    });
                }
            }
            EngineEvent::PoolSpaceStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_SPACE_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        error!(
                            "PoolSpaceStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
            EngineEvent::PoolStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        error!(
                            "PoolStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
        }
    }
}
<commit_msg>Change error log messages to warn log messages<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

use std::{cell::RefCell, rc::Rc};

use dbus::Connection;

use crate::{
    dbus_api::{consts, util::prop_changed_dispatch},
    engine::{EngineEvent, EngineListener, MaybeDbusPath},
};

#[derive(Debug)]
pub struct EventHandler {
    dbus_conn: Rc<RefCell<Connection>>,
}

impl EventHandler {
    pub fn new(dbus_conn: Rc<RefCell<Connection>>) -> EventHandler {
        EventHandler { dbus_conn }
    }
}

impl EngineListener for EventHandler {
    fn notify(&self, event: &EngineEvent) {
        match *event {
            EngineEvent::BlockdevStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::BLOCKDEV_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::BLOCKDEV_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        warn!(
                            "BlockdevStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
            EngineEvent::FilesystemRenamed {
                dbus_path,
                from,
                to,
            } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::FILESYSTEM_NAME_PROP,
                        to.to_string(),
                        &dbus_path,
                        consts::FILESYSTEM_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        warn!(
                            "FilesystemRenamed: {} from: {} to: {} failed to send dbus update.",
                            dbus_path, from, to,
                        );
                    });
                }
            }
            EngineEvent::PoolExtendStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_EXTEND_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        warn!(
                            "PoolExtendStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
            EngineEvent::PoolRenamed {
                dbus_path,
                from,
                to,
            } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_NAME_PROP,
                        to.to_string(),
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        warn!(
                            "PoolRenamed: {} from: {} to: {} failed to send dbus update.",
                            dbus_path, from, to,
                        );
                    });
                }
            }
            EngineEvent::PoolSpaceStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_SPACE_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        warn!(
                            "PoolSpaceStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
            EngineEvent::PoolStateChanged { dbus_path, state } => {
                if let MaybeDbusPath(Some(ref dbus_path)) = *dbus_path {
                    prop_changed_dispatch(
                        &self.dbus_conn.borrow(),
                        consts::POOL_STATE_PROP,
                        state as u16,
                        &dbus_path,
                        consts::POOL_INTERFACE_NAME,
                    )
                    .unwrap_or_else(|()| {
                        warn!(
                            "PoolStateChanged: {} state: {} failed to send dbus update.",
                            dbus_path, state as u16,
                        );
                    });
                }
            }
        }
    }
}
<|endoftext|>