<commit_before>//! Utilities for working with the WTF-8 encoding.
//!
//! WTF-8 is a hack that allows to represent potentially ill-formed UTF-16 byte
//! strings in a way somewhat compatible with UTF-8. Such bugs UTF-16 strings
//! can be found for example in Windows paths for legacy reasons.
//!
//! See the official [WTF-8][wtf8] specification for more information.
//!
//! [wtf8]: https://simonsapin.github.io/wtf-8

/// Converts the given potentially ill-formed UTF-16 into a WTF-8 byte sequence.
///
/// If the given byte sequence is a well-formed UTF-16 string, then the result
/// is guaranteed to be a valid UTF-8string.
pub fn from_ill_formed_utf16<I>(units: I) -> Vec<u8>
where
    I: Iterator<Item = u16>,
{
    let mut res = Vec::new();

    let mut iter = units.peekable();
    loop {
        let unit = match iter.next() {
            Some(unit) => unit,
            None => return res,
        };

        // Potentially ill-formed UTF-16 to code point conversion based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16
        let mut point = unit as u32;
        if is_lead_surrogate(&unit) {
            if let Some(next) = iter.next_if(is_trail_surrogate) {
                let lead = (unit as u32 - 0xD800) << 10;
                let trail = next as u32 - 0xDC00;
                point = 0x10000 + lead + trail;
            }
        }

        // Code point to WTF-8 encoding procedure based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#encoding-wtf-8
        match point {
            0x0000..=0x007F => {
                res.push(point as u8);
            }
            0x0080..=0x07FF => {
                res.push(0xC0 | (point >> 6) as u8);
                res.push(0x80 | (point & 0x3F) as u8);
            }
            0x0800..=0xFFFF => {
                res.push(0xE0 | (point >> 12) as u8);
                res.push(0x80 | ((point >> 6) as u8 & 0x3F));
                res.push(0x80 | (point & 0x3F) as u8);
            }
            0x10000..=0x10FFFF => {
                res.push(0xF0 | (point >> 18) as u8);
                res.push(0x80 | ((point >> 12) as u8 & 0x3F));
                res.push(0x80 | ((point >> 6) as u8 & 0x3F));
                res.push(0x80 | (point & 0x3F) as u8);
            }
            _ => unreachable!(), // Not possible by construction.
        }
    }
}

/// Converts the given WTF-8 byte sequence into potentially ill-formed UTF-16.
///
/// If the given byte sequence is a valid UTF-8 string, then the result is
/// guaranteed to be a well-formed UTF-16 string.
///
/// If the input is not a valid WTF-8 byte sequence, an error is returned.
pub fn into_ill_formed_utf16<I>(units: I) -> Result<Vec<u16>, ParseError>
where
    I: Iterator<Item = u8>
{
    let mut res = Vec::new();

    let mut iter = units.peekable();
    loop {
        let byte1 = match iter.next() {
            Some(byte1) => byte1,
            None => return Ok(res),
        };

        // TODO: Add support for proper error handling.

        // WTF-8 to a code point decoding procedure based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#decoding-wtf-8
        let mut point = 0;
        match byte1 {
            0x00..=0x7F => {
                point += byte1 as u32;
            }
            0xC2..=0xDF => {
                let byte2 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                point += ((byte1 & 0x1F) as u32) << 6;
                point += ((byte2 & 0x3F) as u32) << 0;
            }
            0xE0..=0xEF => {
                let byte2 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                let byte3 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                point += ((byte1 & 0x0F) as u32) << 12;
                point += ((byte2 & 0x3F) as u32) << 6;
                point += ((byte3 & 0x3F) as u32) << 0;
            }
            0xF0..=0xF4 => {
                let byte2 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                let byte3 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                let byte4 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                point += ((byte1 & 0x07) as u32) << 18;
                point += ((byte2 & 0x3F) as u32) << 12;
                point += ((byte3 & 0x3F) as u32) << 6;
                point += ((byte4 & 0x3F) as u32) << 0;
            }
            _ => return Err(ParseError::IllegalByte(byte1)),
        }

        // Code point to potentially ill-formed UTF-16 coversion based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#encoding-ill-formed-utf-16
        if is_supplementary(&point) {
            res.push(((point - 0x10000) >> 10) as u16 + 0xD800);
            res.push(((point - 0x10000) as u16 & 0x3FF) + 0xDC00);
        } else {
            res.push(point as u16);
        }
    }
}

/// Determines whether the given UTF-16 code unit is a lead surrogate.
#[inline]
fn is_lead_surrogate(unit: &u16) -> bool {
    matches!(unit, 0xD800..=0xDBFF)
}

/// Determines whether the given UTF-16 code unit is a trail surrogate.
#[inline]
fn is_trail_surrogate(unit: &u16) -> bool {
    matches!(unit, 0xDC00..=0xDFFF)
}

/// Determines whether the given code point is in the supplementary.
#[inline]
fn is_supplementary(point: &u32) -> bool {
    matches!(point, 0x10000..=0x10FFFF)
}

/// An error type for failures related to WTF-8 parsing.
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
    /// The input ended but more bytes were expected.
    UnexpectedEnd,
    /// There was an illegal byte in the input.
    IllegalByte(u8),
}

impl std::fmt::Display for ParseError {

    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(fmt, "invalid wtf-8: ")?;

        use ParseError::*;
        match self {
            UnexpectedEnd => write!(fmt, "unexpected end"),
            IllegalByte(byte) => write!(fmt, "illegal byte: {:#02x}", byte),
        }
    }
}

impl std::error::Error for ParseError {

    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use quickcheck_macros::quickcheck;

    #[test]
    fn from_empty() {
        assert_eq!(from_ill_formed_utf16(std::iter::empty()), b"");
    }

    #[test]
    fn from_small_bytes() {
        let bytes_utf16 = vec![0x04, 0x08, 0x16, 0x23, 0x42];
        let bytes_wtf8 = from_ill_formed_utf16(bytes_utf16.into_iter());
        assert_eq!(bytes_wtf8, b"\x04\x08\x16\x23\x42");
    }

    #[test]
    fn from_bmp_bytes() {
        let bytes_utf16 = vec![0xABCD, 0xBEEF, 0xFFFF];
        let bytes_wtf8 = from_ill_formed_utf16(bytes_utf16.into_iter());
        assert_eq!(bytes_wtf8, b"\xEA\xAF\x8D\xEB\xBB\xAF\xEF\xBF\xBF");
    }

    #[test]
    fn from_sup_bytes() {
        let bytes_utf16 = vec![0xD800, 0xDC00, 0xDBFF, 0xDFFF];
        let bytes_wtf8 = from_ill_formed_utf16(bytes_utf16.into_iter());
        assert_eq!(bytes_wtf8, b"\xF0\x90\x80\x80\xF4\x8F\xBF\xBF");
    }

    #[test]
    fn from_ascii_string() {
        let string = "foo bar baz";
        let string_wtf8 = from_ill_formed_utf16(string.encode_utf16());
        let string_utf8 = string.as_bytes();
        assert_eq!(string_wtf8, string_utf8);
    }

    #[test]
    fn from_unicode_string() {
        let string = "zażółć gęślą jaźń";
        let string_wtf8 = from_ill_formed_utf16(string.encode_utf16());
        let string_utf8 = string.as_bytes();
        assert_eq!(string_wtf8, string_utf8);
    }

    #[test]
    fn into_empty() {
        assert_eq!(into_ill_formed_utf16(std::iter::empty()), Ok(vec![]));
    }

    #[test]
    fn into_ascii_string() {
        let string = "foo bar baz";
        let string_wtf8 = into_ill_formed_utf16(string.bytes());
        let string_utf16 = string.encode_utf16().collect::<Vec<_>>();
        assert_eq!(string_wtf8, Ok(string_utf16));
    }

    #[test]
    fn into_unicode_string() {
        let string = "zażółć gęślą jaźń";
        let string_wtf8 = into_ill_formed_utf16(string.bytes());
        let string_utf16 = string.encode_utf16().collect::<Vec<_>>();
        assert_eq!(string_wtf8, Ok(string_utf16));
    }

    // TODO: Write `into` tests that cover failure scanarios.

    #[quickcheck]
    fn from_any_string(input: String) {
        let units = input.encode_utf16();

        let string_wtf8 = from_ill_formed_utf16(units);
        let string_utf8 = input.as_bytes();
        assert_eq!(string_wtf8, string_utf8);
    }

    #[quickcheck]
    fn into_from_any_string(input: String) {
        let units = input.bytes();

        let string_utf16 = into_ill_formed_utf16(units);
        let string_wtf8 = from_ill_formed_utf16(string_utf16.into_iter());
        assert_eq!(String::from_utf8(string_wtf8).unwrap(), input);
    }

    #[quickcheck]
    fn from_into_any_units(input: Vec<u16>) {
        let units = input.iter().map(|unit| *unit);

        let string_wtf8 = from_ill_formed_utf16(units);
        let string_utf16 = into_ill_formed_utf16(string_wtf8.into_iter());
        assert_eq!(string_utf16, input);
    }
}
<commit_msg>Remove a no longer relevant TODO comment.<commit_after>//! Utilities for working with the WTF-8 encoding.
//!
//! WTF-8 is a hack that allows to represent potentially ill-formed UTF-16 byte
//! strings in a way somewhat compatible with UTF-8. Such bugs UTF-16 strings
//! can be found for example in Windows paths for legacy reasons.
//!
//! See the official [WTF-8][wtf8] specification for more information.
//!
//! [wtf8]: https://simonsapin.github.io/wtf-8

/// Converts the given potentially ill-formed UTF-16 into a WTF-8 byte sequence.
///
/// If the given byte sequence is a well-formed UTF-16 string, then the result
/// is guaranteed to be a valid UTF-8string.
pub fn from_ill_formed_utf16<I>(units: I) -> Vec<u8>
where
    I: Iterator<Item = u16>,
{
    let mut res = Vec::new();

    let mut iter = units.peekable();
    loop {
        let unit = match iter.next() {
            Some(unit) => unit,
            None => return res,
        };

        // Potentially ill-formed UTF-16 to code point conversion based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#decoding-ill-formed-utf-16
        let mut point = unit as u32;
        if is_lead_surrogate(&unit) {
            if let Some(next) = iter.next_if(is_trail_surrogate) {
                let lead = (unit as u32 - 0xD800) << 10;
                let trail = next as u32 - 0xDC00;
                point = 0x10000 + lead + trail;
            }
        }

        // Code point to WTF-8 encoding procedure based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#encoding-wtf-8
        match point {
            0x0000..=0x007F => {
                res.push(point as u8);
            }
            0x0080..=0x07FF => {
                res.push(0xC0 | (point >> 6) as u8);
                res.push(0x80 | (point & 0x3F) as u8);
            }
            0x0800..=0xFFFF => {
                res.push(0xE0 | (point >> 12) as u8);
                res.push(0x80 | ((point >> 6) as u8 & 0x3F));
                res.push(0x80 | (point & 0x3F) as u8);
            }
            0x10000..=0x10FFFF => {
                res.push(0xF0 | (point >> 18) as u8);
                res.push(0x80 | ((point >> 12) as u8 & 0x3F));
                res.push(0x80 | ((point >> 6) as u8 & 0x3F));
                res.push(0x80 | (point & 0x3F) as u8);
            }
            _ => unreachable!(), // Not possible by construction.
        }
    }
}

