<commit_before>use serde::Deserialize;
use serde_json::from_reader;
use std::f32::consts;
use std::fs::File;
use std::ops::{Add, Div, Mul, Sub};
use std::path::Path;

use linear::{UnitQuaternion, Vector2, Vector3, Vector4};
use resource::{Cache, Load, LoadError};

/// Time used as sampling type in splines.
pub type Time = f32;

/// A spline control point.
///
/// This type associates a value at a given type. It also contains an interpolation object used to
/// determine how to interpolate values on the segment defined by this key and the next one.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub struct Key<T> {
  /// Time at which the `Key` should be reached.
  pub t: Time,
  /// Actual value.
  pub value: T,
  /// Interpolation mode.
  #[serde(default)]
  pub interpolation: Interpolation
}

impl<T> Key<T> {
  pub fn new(t: Time, value: T, interpolation: Interpolation) -> Self {
    Key {
      t: t,
      value: value,
      interpolation: interpolation
    }
  }
}

/// Interpolation mode.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub enum Interpolation {
  /// Hold a `Key` until the time passes the normalized step threshold, in which case the next
  /// key is used.
  ///
  /// *Note: if you set the threshold to `0.5`, the first key will be used until the time is half
  /// between the two keys; the second key will be in used afterwards. If you set it to `1.0`, the
  /// first key will be kept until the next key.*
  #[serde(rename = "step")]
  Step(f32),
  /// Linear interpolation between a key and the next one.
  #[serde(rename = "linear")]
  Linear,
  /// Cosine interpolation between a key and the next one.
  #[serde(rename = "cosine")]
  Cosine,
  /// Catmull-Rom interpolation.
  #[serde(rename = "catmull_rom")]
  CatmullRom
}

impl Default for Interpolation {
  fn default() -> Self {
    Interpolation::Linear
  }
}

/// Spline curve used to provide interpolation between control points (keys).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Spline<T> {
  keys: Vec<Key<T>>,
}

impl<T> Spline<T> {
  pub fn new(mut cps: Vec<Key<T>>) -> Self {
    cps.sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap());

    Spline {
      keys: cps
    }
  }
}

impl<'a, T> Load<'a> for Spline<T> where T: Deserialize {
  type Args = ();

  fn load<P>(path: P, _: &mut Cache<'a>, _: Self::Args) -> Result<Self, LoadError> where P: AsRef<Path> {
    let path = path.as_ref();

    info!("loading spline: {:?}", path);

    let file = File::open(path).map_err(|e| LoadError::FileNotFound(path.to_path_buf(), format!("{:?}", e)))?;
    let keys = from_reader(file).map_err(|e| LoadError::ParseFailed(format!("{:?}", e)))?;

    Ok(Spline::new(keys))
  }
}

pub struct SplineIterator<'a, T> where T: 'a {
  anim_param: &'a Spline<T>,
  i: usize
}

impl<'a, T> Iterator for SplineIterator<'a, T> {
  type Item = &'a Key<T>;

  fn next(&mut self) -> Option<Self::Item> {
    let r = self.anim_param.keys.get(self.i);

    if let Some(_) = r {
      self.i += 1;
    }

    r
  }
}

impl<'a, T> IntoIterator for &'a Spline<T> {
  type Item = &'a Key<T>;
  type IntoIter = SplineIterator<'a, T>;

  fn into_iter(self) -> Self::IntoIter {
    SplineIterator {
      anim_param: self,
      i: 0
    }
  }
}

/// Implement this trait if your type is a key you want to sample with.
pub trait Interpolate: Copy {
  /// Linear interpolation.
  fn lerp(a: Self, b: Self, t: Time) -> Self;
  /// Cubic hermite interpolation.
  fn cubic_hermite(_: (Self, Time), a: (Self, Time), b: (Self, Time), _: (Self, Time), t: Time) -> Self {
    Self::lerp(a.0, b.0, t)
  }
}

impl Interpolate for f32 {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for Vector2<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for Vector3<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for Vector4<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for UnitQuaternion<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    a * UnitQuaternion::new(&(UnitQuaternion::new(&a.quaternion().conjugate()) * b).quaternion().powf(t))
  }
}

// Default implementation of Interpolate::lerp.
pub fn lerp<T>(a: T, b: T, t: Time) -> T where T: Add<Output = T> + Mul<Time, Output = T> {
  a * (1. - t) + b * t
}

// Default implementation of Interpolate::cubic_hermit.

pub fn cubic_hermite<T>(x: (T, Time), a: (T, Time), b: (T, Time), y: (T, Time), t: Time) -> T
    where T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Time, Output = T> + Div<Time, Output = T> {
  // time stuff
  let t2 = t * t;
  let t3 = t2 * t;
  let two_t3 = 2. * t3;
  let three_t2 = 3. * t2;

  // tangents
  let m0 = (b.0 - x.0) / (b.1 - x.1);
	let m1 = (y.0 - a.0) / (y.1 - a.1);

  a.0 * (two_t3 - three_t2 + 1.) + m0 * (t3 - 2. * t2 + t) + b.0 * (-two_t3 + three_t2) + m1 * (t3 - t2)
}

/// Samplers can sample `Spline` by providing a `Time`. They should be mutable so that they can
/// maintain an internal state for optimization purposes.
#[derive(Copy, Clone, Default)]
pub struct Sampler {
  /// Playback cursor – gives the lower control point index of the current portion of the curve
  /// we’re sampling at.
  cursor: usize
}

impl Sampler {
  pub fn new() -> Self {
    Sampler {
      cursor: 0
    }
  }

  /// Sample a spline. If `random_sampling` is set, random sampling is generally faster than
  /// continuous sampling. Though, if you use continuous sampling, set `random_sampling` to `false`
	/// for max speed performance.
  pub fn sample<T>(&mut self, t: Time, param: &Spline<T>, random_sampling: bool) -> Option<T>
      where T: Interpolate {
    let i = if random_sampling {
      binary_search_lower_cp(&param.keys, t)
    } else {
      let i = around_search_lower_cp(&param.keys, self.cursor, t);

      // if we’ve found the index, replace the cursor to speed up next searches
      if let Some(cursor) = i {
        self.cursor = cursor;
      }

      i
    };

    let i = match i {
      Some(i) => i,
      None => return None
    };

    let cp0 = &param.keys[i];

    match cp0.interpolation {
      Interpolation::Step(threshold) => {
        let cp1 = &param.keys[i+1];
        let nt = normalize_time(t, cp0, cp1);
        Some(if nt < threshold { cp0.value } else { cp1.value })
      },
      Interpolation::Linear => {
        let cp1 = &param.keys[i+1];
        let nt = normalize_time(t, cp0, cp1);

        Some(Interpolate::lerp(cp0.value, cp1.value, nt))
      },
      Interpolation::Cosine => {
        let cp1 = &param.keys[i+1];
        let nt = normalize_time(t, cp0, cp1);
        let cos_nt = (1. - f32::cos(nt * consts::PI)) * 0.5;

        Some(Interpolate::lerp(cp0.value, cp1.value, cos_nt))
      },
      Interpolation::CatmullRom => {
        // We need at least four points for Catmull Rom; ensure we have them, otherwise, return
        // None.
        if i == 0 || i >= param.keys.len() - 2 {
          None
        } else {
          let cp1 = &param.keys[i+1];
          let cpm0 = &param.keys[i-1];
          let cpm1 = &param.keys[i+2];
          let nt = normalize_time(t, cp0, cp1);

          Some(Interpolate::cubic_hermite((cpm0.value, cpm0.t), (cp0.value, cp0.t), (cp1.value, cp1.t), (cpm1.value, cpm1.t), nt))
        }
      }
    }
  }
}

// Normalize a time ([0;1]) given two control points.
pub fn normalize_time<T>(t: Time, cp: &Key<T>, cp1: &Key<T>) -> Time {
  (t - cp.t) / (cp1.t - cp.t)
}

// Find the lower control point corresponding to a given time. Random version.
fn binary_search_lower_cp<T>(cps: &[Key<T>], t: Time) -> Option<usize> {
  let len = cps.len() as i32;
  if len < 2 {
    return None;
  }

  let mut down = 0;
  let mut up = len - 1;

  while down <= up {
    let m = (up + down) / 2;
    if m < 0 || m >= len - 1 {
      return None;
    }

    let cp0 = &cps[m as usize];

    if cp0.t > t {
      up = m-1;
    } else {
      let cp1 = &cps[(m+1) as usize];

      if t >= cp1.t {
        down = m+1;
      } else {
        return Some(m as usize)
      }
    }
  }

  None
}