/// Converts the given WTF-8 byte sequence into potentially ill-formed UTF-16.
///
/// If the given byte sequence is a valid UTF-8 string, then the result is
/// guaranteed to be a well-formed UTF-16 string.
///
/// If the input is not a valid WTF-8 byte sequence, an error is returned.
pub fn into_ill_formed_utf16<I>(units: I) -> Result<Vec<u16>, ParseError>
where
    I: Iterator<Item = u8>
{
    let mut res = Vec::new();

    let mut iter = units.peekable();
    loop {
        let byte1 = match iter.next() {
            Some(byte1) => byte1,
            None => return Ok(res),
        };

        // WTF-8 to a code point decoding procedure based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#decoding-wtf-8
        let mut point = 0;
        match byte1 {
            0x00..=0x7F => {
                point += byte1 as u32;
            }
            0xC2..=0xDF => {
                let byte2 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                point += ((byte1 & 0x1F) as u32) << 6;
                point += ((byte2 & 0x3F) as u32) << 0;
            }
            0xE0..=0xEF => {
                let byte2 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                let byte3 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                point += ((byte1 & 0x0F) as u32) << 12;
                point += ((byte2 & 0x3F) as u32) << 6;
                point += ((byte3 & 0x3F) as u32) << 0;
            }
            0xF0..=0xF4 => {
                let byte2 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                let byte3 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                let byte4 = iter.next().ok_or(ParseError::UnexpectedEnd)?;
                point += ((byte1 & 0x07) as u32) << 18;
                point += ((byte2 & 0x3F) as u32) << 12;
                point += ((byte3 & 0x3F) as u32) << 6;
                point += ((byte4 & 0x3F) as u32) << 0;
            }
            _ => return Err(ParseError::IllegalByte(byte1)),
        }

        // Code point to potentially ill-formed UTF-16 coversion based on [1].
        //
        // [1]: https://simonsapin.github.io/wtf-8/#encoding-ill-formed-utf-16
        if is_supplementary(&point) {
            res.push(((point - 0x10000) >> 10) as u16 + 0xD800);
            res.push(((point - 0x10000) as u16 & 0x3FF) + 0xDC00);
        } else {
            res.push(point as u16);
        }
    }
}

/// Determines whether the given UTF-16 code unit is a lead surrogate.
#[inline]
fn is_lead_surrogate(unit: &u16) -> bool {
    matches!(unit, 0xD800..=0xDBFF)
}

/// Determines whether the given UTF-16 code unit is a trail surrogate.
#[inline]
fn is_trail_surrogate(unit: &u16) -> bool {
    matches!(unit, 0xDC00..=0xDFFF)
}

/// Determines whether the given code point is in the supplementary.
#[inline]
fn is_supplementary(point: &u32) -> bool {
    matches!(point, 0x10000..=0x10FFFF)
}

/// An error type for failures related to WTF-8 parsing.
#[derive(Debug, PartialEq, Eq)]
pub enum ParseError {
    /// The input ended but more bytes were expected.
    UnexpectedEnd,
    /// There was an illegal byte in the input.
    IllegalByte(u8),
}

impl std::fmt::Display for ParseError {

    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(fmt, "invalid wtf-8: ")?;

        use ParseError::*;
        match self {
            UnexpectedEnd => write!(fmt, "unexpected end"),
            IllegalByte(byte) => write!(fmt, "illegal byte: {:#02x}", byte),
        }
    }
}

impl std::error::Error for ParseError {

    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

#[cfg(test)]
mod tests {

    use super::*;

    use quickcheck_macros::quickcheck;

    #[test]
    fn from_empty() {
        assert_eq!(from_ill_formed_utf16(std::iter::empty()), b"");
    }

    #[test]
    fn from_small_bytes() {
        let bytes_utf16 = vec![0x04, 0x08, 0x16, 0x23, 0x42];
        let bytes_wtf8 = from_ill_formed_utf16(bytes_utf16.into_iter());
        assert_eq!(bytes_wtf8, b"\x04\x08\x16\x23\x42");
    }

    #[test]
    fn from_bmp_bytes() {
        let bytes_utf16 = vec![0xABCD, 0xBEEF, 0xFFFF];
        let bytes_wtf8 = from_ill_formed_utf16(bytes_utf16.into_iter());
        assert_eq!(bytes_wtf8, b"\xEA\xAF\x8D\xEB\xBB\xAF\xEF\xBF\xBF");
    }

    #[test]
    fn from_sup_bytes() {
        let bytes_utf16 = vec![0xD800, 0xDC00, 0xDBFF, 0xDFFF];
        let bytes_wtf8 = from_ill_formed_utf16(bytes_utf16.into_iter());
        assert_eq!(bytes_wtf8, b"\xF0\x90\x80\x80\xF4\x8F\xBF\xBF");
    }

    #[test]
    fn from_ascii_string() {
        let string = "foo bar baz";
        let string_wtf8 = from_ill_formed_utf16(string.encode_utf16());
        let string_utf8 = string.as_bytes();
        assert_eq!(string_wtf8, string_utf8);
    }

    #[test]
    fn from_unicode_string() {
        let string = "zażółć gęślą jaźń";
        let string_wtf8 = from_ill_formed_utf16(string.encode_utf16());
        let string_utf8 = string.as_bytes();
        assert_eq!(string_wtf8, string_utf8);
    }

    #[test]
    fn into_empty() {
        assert_eq!(into_ill_formed_utf16(std::iter::empty()), Ok(vec![]));
    }

    #[test]
    fn into_ascii_string() {
        let string = "foo bar baz";
        let string_wtf8 = into_ill_formed_utf16(string.bytes());
        let string_utf16 = string.encode_utf16().collect::<Vec<_>>();
        assert_eq!(string_wtf8, Ok(string_utf16));
    }

    #[test]
    fn into_unicode_string() {
        let string = "zażółć gęślą jaźń";
        let string_wtf8 = into_ill_formed_utf16(string.bytes());
        let string_utf16 = string.encode_utf16().collect::<Vec<_>>();
        assert_eq!(string_wtf8, Ok(string_utf16));
    }

    // TODO: Write `into` tests that cover failure scanarios.

    #[quickcheck]
    fn from_any_string(input: String) {
        let units = input.encode_utf16();

        let string_wtf8 = from_ill_formed_utf16(units);
        let string_utf8 = input.as_bytes();
        assert_eq!(string_wtf8, string_utf8);
    }

    #[quickcheck]
    fn into_from_any_string(input: String) {
        let units = input.bytes();

        let string_utf16 = into_ill_formed_utf16(units);
        let string_wtf8 = from_ill_formed_utf16(string_utf16.into_iter());
        assert_eq!(String::from_utf8(string_wtf8).unwrap(), input);
    }

    #[quickcheck]
    fn from_into_any_units(input: Vec<u16>) {
        let units = input.iter().map(|unit| *unit);

        let string_wtf8 = from_ill_formed_utf16(units);
        let string_utf16 = into_ill_formed_utf16(string_wtf8.into_iter());
        assert_eq!(string_utf16, input);
    }
}
<|endoftext|>


<commit_before>use crate::result::*;
use crate::sign::Signature;
use chrono::Utc;
use rand::{thread_rng, Rng};
use reqwest;
use reqwest::header::HeaderMap;
pub use reqwest::{Method, Request, RequestBuilder, Response, StatusCode, Url};
use std::sync::RwLock;
use std::time::{Duration, Instant};

const BASE_URL: &'static str = "https://marketplace.walmartapis.com";

#[derive(Debug, Clone, Copy)]
pub enum WalmartMarketplace {
  USA,
  Canada,
}

pub enum WalmartCredential {
  TokenApi {
    client_id: String,
    client_secret: String,
  },
  Signature {
    channel_type: String,
    consumer_id: String,
    private_key: String,
  },
}

enum AuthState {
  TokenApi {
    client_id: String,
    client_secret: String,
    bearer_token: RwLock<Option<BearerToken>>,
  },
  Signature {
    channel_type: String,
    signature: Signature,
  },
}

struct BearerToken {
  access_token: String,
  expires_at: Instant,
}

pub trait ExtendUrlParams {
  fn extend_url_params(self, url: &mut Url);
}

impl ExtendUrlParams for () {
  fn extend_url_params(self, _: &mut Url) {}
}

impl<'a> ExtendUrlParams for &'a str {
  fn extend_url_params(self, url: &mut Url) {
    url.set_query(Some(self));
  }
}

impl<'a> ExtendUrlParams for String {
  fn extend_url_params(self, url: &mut Url) {
    if !self.is_empty() {
      url.set_query(Some(self.as_ref()));
    }
  }
}

impl<T1: AsRef<str>, T2: AsRef<str>> ExtendUrlParams for Vec<(T1, T2)> {
  fn extend_url_params(self, url: &mut Url) {
    url.query_pairs_mut().extend_pairs(self);
  }
}

pub struct Client {
  marketplace: WalmartMarketplace,
  base_url: Url,
  auth_state: AuthState,
  http: reqwest::Client,
}

impl Client {
  pub fn new(
    marketplace: WalmartMarketplace,
    credential: WalmartCredential,
  ) -> WalmartResult<Client> {
    let http = reqwest::Client::new();
    Client::with_http_client(marketplace, credential, http)
  }

  pub fn with_http_client(
    marketplace: WalmartMarketplace,
    credential: WalmartCredential,
    http: reqwest::Client,
  ) -> WalmartResult<Client> {
    Ok(Client {
      marketplace,
      base_url: Url::parse(BASE_URL)?,
      auth_state: match credential {
        WalmartCredential::Signature {
          consumer_id,
          private_key,
          channel_type,
        } => AuthState::Signature {
          signature: Signature::new(&consumer_id, &private_key)?,
          channel_type,
        },
        WalmartCredential::TokenApi {
          client_id,
          client_secret,
        } => AuthState::TokenApi {
          client_id,
          client_secret,
          bearer_token: RwLock::new(None),
        },
      },
      http,
    })
  }