// Find the lower control point corresponding to a given time. Continuous version. `i` is the last
// known found index.
fn around_search_lower_cp<T>(cps: &[Key<T>], mut i: usize, t: Time) -> Option<usize> {
  let len = cps.len();

  if len < 2 {
    return None;
  }

  loop {
    let cp = &cps[i];
    let cp1 = &cps[i+1];

    if t >= cp1.t {
      if i >= len - 2 {
        return None;
      }

      i += 1;
    } else if t < cp.t {
      if i == 0 {
        return None;
      }

      i -= 1;
    } else {
      break; // found
    }
  }

  Some(i)
}
<commit_msg>Format.<commit_after>use serde::Deserialize;
use serde_json::from_reader;
use std::f32::consts;
use std::fs::File;
use std::ops::{Add, Div, Mul, Sub};
use std::path::Path;

use linear::{UnitQuaternion, Vector2, Vector3, Vector4};
use resource::{Cache, Load, LoadError};

/// Time used as sampling type in splines.
pub type Time = f32;

/// A spline control point.
///
/// This type associates a value at a given type. It also contains an interpolation object used to
/// determine how to interpolate values on the segment defined by this key and the next one.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub struct Key<T> {
  /// Time at which the `Key` should be reached.
  pub t: Time,
  /// Actual value.
  pub value: T,
  /// Interpolation mode.
  #[serde(default)]
  pub interpolation: Interpolation
}

impl<T> Key<T> {
  pub fn new(t: Time, value: T, interpolation: Interpolation) -> Self {
    Key {
      t: t,
      value: value,
      interpolation: interpolation
    }
  }
}

/// Interpolation mode.
#[derive(Copy, Clone, Debug, Deserialize, Serialize)]
pub enum Interpolation {
  /// Hold a `Key` until the time passes the normalized step threshold, in which case the next
  /// key is used.
  ///
  /// *Note: if you set the threshold to `0.5`, the first key will be used until the time is half
  /// between the two keys; the second key will be in used afterwards. If you set it to `1.0`, the
  /// first key will be kept until the next key.*
  #[serde(rename = "step")]
  Step(f32),
  /// Linear interpolation between a key and the next one.
  #[serde(rename = "linear")]
  Linear,
  /// Cosine interpolation between a key and the next one.
  #[serde(rename = "cosine")]
  Cosine,
  /// Catmull-Rom interpolation.
  #[serde(rename = "catmull_rom")]
  CatmullRom
}

impl Default for Interpolation {
  fn default() -> Self {
    Interpolation::Linear
  }
}

/// Spline curve used to provide interpolation between control points (keys).
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Spline<T> {
  keys: Vec<Key<T>>,
}

impl<T> Spline<T> {
  pub fn new(mut cps: Vec<Key<T>>) -> Self {
    cps.sort_by(|k0, k1| k0.t.partial_cmp(&k1.t).unwrap());

    Spline {
      keys: cps
    }
  }
}

impl<'a, T> Load<'a> for Spline<T> where T: Deserialize {
  type Args = ();

  fn load<P>(path: P, _: &mut Cache<'a>, _: Self::Args) -> Result<Self, LoadError> where P: AsRef<Path> {
    let path = path.as_ref();

    info!("loading spline: {:?}", path);

    let file = File::open(path).map_err(|e| LoadError::FileNotFound(path.to_path_buf(), format!("{:?}", e)))?;
    let keys = from_reader(file).map_err(|e| LoadError::ParseFailed(format!("{:?}", e)))?;

    Ok(Spline::new(keys))
  }
}

pub struct SplineIterator<'a, T> where T: 'a {
  anim_param: &'a Spline<T>,
  i: usize
}

impl<'a, T> Iterator for SplineIterator<'a, T> {
  type Item = &'a Key<T>;

  fn next(&mut self) -> Option<Self::Item> {
    let r = self.anim_param.keys.get(self.i);

    if let Some(_) = r {
      self.i += 1;
    }

    r
  }
}

impl<'a, T> IntoIterator for &'a Spline<T> {
  type Item = &'a Key<T>;
  type IntoIter = SplineIterator<'a, T>;

  fn into_iter(self) -> Self::IntoIter {
    SplineIterator {
      anim_param: self,
      i: 0
    }
  }
}

/// Implement this trait if your type is a key you want to sample with.
pub trait Interpolate: Copy {
  /// Linear interpolation.
  fn lerp(a: Self, b: Self, t: Time) -> Self;
  /// Cubic hermite interpolation.
  fn cubic_hermite(_: (Self, Time), a: (Self, Time), b: (Self, Time), _: (Self, Time), t: Time) -> Self {
    Self::lerp(a.0, b.0, t)
  }
}

impl Interpolate for f32 {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for Vector2<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for Vector3<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for Vector4<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    lerp(a, b, t)
  }

  fn cubic_hermite(x: (Self, Time), a: (Self, Time), b: (Self, Time), y: (Self, Time), t: Time) -> Self {
    cubic_hermite(x, a, b, y, t)
  }
}

impl Interpolate for UnitQuaternion<f32> {
  fn lerp(a: Self, b: Self, t: Time) -> Self {
    a * UnitQuaternion::new(&(UnitQuaternion::new(&a.quaternion().conjugate()) * b).quaternion().powf(t))
  }
}

// Default implementation of Interpolate::lerp.
pub fn lerp<T>(a: T, b: T, t: Time) -> T where T: Add<Output = T> + Mul<Time, Output = T> {
  a * (1. - t) + b * t
}

// Default implementation of Interpolate::cubic_hermit.
pub fn cubic_hermite<T>(x: (T, Time), a: (T, Time), b: (T, Time), y: (T, Time), t: Time) -> T
    where T: Copy + Add<Output = T> + Sub<Output = T> + Mul<Time, Output = T> + Div<Time, Output = T> {
  // time stuff
  let t2 = t * t;
  let t3 = t2 * t;
  let two_t3 = 2. * t3;
  let three_t2 = 3. * t2;

  // tangents
  let m0 = (b.0 - x.0) / (b.1 - x.1);
	let m1 = (y.0 - a.0) / (y.1 - a.1);

  a.0 * (two_t3 - three_t2 + 1.) + m0 * (t3 - 2. * t2 + t) + b.0 * (-two_t3 + three_t2) + m1 * (t3 - t2)
}

/// Samplers can sample `Spline` by providing a `Time`. They should be mutable so that they can
/// maintain an internal state for optimization purposes.
#[derive(Copy, Clone, Default)]
pub struct Sampler {
  /// Playback cursor – gives the lower control point index of the current portion of the curve
  /// we’re sampling at.
  cursor: usize
}

impl Sampler {
  pub fn new() -> Self {
    Sampler {
      cursor: 0
    }
  }

  /// Sample a spline. If `random_sampling` is set, random sampling is generally faster than
  /// continuous sampling. Though, if you use continuous sampling, set `random_sampling` to `false`
	/// for max speed performance.
  pub fn sample<T>(&mut self, t: Time, param: &Spline<T>, random_sampling: bool) -> Option<T>
      where T: Interpolate {
    let i = if random_sampling {
      binary_search_lower_cp(&param.keys, t)
    } else {
      let i = around_search_lower_cp(&param.keys, self.cursor, t);

      // if we’ve found the index, replace the cursor to speed up next searches
      if let Some(cursor) = i {
        self.cursor = cursor;
      }

      i
    };

    let i = match i {
      Some(i) => i,
      None => return None
    };

    let cp0 = &param.keys[i];

    match cp0.interpolation {
      Interpolation::Step(threshold) => {
        let cp1 = &param.keys[i+1];
        let nt = normalize_time(t, cp0, cp1);
        Some(if nt < threshold { cp0.value } else { cp1.value })
      },
      Interpolation::Linear => {
        let cp1 = &param.keys[i+1];
        let nt = normalize_time(t, cp0, cp1);

        Some(Interpolate::lerp(cp0.value, cp1.value, nt))
      },
      Interpolation::Cosine => {
        let cp1 = &param.keys[i+1];
        let nt = normalize_time(t, cp0, cp1);
        let cos_nt = (1. - f32::cos(nt * consts::PI)) * 0.5;

        Some(Interpolate::lerp(cp0.value, cp1.value, cos_nt))
      },
      Interpolation::CatmullRom => {
        // We need at least four points for Catmull Rom; ensure we have them, otherwise, return
        // None.
        if i == 0 || i >= param.keys.len() - 2 {
          None
        } else {
          let cp1 = &param.keys[i+1];
          let cpm0 = &param.keys[i-1];
          let cpm1 = &param.keys[i+2];
          let nt = normalize_time(t, cp0, cp1);

          Some(Interpolate::cubic_hermite((cpm0.value, cpm0.t), (cp0.value, cp0.t), (cp1.value, cp1.t), (cpm1.value, cpm1.t), nt))
        }
      }
    }
  }
}

// Normalize a time ([0;1]) given two control points.
pub fn normalize_time<T>(t: Time, cp: &Key<T>, cp1: &Key<T>) -> Time {
  (t - cp.t) / (cp1.t - cp.t)
}