  fn request<P>(&self, method: Method, path: &str, params: P) -> WalmartResult<RequestBuilder>
  where
    P: ExtendUrlParams,
  {
    let mut url = match self.marketplace {
      WalmartMarketplace::USA => self.base_url.join(path)?,
      WalmartMarketplace::Canada => {
        // add `ca` to url
        let path = path
          .split('/')
          .enumerate()
          .map(|(i, seg)| {
            if i == 1 {
              format!("{}/ca", seg)
            } else {
              seg.to_string()
            }
          })
          .collect::<Vec<String>>()
          .join("/");
        self.base_url.join(&path)?
      }
    };
    params.extend_url_params(&mut url);

    debug!("request: method = {}, url = {}", method, url);

    let timestamp = Utc::now();
    let timestamp = timestamp.timestamp() * 1000 + timestamp.timestamp_subsec_millis() as i64;

    let mut req = self.http.request(method.clone(), url.as_str());

    let mut headers = HeaderMap::new();
    let rid: String = thread_rng().gen_ascii_chars().take(10).collect();
    headers.insert("WM_SVC.NAME", "Walmart Marketplace".parse()?);
    headers.insert("WM_QOS.CORRELATION_ID", rid.parse()?);
    headers.insert("WM_SEC.TIMESTAMP", timestamp.to_string().parse()?);

    match self.auth_state {
      AuthState::Signature {
        ref channel_type,
        ref signature,
      } => {
        let sign = signature.sign(url.as_str(), method.clone(), timestamp)?;
        debug!("auth: Signature: sign = {}", sign);
        headers.insert("WM_CONSUMER.CHANNEL.TYPE", channel_type.parse()?);
        headers.insert("WM_CONSUMER.ID", signature.consumer_id().parse()?);
        headers.insert("WM_SEC.AUTH_SIGNATURE", sign.parse()?);
      }
      AuthState::TokenApi {
        ref client_id,
        ref client_secret,
        ..
      } => {
        let access_token = self.get_access_token(false)?;
        debug!("auth: TokenApi: access_token = {}", access_token);
        headers.insert("WM_SEC.ACCESS_TOKEN", access_token.parse()?);
        req = req.basic_auth(client_id, Some(client_secret));
      }
    }
    let req = req.headers(headers);
    Ok(req)
  }

  fn clear_access_token(&self) {
    match self.auth_state {
      AuthState::TokenApi {
        ref bearer_token, ..
      } => {
        bearer_token.write().unwrap().take();
      }
      _ => {}
    }
  }

  fn get_access_token(&self, force_renew: bool) -> WalmartResult<String> {
    use std::collections::HashMap;
    #[derive(Debug, Deserialize)]
    struct WalmartBearerToken {
      access_token: String,
      token_type: String,
      expires_in: u64,
    }

    match self.auth_state {
      AuthState::TokenApi {
        ref bearer_token,
        ref client_id,
        ref client_secret,
      } => {
        if !force_renew {
          let lock = bearer_token.read().unwrap();
          if let Some(ref token) = lock.as_ref() {
            if token.expires_at.saturating_duration_since(Instant::now()) > Duration::from_secs(120)
            {
              return Ok(token.access_token.clone());
            }
          }
          drop(lock);
        }

        let mut form = HashMap::new();
        form.insert("grant_type", "client_credentials");

        let mut headers = HeaderMap::new();
        let rid: String = thread_rng().gen_ascii_chars().take(10).collect();
        headers.insert("WM_SVC.NAME", "Walmart Marketplace".parse()?);
        headers.insert("WM_QOS.CORRELATION_ID", rid.parse()?);
        headers.insert("Accept", "application/json".parse()?);

        let mut res = self
          .http
          .request(Method::POST, &format!("{}/v3/token", BASE_URL))
          .headers(headers)
          .form(&form)
          .basic_auth(client_id, Some(client_secret))
          .send()?;

        let token: WalmartBearerToken = res.json()?;
        let access_token = token.access_token.clone();

        if token.token_type != "Bearer" {
          return Err(WalmartError::Msg(format!(
            "unsupported token type: {}",
            token.token_type
          )));
        }

        debug!("token: {:#?}", token);

        let mut lock = bearer_token.write().unwrap();
        std::mem::replace(
          &mut lock as &mut Option<_>,
          Some(BearerToken {
            access_token: token.access_token,
            expires_at: Instant::now() + Duration::from_secs(token.expires_in),
          }),
        );

        Ok(access_token)
      }
      _ => {
        return Err(WalmartError::Msg(
          "cannot get bearer with Signature Authentication".to_string(),
        ))
      }
    }
  }

  pub fn request_json<P>(
    &self,
    method: Method,
    path: &str,
    params: P,
  ) -> WalmartResult<RequestBuilder>
  where
    P: ExtendUrlParams,
  {
    use reqwest::header::{HeaderValue, ACCEPT};

    self
      .request(method, path, params)
      .map(|req| req.header(ACCEPT, HeaderValue::from_static("application/json")))
  }

  pub fn request_xml<P>(
    &self,
    method: Method,
    path: &str,
    params: P,
  ) -> WalmartResult<RequestBuilder>
  where
    P: ExtendUrlParams,
  {
    use reqwest::header::{HeaderValue, ACCEPT};

    self
      .request(method, path, params)
      .map(|req| req.header(ACCEPT, HeaderValue::from_static("application/xml")))
  }

  pub fn send(&self, req: RequestBuilder) -> WalmartResult<Response> {
    match req.send() {
      Ok(res) => {
        if res.status() == StatusCode::UNAUTHORIZED {
          self.clear_access_token();
        }
        Ok(res)
      }
      Err(err) => Err(err.into()),
    }
  }

  pub(crate) fn get_marketplace(&self) -> WalmartMarketplace {
    self.marketplace
  }
}

// #[cfg(test)]
// mod tests {
//   use super::*;
//   use dotenv::dotenv;
//   use std::env;

//   #[test]
//   fn client() {
//     use std::io::Read;
//     dotenv().ok();

//     let client = Client::new(&env::var("WALMART_CONSUMER_ID").unwrap(), &env::var("WALMART_PRIVATE_KEY").unwrap()).unwrap();
//     let mut res = client.request_json(Method::Get, "/v3/feeds/117E39F0B7654B08A059457FB6E803FF@AQYBAAA", ()).unwrap().send().unwrap();
//     println!("status: {}", res.status());
//     let mut json = String::new();
//     res.read_to_string(&mut json).unwrap();
//     println!("body: {}", json);
//     {
//       use std::fs::File;
//       use std::io::Write;
//       let mut f = File::create("samples/get_feed_aand_item_status.json").unwrap();
//       write!(&mut f, "{}", json).unwrap()
//     }
//   }
// }
<commit_msg>refactor: expose client.get_marketplace<commit_after>use crate::result::*;
use crate::sign::Signature;
use chrono::Utc;
use rand::{thread_rng, Rng};
use reqwest;
use reqwest::header::HeaderMap;
pub use reqwest::{Method, Request, RequestBuilder, Response, StatusCode, Url};
use std::sync::RwLock;
use std::time::{Duration, Instant};

const BASE_URL: &'static str = "https://marketplace.walmartapis.com";

#[derive(Debug, Clone, Copy)]
pub enum WalmartMarketplace {
  USA,
  Canada,
}

pub enum WalmartCredential {
  TokenApi {
    client_id: String,
    client_secret: String,
  },
  Signature {
    channel_type: String,
    consumer_id: String,
    private_key: String,
  },
}

enum AuthState {
  TokenApi {
    client_id: String,
    client_secret: String,
    bearer_token: RwLock<Option<BearerToken>>,
  },
  Signature {
    channel_type: String,
    signature: Signature,
  },
}

struct BearerToken {
  access_token: String,
  expires_at: Instant,
}

pub trait ExtendUrlParams {
  fn extend_url_params(self, url: &mut Url);
}

impl ExtendUrlParams for () {
  fn extend_url_params(self, _: &mut Url) {}
}

impl<'a> ExtendUrlParams for &'a str {
  fn extend_url_params(self, url: &mut Url) {
    url.set_query(Some(self));
  }
}

impl<'a> ExtendUrlParams for String {
  fn extend_url_params(self, url: &mut Url) {
    if !self.is_empty() {
      url.set_query(Some(self.as_ref()));
    }
  }
}

impl<T1: AsRef<str>, T2: AsRef<str>> ExtendUrlParams for Vec<(T1, T2)> {
  fn extend_url_params(self, url: &mut Url) {
    url.query_pairs_mut().extend_pairs(self);
  }
}

pub struct Client {
  marketplace: WalmartMarketplace,
  base_url: Url,
  auth_state: AuthState,
  http: reqwest::Client,
}

impl Client {
  pub fn new(
    marketplace: WalmartMarketplace,
    credential: WalmartCredential,
  ) -> WalmartResult<Client> {
    let http = reqwest::Client::new();
    Client::with_http_client(marketplace, credential, http)
  }

  pub fn with_http_client(
    marketplace: WalmartMarketplace,
    credential: WalmartCredential,
    http: reqwest::Client,
  ) -> WalmartResult<Client> {
    Ok(Client {
      marketplace,
      base_url: Url::parse(BASE_URL)?,
      auth_state: match credential {
        WalmartCredential::Signature {
          consumer_id,
          private_key,
          channel_type,
        } => AuthState::Signature {
          signature: Signature::new(&consumer_id, &private_key)?,
          channel_type,
        },
        WalmartCredential::TokenApi {
          client_id,
          client_secret,
        } => AuthState::TokenApi {
          client_id,
          client_secret,
          bearer_token: RwLock::new(None),
        },
      },
      http,
    })
  }

  fn request<P>(&self, method: Method, path: &str, params: P) -> WalmartResult<RequestBuilder>
  where
    P: ExtendUrlParams,
  {
    let mut url = match self.marketplace {
      WalmartMarketplace::USA => self.base_url.join(path)?,
      WalmartMarketplace::Canada => {
        // add `ca` to url
        let path = path
          .split('/')
          .enumerate()
          .map(|(i, seg)| {
            if i == 1 {
              format!("{}/ca", seg)
            } else {
              seg.to_string()
            }
          })
          .collect::<Vec<String>>()
          .join("/");
        self.base_url.join(&path)?
      }
    };
    params.extend_url_params(&mut url);

    debug!("request: method = {}, url = {}", method, url);

    let timestamp = Utc::now();
    let timestamp = timestamp.timestamp() * 1000 + timestamp.timestamp_subsec_millis() as i64;

    let mut req = self.http.request(method.clone(), url.as_str());

    let mut headers = HeaderMap::new();
    let rid: String = thread_rng().gen_ascii_chars().take(10).collect();
    headers.insert("WM_SVC.NAME", "Walmart Marketplace".parse()?);
    headers.insert("WM_QOS.CORRELATION_ID", rid.parse()?);
    headers.insert("WM_SEC.TIMESTAMP", timestamp.to_string().parse()?);

    match self.auth_state {
      AuthState::Signature {
        ref channel_type,
        ref signature,
      } => {
        let sign = signature.sign(url.as_str(), method.clone(), timestamp)?;
        debug!("auth: Signature: sign = {}", sign);
        headers.insert("WM_CONSUMER.CHANNEL.TYPE", channel_type.parse()?);
        headers.insert("WM_CONSUMER.ID", signature.consumer_id().parse()?);
        headers.insert("WM_SEC.AUTH_SIGNATURE", sign.parse()?);
      }
      AuthState::TokenApi {
        ref client_id,
        ref client_secret,
        ..
      } => {
        let access_token = self.get_access_token(false)?;
        debug!("auth: TokenApi: access_token = {}", access_token);
        headers.insert("WM_SEC.ACCESS_TOKEN", access_token.parse()?);
        req = req.basic_auth(client_id, Some(client_secret));
      }
    }
    let req = req.headers(headers);
    Ok(req)
  }