// Find the lower control point corresponding to a given time. Random version.
fn binary_search_lower_cp<T>(cps: &[Key<T>], t: Time) -> Option<usize> {
  let len = cps.len() as i32;
  if len < 2 {
    return None;
  }

  let mut down = 0;
  let mut up = len - 1;

  while down <= up {
    let m = (up + down) / 2;
    if m < 0 || m >= len - 1 {
      return None;
    }

    let cp0 = &cps[m as usize];

    if cp0.t > t {
      up = m-1;
    } else {
      let cp1 = &cps[(m+1) as usize];

      if t >= cp1.t {
        down = m+1;
      } else {
        return Some(m as usize)
      }
    }
  }

  None
}

// Find the lower control point corresponding to a given time. Continuous version. `i` is the last
// known found index.
fn around_search_lower_cp<T>(cps: &[Key<T>], mut i: usize, t: Time) -> Option<usize> {
  let len = cps.len();

  if len < 2 {
    return None;
  }

  loop {
    let cp = &cps[i];
    let cp1 = &cps[i+1];

    if t >= cp1.t {
      if i >= len - 2 {
        return None;
      }

      i += 1;
    } else if t < cp.t {
      if i == 0 {
        return None;
      }

      i -= 1;
    } else {
      break; // found
    }
  }

  Some(i)
}
<|endoftext|>


<commit_before>use hyper::{Body, Client, Method, Request};
#[allow(unused)]
use log::{info, warn};

use serde_json::json;
use std::fs;
use std::process::exit;

use librespot::core::authentication::Credentials;
use librespot::core::config::SessionConfig;
use librespot::core::keymaster;
use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId;

use librespot::playback::audio_backend;
use librespot::playback::config::{AudioFormat, PlayerConfig};
use librespot::playback::player::{Player, PlayerEvent};

const VERSION: &'static str = concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION"));

const SCOPES: &str = "user-read-private,playlist-read-private,playlist-read-collaborative,playlist-modify-public,playlist-modify-private,user-follow-modify,user-follow-read,user-library-read,user-library-modify,user-top-read,user-read-recently-played";

#[cfg(debug_assertions)]
const DEBUGMODE: bool = true;
#[cfg(not(debug_assertions))]
const DEBUGMODE: bool = false;

pub fn check(version_info: String) {
    println!("ok {}", version_info.to_string());

    let capabilities = json!({
        "version": env!("CARGO_PKG_VERSION").to_string(),
        "autoplay": true,
        "lms-auth": true,
        "volume-normalisation": true,
        "debug": DEBUGMODE,
        "ogg-direct": true,
        "save-token": true,
        "podcasts": true,
        "zeroconf-port": true
    });

    println!("{}", capabilities.to_string());
    exit(0);
}

// inspired by examples/get_token.rs
pub async fn get_token(
    client_id: Option<String>,
    scopes: Option<String>,
    save_token: Option<String>,
    last_credentials: Option<Credentials>,
    session_config: SessionConfig,
) {
    match last_credentials {
        Some(last_credentials) => {
            if let Some(client_id) = client_id {
                let scopes = scopes.unwrap_or(SCOPES.to_string());

                let session = Session::connect(session_config, last_credentials, None)
                    .await
                    .unwrap();

                let token = keymaster::get_token(&session, &client_id, &scopes)
                    .await
                    .unwrap();

                let json_token = json!({
                    // keep backwards compatibility with older versions
                    "accessToken": token.access_token.to_string(),
                    "expiresIn": token.expires_in,
                });

                if let Some(save_token) = save_token {
                    fs::write(save_token.to_string(), format!("{}", json_token))
                        .expect("Can't write token file");
                } else {
                    println!("{}", json_token);
                }
            } else {
                println!("Use --client-id to provide a CLIENT_ID");
            }
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// inspired by examples/play.rs
pub async fn play_track(
    track_id: String,
    start_position: u32,
    last_credentials: Option<Credentials>,
    player_config: PlayerConfig,
    session_config: SessionConfig,
) {
    match last_credentials {
        Some(last_credentials) => {
            let backend = audio_backend::find(None).unwrap();
            let audio_format = AudioFormat::default();

            let track = SpotifyId::from_uri(
                track_id
                    .replace("spotty://", "spotify:track:")
                    .replace("://", ":")
                    .as_str(),
            );

            match track {
                Ok(track) => {
                    let session = Session::connect(session_config, last_credentials, None)
                        .await
                        .unwrap();

                    let (mut player, _) = Player::new(player_config, session, None, move || {
                        backend(None, audio_format)
                    });

                    player.load(track, true, start_position);
                    player.await_end_of_track().await;
                }
                Err(error) => {
                    warn!("Problem getting a Spotify ID for {}: {:?}", track_id, error);
                    return;
                }
            };
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// Connect mode support

#[derive(Clone)]
pub struct LMS {
    base_url: Option<String>,
    player_mac: Option<String>,
    auth: Option<String>,
}

#[allow(unused)]
impl LMS {
    pub fn new(base_url: Option<String>, player_mac: Option<String>, auth: Option<String>) -> LMS {
        LMS {
            base_url: Some(format!(
                "http://{}/jsonrpc.js",
                base_url.unwrap_or("localhost:9000".to_string())
            )),
            player_mac: player_mac,
            auth: auth,
        }
    }

    pub fn is_configured(&self) -> bool {
        if self.base_url != None {
            if self.player_mac != None {
                return true;
            }
        }

        return false;
    }

    pub async fn signal_event(&self, event: PlayerEvent) {
        let mut command = r#"["spottyconnect","change"]"#.to_string();

        match event {
            PlayerEvent::Changed {
                old_track_id,
                new_track_id,
            } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: changed, old track: {}, new track: {}",
                    old_track_id.to_base62().unwrap_or_default(),
                    new_track_id.to_base62().unwrap_or_default()
                );
                command = format!(
                    r#"["spottyconnect","change","{}","{}"]"#,
                    new_track_id.to_base62().unwrap_or_default().to_string(),
                    old_track_id.to_base62().unwrap_or_default().to_string()
                );
            }
            PlayerEvent::Started { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: started, track: {}",
                    track_id.to_base62().unwrap_or_default()
                );
                command = format!(
                    r#"["spottyconnect","start","{}"]"#,
                    track_id.to_base62().unwrap_or_default().to_string()
                );
            }
            PlayerEvent::Stopped { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: stopped, track: {}",
                    track_id.to_base62().unwrap_or_default()
                );
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::Playing {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: playing, track: {}, duration: {}, position: {}",
                    track_id.to_base62().unwrap_or_default(),
                    duration_ms,
                    position_ms
                );
                // we're not implementing the seek event here, as it's going to read player state anyway
                // but signal a change if the new position has changed and is > 0
                if position_ms <= 0 {
                    return;
                }
                command = r#"["spottyconnect","change"]"#.to_string();
            }
            PlayerEvent::Paused {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: paused, track: {}, duration: {}, position: {}",
                    track_id.to_base62().unwrap_or_default(),
                    duration_ms,
                    position_ms
                );
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::VolumeSet { volume } => {
                let mut new_volume = volume as u32;
                if new_volume > 0 {
                    new_volume = new_volume * 100 / u32::pow(2, 16);
                }

                if new_volume > 100 {
                    new_volume = 100;
                };

                #[cfg(debug_assertions)]
                info!("event: volume: {}", volume);
                // we're not using the volume here, as LMS will read player state anyway
                command = format!(r#"["spottyconnect","volume",{}]"#, new_volume.to_string());
            }
            _ => return,
        }

        if !self.is_configured() {
            #[cfg(debug_assertions)]
            warn!("LMS connection is not configured");
            #[cfg(debug_assertions)]
            info!("{}", command);
            return;
        }

        if let Some(ref base_url) = self.base_url {
            #[cfg(debug_assertions)]
            info!("Base URL to talk to LMS: {}", base_url);

            if let Some(ref player_mac) = self.player_mac {
                #[cfg(debug_assertions)]
                info!("Player MAC address to control: {}", player_mac);

                #[cfg(debug_assertions)]
                info!("Command to send to player: {}", command);

                let json = format!(
                    r#"{{"id": 1,"method":"slim.request","params":["{}",{}]}}"#,
                    player_mac, command
                );

                let mut auth_header = "".to_string();
                if let Some(ref auth) = self.auth {
                    auth_header = auth.trim().to_string();
                }

                let req = Request::builder()
                    .method(Method::POST)
                    .uri(base_url.to_string())
                    .header("user-agent", format!("{}", VERSION))
                    .header("content-type", "application/json")
                    .header("authorization", format!("Basic {}", auth_header))
                    .header("x-scanner", "1")
                    .body(Body::from(json.clone()))
                    .unwrap();

                let client = Client::new();
                let resp = client.request(req).await;

                match resp {
                    Ok(resp) => {
                        #[cfg(debug_assertions)]
                        info!("Response: {}", resp.status());
                    }
                    Err(error) => {
                        warn!("Problem posting to {} / {}: {:?}", base_url, json, error);
                    }
                }
            }
        }
    }
}
<commit_msg>Improved error handling when fetching tokens or track data.<commit_after>use hyper::{Body, Client, Method, Request};
#[allow(unused)]
use log::{error, info, warn};

use serde_json::json;
use std::fs;
use std::process::exit;

use librespot::core::authentication::Credentials;
use librespot::core::config::SessionConfig;
use librespot::core::keymaster;
use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId;

use librespot::playback::audio_backend;
use librespot::playback::config::{AudioFormat, PlayerConfig};
use librespot::playback::player::{Player, PlayerEvent};

const VERSION: &'static str = concat!(env!("CARGO_PKG_NAME"), " v", env!("CARGO_PKG_VERSION"));

const SCOPES: &str = "user-read-private,playlist-read-private,playlist-read-collaborative,playlist-modify-public,playlist-modify-private,user-follow-modify,user-follow-read,user-library-read,user-library-modify,user-top-read,user-read-recently-played";

#[cfg(debug_assertions)]
const DEBUGMODE: bool = true;
#[cfg(not(debug_assertions))]
const DEBUGMODE: bool = false;

pub fn check(version_info: String) {
    println!("ok {}", version_info.to_string());

    let capabilities = json!({
        "version": env!("CARGO_PKG_VERSION").to_string(),
        "autoplay": true,
        "lms-auth": true,
        "volume-normalisation": true,
        "debug": DEBUGMODE,
        "ogg-direct": true,
        "save-token": true,
        "podcasts": true,
        "zeroconf-port": true
    });

    println!("{}", capabilities.to_string());
    exit(0);
}

// inspired by examples/get_token.rs
pub async fn get_token(
    client_id: Option<String>,
    scopes: Option<String>,
    save_token: Option<String>,
    last_credentials: Option<Credentials>,
    session_config: SessionConfig,
) {
    match last_credentials {
        Some(last_credentials) => {
            if let Some(client_id) = client_id {
                let scopes = scopes.unwrap_or(SCOPES.to_string());

                match Session::connect(session_config, last_credentials, None).await {
                    Ok(session) => {
                        match keymaster::get_token(&session, &client_id, &scopes).await {
                            Ok(token) => {
                                let json_token = json!({
                                    "accessToken": token.access_token.to_string(),
                                    "expiresIn": token.expires_in,
                                });

                                if let Some(save_token) = save_token {
                                    fs::write(save_token.to_string(), format!("{}", json_token))
                                        .expect("Can't write token file");
                                } else {
                                    println!("{}", json_token);
                                }
                            }
                            Err(error) => {
                                error!("Failed to fetch token: {:?}", error);
                                println!("{{ \"error\": \"Failed to get token.\" }}");
                            }
                        }
                    }
                    Err(error) => {
                        error!("Failed to create session: {:?}", error);
                        println!("{{ \"error\": \"Failed to create session.\" }}");
                    }
                }
            } else {
                println!("Use --client-id to provide a CLIENT_ID");
            }
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// inspired by examples/play.rs
pub async fn play_track(
    track_id: String,
    start_position: u32,
    last_credentials: Option<Credentials>,
    player_config: PlayerConfig,
    session_config: SessionConfig,
) {
    match last_credentials {
        Some(last_credentials) => {
            let backend = audio_backend::find(None).unwrap();
            let audio_format = AudioFormat::default();

            let track = SpotifyId::from_uri(
                track_id
                    .replace("spotty://", "spotify:track:")
                    .replace("://", ":")
                    .as_str(),
            );

            match track {
                Ok(track) => match Session::connect(session_config, last_credentials, None).await {
                    Ok(session) => {
                        let (mut player, _) =
                            Player::new(player_config, session, None, move || {
                                backend(None, audio_format)
                            });

                        player.load(track, true, start_position);
                        player.await_end_of_track().await;
                    }
                    Err(error) => {
                        error!("Failed to create session: {:?}", error);
                        return;
                    }
                },
                Err(error) => {
                    error!("Problem getting a Spotify ID for {}: {:?}", track_id, error);
                    return;
                }
            };
        }
        None => {
            println!("Missing credentials");
        }
    }
}

// Connect mode support

#[derive(Clone)]
pub struct LMS {
    base_url: Option<String>,
    player_mac: Option<String>,
    auth: Option<String>,
}

#[allow(unused)]
impl LMS {
    pub fn new(base_url: Option<String>, player_mac: Option<String>, auth: Option<String>) -> LMS {
        LMS {
            base_url: Some(format!(
                "http://{}/jsonrpc.js",
                base_url.unwrap_or("localhost:9000".to_string())
            )),
            player_mac: player_mac,
            auth: auth,
        }
    }

    pub fn is_configured(&self) -> bool {
        if self.base_url != None {
            if self.player_mac != None {
                return true;
            }
        }

        return false;
    }

    pub async fn signal_event(&self, event: PlayerEvent) {
        let mut command = r#"["spottyconnect","change"]"#.to_string();

        match event {
            PlayerEvent::Changed {
                old_track_id,
                new_track_id,
            } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: changed, old track: {}, new track: {}",
                    old_track_id.to_base62().unwrap_or_default(),
                    new_track_id.to_base62().unwrap_or_default()
                );
                command = format!(
                    r#"["spottyconnect","change","{}","{}"]"#,
                    new_track_id.to_base62().unwrap_or_default().to_string(),
                    old_track_id.to_base62().unwrap_or_default().to_string()
                );
            }
            PlayerEvent::Started { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: started, track: {}",
                    track_id.to_base62().unwrap_or_default()
                );
                command = format!(
                    r#"["spottyconnect","start","{}"]"#,
                    track_id.to_base62().unwrap_or_default().to_string()
                );
            }
            PlayerEvent::Stopped { track_id, .. } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: stopped, track: {}",
                    track_id.to_base62().unwrap_or_default()
                );
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::Playing {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: playing, track: {}, duration: {}, position: {}",
                    track_id.to_base62().unwrap_or_default(),
                    duration_ms,
                    position_ms
                );
                // we're not implementing the seek event here, as it's going to read player state anyway
                // but signal a change if the new position has changed and is > 0
                if position_ms <= 0 {
                    return;
                }
                command = r#"["spottyconnect","change"]"#.to_string();
            }
            PlayerEvent::Paused {
                track_id,
                duration_ms,
                position_ms,
                ..
            } => {
                #[cfg(debug_assertions)]
                info!(
                    "event: paused, track: {}, duration: {}, position: {}",
                    track_id.to_base62().unwrap_or_default(),
                    duration_ms,
                    position_ms
                );
                command = r#"["spottyconnect","stop"]"#.to_string();
            }
            PlayerEvent::VolumeSet { volume } => {
                let mut new_volume = volume as u32;
                if new_volume > 0 {
                    new_volume = new_volume * 100 / u32::pow(2, 16);
                }

                if new_volume > 100 {
                    new_volume = 100;
                };

                #[cfg(debug_assertions)]
                info!("event: volume: {}", volume);
                // we're not using the volume here, as LMS will read player state anyway
                command = format!(r#"["spottyconnect","volume",{}]"#, new_volume.to_string());
            }
            _ => return,
        }

        if !self.is_configured() {
            #[cfg(debug_assertions)]
            warn!("LMS connection is not configured");
            #[cfg(debug_assertions)]
            info!("{}", command);
            return;
        }

        if let Some(ref base_url) = self.base_url {
            #[cfg(debug_assertions)]
            info!("Base URL to talk to LMS: {}", base_url);

            if let Some(ref player_mac) = self.player_mac {
                #[cfg(debug_assertions)]
                info!("Player MAC address to control: {}", player_mac);

                #[cfg(debug_assertions)]
                info!("Command to send to player: {}", command);

                let json = format!(
                    r#"{{"id": 1,"method":"slim.request","params":["{}",{}]}}"#,
                    player_mac, command
                );

                let mut auth_header = "".to_string();
                if let Some(ref auth) = self.auth {
                    auth_header = auth.trim().to_string();
                }

                let req = Request::builder()
                    .method(Method::POST)
                    .uri(base_url.to_string())
                    .header("user-agent", format!("{}", VERSION))
                    .header("content-type", "application/json")
                    .header("authorization", format!("Basic {}", auth_header))
                    .header("x-scanner", "1")
                    .body(Body::from(json.clone()))
                    .unwrap();

                let client = Client::new();
                let resp = client.request(req).await;

                match resp {
                    Ok(resp) => {
                        #[cfg(debug_assertions)]
                        info!("Response: {}", resp.status());
                    }
                    Err(error) => {
                        warn!("Problem posting to {} / {}: {:?}", base_url, json, error);
                    }
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>pub use sensors::SensorTemplate;
pub use sensors::Sensors;
pub use sensors::TemperatureSensor;
pub use sensors::PeopleNowPresentSensor;

use std::collections::HashMap;

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Location {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<String>,
    pub lat: f64,
    pub lon: f64,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Spacefed {
    pub spacenet: bool,
    pub spacesaml: bool,
    pub spacephone: bool,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Icon {
    pub open: String,
    pub close: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct State {
    pub open: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lastchange: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trigger_person: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon: Option<Icon>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Event {
    pub name: String,
    pub _type: String,
    pub timestamp: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extra: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Keymaster {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub irc_nick: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub twitter: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct GoogleContact {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub plus: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Contact {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sip: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keymasters: Option<Vec<Keymaster>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub irc: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub twitter: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub facebook: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub google: Option<GoogleContact>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub identica: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub foursquare: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ml: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub jabber: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub issue_mail: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Feed {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub _type: Option<String>,
    pub url: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Feeds {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub blog: Option<Feed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub wiki: Option<Feed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub calendar: Option<Feed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub flickr: Option<Feed>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Cache {
    pub schedule: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct RadioShow {
    pub name: String,
    pub url: String,
    pub _type: String,
    pub start: String,
    pub end: String,
}

/// The main Space API status object.
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Status {

    // Hackerspace properties
    pub api: String,
    pub space: String,
    pub logo: String,
    pub url: String,
    pub location: Location,
    pub contact: Contact,

    // Hackerspace features / projects
    #[serde(skip_serializing_if = "Option::is_none")]
    pub spacefed: Option<Spacefed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub projects: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cam: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub feeds: Option<Feeds>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub events: Option<Vec<Event>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub radio_show: Option<Vec<RadioShow>>,

    // SpaceAPI internal usage
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cache: Option<Cache>,
    pub issue_report_channels: Vec<String>,

    // Mutable data
    pub state: State,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sensors: Option<Sensors>,

    // Version extension
    // TODO: Once we move to serde, maybe we can store this
    // as `HashMap<String, &'static str>`?
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ext_versions: Option<HashMap<String, String>>,
}

impl Status {
    /// Create a new Status object with only the absolutely required fields.
    pub fn new<S: Into<String>>(space: S, logo: S, url: S, location: Location, contact: Contact,
                                issue_report_channels: Vec<String>) -> Status {
        Status {
            api: "0.13".into(),
            space: space.into(),
            logo: logo.into(),
            url: url.into(),
            location: location,
            contact: contact,

            spacefed: None,
            projects: None,
            cam: None,
            feeds: None,
            events: None,
            radio_show: None,

            cache: None,
            issue_report_channels: issue_report_channels,

            state: State {
                open: None,
                lastchange: None,
                trigger_person: None,
                message: None,
                icon: None,
            },
            sensors: None,

            ext_versions: None,
        }
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use serde_json::{to_string, from_str};

    #[test]
    fn serialize_deserialize_cache() {
        let a = Cache { schedule: "bla".into() };
        let b: Cache = from_str(&to_string(&a).unwrap()).unwrap();
        assert_eq!(a.schedule, b.schedule);
    }

    #[test]
    fn serialize_deserialize_simple_contact() {
        let a: Contact = Contact {
            phone: None,
            sip: None,
            keymasters: Some(vec![
                              Keymaster {
                                  name: Some("Joe".into()),
                                  irc_nick: None,
                                  phone: None,
                                  email: Some("joe@example.com".into()),
                                  twitter: None,
                              },
            ]),
            irc: Some("bla".into()),
            twitter: None,
            facebook: None,
            google: Some(GoogleContact { plus: Some("http://gplus/profile".into()) }),
            identica: None,
            foursquare: None,
            email: Some("bli@bla".into()),
            ml: None,
            jabber: None,
            issue_mail: None,
        };
        let b: Contact = from_str(&to_string(&a).unwrap()).unwrap();

        assert_eq!(a, b);
    }

    #[test]
    fn serialize_skip_none() {
        let f1 = Feed {
            _type: Some("rss".to_string()),
            url: "https://some/rss.xml".to_string(),
        };
        let f2 = Feed {
            _type: None,
            url: "https://some/rss.xml".to_string(),
        };
        assert_eq!(to_string(&f1).unwrap(),
            "{\"_type\":\"rss\",\"url\":\"https://some/rss.xml\"}".to_string());
        assert_eq!(to_string(&f2).unwrap(),
            "{\"url\":\"https://some/rss.xml\"}".to_string());
    }

}

<commit_msg>Use Default trait<commit_after>pub use sensors::SensorTemplate;
pub use sensors::Sensors;
pub use sensors::TemperatureSensor;
pub use sensors::PeopleNowPresentSensor;

use std::collections::HashMap;

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Location {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub address: Option<String>,
    pub lat: f64,
    pub lon: f64,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Spacefed {
    pub spacenet: bool,
    pub spacesaml: bool,
    pub spacephone: bool,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Icon {
    pub open: String,
    pub close: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct State {
    pub open: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub lastchange: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trigger_person: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub message: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon: Option<Icon>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Event {
    pub name: String,
    pub _type: String,
    pub timestamp: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extra: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Keymaster {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub irc_nick: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub twitter: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct GoogleContact {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub plus: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Contact {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub phone: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sip: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keymasters: Option<Vec<Keymaster>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub irc: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub twitter: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub facebook: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub google: Option<GoogleContact>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub identica: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub foursquare: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ml: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub jabber: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub issue_mail: Option<String>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Feed {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub _type: Option<String>,
    pub url: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Feeds {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub blog: Option<Feed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub wiki: Option<Feed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub calendar: Option<Feed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub flickr: Option<Feed>,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Cache {
    pub schedule: String,
}

#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct RadioShow {
    pub name: String,
    pub url: String,
    pub _type: String,
    pub start: String,
    pub end: String,
}

/// The main Space API status object.
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq)]
pub struct Status {

    // Hackerspace properties
    pub api: String,
    pub space: String,
    pub logo: String,
    pub url: String,
    pub location: Location,
    pub contact: Contact,

    // Hackerspace features / projects
    #[serde(skip_serializing_if = "Option::is_none")]
    pub spacefed: Option<Spacefed>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub projects: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cam: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub feeds: Option<Feeds>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub events: Option<Vec<Event>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub radio_show: Option<Vec<RadioShow>>,

    // SpaceAPI internal usage
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cache: Option<Cache>,
    pub issue_report_channels: Vec<String>,

    // Mutable data
    pub state: State,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sensors: Option<Sensors>,

    // Version extension
    // TODO: Once we move to serde, maybe we can store this
    // as `HashMap<String, &'static str>`?
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ext_versions: Option<HashMap<String, String>>,
}

impl Status {
    /// Create a new Status object with only the absolutely required fields.
    pub fn new<S: Into<String>>(space: S, logo: S, url: S, location: Location, contact: Contact,
                                issue_report_channels: Vec<String>) -> Status {
        Status {
            api: "0.13".into(),
            space: space.into(),
            logo: logo.into(),
            url: url.into(),
            location: location,
            contact: contact,
            issue_report_channels: issue_report_channels,
            ..Default::default()
        }
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use serde_json::{to_string, from_str};

    #[test]
    fn serialize_deserialize_cache() {
        let a = Cache { schedule: "bla".into() };
        let b: Cache = from_str(&to_string(&a).unwrap()).unwrap();
        assert_eq!(a.schedule, b.schedule);
    }

    #[test]
    fn serialize_deserialize_simple_contact() {
        let a: Contact = Contact {
            keymasters: Some(vec![
                              Keymaster {
                                  name: Some("Joe".into()),
                                  irc_nick: None,
                                  phone: None,
                                  email: Some("joe@example.com".into()),
                                  twitter: None,
                              },
            ]),
            irc: Some("bla".into()),
            google: Some(GoogleContact { plus: Some("http://gplus/profile".into()) }),
            email: Some("bli@bla".into()),
            ..Default::default()
        };
        let b: Contact = from_str(&to_string(&a).unwrap()).unwrap();

        assert_eq!(a, b);
    }

    #[test]
    fn serialize_skip_none() {
        let f1 = Feed {
            _type: Some("rss".to_string()),
            url: "https://some/rss.xml".to_string(),
        };
        let f2 = Feed {
            _type: None,
            url: "https://some/rss.xml".to_string(),
        };
        assert_eq!(to_string(&f1).unwrap(),
            "{\"_type\":\"rss\",\"url\":\"https://some/rss.xml\"}".to_string());
        assert_eq!(to_string(&f2).unwrap(),
            "{\"url\":\"https://some/rss.xml\"}".to_string());
    }

}

<|endoftext|>


<commit_before>use std::os::raw::{c_int, c_double, c_void};
use std::{ptr, slice};

use ffi;
use base::*;

macro_rules! write_stream {
    ($name:ident, $t:ty) => (
        /// Expects a vector of `channel_count` channel `buffers`
        /// containing audio data that are written into
        /// the output device buffer.
        /// Returns the number of actually written frames.
        /// If the provided buffers contain less frames
        /// than `min_frame_count`, or less buffers
        /// as `channel_count` are provided,
        /// then a `ffi::SioError::Invalid` is returned.
        pub fn $name(&self, min_frame_count: i32, buffers: &Vec<Vec<$t>>) -> SioResult<i32> {
            let channel_count = self.get_layout().channel_count();
            // check if buffer contains frames for all channels
            if buffers.len() < channel_count as usize {
                return Err(ffi::SioError::Invalid);
            }
            // check if there are at least min_frame_count frames for all channels
            if !buffers.iter().map(|c| c.len()).all(|l| l >= min_frame_count as usize) {
                return Err(ffi::SioError::Invalid);
            }

            // assuming that every channel buffer has the same length
            let mut frame_count = buffers[0].len() as c_int;
            let mut raw_areas: *mut ffi::SoundIoChannelArea = ptr::null_mut();
            let actual_frame_count = try!(self.begin_write(&mut raw_areas, &frame_count));
            let areas = unsafe { slice::from_raw_parts_mut(raw_areas, channel_count as usize) };
            for idx in 0..actual_frame_count as usize {
                for channel in 0..channel_count as usize {
                    let area = areas[channel];
                    let addr = (area.ptr as usize + area.step as usize * idx) as *mut $t;
                    unsafe { *addr = buffers[channel][idx] }
                }
            }
            self.end_write().map_or(Ok(actual_frame_count), |err| Err(err))
        }
    )
}

extern "C" fn write_wrapper<W>(raw_out: *mut ffi::SoundIoOutStream, min: c_int, max: c_int)
    where W: Fn(OutStream, i32, i32)
{
    let out = OutStream::new(raw_out);
    let callbacks_ptr = unsafe { (*out.stream).userdata as *const Box<OutStreamCallbacks> };
    let callbacks: &Box<OutStreamCallbacks> = unsafe { &*callbacks_ptr };
    callbacks.write.as_ref().map(|ref f| f(out, min as i32, max as i32));
}

extern "C" fn underflow_wrapper<U>(raw_out: *mut ffi::SoundIoOutStream)
    where U: Fn(OutStream)
{
    let out = OutStream::new(raw_out);
    let callbacks_ptr = unsafe { (*out.stream).userdata as *const Box<OutStreamCallbacks> };
    let callbacks: &Box<OutStreamCallbacks> = unsafe { &*callbacks_ptr };
    callbacks.underflow.as_ref().map(|ref f| f(out));
}

extern "C" fn error_wrapper<E>(raw_out: *mut ffi::SoundIoOutStream, error: ffi::SioError)
    where E: Fn(OutStream, ffi::SioError)
{
    let out = OutStream::new(raw_out);
    let callbacks_ptr = unsafe { (*out.stream).userdata as *const Box<OutStreamCallbacks> };
    let callbacks: &Box<OutStreamCallbacks> = unsafe { &*callbacks_ptr };
    callbacks.error.as_ref().map(|ref f| f(out, error));
}

struct OutStreamCallbacks<'a> {
    write: Option<Box<Fn(OutStream, i32, i32) + 'a>>,
    underflow: Option<Box<Fn(OutStream) + 'a>>,
    error: Option<Box<Fn(OutStream, ffi::SioError) + 'a>>,
}
impl<'a> Default for OutStreamCallbacks<'a> {
    fn default() -> Self {
        OutStreamCallbacks {
            write: None,
            underflow: None,
            error: None,
        }
    }
}
impl<'a> Drop for OutStreamCallbacks<'a> {
    fn drop(&mut self) {}
}

/// An audio output stream, returned from a `Device`.
pub struct OutStream<'a> {
    stream: *mut ffi::SoundIoOutStream,
    callbacks: Box<OutStreamCallbacks<'a>>,
}
impl<'a> OutStream<'a> {
    pub fn new(raw_stream: *mut ffi::SoundIoOutStream) -> Self {
        let callbacks = Box::new(OutStreamCallbacks::default());
        OutStream {
            stream: raw_stream,
            callbacks: callbacks,
        }
    }

    /// Change settings (e.g. `set_format`) **before** calling `open`.
    /// After you call this function, `OutStream::software_latency` is set to
    /// the correct value.
    ///
    /// The next thing to do is call `outstream_start`.
    /// If this function returns an error, the outstream is in an invalid state and
    /// you must call `destroy` on it.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioErrorInvalid`
    ///     - TODO: implement format setting
    ///     - SoundIoDevice::aim is not #SoundIoDeviceAimOutput
    ///     - SoundIoOutStream::format is not valid
    ///     - SoundIoOutStream::channel_count is greater than #SOUNDIO_MAX_CHANNELS
    /// - `ffi::SioError::NoMem`
    /// - `ffi::SioError::OpeningDevice`
    /// - `ffi::SioError::BackendDisconnected`
    /// - `ffi::SioError::SystemResources`
    /// - `ffi::SioError::NoSuchClient` - when JACK returns `JackNoSuchClient`
    /// - `ffi::SioErrorOpeningDevice`
    /// - `ffi::SioErrorIncompatibleBackend` - `OutStream::channel_count` is
    ///   greater than the number of channels the backend can handle.
    /// - `ffi::SioErrorIncompatibleDevice` - stream parameters requested are not
    ///   compatible with the chosen device.
    pub fn open(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_open(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// After you call this function, the registered `write_callback` will be called.
    ///
    /// This function might directly call the `write_callback`.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioError::Streaming`
    /// - `ffi::SioError::NoMem`
    /// - `ffi::SioError::SystemResources`
    /// - `ffi::SioError::BackendDisconnected`
    pub fn start(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_start(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// Registers the given callback as `write_callback` that is called as soon as you call `start`.
    ///
    /// In this callback, you call `OutStream::write_stream_FMT` where `FMT` is one of the supported
    /// format, `u16`, `f32` etc.
    ///
    /// `frame_count_max` will always be greater than 0. Note that you
    /// should write as many frames as you can; `frame_count_min` might be 0 and
    /// you can still get a buffer underflow if you always write
    /// `frame_count_min` frames.
    ///
    /// For Dummy, ALSA, and PulseAudio, `frame_count_min` will be 0. For JACK
    /// and CoreAudio `frame_count_min` will be equal to `frame_count_max`.
    ///
    /// The code in the supplied function must be suitable for real-time
    /// execution. That means that it cannot call functions that might block
    /// for a long time. This includes all I/O functions (disk, TTY, network),
    /// malloc, free, printf, pthread_mutex_lock, sleep, wait, poll, select,
    /// pthread_join, pthread_cond_wait, etc.
    pub fn register_write_callback<W>(&mut self, callback: Box<W>)
        where W: Fn(OutStream, i32, i32) + 'a
    {
        // stored box reference to callback closure
        self.callbacks.write = Some(callback);
        unsafe {
            // register wrapper for write_callback
            (*self.stream).write_callback = Some(write_wrapper::<W>);
            // store reference to callbacks struct in userdata pointer
            (*self.stream).userdata =
                &self.callbacks as *const Box<OutStreamCallbacks> as *mut c_void
        }
    }

    /// Registers the given callback as `underflow_callback`.
    /// This *optional* callback happens when the sound device runs out of buffered audio data to play.
    /// After this occurs, the outstream waits until the buffer is full to resume playback.
    /// This is called from the `OutStream::write_callback` thread context.
    pub fn register_underflow_callback<U>(&mut self, callback: Box<U>)
        where U: Fn(OutStream) + 'a
    {
        self.callbacks.underflow = Some(callback);
        unsafe {
            // register wrapper for write_callback
            (*self.stream).underflow_callback = Some(underflow_wrapper::<U>);
            // store reference to callbacks struct in userdata pointer
            (*self.stream).userdata =
                &self.callbacks as *const Box<OutStreamCallbacks> as *mut c_void
        }
    }

    /// *Optional* callback. `err` is always `ffi::SioError::ErrorStreaming`.
    /// This is an unrecoverable error. The stream is in an
    /// invalid state and must be destroyed, call `OutStream::destroy`.
    /// If you do not supply `error_callback`, the default callback will print
    /// a message to stderr and then call `abort`.
    /// This is called from the `OutStream::write_callback` thread context.
    pub fn register_error_callback<E>(&mut self, callback: Box<E>)
        where E: Fn(OutStream, ffi::SioError) + 'a
    {
        self.callbacks.error = Some(callback);
        unsafe {
            // register wrapper for write_callback
            (*self.stream).error_callback = Some(error_wrapper::<E>);
            // store reference to callbacks struct in userdata pointer
            (*self.stream).userdata =
                &self.callbacks as *const Box<OutStreamCallbacks> as *mut c_void
        }
    }

    write_stream!(write_stream_i8, i8);
    write_stream!(write_stream_u8, u8);
    write_stream!(write_stream_i16, i16);
    write_stream!(write_stream_u16, u16);
    write_stream!(write_stream_i32, i32);
    write_stream!(write_stream_u32, u32);
    write_stream!(write_stream_f32, f32);
    write_stream!(write_stream_f64, f64);

    fn begin_write(&self,
                   areas: *mut *mut ffi::SoundIoChannelArea,
                   frame_count: &i32)
                   -> SioResult<i32> {
        let mut actual_frame_count = *frame_count as c_int;
        match unsafe {
            ffi::soundio_outstream_begin_write(self.stream,
                                               areas,
                                               &mut actual_frame_count as *mut c_int)
        } {
            ffi::SioError::None => Ok(actual_frame_count),
            err @ _ => Err(err),
        }
    }

    fn end_write(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_end_write(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// Clears the output stream buffer.
    /// This function can be called from any thread.
    /// This function can be called regardless of whether the outstream is paused
    /// or not.
    /// Some backends do not support clearing the buffer. On these backends this
    /// function will return `ffi::SioError::IncompatibleBackend`.
    /// Some devices do not support clearing the buffer. On these devices this
    /// function might return `ffi::SioError::IncompatibleDevice`.
    /// Possible errors:
    ///
    /// - `ffi::SioError::Streaming`
    /// - `ffi::SioError::IncompatibleBackend`
    /// - `ffi::SioError::IncompatibleDevice`
    pub fn clear_buffer(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_clear_buffer(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// If the underlying backend and device support pausing, this pauses the
    /// stream. `OutStream::write_callback` may be called a few more times if
    /// the buffer is not full.
    /// Pausing might put the hardware into a low power state which is ideal if your
    /// software is silent for some time.
    /// This function may be called from any thread context, including
    /// `OutStream::write_callback`.
    /// Pausing when already paused or unpausing when already unpaused has no
    /// effect and returns `None`.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioError::BackendDisconnected`
    /// - `ffi::SioError::Streaming`
    /// - `ffi::SioError::IncompatibleDevice` - device does not support
    ///   pausing/unpausing. This error code might not be returned even if the
    ///   device does not support pausing/unpausing.
    /// - `ffi::SioError::IncompatibleBackend` - backend does not support
    ///   pausing/unpausing.
    /// - `ffi::SioError::Invalid` - outstream not opened and started
    pub fn pause(&self, pause: bool) -> Option<ffi::SioError> {
        let pause_c_bool = match pause {
            true => 1u8,
            false => 0u8,
        };
        match unsafe { ffi::soundio_outstream_pause(self.stream, pause_c_bool) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// Obtain the total number of seconds that the next frame written after the
    /// last frame written will take to become
    /// audible.
    /// This includes both software and hardware latency.
    ///
    /// This function must be called only from within `OutStream::write_callback`.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioError::Streaming`
    pub fn get_latency(&self) -> SioResult<f64> {
        let mut latency = 0.0f64;
        match unsafe {
            ffi::soundio_outstream_get_latency(self.stream, &mut latency as *mut c_double)
        } {
            ffi::SioError::None => Ok(latency),
            err @ _ => Err(err),
        }

    }

    /// Returns the current `format` or a `ffi::SioError::Invalid` if
    /// the format is not set.
    pub fn current_format(&self) -> SioResult<ffi::SioFormat> {
        match unsafe { (*self.stream).format } {
            ffi::SioFormat::Invalid => Err(ffi::SioError::Invalid),
            fmt @ _ => Ok(fmt),
        }
    }

    /// Returns the channel layout of the output stream.
    pub fn get_layout(&self) -> ChannelLayout {
        ChannelLayout::new(unsafe { &(*self.stream).layout })
    }

    /// Returns the sample rate of the output stream.
    pub fn get_sample_rate(&self) -> i32 {
        unsafe { (*self.stream).sample_rate as i32 }
    }

    /// Returns the underlying device of the output stream.
    pub fn get_device(&self) -> Device {
        let dev = Device::new(unsafe { (*self.stream).device });
        dev.inc_ref();
        dev
    }

    /// Destroys the output stream.
    /// Calls this when your application shuts down.
    /// NOTE: This can break if a callback is still active.
    pub fn destroy(&self) {
        unsafe { ffi::soundio_outstream_destroy(self.stream) }
    }
}
impl<'a> Drop for OutStream<'a> {
    fn drop(&mut self) {
        // TODO: call destroy manually.
        // OutStream will get dropped each time a new
        // struct is created from the same *mut pointer.
    }
}
<commit_msg>Update rustdoc<commit_after>use std::os::raw::{c_int, c_double, c_void};
use std::{ptr, slice};

use ffi;
use base::*;

macro_rules! write_stream {
    ($name:ident, $t:ty) => (
        /// Expects a vector of `channel_count` channel `buffers`
        /// containing audio data that are written into
        /// the output device buffer.
        /// Returns the number of actually written frames.
        /// If the provided buffers contain less frames
        /// than `min_frame_count`, or less buffers
        /// as `channel_count` are provided,
        /// then a `ffi::SioError::Invalid` is returned.
        pub fn $name(&self, min_frame_count: i32, buffers: &Vec<Vec<$t>>) -> SioResult<i32> {
            let channel_count = self.get_layout().channel_count();
            // check if buffer contains frames for all channels
            if buffers.len() < channel_count as usize {
                return Err(ffi::SioError::Invalid);
            }
            // check if there are at least min_frame_count frames for all channels
            if !buffers.iter().map(|c| c.len()).all(|l| l >= min_frame_count as usize) {
                return Err(ffi::SioError::Invalid);
            }

            // assuming that every channel buffer has the same length
            let mut frame_count = buffers[0].len() as c_int;
            let mut raw_areas: *mut ffi::SoundIoChannelArea = ptr::null_mut();
            let actual_frame_count = try!(self.begin_write(&mut raw_areas, &frame_count));
            let areas = unsafe { slice::from_raw_parts_mut(raw_areas, channel_count as usize) };
            for idx in 0..actual_frame_count as usize {
                for channel in 0..channel_count as usize {
                    let area = areas[channel];
                    let addr = (area.ptr as usize + area.step as usize * idx) as *mut $t;
                    unsafe { *addr = buffers[channel][idx] }
                }
            }
            self.end_write().map_or(Ok(actual_frame_count), |err| Err(err))
        }
    )
}

extern "C" fn write_wrapper<W>(raw_out: *mut ffi::SoundIoOutStream, min: c_int, max: c_int)
    where W: Fn(OutStream, i32, i32)
{
    let out = OutStream::new(raw_out);
    let callbacks_ptr = unsafe { (*out.stream).userdata as *const Box<OutStreamCallbacks> };
    let callbacks: &Box<OutStreamCallbacks> = unsafe { &*callbacks_ptr };
    callbacks.write.as_ref().map(|ref f| f(out, min as i32, max as i32));
}

extern "C" fn underflow_wrapper<U>(raw_out: *mut ffi::SoundIoOutStream)
    where U: Fn(OutStream)
{
    let out = OutStream::new(raw_out);
    let callbacks_ptr = unsafe { (*out.stream).userdata as *const Box<OutStreamCallbacks> };
    let callbacks: &Box<OutStreamCallbacks> = unsafe { &*callbacks_ptr };
    callbacks.underflow.as_ref().map(|ref f| f(out));
}

extern "C" fn error_wrapper<E>(raw_out: *mut ffi::SoundIoOutStream, error: ffi::SioError)
    where E: Fn(OutStream, ffi::SioError)
{
    let out = OutStream::new(raw_out);
    let callbacks_ptr = unsafe { (*out.stream).userdata as *const Box<OutStreamCallbacks> };
    let callbacks: &Box<OutStreamCallbacks> = unsafe { &*callbacks_ptr };
    callbacks.error.as_ref().map(|ref f| f(out, error));
}

struct OutStreamCallbacks<'a> {
    write: Option<Box<Fn(OutStream, i32, i32) + 'a>>,
    underflow: Option<Box<Fn(OutStream) + 'a>>,
    error: Option<Box<Fn(OutStream, ffi::SioError) + 'a>>,
}
impl<'a> Default for OutStreamCallbacks<'a> {
    fn default() -> Self {
        OutStreamCallbacks {
            write: None,
            underflow: None,
            error: None,
        }
    }
}
impl<'a> Drop for OutStreamCallbacks<'a> {
    fn drop(&mut self) {}
}

/// An audio output stream, returned from a `Device`.
pub struct OutStream<'a> {
    stream: *mut ffi::SoundIoOutStream,
    callbacks: Box<OutStreamCallbacks<'a>>,
}
impl<'a> OutStream<'a> {
    pub fn new(raw_stream: *mut ffi::SoundIoOutStream) -> Self {
        let callbacks = Box::new(OutStreamCallbacks::default());
        OutStream {
            stream: raw_stream,
            callbacks: callbacks,
        }
    }

    /// Change settings (e.g. `set_format`) **before** calling `open`.
    /// After you call this function, `OutStream::software_latency` is set to
    /// the correct value.
    ///
    /// The next thing to do is call `outstream_start`.
    /// If this function returns an error, the outstream is in an invalid state and
    /// you must call `destroy` on it.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioErrorInvalid`
    ///     - device is not an *output* device
    ///     - format is not valid
    ///     - `channel_count` is greater than 24
    /// - `ffi::SioError::NoMem`
    /// - `ffi::SioError::OpeningDevice`
    /// - `ffi::SioError::BackendDisconnected`
    /// - `ffi::SioError::SystemResources`
    /// - `ffi::SioError::NoSuchClient` - when JACK returns `JackNoSuchClient`
    /// - `ffi::SioErrorOpeningDevice`
    /// - `ffi::SioErrorIncompatibleBackend` - `OutStream::channel_count` is
    ///   greater than the number of channels the backend can handle.
    /// - `ffi::SioErrorIncompatibleDevice` - stream parameters requested are not
    ///   compatible with the chosen device.
    pub fn open(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_open(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// After you call this function, the registered `write_callback` will be called.
    ///
    /// This function might directly call the `write_callback`.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioError::Streaming`
    /// - `ffi::SioError::NoMem`
    /// - `ffi::SioError::SystemResources`
    /// - `ffi::SioError::BackendDisconnected`
    pub fn start(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_start(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// Registers the given callback as `write_callback` that is called as soon as you call `start`.
    ///
    /// In this callback, you call `OutStream::write_stream_FMT` where `FMT` is one of the supported
    /// format, `u16`, `f32` etc.
    ///
    /// `frame_count_max` will always be greater than 0. Note that you
    /// should write as many frames as you can; `frame_count_min` might be 0 and
    /// you can still get a buffer underflow if you always write
    /// `frame_count_min` frames.
    ///
    /// For Dummy, ALSA, and PulseAudio, `frame_count_min` will be 0. For JACK
    /// and CoreAudio `frame_count_min` will be equal to `frame_count_max`.
    ///
    /// The code in the supplied function must be suitable for real-time
    /// execution. That means that it cannot call functions that might block
    /// for a long time. This includes all I/O functions (disk, TTY, network),
    /// malloc, free, printf, pthread_mutex_lock, sleep, wait, poll, select,
    /// pthread_join, pthread_cond_wait, etc.
    pub fn register_write_callback<W>(&mut self, callback: Box<W>)
        where W: Fn(OutStream, i32, i32) + 'a
    {
        // stored box reference to callback closure
        self.callbacks.write = Some(callback);
        unsafe {
            // register wrapper for write_callback
            (*self.stream).write_callback = Some(write_wrapper::<W>);
            // store reference to callbacks struct in userdata pointer
            (*self.stream).userdata =
                &self.callbacks as *const Box<OutStreamCallbacks> as *mut c_void
        }
    }

    /// Registers the given callback as `underflow_callback`.
    /// This *optional* callback happens when the sound device runs out of buffered audio data to play.
    /// After this occurs, the outstream waits until the buffer is full to resume playback.
    /// This is called from the `OutStream::write_callback` thread context.
    pub fn register_underflow_callback<U>(&mut self, callback: Box<U>)
        where U: Fn(OutStream) + 'a
    {
        self.callbacks.underflow = Some(callback);
        unsafe {
            // register wrapper for write_callback
            (*self.stream).underflow_callback = Some(underflow_wrapper::<U>);
            // store reference to callbacks struct in userdata pointer
            (*self.stream).userdata =
                &self.callbacks as *const Box<OutStreamCallbacks> as *mut c_void
        }
    }

    /// *Optional* callback. `err` is always `ffi::SioError::ErrorStreaming`.
    /// This is an unrecoverable error. The stream is in an
    /// invalid state and must be destroyed, call `OutStream::destroy`.
    /// If you do not supply `error_callback`, the default callback will print
    /// a message to stderr and then call `abort`.
    /// This is called from the `OutStream::write_callback` thread context.
    pub fn register_error_callback<E>(&mut self, callback: Box<E>)
        where E: Fn(OutStream, ffi::SioError) + 'a
    {
        self.callbacks.error = Some(callback);
        unsafe {
            // register wrapper for write_callback
            (*self.stream).error_callback = Some(error_wrapper::<E>);
            // store reference to callbacks struct in userdata pointer
            (*self.stream).userdata =
                &self.callbacks as *const Box<OutStreamCallbacks> as *mut c_void
        }
    }

    write_stream!(write_stream_i8, i8);
    write_stream!(write_stream_u8, u8);
    write_stream!(write_stream_i16, i16);
    write_stream!(write_stream_u16, u16);
    write_stream!(write_stream_i32, i32);
    write_stream!(write_stream_u32, u32);
    write_stream!(write_stream_f32, f32);
    write_stream!(write_stream_f64, f64);

    fn begin_write(&self,
                   areas: *mut *mut ffi::SoundIoChannelArea,
                   frame_count: &i32)
                   -> SioResult<i32> {
        let mut actual_frame_count = *frame_count as c_int;
        match unsafe {
            ffi::soundio_outstream_begin_write(self.stream,
                                               areas,
                                               &mut actual_frame_count as *mut c_int)
        } {
            ffi::SioError::None => Ok(actual_frame_count),
            err @ _ => Err(err),
        }
    }

    fn end_write(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_end_write(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// Clears the output stream buffer.
    /// This function can be called from any thread.
    /// This function can be called regardless of whether the outstream is paused
    /// or not.
    /// Some backends do not support clearing the buffer. On these backends this
    /// function will return `ffi::SioError::IncompatibleBackend`.
    /// Some devices do not support clearing the buffer. On these devices this
    /// function might return `ffi::SioError::IncompatibleDevice`.
    /// Possible errors:
    ///
    /// - `ffi::SioError::Streaming`
    /// - `ffi::SioError::IncompatibleBackend`
    /// - `ffi::SioError::IncompatibleDevice`
    pub fn clear_buffer(&self) -> Option<ffi::SioError> {
        match unsafe { ffi::soundio_outstream_clear_buffer(self.stream) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// If the underlying backend and device support pausing, this pauses the
    /// stream. `OutStream::write_callback` may be called a few more times if
    /// the buffer is not full.
    /// Pausing might put the hardware into a low power state which is ideal if your
    /// software is silent for some time.
    /// This function may be called from any thread context, including
    /// `OutStream::write_callback`.
    /// Pausing when already paused or unpausing when already unpaused has no
    /// effect and returns `None`.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioError::BackendDisconnected`
    /// - `ffi::SioError::Streaming`
    /// - `ffi::SioError::IncompatibleDevice` - device does not support
    ///   pausing/unpausing. This error code might not be returned even if the
    ///   device does not support pausing/unpausing.
    /// - `ffi::SioError::IncompatibleBackend` - backend does not support
    ///   pausing/unpausing.
    /// - `ffi::SioError::Invalid` - outstream not opened and started
    pub fn pause(&self, pause: bool) -> Option<ffi::SioError> {
        let pause_c_bool = match pause {
            true => 1u8,
            false => 0u8,
        };
        match unsafe { ffi::soundio_outstream_pause(self.stream, pause_c_bool) } {
            ffi::SioError::None => None,
            err @ _ => Some(err),
        }
    }

    /// Obtain the total number of seconds that the next frame written after the
    /// last frame written will take to become
    /// audible.
    /// This includes both software and hardware latency.
    ///
    /// This function must be called only from within `OutStream::write_callback`.
    ///
    /// Possible errors:
    ///
    /// - `ffi::SioError::Streaming`
    pub fn get_latency(&self) -> SioResult<f64> {
        let mut latency = 0.0f64;
        match unsafe {
            ffi::soundio_outstream_get_latency(self.stream, &mut latency as *mut c_double)
        } {
            ffi::SioError::None => Ok(latency),
            err @ _ => Err(err),
        }

    }

    /// Returns the current `format` or a `ffi::SioError::Invalid` if
    /// the format is not set.
    pub fn current_format(&self) -> SioResult<ffi::SioFormat> {
        match unsafe { (*self.stream).format } {
            ffi::SioFormat::Invalid => Err(ffi::SioError::Invalid),
            fmt @ _ => Ok(fmt),
        }
    }

    /// Returns the channel layout of the output stream.
    pub fn get_layout(&self) -> ChannelLayout {
        ChannelLayout::new(unsafe { &(*self.stream).layout })
    }

    /// Returns the sample rate of the output stream.
    pub fn get_sample_rate(&self) -> i32 {
        unsafe { (*self.stream).sample_rate as i32 }
    }

    /// Returns the underlying device of the output stream.
    pub fn get_device(&self) -> Device {
        let dev = Device::new(unsafe { (*self.stream).device });
        dev.inc_ref();
        dev
    }

    /// Destroys the output stream.
    /// Calls this when your application shuts down.
    /// NOTE: This can break if a callback is still active.
    pub fn destroy(&self) {
        unsafe { ffi::soundio_outstream_destroy(self.stream) }
    }
}
impl<'a> Drop for OutStream<'a> {
    fn drop(&mut self) {
        // TODO: call destroy manually.
        // OutStream will get dropped each time a new
        // struct is created from the same *mut pointer.
    }
}
<|endoftext|>