  fn clear_access_token(&self) {
    match self.auth_state {
      AuthState::TokenApi {
        ref bearer_token, ..
      } => {
        bearer_token.write().unwrap().take();
      }
      _ => {}
    }
  }

  fn get_access_token(&self, force_renew: bool) -> WalmartResult<String> {
    use std::collections::HashMap;
    #[derive(Debug, Deserialize)]
    struct WalmartBearerToken {
      access_token: String,
      token_type: String,
      expires_in: u64,
    }

    match self.auth_state {
      AuthState::TokenApi {
        ref bearer_token,
        ref client_id,
        ref client_secret,
      } => {
        if !force_renew {
          let lock = bearer_token.read().unwrap();
          if let Some(ref token) = lock.as_ref() {
            if token.expires_at.saturating_duration_since(Instant::now()) > Duration::from_secs(120)
            {
              return Ok(token.access_token.clone());
            }
          }
          drop(lock);
        }

        let mut form = HashMap::new();
        form.insert("grant_type", "client_credentials");

        let mut headers = HeaderMap::new();
        let rid: String = thread_rng().gen_ascii_chars().take(10).collect();
        headers.insert("WM_SVC.NAME", "Walmart Marketplace".parse()?);
        headers.insert("WM_QOS.CORRELATION_ID", rid.parse()?);
        headers.insert("Accept", "application/json".parse()?);

        let mut res = self
          .http
          .request(Method::POST, &format!("{}/v3/token", BASE_URL))
          .headers(headers)
          .form(&form)
          .basic_auth(client_id, Some(client_secret))
          .send()?;

        let token: WalmartBearerToken = res.json()?;
        let access_token = token.access_token.clone();

        if token.token_type != "Bearer" {
          return Err(WalmartError::Msg(format!(
            "unsupported token type: {}",
            token.token_type
          )));
        }

        debug!("token: {:#?}", token);

        let mut lock = bearer_token.write().unwrap();
        std::mem::replace(
          &mut lock as &mut Option<_>,
          Some(BearerToken {
            access_token: token.access_token,
            expires_at: Instant::now() + Duration::from_secs(token.expires_in),
          }),
        );

        Ok(access_token)
      }
      _ => {
        return Err(WalmartError::Msg(
          "cannot get bearer with Signature Authentication".to_string(),
        ))
      }
    }
  }

  pub fn request_json<P>(
    &self,
    method: Method,
    path: &str,
    params: P,
  ) -> WalmartResult<RequestBuilder>
  where
    P: ExtendUrlParams,
  {
    use reqwest::header::{HeaderValue, ACCEPT};

    self
      .request(method, path, params)
      .map(|req| req.header(ACCEPT, HeaderValue::from_static("application/json")))
  }

  pub fn request_xml<P>(
    &self,
    method: Method,
    path: &str,
    params: P,
  ) -> WalmartResult<RequestBuilder>
  where
    P: ExtendUrlParams,
  {
    use reqwest::header::{HeaderValue, ACCEPT};

    self
      .request(method, path, params)
      .map(|req| req.header(ACCEPT, HeaderValue::from_static("application/xml")))
  }

  pub fn send(&self, req: RequestBuilder) -> WalmartResult<Response> {
    match req.send() {
      Ok(res) => {
        if res.status() == StatusCode::UNAUTHORIZED {
          self.clear_access_token();
        }
        Ok(res)
      }
      Err(err) => Err(err.into()),
    }
  }

  pub fn get_marketplace(&self) -> WalmartMarketplace {
    self.marketplace
  }
}

// #[cfg(test)]
// mod tests {
//   use super::*;
//   use dotenv::dotenv;
//   use std::env;

//   #[test]
//   fn client() {
//     use std::io::Read;
//     dotenv().ok();

//     let client = Client::new(&env::var("WALMART_CONSUMER_ID").unwrap(), &env::var("WALMART_PRIVATE_KEY").unwrap()).unwrap();
//     let mut res = client.request_json(Method::Get, "/v3/feeds/117E39F0B7654B08A059457FB6E803FF@AQYBAAA", ()).unwrap().send().unwrap();
//     println!("status: {}", res.status());
//     let mut json = String::new();
//     res.read_to_string(&mut json).unwrap();
//     println!("body: {}", json);
//     {
//       use std::fs::File;
//       use std::io::Write;
//       let mut f = File::create("samples/get_feed_aand_item_status.json").unwrap();
//       write!(&mut f, "{}", json).unwrap()
//     }
//   }
// }
<|endoftext|>


<commit_before>//! Types and traits for TLS support.
pub use priv_io::Stream;

use std::error::Error;
use std::io::prelude::*;
use std::fmt;

#[cfg(feature = "with-native-tls")]
pub mod native_tls;
#[cfg(feature = "with-openssl")]
pub mod openssl;
#[cfg(feature = "with-schannel")]
pub mod schannel;
#[cfg(feature = "with-security-framework")]
pub mod security_framework;

/// A trait implemented by TLS streams.
pub trait TlsStream: fmt::Debug + Read + Write + Send {
    /// Returns a reference to the underlying `Stream`.
    fn get_ref(&self) -> &Stream;

    /// Returns a mutable reference to the underlying `Stream`.
    fn get_mut(&mut self) -> &mut Stream;
}

/// A trait implemented by types that can initiate a TLS session over a Postgres
/// stream.
pub trait TlsHandshake: fmt::Debug {
    /// Performs a client-side TLS handshake, returning a wrapper around the
    /// provided stream.
    ///
    /// The host portion of the connection parameters is provided for hostname
    /// verification.
    fn tls_handshake(&self,
                     host: &str,
                     stream: Stream)
                     -> Result<Box<TlsStream>, Box<Error + Sync + Send>>;
}
<commit_msg>Enusre `Box<T: TlsHandshake>` implements `TlsHandshake`<commit_after>//! Types and traits for TLS support.
pub use priv_io::Stream;

use std::error::Error;
use std::io::prelude::*;
use std::fmt;

#[cfg(feature = "with-native-tls")]
pub mod native_tls;
#[cfg(feature = "with-openssl")]
pub mod openssl;
#[cfg(feature = "with-schannel")]
pub mod schannel;
#[cfg(feature = "with-security-framework")]
pub mod security_framework;

/// A trait implemented by TLS streams.
pub trait TlsStream: fmt::Debug + Read + Write + Send {
    /// Returns a reference to the underlying `Stream`.
    fn get_ref(&self) -> &Stream;

    /// Returns a mutable reference to the underlying `Stream`.
    fn get_mut(&mut self) -> &mut Stream;
}

/// A trait implemented by types that can initiate a TLS session over a Postgres
/// stream.
pub trait TlsHandshake: fmt::Debug {
    /// Performs a client-side TLS handshake, returning a wrapper around the
    /// provided stream.
    ///
    /// The host portion of the connection parameters is provided for hostname
    /// verification.
    fn tls_handshake(&self,
                     host: &str,
                     stream: Stream)
                     -> Result<Box<TlsStream>, Box<Error + Sync + Send>>;
}

impl<T: TlsHandshake + ?Sized> TlsHandshake for Box<T> {
    fn tls_handshake(&self,
                     host: &str,
                     stream: Stream)
                     -> Result<Box<TlsStream>, Box<Error + Sync + Send>> {
        (**self).tls_handshake(host, stream)
    }
}

<|endoftext|>


<commit_before>use crate::gen::block::Block;
use crate::gen::ifndef;
use crate::gen::out::{Content, OutFile};

#[derive(Default, PartialEq)]
pub struct Builtins<'a> {
    pub panic: bool,
    pub rust_string: bool,
    pub rust_str: bool,
    pub rust_slice: bool,
    pub rust_box: bool,
    pub rust_vec: bool,
    pub rust_fn: bool,
    pub rust_isize: bool,
    pub opaque: bool,
    pub layout: bool,
    pub unsafe_bitcopy: bool,
    pub unsafe_bitcopy_t: bool,
    pub rust_error: bool,
    pub manually_drop: bool,
    pub maybe_uninit: bool,
    pub trycatch: bool,
    pub ptr_len: bool,
    pub repr_fat: bool,
    pub rust_str_new_unchecked: bool,
    pub rust_str_repr: bool,
    pub rust_slice_new: bool,
    pub rust_slice_repr: bool,
    pub exception: bool,
    pub relocatable: bool,
    pub relocatable_or_array: bool,
    pub friend_impl: bool,
    pub is_complete: bool,
    pub destroy: bool,
    pub deleter_if: bool,
    pub content: Content<'a>,
}

impl<'a> Builtins<'a> {
    pub fn new() -> Self {
        Builtins::default()
    }
}

pub(super) fn write(out: &mut OutFile) {
    if out.builtin == Default::default() {
        return;
    }

    let include = &mut out.include;
    let builtin = &mut out.builtin;
    let out = &mut builtin.content;

    if builtin.rust_string {
        include.array = true;
        include.cstdint = true;
        include.string = true;
    }

    if builtin.rust_str {
        include.array = true;
        include.cstdint = true;
        include.string = true;
        builtin.friend_impl = true;
    }

    if builtin.rust_vec {
        include.algorithm = true;
        include.array = true;
        include.cassert = true;
        include.cstddef = true;
        include.cstdint = true;
        include.initializer_list = true;
        include.iterator = true;
        include.new = true;
        include.stdexcept = true;
        include.type_traits = true;
        include.utility = true;
        builtin.panic = true;
        builtin.rust_slice = true;
        builtin.unsafe_bitcopy_t = true;
    }

    if builtin.rust_slice {
        include.array = true;
        include.cassert = true;
        include.cstddef = true;
        include.cstdint = true;
        include.iterator = true;
        include.stdexcept = true;
        include.type_traits = true;
        builtin.friend_impl = true;
        builtin.layout = true;
        builtin.panic = true;
    }

    if builtin.rust_box {
        include.new = true;
        include.type_traits = true;
        include.utility = true;
    }

    if builtin.rust_fn {
        include.utility = true;
    }

    if builtin.rust_error {
        include.exception = true;
        builtin.friend_impl = true;
    }

    if builtin.rust_isize {
        include.basetsd = true;
        include.sys_types = true;
    }

    if builtin.relocatable_or_array {
        include.cstddef = true;
        builtin.relocatable = true;
    }

    if builtin.relocatable {
        include.type_traits = true;
    }

    if builtin.layout {
        include.type_traits = true;
        include.cstddef = true;
        builtin.is_complete = true;
    }

    if builtin.is_complete {
        include.cstddef = true;
        include.type_traits = true;
    }

    if builtin.unsafe_bitcopy {
        builtin.unsafe_bitcopy_t = true;
    }

    out.begin_block(Block::Namespace("rust"));
    out.begin_block(Block::InlineNamespace("cxxbridge1"));

    let cxx_header = include.has_cxx_header();
    if !cxx_header {
        writeln!(out, "// #include \"rust/cxx.h\"");

        ifndef::write(out, builtin.panic, "CXXBRIDGE1_PANIC");

        if builtin.rust_string {
            out.next_section();
            writeln!(out, "struct unsafe_bitcopy_t;");
        }

        if builtin.friend_impl {
            out.begin_block(Block::AnonymousNamespace);
            writeln!(out, "template <typename T>");
            writeln!(out, "class impl;");
            out.end_block(Block::AnonymousNamespace);
        }

        out.next_section();
        if builtin.rust_str && !builtin.rust_string {
            writeln!(out, "class String;");
        }
        if builtin.layout && !builtin.opaque {
            writeln!(out, "class Opaque;");
        }

        if builtin.rust_slice {
            out.next_section();
            writeln!(out, "template <typename T>");
            writeln!(out, "::std::size_t size_of();");
            writeln!(out, "template <typename T>");
            writeln!(out, "::std::size_t align_of();");
        }

        ifndef::write(out, builtin.rust_string, "CXXBRIDGE1_RUST_STRING");
        ifndef::write(out, builtin.rust_str, "CXXBRIDGE1_RUST_STR");
        ifndef::write(out, builtin.rust_slice, "CXXBRIDGE1_RUST_SLICE");
        ifndef::write(out, builtin.rust_box, "CXXBRIDGE1_RUST_BOX");
        ifndef::write(out, builtin.unsafe_bitcopy_t, "CXXBRIDGE1_RUST_BITCOPY_T");
        ifndef::write(out, builtin.unsafe_bitcopy, "CXXBRIDGE1_RUST_BITCOPY");
        ifndef::write(out, builtin.rust_vec, "CXXBRIDGE1_RUST_VEC");
        ifndef::write(out, builtin.rust_fn, "CXXBRIDGE1_RUST_FN");
        ifndef::write(out, builtin.rust_error, "CXXBRIDGE1_RUST_ERROR");
        ifndef::write(out, builtin.rust_isize, "CXXBRIDGE1_RUST_ISIZE");
        ifndef::write(out, builtin.opaque, "CXXBRIDGE1_RUST_OPAQUE");
        ifndef::write(out, builtin.is_complete, "CXXBRIDGE1_IS_COMPLETE");
        ifndef::write(out, builtin.layout, "CXXBRIDGE1_LAYOUT");
        ifndef::write(out, builtin.relocatable, "CXXBRIDGE1_RELOCATABLE");
    }

    if builtin.rust_str_new_unchecked {
        out.next_section();
        writeln!(out, "class Str::uninit {{}};");
        writeln!(out, "inline Str::Str(uninit) noexcept {{}}");
    }

    if builtin.rust_slice_new {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "class Slice<T>::uninit {{}};");
        writeln!(out, "template <typename T>");
        writeln!(out, "inline Slice<T>::Slice(uninit) noexcept {{}}");
    }

    out.begin_block(Block::Namespace("detail"));

    if builtin.maybe_uninit {
        include.cstddef = true;
        include.new = true;
        out.next_section();
        writeln!(out, "template <typename T, typename = void *>");
        writeln!(out, "struct operator_new {{");
        writeln!(
            out,
            "  void *operator()(::std::size_t sz) {{ return ::operator new(sz); }}",
        );
        writeln!(out, "}};");
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(
            out,
            "struct operator_new<T, decltype(T::operator new(sizeof(T)))> {{",
        );
        writeln!(
            out,
            "  void *operator()(::std::size_t sz) {{ return T::operator new(sz); }}",
        );
        writeln!(out, "}};");
    }

    out.end_block(Block::Namespace("detail"));

    if builtin.manually_drop {
        out.next_section();
        include.utility = true;
        writeln!(out, "template <typename T>");
        writeln!(out, "union ManuallyDrop {{");
        writeln!(out, "  T value;");
        writeln!(
            out,
            "  ManuallyDrop(T &&value) : value(::std::move(value)) {{}}",
        );
        writeln!(out, "  ~ManuallyDrop() {{}}");
        writeln!(out, "}};");
    }

    if builtin.maybe_uninit {
        include.cstddef = true;
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "union MaybeUninit {{");
        writeln!(out, "  T value;");
        writeln!(
            out,
            "  void *operator new(::std::size_t sz) {{ return detail::operator_new<T>{{}}(sz); }}",
        );
        writeln!(out, "  MaybeUninit() {{}}");
        writeln!(out, "  ~MaybeUninit() {{}}");
        writeln!(out, "}};");
    }

    if builtin.repr_fat {
        include.array = true;
        include.cstdint = true;
        out.next_section();
        out.begin_block(Block::Namespace("repr"));
        writeln!(out, "using Fat = ::std::array<::std::uintptr_t, 2>;");
        out.end_block(Block::Namespace("repr"));
    }

    if builtin.ptr_len {
        include.cstddef = true;
        out.next_section();
        out.begin_block(Block::Namespace("repr"));
        writeln!(out, "struct PtrLen final {{");
        writeln!(out, "  void *ptr;");
        writeln!(out, "  ::std::size_t len;");
        writeln!(out, "}};");
        out.end_block(Block::Namespace("repr"));
    }

    out.begin_block(Block::AnonymousNamespace);

    if builtin.rust_str_new_unchecked || builtin.rust_str_repr {
        out.next_section();
        writeln!(out, "template <>");
        writeln!(out, "class impl<Str> final {{");
        writeln!(out, "public:");
        if builtin.rust_str_new_unchecked {
            writeln!(
                out,
                "  static Str new_unchecked(repr::Fat repr) noexcept {{",
            );
            writeln!(out, "    Str str = Str::uninit{{}};");
            writeln!(out, "    str.repr = repr;");
            writeln!(out, "    return str;");
            writeln!(out, "  }}");
        }
        if builtin.rust_str_repr {
            writeln!(out, "  static repr::Fat repr(Str str) noexcept {{");
            writeln!(out, "    return str.repr;");
            writeln!(out, "  }}");
        }
        writeln!(out, "}};");
    }

    if builtin.rust_slice_new || builtin.rust_slice_repr {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "class impl<Slice<T>> final {{");
        writeln!(out, "public:");
        if builtin.rust_slice_new {
            writeln!(out, "  static Slice<T> slice(repr::Fat repr) noexcept {{");
            writeln!(out, "    Slice<T> slice = typename Slice<T>::uninit{{}};");
            writeln!(out, "    slice.repr = repr;");
            writeln!(out, "    return slice;");
            writeln!(out, "  }}");
        }
        if builtin.rust_slice_repr {
            writeln!(out, "  static repr::Fat repr(Slice<T> slice) noexcept {{");
            writeln!(out, "    return slice.repr;");
            writeln!(out, "  }}");
        }
        writeln!(out, "}};");
    }

    if builtin.rust_error {
        out.next_section();
        writeln!(out, "template <>");
        writeln!(out, "class impl<Error> final {{");
        writeln!(out, "public:");
        writeln!(out, "  static Error error(repr::PtrLen repr) noexcept {{");
        writeln!(out, "    Error error;");
        writeln!(out, "    error.msg = static_cast<const char *>(repr.ptr);");
        writeln!(out, "    error.len = repr.len;");
        writeln!(out, "    return error;");
        writeln!(out, "  }}");
        writeln!(out, "}};");
    }

    if builtin.destroy {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "void destroy(T *ptr) {{");
        writeln!(out, "  ptr->~T();");
        writeln!(out, "}}");
    }

    if builtin.deleter_if {
        out.next_section();
        writeln!(out, "template <bool> struct deleter_if {{");
        writeln!(out, "  template <typename T> void operator()(T *) {{}}");
        writeln!(out, "}};");
        out.next_section();
        writeln!(out, "template <> struct deleter_if<true> {{");
        writeln!(
            out,
            "  template <typename T> void operator()(T *ptr) {{ ptr->~T(); }}",
        );
        writeln!(out, "}};");
    }

    if builtin.relocatable_or_array {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "struct IsRelocatableOrArray : IsRelocatable<T> {{}};");
        writeln!(out, "template <typename T, ::std::size_t N>");
        writeln!(
            out,
            "struct IsRelocatableOrArray<T[N]> : IsRelocatableOrArray<T> {{}};",
        );
    }

    out.end_block(Block::AnonymousNamespace);
    out.end_block(Block::InlineNamespace("cxxbridge1"));

    if builtin.trycatch {
        out.begin_block(Block::Namespace("behavior"));
        include.exception = true;
        include.type_traits = true;
        include.utility = true;
        writeln!(out, "class missing {{}};");
        writeln!(out, "missing trycatch(...);");
        writeln!(out);
        writeln!(out, "template <typename Try, typename Fail>");
        writeln!(out, "static typename ::std::enable_if<");
        writeln!(
            out,
            "    ::std::is_same<decltype(trycatch(::std::declval<Try>(), ::std::declval<Fail>())),",
        );
        writeln!(out, "                 missing>::value>::type");
        writeln!(out, "trycatch(Try &&func, Fail &&fail) noexcept try {{");
        writeln!(out, "  func();");
        writeln!(out, "}} catch (const ::std::exception &e) {{");
        writeln!(out, "  fail(e.what());");
        writeln!(out, "}}");
        out.end_block(Block::Namespace("behavior"));
    }

    out.end_block(Block::Namespace("rust"));

    if builtin.exception {
        include.cstddef = true;
        out.begin_block(Block::ExternC);
        writeln!(
            out,
            "const char *cxxbridge1$exception(const char *, ::std::size_t);",
        );
        out.end_block(Block::ExternC);
    }
}
<commit_msg>Deduplicate rust::repr namespace begin/end<commit_after>use crate::gen::block::Block;
use crate::gen::ifndef;
use crate::gen::out::{Content, OutFile};

#[derive(Default, PartialEq)]
pub struct Builtins<'a> {
    pub panic: bool,
    pub rust_string: bool,
    pub rust_str: bool,
    pub rust_slice: bool,
    pub rust_box: bool,
    pub rust_vec: bool,
    pub rust_fn: bool,
    pub rust_isize: bool,
    pub opaque: bool,
    pub layout: bool,
    pub unsafe_bitcopy: bool,
    pub unsafe_bitcopy_t: bool,
    pub rust_error: bool,
    pub manually_drop: bool,
    pub maybe_uninit: bool,
    pub trycatch: bool,
    pub ptr_len: bool,
    pub repr_fat: bool,
    pub rust_str_new_unchecked: bool,
    pub rust_str_repr: bool,
    pub rust_slice_new: bool,
    pub rust_slice_repr: bool,
    pub exception: bool,
    pub relocatable: bool,
    pub relocatable_or_array: bool,
    pub friend_impl: bool,
    pub is_complete: bool,
    pub destroy: bool,
    pub deleter_if: bool,
    pub content: Content<'a>,
}

impl<'a> Builtins<'a> {
    pub fn new() -> Self {
        Builtins::default()
    }
}

pub(super) fn write(out: &mut OutFile) {
    if out.builtin == Default::default() {
        return;
    }

    let include = &mut out.include;
    let builtin = &mut out.builtin;
    let out = &mut builtin.content;

    if builtin.rust_string {
        include.array = true;
        include.cstdint = true;
        include.string = true;
    }

    if builtin.rust_str {
        include.array = true;
        include.cstdint = true;
        include.string = true;
        builtin.friend_impl = true;
    }

    if builtin.rust_vec {
        include.algorithm = true;
        include.array = true;
        include.cassert = true;
        include.cstddef = true;
        include.cstdint = true;
        include.initializer_list = true;
        include.iterator = true;
        include.new = true;
        include.stdexcept = true;
        include.type_traits = true;
        include.utility = true;
        builtin.panic = true;
        builtin.rust_slice = true;
        builtin.unsafe_bitcopy_t = true;
    }

    if builtin.rust_slice {
        include.array = true;
        include.cassert = true;
        include.cstddef = true;
        include.cstdint = true;
        include.iterator = true;
        include.stdexcept = true;
        include.type_traits = true;
        builtin.friend_impl = true;
        builtin.layout = true;
        builtin.panic = true;
    }

    if builtin.rust_box {
        include.new = true;
        include.type_traits = true;
        include.utility = true;
    }

    if builtin.rust_fn {
        include.utility = true;
    }

    if builtin.rust_error {
        include.exception = true;
        builtin.friend_impl = true;
    }

    if builtin.rust_isize {
        include.basetsd = true;
        include.sys_types = true;
    }

    if builtin.relocatable_or_array {
        include.cstddef = true;
        builtin.relocatable = true;
    }

    if builtin.relocatable {
        include.type_traits = true;
    }

    if builtin.layout {
        include.type_traits = true;
        include.cstddef = true;
        builtin.is_complete = true;
    }

    if builtin.is_complete {
        include.cstddef = true;
        include.type_traits = true;
    }

    if builtin.unsafe_bitcopy {
        builtin.unsafe_bitcopy_t = true;
    }

    out.begin_block(Block::Namespace("rust"));
    out.begin_block(Block::InlineNamespace("cxxbridge1"));

    let cxx_header = include.has_cxx_header();
    if !cxx_header {
        writeln!(out, "// #include \"rust/cxx.h\"");

        ifndef::write(out, builtin.panic, "CXXBRIDGE1_PANIC");

        if builtin.rust_string {
            out.next_section();
            writeln!(out, "struct unsafe_bitcopy_t;");
        }

        if builtin.friend_impl {
            out.begin_block(Block::AnonymousNamespace);
            writeln!(out, "template <typename T>");
            writeln!(out, "class impl;");
            out.end_block(Block::AnonymousNamespace);
        }

        out.next_section();
        if builtin.rust_str && !builtin.rust_string {
            writeln!(out, "class String;");
        }
        if builtin.layout && !builtin.opaque {
            writeln!(out, "class Opaque;");
        }

        if builtin.rust_slice {
            out.next_section();
            writeln!(out, "template <typename T>");
            writeln!(out, "::std::size_t size_of();");
            writeln!(out, "template <typename T>");
            writeln!(out, "::std::size_t align_of();");
        }

        ifndef::write(out, builtin.rust_string, "CXXBRIDGE1_RUST_STRING");
        ifndef::write(out, builtin.rust_str, "CXXBRIDGE1_RUST_STR");
        ifndef::write(out, builtin.rust_slice, "CXXBRIDGE1_RUST_SLICE");
        ifndef::write(out, builtin.rust_box, "CXXBRIDGE1_RUST_BOX");
        ifndef::write(out, builtin.unsafe_bitcopy_t, "CXXBRIDGE1_RUST_BITCOPY_T");
        ifndef::write(out, builtin.unsafe_bitcopy, "CXXBRIDGE1_RUST_BITCOPY");
        ifndef::write(out, builtin.rust_vec, "CXXBRIDGE1_RUST_VEC");
        ifndef::write(out, builtin.rust_fn, "CXXBRIDGE1_RUST_FN");
        ifndef::write(out, builtin.rust_error, "CXXBRIDGE1_RUST_ERROR");
        ifndef::write(out, builtin.rust_isize, "CXXBRIDGE1_RUST_ISIZE");
        ifndef::write(out, builtin.opaque, "CXXBRIDGE1_RUST_OPAQUE");
        ifndef::write(out, builtin.is_complete, "CXXBRIDGE1_IS_COMPLETE");
        ifndef::write(out, builtin.layout, "CXXBRIDGE1_LAYOUT");
        ifndef::write(out, builtin.relocatable, "CXXBRIDGE1_RELOCATABLE");
    }

    if builtin.rust_str_new_unchecked {
        out.next_section();
        writeln!(out, "class Str::uninit {{}};");
        writeln!(out, "inline Str::Str(uninit) noexcept {{}}");
    }

    if builtin.rust_slice_new {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "class Slice<T>::uninit {{}};");
        writeln!(out, "template <typename T>");
        writeln!(out, "inline Slice<T>::Slice(uninit) noexcept {{}}");
    }

    out.begin_block(Block::Namespace("detail"));

    if builtin.maybe_uninit {
        include.cstddef = true;
        include.new = true;
        out.next_section();
        writeln!(out, "template <typename T, typename = void *>");
        writeln!(out, "struct operator_new {{");
        writeln!(
            out,
            "  void *operator()(::std::size_t sz) {{ return ::operator new(sz); }}",
        );
        writeln!(out, "}};");
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(
            out,
            "struct operator_new<T, decltype(T::operator new(sizeof(T)))> {{",
        );
        writeln!(
            out,
            "  void *operator()(::std::size_t sz) {{ return T::operator new(sz); }}",
        );
        writeln!(out, "}};");
    }

    out.end_block(Block::Namespace("detail"));

    if builtin.manually_drop {
        out.next_section();
        include.utility = true;
        writeln!(out, "template <typename T>");
        writeln!(out, "union ManuallyDrop {{");
        writeln!(out, "  T value;");
        writeln!(
            out,
            "  ManuallyDrop(T &&value) : value(::std::move(value)) {{}}",
        );
        writeln!(out, "  ~ManuallyDrop() {{}}");
        writeln!(out, "}};");
    }

    if builtin.maybe_uninit {
        include.cstddef = true;
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "union MaybeUninit {{");
        writeln!(out, "  T value;");
        writeln!(
            out,
            "  void *operator new(::std::size_t sz) {{ return detail::operator_new<T>{{}}(sz); }}",
        );
        writeln!(out, "  MaybeUninit() {{}}");
        writeln!(out, "  ~MaybeUninit() {{}}");
        writeln!(out, "}};");
    }

    out.begin_block(Block::Namespace("repr"));

    if builtin.repr_fat {
        include.array = true;
        include.cstdint = true;
        out.next_section();
        writeln!(out, "using Fat = ::std::array<::std::uintptr_t, 2>;");
    }

    if builtin.ptr_len {
        include.cstddef = true;
        out.next_section();
        writeln!(out, "struct PtrLen final {{");
        writeln!(out, "  void *ptr;");
        writeln!(out, "  ::std::size_t len;");
        writeln!(out, "}};");
    }

    out.end_block(Block::Namespace("repr"));

    out.begin_block(Block::AnonymousNamespace);

    if builtin.rust_str_new_unchecked || builtin.rust_str_repr {
        out.next_section();
        writeln!(out, "template <>");
        writeln!(out, "class impl<Str> final {{");
        writeln!(out, "public:");
        if builtin.rust_str_new_unchecked {
            writeln!(
                out,
                "  static Str new_unchecked(repr::Fat repr) noexcept {{",
            );
            writeln!(out, "    Str str = Str::uninit{{}};");
            writeln!(out, "    str.repr = repr;");
            writeln!(out, "    return str;");
            writeln!(out, "  }}");
        }
        if builtin.rust_str_repr {
            writeln!(out, "  static repr::Fat repr(Str str) noexcept {{");
            writeln!(out, "    return str.repr;");
            writeln!(out, "  }}");
        }
        writeln!(out, "}};");
    }

    if builtin.rust_slice_new || builtin.rust_slice_repr {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "class impl<Slice<T>> final {{");
        writeln!(out, "public:");
        if builtin.rust_slice_new {
            writeln!(out, "  static Slice<T> slice(repr::Fat repr) noexcept {{");
            writeln!(out, "    Slice<T> slice = typename Slice<T>::uninit{{}};");
            writeln!(out, "    slice.repr = repr;");
            writeln!(out, "    return slice;");
            writeln!(out, "  }}");
        }
        if builtin.rust_slice_repr {
            writeln!(out, "  static repr::Fat repr(Slice<T> slice) noexcept {{");
            writeln!(out, "    return slice.repr;");
            writeln!(out, "  }}");
        }
        writeln!(out, "}};");
    }

    if builtin.rust_error {
        out.next_section();
        writeln!(out, "template <>");
        writeln!(out, "class impl<Error> final {{");
        writeln!(out, "public:");
        writeln!(out, "  static Error error(repr::PtrLen repr) noexcept {{");
        writeln!(out, "    Error error;");
        writeln!(out, "    error.msg = static_cast<const char *>(repr.ptr);");
        writeln!(out, "    error.len = repr.len;");
        writeln!(out, "    return error;");
        writeln!(out, "  }}");
        writeln!(out, "}};");
    }

    if builtin.destroy {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "void destroy(T *ptr) {{");
        writeln!(out, "  ptr->~T();");
        writeln!(out, "}}");
    }

    if builtin.deleter_if {
        out.next_section();
        writeln!(out, "template <bool> struct deleter_if {{");
        writeln!(out, "  template <typename T> void operator()(T *) {{}}");
        writeln!(out, "}};");
        out.next_section();
        writeln!(out, "template <> struct deleter_if<true> {{");
        writeln!(
            out,
            "  template <typename T> void operator()(T *ptr) {{ ptr->~T(); }}",
        );
        writeln!(out, "}};");
    }

    if builtin.relocatable_or_array {
        out.next_section();
        writeln!(out, "template <typename T>");
        writeln!(out, "struct IsRelocatableOrArray : IsRelocatable<T> {{}};");
        writeln!(out, "template <typename T, ::std::size_t N>");
        writeln!(
            out,
            "struct IsRelocatableOrArray<T[N]> : IsRelocatableOrArray<T> {{}};",
        );
    }

    out.end_block(Block::AnonymousNamespace);
    out.end_block(Block::InlineNamespace("cxxbridge1"));

    if builtin.trycatch {
        out.begin_block(Block::Namespace("behavior"));
        include.exception = true;
        include.type_traits = true;
        include.utility = true;
        writeln!(out, "class missing {{}};");
        writeln!(out, "missing trycatch(...);");
        writeln!(out);
        writeln!(out, "template <typename Try, typename Fail>");
        writeln!(out, "static typename ::std::enable_if<");
        writeln!(
            out,
            "    ::std::is_same<decltype(trycatch(::std::declval<Try>(), ::std::declval<Fail>())),",
        );
        writeln!(out, "                 missing>::value>::type");
        writeln!(out, "trycatch(Try &&func, Fail &&fail) noexcept try {{");
        writeln!(out, "  func();");
        writeln!(out, "}} catch (const ::std::exception &e) {{");
        writeln!(out, "  fail(e.what());");
        writeln!(out, "}}");
        out.end_block(Block::Namespace("behavior"));
    }

    out.end_block(Block::Namespace("rust"));

    if builtin.exception {
        include.cstddef = true;
        out.begin_block(Block::ExternC);
        writeln!(
            out,
            "const char *cxxbridge1$exception(const char *, ::std::size_t);",
        );
        out.end_block(Block::ExternC);
    }
}
<|endoftext|>


<commit_before>// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#![feature(custom_attribute, plugin)]
#![plugin(flamer)]

extern crate cgmath;
extern crate chrono;
extern crate env_logger;
extern crate flame;
extern crate gfx;
extern crate gfx_device_gl;
extern crate gfx_window_glutin;
extern crate glutin;
extern crate richter;
extern crate rodio;

use std::cell::RefCell;
use std::env;
use std::net::ToSocketAddrs;
use std::path::Path;
use std::process::exit;
use std::rc::Rc;

use richter::client;
use richter::client::Client;
use richter::client::input::Bindings;
use richter::client::input::GameInput;
use richter::client::input::MouseWheel;
use richter::client::render;
use richter::client::render::SceneRenderer;
use richter::common;
use richter::common::console::CmdRegistry;
use richter::common::console::Console;
use richter::common::console::CvarRegistry;
use richter::common::host::Host;
use richter::common::host::Program;
use richter::common::net::SignOnStage;
use richter::common::pak::Pak;

use cgmath::Matrix4;
use cgmath::SquareMatrix;
use chrono::Duration;
use gfx::Encoder;
use gfx::handle::DepthStencilView;
use gfx::handle::RenderTargetView;
use gfx_device_gl::CommandBuffer;
use gfx_device_gl::Device;
use gfx_device_gl::Factory as GlFactory;
use gfx_device_gl::Resources;
use glutin::ElementState;
use glutin::Event;
use glutin::EventsLoop;
use glutin::GlContext;
use glutin::GlWindow;
use glutin::KeyboardInput;
use glutin::WindowEvent;
use rodio::Endpoint;

struct ClientProgram {
    pak: Rc<Pak>,
    cvars: Rc<RefCell<CvarRegistry>>,
    cmds: Rc<RefCell<CmdRegistry>>,
    console: Rc<RefCell<Console>>,

    events_loop: RefCell<EventsLoop>,
    window: RefCell<GlWindow>,

    device: RefCell<Device>,
    factory: RefCell<GlFactory>,
    encoder: RefCell<Encoder<Resources, CommandBuffer>>,
    color: RenderTargetView<Resources, render::ColorFormat>,
    depth: DepthStencilView<Resources, render::DepthFormat>,

    bindings: Rc<RefCell<Bindings>>,
    endpoint: Rc<Endpoint>,

    palette: render::Palette,

    client: Option<RefCell<Client>>,
    actions: RefCell<GameInput>,
    renderer: Option<RefCell<SceneRenderer>>,
}

impl ClientProgram  {
    pub fn new() -> ClientProgram {
        let mut pak = Pak::new();
        for pak_id in 0..common::MAX_PAKFILES {
            // TODO: check `-basedir` command line argument
            let basedir = common::DEFAULT_BASEDIR;
            let path_string = format!("{}/pak{}.pak", basedir, pak_id);
            let path = Path::new(&path_string);

            // keep adding PAKs until we don't find one or we hit MAX_PAKFILES
            if !path.exists() {
                break;
            }

            pak.add(path).unwrap();
        }

        let cvars = Rc::new(RefCell::new(CvarRegistry::new()));
        client::register_cvars(&cvars.borrow_mut());

        let cmds = Rc::new(RefCell::new(CmdRegistry::new()));
        // TODO: register commands as other subsystems come online

        let console = Rc::new(RefCell::new(Console::new(cmds.clone(), cvars.clone())));

        let bindings = Rc::new(RefCell::new(Bindings::new(cvars.clone(), cmds.clone())));
        bindings.borrow_mut().assign_defaults();

        let events_loop = glutin::EventsLoop::new();
        let window_builder = glutin::WindowBuilder::new()
            .with_title("Richter client")
            .with_dimensions(1366, 768);
        let context_builder = glutin::ContextBuilder::new()
            .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 3)))
            .with_vsync(true);

        let (window, device, mut factory, color, depth) =
            gfx_window_glutin::init::<render::ColorFormat, render::DepthFormat>(
                window_builder,
                context_builder,
                &events_loop,
            );

        let encoder = factory.create_command_buffer().into();

        let endpoint = Rc::new(rodio::get_endpoints_list().next().unwrap());

        let palette = render::Palette::load(&pak, "gfx/palette.lmp");

        ClientProgram {
            pak: Rc::new(pak),
            cvars,
            cmds,
            console,
            events_loop: RefCell::new(events_loop),
            window: RefCell::new(window),
            device: RefCell::new(device),
            factory: RefCell::new(factory),
            encoder: RefCell::new(encoder),
            color: color,
            depth: depth,
            bindings,
            endpoint,
            palette,
            client: None,
            actions: RefCell::new(GameInput::new()),
            renderer: None,
        }
    }

    fn connect<A>(&mut self, server_addrs: A)
    where
        A: ToSocketAddrs,
    {
        self.client = Some(RefCell::new({
            let cl = Client::connect(
                server_addrs,
                self.pak.clone(),
                self.cvars.clone(),
                self.cmds.clone(),
                self.console.clone(),
                self.endpoint.clone(),
            ).unwrap();
            cl.register_cmds(&mut self.cmds.borrow_mut());
            cl
        }
        ));

    }
}

impl Program for ClientProgram  {
    #[flame]
    fn frame(&mut self, frame_duration: Duration) {
        if let Some(ref client) = self.client {
            client.borrow_mut().frame(frame_duration).unwrap();

            if client.borrow().get_signon_stage() == SignOnStage::Done {
                if self.renderer.is_none() {
                    self.renderer = Some(RefCell::new(SceneRenderer::new(
                        client.borrow().get_models().unwrap(),
                        &self.palette,
                        &mut self.factory.borrow_mut(),
                    )));
                }

                self.bindings
                    .borrow()
                    .handle(&mut self.actions.borrow_mut(), MouseWheel::Up, ElementState::Released);
                self.bindings.borrow().handle(
                    &mut self.actions.borrow_mut(),
                    MouseWheel::Down,
                    ElementState::Released,
                );

                self.events_loop
                    .borrow_mut()
                    .poll_events(|event| match event {
                        Event::WindowEvent { event, .. } => match event {
                            WindowEvent::Closed => {
                                // TODO: handle quit properly
                                flame::dump_html(&mut std::fs::File::create("./flame.html").unwrap()).unwrap();
                                std::process::exit(0);
                            }

                            WindowEvent::KeyboardInput {
                                input:
                                    KeyboardInput {
                                        state,
                                        virtual_keycode: Some(key),
                                        ..
                                    },
                                ..
                            } => {
                                self.bindings.borrow().handle(&mut self.actions.borrow_mut(), key, state);
                            }

                            WindowEvent::MouseInput { state, button, .. } => {
                                self.bindings.borrow().handle(&mut self.actions.borrow_mut(), button, state);
                            }

                            WindowEvent::MouseWheel { delta, .. } => {
                                self.bindings.borrow().handle(
                                    &mut self.actions.borrow_mut(),
                                    delta,
                                    ElementState::Pressed,
                                );
                            }

                            _ => (),
                        },

                        _ => (),
                    });
                println!("{:?}", &mut self.actions.borrow());
                client
                    .borrow_mut()
                    .handle_input(&mut self.actions.borrow(), frame_duration, 0)
                    .unwrap();
            }

            // run console commands
            self.console.borrow_mut().execute();

            client.borrow_mut().send().unwrap();

            if let Some(ref renderer) = self.renderer {
                let cl = client.borrow();

                let fov_x = self.cvars.borrow().get_value("fov").unwrap();
                let (win_w, win_h) = self.window.borrow().get_inner_size().unwrap();
                let aspect = win_w as f32 / win_h as f32;
                let fov_y = common::math::fov_x_to_fov_y(cgmath::Deg(fov_x), aspect).unwrap();

                let perspective = cgmath::perspective(
                    fov_y,
                    aspect,
                    1.0,
                    65536.0
                );

                let camera = render::Camera::new(
                    cl.get_view_origin(),
                    cl.get_view_angles(),
                    perspective,
                );

                use gfx::Factory;
                use gfx::traits::FactoryExt;
                let (_, dummy_texture) = self.factory.borrow_mut().create_texture_immutable_u8::<render::ColorFormat>(
                    gfx::texture::Kind::D2(0, 0, gfx::texture::AaMode::Single),
                    gfx::texture::Mipmap::Allocated,
                    &[&[]]
                ).expect("dummy texture generation failed");

                let sampler = self.factory.borrow_mut().create_sampler(gfx::texture::SamplerInfo::new(
                    gfx::texture::FilterMethod::Scale,
                    gfx::texture::WrapMode::Tile,
                ));

                let mut data = render::pipe::Data {
                    vertex_buffer: self.factory.borrow_mut().create_vertex_buffer(&[]),
                    transform: Matrix4::identity().into(),
                    sampler: (dummy_texture, sampler),
                    out_color: self.color.clone(),
                    out_depth: self.depth.clone(),
                };

                println!("Beginning render pass.");

                self.encoder.borrow_mut().clear(&data.out_color, [0.0, 0.0, 0.0, 1.0]);
                self.encoder.borrow_mut().clear_depth(&data.out_depth, 1.0);
                renderer.borrow_mut().render(
                    &mut self.encoder.borrow_mut(),
                    &mut data,
                    client.borrow().get_entities().unwrap(),
                    client.borrow().get_time(),
                    &camera,
                );

                use std::ops::DerefMut;
                self.encoder.borrow_mut().flush(self.device.borrow_mut().deref_mut());
                self.window.borrow_mut().swap_buffers().unwrap();

                use gfx::Device;
                self.device.borrow_mut().cleanup();
            }
        }
    }
}

fn main() {
    env_logger::init();

    let args: Vec<String> = env::args().collect();

    if args.len() != 2 {
        println!("Usage: {} <server_address>", args[0]);
        exit(1);
    }

    let mut client_program = ClientProgram::new();
    client_program.connect(&args[1]);
    let mut host = Host::new(client_program);

    loop {
        host.frame();
    }
}
<commit_msg>Move sampler/userdata creation out of render loop<commit_after>// Copyright © 2018 Cormac O'Brien
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#![feature(custom_attribute, plugin)]
#![plugin(flamer)]

extern crate cgmath;
extern crate chrono;
extern crate env_logger;
extern crate flame;
extern crate gfx;
extern crate gfx_device_gl;
extern crate gfx_window_glutin;
extern crate glutin;
extern crate richter;
extern crate rodio;

use std::cell::RefCell;
use std::env;
use std::net::ToSocketAddrs;
use std::path::Path;
use std::process::exit;
use std::rc::Rc;

use richter::client;
use richter::client::Client;
use richter::client::input::Bindings;
use richter::client::input::GameInput;
use richter::client::input::MouseWheel;
use richter::client::render;
use richter::client::render::SceneRenderer;
use richter::common;
use richter::common::console::CmdRegistry;
use richter::common::console::Console;
use richter::common::console::CvarRegistry;
use richter::common::host::Host;
use richter::common::host::Program;
use richter::common::net::SignOnStage;
use richter::common::pak::Pak;

use cgmath::Matrix4;
use cgmath::SquareMatrix;
use chrono::Duration;
use gfx::Encoder;
use gfx::handle::DepthStencilView;
use gfx::handle::RenderTargetView;
use gfx_device_gl::CommandBuffer;
use gfx_device_gl::Device;
use gfx_device_gl::Factory as GlFactory;
use gfx_device_gl::Resources;
use glutin::ElementState;
use glutin::Event;
use glutin::EventsLoop;
use glutin::GlContext;
use glutin::GlWindow;
use glutin::KeyboardInput;
use glutin::WindowEvent;
use rodio::Endpoint;

struct ClientProgram {
    pak: Rc<Pak>,
    cvars: Rc<RefCell<CvarRegistry>>,
    cmds: Rc<RefCell<CmdRegistry>>,
    console: Rc<RefCell<Console>>,

    events_loop: RefCell<EventsLoop>,
    window: RefCell<GlWindow>,

    device: RefCell<Device>,
    factory: RefCell<GlFactory>,
    encoder: RefCell<Encoder<Resources, CommandBuffer>>,
    color: RenderTargetView<Resources, render::ColorFormat>,
    depth: DepthStencilView<Resources, render::DepthFormat>,
    data: RefCell<render::pipe::Data<Resources>>,

    bindings: Rc<RefCell<Bindings>>,
    endpoint: Rc<Endpoint>,

    palette: render::Palette,

    client: Option<RefCell<Client>>,
    actions: RefCell<GameInput>,
    renderer: Option<RefCell<SceneRenderer>>,
}

impl ClientProgram  {
    pub fn new() -> ClientProgram {
        let mut pak = Pak::new();
        for pak_id in 0..common::MAX_PAKFILES {
            // TODO: check `-basedir` command line argument
            let basedir = common::DEFAULT_BASEDIR;
            let path_string = format!("{}/pak{}.pak", basedir, pak_id);
            let path = Path::new(&path_string);

            // keep adding PAKs until we don't find one or we hit MAX_PAKFILES
            if !path.exists() {
                break;
            }

            pak.add(path).unwrap();
        }

        let cvars = Rc::new(RefCell::new(CvarRegistry::new()));
        client::register_cvars(&cvars.borrow_mut());

        let cmds = Rc::new(RefCell::new(CmdRegistry::new()));
        // TODO: register commands as other subsystems come online

        let console = Rc::new(RefCell::new(Console::new(cmds.clone(), cvars.clone())));

        let bindings = Rc::new(RefCell::new(Bindings::new(cvars.clone(), cmds.clone())));
        bindings.borrow_mut().assign_defaults();

        let events_loop = glutin::EventsLoop::new();
        let window_builder = glutin::WindowBuilder::new()
            .with_title("Richter client")
            .with_dimensions(1366, 768);
        let context_builder = glutin::ContextBuilder::new()
            .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 3)))
            .with_vsync(false);

        let (window, device, mut factory, color, depth) =
            gfx_window_glutin::init::<render::ColorFormat, render::DepthFormat>(
                window_builder,
                context_builder,
                &events_loop,
            );

        use gfx::Factory;
        use gfx::traits::FactoryExt;
        let (_, dummy_texture) = factory.create_texture_immutable_u8::<render::ColorFormat>(
            gfx::texture::Kind::D2(0, 0, gfx::texture::AaMode::Single),
            gfx::texture::Mipmap::Allocated,
            &[&[]]
        ).expect("dummy texture generation failed");

        let sampler = factory.create_sampler(gfx::texture::SamplerInfo::new(
            gfx::texture::FilterMethod::Scale,
            gfx::texture::WrapMode::Tile,
        ));

        let mut data = render::pipe::Data {
            vertex_buffer: factory.create_vertex_buffer(&[]),
            transform: Matrix4::identity().into(),
            sampler: (dummy_texture, sampler),
            out_color: color.clone(),
            out_depth: depth.clone(),
        };

        let encoder = factory.create_command_buffer().into();

        let endpoint = Rc::new(rodio::get_endpoints_list().next().unwrap());

        let palette = render::Palette::load(&pak, "gfx/palette.lmp");

        ClientProgram {
            pak: Rc::new(pak),
            cvars,
            cmds,
            console,
            events_loop: RefCell::new(events_loop),
            window: RefCell::new(window),
            device: RefCell::new(device),
            factory: RefCell::new(factory),
            encoder: RefCell::new(encoder),
            data: RefCell::new(data),
            color: color,
            depth: depth,
            bindings,
            endpoint,
            palette,
            client: None,
            actions: RefCell::new(GameInput::new()),
            renderer: None,
        }
    }

    fn connect<A>(&mut self, server_addrs: A)
    where
        A: ToSocketAddrs,
    {
        self.client = Some(RefCell::new({
            let cl = Client::connect(
                server_addrs,
                self.pak.clone(),
                self.cvars.clone(),
                self.cmds.clone(),
                self.console.clone(),
                self.endpoint.clone(),
            ).unwrap();
            cl.register_cmds(&mut self.cmds.borrow_mut());
            cl
        }
        ));

    }
}

impl Program for ClientProgram  {
    #[flame]
    fn frame(&mut self, frame_duration: Duration) {
        println!("{}", frame_duration.num_milliseconds());
        if let Some(ref client) = self.client {
            client.borrow_mut().frame(frame_duration).unwrap();

            if client.borrow().get_signon_stage() == SignOnStage::Done {
                if self.renderer.is_none() {
                    self.renderer = Some(RefCell::new(SceneRenderer::new(
                        client.borrow().get_models().unwrap(),
                        &self.palette,
                        &mut self.factory.borrow_mut(),
                    )));
                }

                self.bindings
                    .borrow()
                    .handle(&mut self.actions.borrow_mut(), MouseWheel::Up, ElementState::Released);
                self.bindings.borrow().handle(
                    &mut self.actions.borrow_mut(),
                    MouseWheel::Down,
                    ElementState::Released,
                );

                self.events_loop
                    .borrow_mut()
                    .poll_events(|event| match event {
                        Event::WindowEvent { event, .. } => match event {
                            WindowEvent::Closed => {
                                // TODO: handle quit properly
                                flame::dump_html(&mut std::fs::File::create("./flame.html").unwrap()).unwrap();
                                std::process::exit(0);
                            }

                            WindowEvent::KeyboardInput {
                                input:
                                    KeyboardInput {
                                        state,
                                        virtual_keycode: Some(key),
                                        ..
                                    },
                                ..
                            } => {
                                self.bindings.borrow().handle(&mut self.actions.borrow_mut(), key, state);
                            }

                            WindowEvent::MouseInput { state, button, .. } => {
                                self.bindings.borrow().handle(&mut self.actions.borrow_mut(), button, state);
                            }

                            WindowEvent::MouseWheel { delta, .. } => {
                                self.bindings.borrow().handle(
                                    &mut self.actions.borrow_mut(),
                                    delta,
                                    ElementState::Pressed,
                                );
                            }

                            _ => (),
                        },

                        _ => (),
                    });
                println!("{:?}", &mut self.actions.borrow());
                client
                    .borrow_mut()
                    .handle_input(&mut self.actions.borrow(), frame_duration, 0)
                    .unwrap();
            }

            // run console commands
            self.console.borrow_mut().execute();

            client.borrow_mut().send().unwrap();

            if let Some(ref renderer) = self.renderer {
                let cl = client.borrow();

                let fov_x = self.cvars.borrow().get_value("fov").unwrap();
                let (win_w, win_h) = self.window.borrow().get_inner_size().unwrap();
                let aspect = win_w as f32 / win_h as f32;
                let fov_y = common::math::fov_x_to_fov_y(cgmath::Deg(fov_x), aspect).unwrap();

                let perspective = cgmath::perspective(
                    fov_y,
                    aspect,
                    1.0,
                    65536.0
                );

                let camera = render::Camera::new(
                    cl.get_view_origin(),
                    cl.get_view_angles(),
                    perspective,
                );

                println!("Beginning render pass.");

                self.encoder.borrow_mut().clear(&self.data.borrow().out_color, [0.0, 0.0, 0.0, 1.0]);
                self.encoder.borrow_mut().clear_depth(&self.data.borrow().out_depth, 1.0);
                renderer.borrow_mut().render(
                    &mut self.encoder.borrow_mut(),
                    &mut self.data.borrow_mut(),
                    client.borrow().get_entities().unwrap(),
                    client.borrow().get_time(),
                    &camera,
                );

                use std::ops::DerefMut;
                self.encoder.borrow_mut().flush(self.device.borrow_mut().deref_mut());
                self.window.borrow_mut().swap_buffers().unwrap();

                use gfx::Device;
                self.device.borrow_mut().cleanup();
            }
        }
    }
}

fn main() {
    env_logger::init();

    let args: Vec<String> = env::args().collect();

    if args.len() != 2 {
        println!("Usage: {} <server_address>", args[0]);
        exit(1);
    }

    let mut client_program = ClientProgram::new();
    client_program.connect(&args[1]);
    let mut host = Host::new(client_program);

    loop {
        host.frame();
    }
}
<|endoftext|>