<commit_before>#![allow(unknown_lints)]
#![allow(clippy::identity_op)] // used for vertical alignment

use std::collections::BTreeMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::Cursor;

use curl::easy::{Easy, List};
use failure::bail;
use http::status::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json;
use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};

pub type Result<T> = std::result::Result<T, failure::Error>;

pub struct Registry {
    /// The base URL for issuing API requests.
    host: String,
    /// Optional authorization token.
    /// If None, commands requiring authorization will fail.
    token: Option<String>,
    /// Curl handle for issuing requests.
    handle: Easy,
}

#[derive(PartialEq, Clone, Copy)]
pub enum Auth {
    Authorized,
    Unauthorized,
}

#[derive(Deserialize)]
pub struct Crate {
    pub name: String,
    pub description: Option<String>,
    pub max_version: String,
}

#[derive(Serialize)]
pub struct NewCrate {
    pub name: String,
    pub vers: String,
    pub deps: Vec<NewCrateDependency>,
    pub features: BTreeMap<String, Vec<String>>,
    pub authors: Vec<String>,
    pub description: Option<String>,
    pub documentation: Option<String>,
    pub homepage: Option<String>,
    pub readme: Option<String>,
    pub readme_file: Option<String>,
    pub keywords: Vec<String>,
    pub categories: Vec<String>,
    pub license: Option<String>,
    pub license_file: Option<String>,
    pub repository: Option<String>,
    pub badges: BTreeMap<String, BTreeMap<String, String>>,
    #[serde(default)]
    pub links: Option<String>,
}

#[derive(Serialize)]
pub struct NewCrateDependency {
    pub optional: bool,
    pub default_features: bool,
    pub name: String,
    pub features: Vec<String>,
    pub version_req: String,
    pub target: Option<String>,
    pub kind: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub registry: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub explicit_name_in_toml: Option<String>,
}

#[derive(Deserialize)]
pub struct User {
    pub id: u32,
    pub login: String,
    pub avatar: Option<String>,
    pub email: Option<String>,
    pub name: Option<String>,
}

pub struct Warnings {
    pub invalid_categories: Vec<String>,
    pub invalid_badges: Vec<String>,
    pub other: Vec<String>,
}

#[derive(Deserialize)]
struct R {
    ok: bool,
}
#[derive(Deserialize)]
struct OwnerResponse {
    ok: bool,
    msg: String,
}
#[derive(Deserialize)]
struct ApiErrorList {
    errors: Vec<ApiError>,
}
#[derive(Deserialize)]
struct ApiError {
    detail: String,
}
#[derive(Serialize)]
struct OwnersReq<'a> {
    users: &'a [&'a str],
}
#[derive(Deserialize)]
struct Users {
    users: Vec<User>,
}
#[derive(Deserialize)]
struct TotalCrates {
    total: u32,
}
#[derive(Deserialize)]
struct Crates {
    crates: Vec<Crate>,
    meta: TotalCrates,
}
impl Registry {
    pub fn new(host: String, token: Option<String>) -> Registry {
        Registry::new_handle(host, token, Easy::new())
    }

    pub fn new_handle(host: String, token: Option<String>, handle: Easy) -> Registry {
        Registry {
            host,
            token,
            handle,
        }
    }

    pub fn host(&self) -> &str {
        &self.host
    }

    pub fn add_owners(&mut self, krate: &str, owners: &[&str]) -> Result<String> {
        let body = serde_json::to_string(&OwnersReq { users: owners })?;
        let body = self.put(&format!("/crates/{}/owners", krate), body.as_bytes())?;
        assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
        Ok(serde_json::from_str::<OwnerResponse>(&body)?.msg)
    }

    pub fn remove_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
        let body = serde_json::to_string(&OwnersReq { users: owners })?;
        let body = self.delete(&format!("/crates/{}/owners", krate), Some(body.as_bytes()))?;
        assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
        Ok(())
    }

    pub fn list_owners(&mut self, krate: &str) -> Result<Vec<User>> {
        let body = self.get(&format!("/crates/{}/owners", krate))?;
        Ok(serde_json::from_str::<Users>(&body)?.users)
    }

    pub fn publish(&mut self, krate: &NewCrate, tarball: &File) -> Result<Warnings> {
        let json = serde_json::to_string(krate)?;
        // Prepare the body. The format of the upload request is:
        //
        //      <le u32 of json>
        //      <json request> (metadata for the package)
        //      <le u32 of tarball>
        //      <source tarball>
        let stat = tarball.metadata()?;
        let header = {
            let mut w = Vec::new();
            w.extend(
                [
                    (json.len() >> 0) as u8,
                    (json.len() >> 8) as u8,
                    (json.len() >> 16) as u8,
                    (json.len() >> 24) as u8,
                ].iter().cloned(),
            );
            w.extend(json.as_bytes().iter().cloned());
            w.extend(
                [
                    (stat.len() >> 0) as u8,
                    (stat.len() >> 8) as u8,
                    (stat.len() >> 16) as u8,
                    (stat.len() >> 24) as u8,
                ].iter().cloned(),
            );
            w
        };
        let size = stat.len() as usize + header.len();
        let mut body = Cursor::new(header).chain(tarball);

        let url = format!("{}/api/v1/crates/new", self.host);

        let token = match self.token.as_ref() {
            Some(s) => s,
            None => bail!("no upload token found, please run `cargo login`"),
        };
        self.handle.put(true)?;
        self.handle.url(&url)?;
        self.handle.in_filesize(size as u64)?;
        let mut headers = List::new();
        headers.append("Accept: application/json")?;
        headers.append(&format!("Authorization: {}", token))?;
        self.handle.http_headers(headers)?;

        let body = handle(&mut self.handle, &mut |buf| body.read(buf).unwrap_or(0))?;

        let response = if body.is_empty() {
            "{}".parse()?
        } else {
            body.parse::<serde_json::Value>()?
        };

        let invalid_categories: Vec<String> = response
            .get("warnings")
            .and_then(|j| j.get("invalid_categories"))
            .and_then(|j| j.as_array())
            .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
            .unwrap_or_else(Vec::new);

        let invalid_badges: Vec<String> = response
            .get("warnings")
            .and_then(|j| j.get("invalid_badges"))
            .and_then(|j| j.as_array())
            .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
            .unwrap_or_else(Vec::new);

        let other: Vec<String> = response
            .get("warnings")
            .and_then(|j| j.get("other"))
            .and_then(|j| j.as_array())
            .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
            .unwrap_or_else(Vec::new);

        Ok(Warnings {
            invalid_categories,
            invalid_badges,
            other,
        })
    }

    pub fn search(&mut self, query: &str, limit: u32) -> Result<(Vec<Crate>, u32)> {
        let formatted_query = percent_encode(query.as_bytes(), QUERY_ENCODE_SET);
        let body = self.req(
            &format!("/crates?q={}&per_page={}", formatted_query, limit),
            None,
            Auth::Unauthorized,
        )?;

        let crates = serde_json::from_str::<Crates>(&body)?;
        Ok((crates.crates, crates.meta.total))
    }

    pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
        let body = self.delete(&format!("/crates/{}/{}/yank", krate, version), None)?;
        assert!(serde_json::from_str::<R>(&body)?.ok);
        Ok(())
    }

    pub fn unyank(&mut self, krate: &str, version: &str) -> Result<()> {
        let body = self.put(&format!("/crates/{}/{}/unyank", krate, version), &[])?;
        assert!(serde_json::from_str::<R>(&body)?.ok);
        Ok(())
    }

    fn put(&mut self, path: &str, b: &[u8]) -> Result<String> {
        self.handle.put(true)?;
        self.req(path, Some(b), Auth::Authorized)
    }

    fn get(&mut self, path: &str) -> Result<String> {
        self.handle.get(true)?;
        self.req(path, None, Auth::Authorized)
    }

    fn delete(&mut self, path: &str, b: Option<&[u8]>) -> Result<String> {
        self.handle.custom_request("DELETE")?;
        self.req(path, b, Auth::Authorized)
    }

    fn req(&mut self, path: &str, body: Option<&[u8]>, authorized: Auth) -> Result<String> {
        self.handle.url(&format!("{}/api/v1{}", self.host, path))?;
        let mut headers = List::new();
        headers.append("Accept: application/json")?;
        headers.append("Content-Type: application/json")?;

        if authorized == Auth::Authorized {
            let token = match self.token.as_ref() {
                Some(s) => s,
                None => bail!("no upload token found, please run `cargo login`"),
            };
            headers.append(&format!("Authorization: {}", token))?;
        }
        self.handle.http_headers(headers)?;
        match body {
            Some(mut body) => {
                self.handle.upload(true)?;
                self.handle.in_filesize(body.len() as u64)?;
                handle(&mut self.handle, &mut |buf| body.read(buf).unwrap_or(0))
            }
            None => handle(&mut self.handle, &mut |_| 0),
        }
    }
}

fn handle(handle: &mut Easy, read: &mut dyn FnMut(&mut [u8]) -> usize) -> Result<String> {
    let mut headers = Vec::new();
    let mut body = Vec::new();
    {
        let mut handle = handle.transfer();
        handle.read_function(|buf| Ok(read(buf)))?;
        handle.write_function(|data| {
            body.extend_from_slice(data);
            Ok(data.len())
        })?;
        handle.header_function(|data| {
            headers.push(String::from_utf8_lossy(data).into_owned());
            true
        })?;
        handle.perform()?;
    }

    let body = match String::from_utf8(body) {
        Ok(body) => body,
        Err(..) => bail!("response body was not valid utf-8"),
    };
    let errors = serde_json::from_str::<ApiErrorList>(&body).ok().map(|s| {
        s.errors.into_iter().map(|s| s.detail).collect::<Vec<_>>()
    });

    match (handle.response_code()?, errors) {
        (0, None) | (200, None) => {},
        (code, Some(errors)) => {
            let code = StatusCode::from_u16(code as _)?;
            bail!("api errors (status {}): {}", code, errors.join(", "))
        }
        (code, None) => bail!(
            "failed to get a 200 OK response, got {}\n\
             headers:\n\
             \t{}\n\
             body:\n\
             {}",
             code,
             headers.join("\n\t"),
             body,
        ),
    }

    Ok(body)
}
<commit_msg>Give a better error message when crates.io requests time out<commit_after>#![allow(unknown_lints)]
#![allow(clippy::identity_op)] // used for vertical alignment

use std::collections::BTreeMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::Cursor;
use std::time::Instant;

use curl::easy::{Easy, List};
use failure::bail;
use http::status::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json;
use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};

pub type Result<T> = std::result::Result<T, failure::Error>;

pub struct Registry {
    /// The base URL for issuing API requests.
    host: String,
    /// Optional authorization token.
    /// If None, commands requiring authorization will fail.
    token: Option<String>,
    /// Curl handle for issuing requests.
    handle: Easy,
}

#[derive(PartialEq, Clone, Copy)]
pub enum Auth {
    Authorized,
    Unauthorized,
}

#[derive(Deserialize)]
pub struct Crate {
    pub name: String,
    pub description: Option<String>,
    pub max_version: String,
}

#[derive(Serialize)]
pub struct NewCrate {
    pub name: String,
    pub vers: String,
    pub deps: Vec<NewCrateDependency>,
    pub features: BTreeMap<String, Vec<String>>,
    pub authors: Vec<String>,
    pub description: Option<String>,
    pub documentation: Option<String>,
    pub homepage: Option<String>,
    pub readme: Option<String>,
    pub readme_file: Option<String>,
    pub keywords: Vec<String>,
    pub categories: Vec<String>,
    pub license: Option<String>,
    pub license_file: Option<String>,
    pub repository: Option<String>,
    pub badges: BTreeMap<String, BTreeMap<String, String>>,
    #[serde(default)]
    pub links: Option<String>,
}

#[derive(Serialize)]
pub struct NewCrateDependency {
    pub optional: bool,
    pub default_features: bool,
    pub name: String,
    pub features: Vec<String>,
    pub version_req: String,
    pub target: Option<String>,
    pub kind: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub registry: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub explicit_name_in_toml: Option<String>,
}

#[derive(Deserialize)]
pub struct User {
    pub id: u32,
    pub login: String,
    pub avatar: Option<String>,
    pub email: Option<String>,
    pub name: Option<String>,
}

pub struct Warnings {
    pub invalid_categories: Vec<String>,
    pub invalid_badges: Vec<String>,
    pub other: Vec<String>,
}

#[derive(Deserialize)]
struct R {
    ok: bool,
}
#[derive(Deserialize)]
struct OwnerResponse {
    ok: bool,
    msg: String,
}
#[derive(Deserialize)]
struct ApiErrorList {
    errors: Vec<ApiError>,
}
#[derive(Deserialize)]
struct ApiError {
    detail: String,
}
#[derive(Serialize)]
struct OwnersReq<'a> {
    users: &'a [&'a str],
}
#[derive(Deserialize)]
struct Users {
    users: Vec<User>,
}
#[derive(Deserialize)]
struct TotalCrates {
    total: u32,
}
#[derive(Deserialize)]
struct Crates {
    crates: Vec<Crate>,
    meta: TotalCrates,
}
impl Registry {
    pub fn new(host: String, token: Option<String>) -> Registry {
        Registry::new_handle(host, token, Easy::new())
    }

    pub fn new_handle(host: String, token: Option<String>, handle: Easy) -> Registry {
        Registry {
            host,
            token,
            handle,
        }
    }

    pub fn host(&self) -> &str {
        &self.host
    }

    pub fn add_owners(&mut self, krate: &str, owners: &[&str]) -> Result<String> {
        let body = serde_json::to_string(&OwnersReq { users: owners })?;
        let body = self.put(&format!("/crates/{}/owners", krate), body.as_bytes())?;
        assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
        Ok(serde_json::from_str::<OwnerResponse>(&body)?.msg)
    }

    pub fn remove_owners(&mut self, krate: &str, owners: &[&str]) -> Result<()> {
        let body = serde_json::to_string(&OwnersReq { users: owners })?;
        let body = self.delete(&format!("/crates/{}/owners", krate), Some(body.as_bytes()))?;
        assert!(serde_json::from_str::<OwnerResponse>(&body)?.ok);
        Ok(())
    }

    pub fn list_owners(&mut self, krate: &str) -> Result<Vec<User>> {
        let body = self.get(&format!("/crates/{}/owners", krate))?;
        Ok(serde_json::from_str::<Users>(&body)?.users)
    }

    pub fn publish(&mut self, krate: &NewCrate, tarball: &File) -> Result<Warnings> {
        let json = serde_json::to_string(krate)?;
        // Prepare the body. The format of the upload request is:
        //
        //      <le u32 of json>
        //      <json request> (metadata for the package)
        //      <le u32 of tarball>
        //      <source tarball>
        let stat = tarball.metadata()?;
        let header = {
            let mut w = Vec::new();
            w.extend(
                [
                    (json.len() >> 0) as u8,
                    (json.len() >> 8) as u8,
                    (json.len() >> 16) as u8,
                    (json.len() >> 24) as u8,
                ].iter().cloned(),
            );
            w.extend(json.as_bytes().iter().cloned());
            w.extend(
                [
                    (stat.len() >> 0) as u8,
                    (stat.len() >> 8) as u8,
                    (stat.len() >> 16) as u8,
                    (stat.len() >> 24) as u8,
                ].iter().cloned(),
            );
            w
        };
        let size = stat.len() as usize + header.len();
        let mut body = Cursor::new(header).chain(tarball);

        let url = format!("{}/api/v1/crates/new", self.host);

        let token = match self.token.as_ref() {
            Some(s) => s,
            None => bail!("no upload token found, please run `cargo login`"),
        };
        self.handle.put(true)?;
        self.handle.url(&url)?;
        self.handle.in_filesize(size as u64)?;
        let mut headers = List::new();
        headers.append("Accept: application/json")?;
        headers.append(&format!("Authorization: {}", token))?;
        self.handle.http_headers(headers)?;

        let body = handle(&mut self.handle, &mut |buf| body.read(buf).unwrap_or(0))?;

        let response = if body.is_empty() {
            "{}".parse()?
        } else {
            body.parse::<serde_json::Value>()?
        };

        let invalid_categories: Vec<String> = response
            .get("warnings")
            .and_then(|j| j.get("invalid_categories"))
            .and_then(|j| j.as_array())
            .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
            .unwrap_or_else(Vec::new);

        let invalid_badges: Vec<String> = response
            .get("warnings")
            .and_then(|j| j.get("invalid_badges"))
            .and_then(|j| j.as_array())
            .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
            .unwrap_or_else(Vec::new);

        let other: Vec<String> = response
            .get("warnings")
            .and_then(|j| j.get("other"))
            .and_then(|j| j.as_array())
            .map(|x| x.iter().flat_map(|j| j.as_str()).map(Into::into).collect())
            .unwrap_or_else(Vec::new);

        Ok(Warnings {
            invalid_categories,
            invalid_badges,
            other,
        })
    }

    pub fn search(&mut self, query: &str, limit: u32) -> Result<(Vec<Crate>, u32)> {
        let formatted_query = percent_encode(query.as_bytes(), QUERY_ENCODE_SET);
        let body = self.req(
            &format!("/crates?q={}&per_page={}", formatted_query, limit),
            None,
            Auth::Unauthorized,
        )?;

        let crates = serde_json::from_str::<Crates>(&body)?;
        Ok((crates.crates, crates.meta.total))
    }

    pub fn yank(&mut self, krate: &str, version: &str) -> Result<()> {
        let body = self.delete(&format!("/crates/{}/{}/yank", krate, version), None)?;
        assert!(serde_json::from_str::<R>(&body)?.ok);
        Ok(())
    }

    pub fn unyank(&mut self, krate: &str, version: &str) -> Result<()> {
        let body = self.put(&format!("/crates/{}/{}/unyank", krate, version), &[])?;
        assert!(serde_json::from_str::<R>(&body)?.ok);
        Ok(())
    }

    fn put(&mut self, path: &str, b: &[u8]) -> Result<String> {
        self.handle.put(true)?;
        self.req(path, Some(b), Auth::Authorized)
    }

    fn get(&mut self, path: &str) -> Result<String> {
        self.handle.get(true)?;
        self.req(path, None, Auth::Authorized)
    }

    fn delete(&mut self, path: &str, b: Option<&[u8]>) -> Result<String> {
        self.handle.custom_request("DELETE")?;
        self.req(path, b, Auth::Authorized)
    }

    fn req(&mut self, path: &str, body: Option<&[u8]>, authorized: Auth) -> Result<String> {
        self.handle.url(&format!("{}/api/v1{}", self.host, path))?;
        let mut headers = List::new();
        headers.append("Accept: application/json")?;
        headers.append("Content-Type: application/json")?;

        if authorized == Auth::Authorized {
            let token = match self.token.as_ref() {
                Some(s) => s,
                None => bail!("no upload token found, please run `cargo login`"),
            };
            headers.append(&format!("Authorization: {}", token))?;
        }
        self.handle.http_headers(headers)?;
        match body {
            Some(mut body) => {
                self.handle.upload(true)?;
                self.handle.in_filesize(body.len() as u64)?;
                handle(&mut self.handle, &mut |buf| body.read(buf).unwrap_or(0))
            }
            None => handle(&mut self.handle, &mut |_| 0),
        }
    }
}

fn handle(handle: &mut Easy, read: &mut dyn FnMut(&mut [u8]) -> usize) -> Result<String> {
    let mut headers = Vec::new();
    let mut body = Vec::new();
    let started;
    {
        let mut handle = handle.transfer();
        handle.read_function(|buf| Ok(read(buf)))?;
        handle.write_function(|data| {
            body.extend_from_slice(data);
            Ok(data.len())
        })?;
        handle.header_function(|data| {
            headers.push(String::from_utf8_lossy(data).into_owned());
            true
        })?;
        started = Instant::now();
        handle.perform()?;
    }

    let body = match String::from_utf8(body) {
        Ok(body) => body,
        Err(..) => bail!("response body was not valid utf-8"),
    };
    let errors = serde_json::from_str::<ApiErrorList>(&body).ok().map(|s| {
        s.errors.into_iter().map(|s| s.detail).collect::<Vec<_>>()
    });

    match (handle.response_code()?, errors) {
        (0, None) | (200, None) => {},
        (503, _) if started.elapsed().as_secs() >= 29 => {
            bail!("Request took more than 30 seconds to complete. If you're\
                   trying to upload a crate it may be too large")
        }
        (code, Some(errors)) => {
            let code = StatusCode::from_u16(code as _)?;
            bail!("api errors (status {}): {}", code, errors.join(", "))
        }
        (code, None) => bail!(
            "failed to get a 200 OK response, got {}\n\
             headers:\n\
             \t{}\n\
             body:\n\
             {}",
             code,
             headers.join("\n\t"),
             body,
        ),
    }

    Ok(body)
}
<|endoftext|>


<commit_before>use futures::channel::mpsc as async_mpsc;

use gettextrs::gettext;

use gstreamer as gst;
use gstreamer::{prelude::*, ClockTime};

use glib;

use log::{debug, error, info, warn};

use std::{
    path::Path,
    sync::{Arc, Mutex},
};

use metadata::Format;

use super::{MediaEvent, Timestamp};

pub struct SplitterPipeline {
    pipeline: gst::Pipeline,
    format: Format,
    chapter: gst::TocEntry,
}

impl SplitterPipeline {
    pub fn check_requirements(format: Format) -> Result<(), String> {
        match format {
            Format::Flac => {
                gst::ElementFactory::make("flacenc", None)
                    .map(drop)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "flacenc", 1)
                    })
            }
            Format::Wave => {
                gst::ElementFactory::make("wavenc", None)
                    .map(drop)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "wavenc", 1)
                    })
            }
            Format::Opus => {
                gst::ElementFactory::make("opusenc", None)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "opusenc", 1)
                    })
                    .and_then(|_| {
                        gst::ElementFactory::make("oggmux", None)
                            .map(drop)
                            .map_err(|_| {
                                gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                                    .replacen("{element}", "oggmux", 1)
                            })
                    })
            }
            Format::Vorbis => {
                gst::ElementFactory::make("vorbisenc", None)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "vorbisenc", 1)
                    })
                    .and_then(|_| {
                        gst::ElementFactory::make("oggmux", None)
                            .map(drop)
                            .map_err(|_| {
                                gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                                    .replacen("{element}", "oggmux", 1)
                            })
                    })
            }
            Format::MP3 => {
                gst::ElementFactory::make("lamemp3enc", None)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "lamemp3enc", 1)
                    })
                    .and_then(|_| {
                        gst::ElementFactory::make("id3v2mux", None)
                            .map(drop)
                            .map_err(|_| {
                                gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                                    .replacen("{element}", "id3v2mux", 1)
                            })
                    })
            }
            _ => panic!(
                "SplitterPipeline::check_requirements unsupported format: {:?}",
                format
            ),
        }
    }

    pub fn try_new(
        input_path: &Path,
        output_path: &Path,
        stream_id: &str,
        format: Format,
        chapter: gst::TocEntry,
        sender: async_mpsc::Sender<MediaEvent>,
    ) -> Result<SplitterPipeline, String> {
        info!(
            "{}",
            gettext("Splitting {}...").replacen("{}", output_path.to_str().unwrap(), 1)
        );
        debug!("stream id {}", &stream_id);

        let mut this = SplitterPipeline {
            pipeline: gst::Pipeline::new(Some("splitter_pipeline")),
            format,
            chapter,
        };

        this.build_pipeline(input_path, output_path, stream_id);
        this.register_bus_inspector(sender);

        this.pipeline
            .set_state(gst::State::Paused)
            .map(|_| this)
            .map_err(|_| gettext("do you have permission to write the file?"))
    }

    pub fn get_current_ts(&self) -> Option<Timestamp> {
        // `get_current_ts` might be called when the pipeline is not ready yet
        // because we build a new pipeline for each split file.
        // When this happen, the ts position is negative so don't force it to 0
        // because we are most likely not at the begining of the source file.
        let mut position_query = gst::Query::new_position(gst::Format::Time);
        self.pipeline.query(&mut position_query);
        let position = position_query.get_result().get_value();
        if position >= 0 {
            Some(position.into())
        } else {
            None
        }
    }

    fn build_pipeline(&mut self, input_path: &Path, output_path: &Path, stream_id: &str) {
        /* There are multiple showstoppers to implementing something ideal
         * to export splitted chapters with audio and video (and subtitles):
         * 1. matroska-mux drops seek events explicitly (a message states: "discard for now").
         * This means that it is currently not possible to build a pipeline that would allow
         * seeking in a matroska media (using demux to interprete timestamps) and mux back to
         * to matroska. One solution would be to export streams to files and mux back
         * crossing fingers everything remains in sync.
         * 2. nlesrc (from gstreamer-editing-services) allows extracting frames from a starting
         * position for a given duration. However, it is designed to work with single stream
         * medias and decodes to raw formats.
         * 3. filesink can't change the file location without setting the pipeline to Null
         * which also unlinks elements.
         * 4. wavenc doesn't send header after a second seek in segment mode.
         * 5. flacenc can't handle a seek.
         *
         * Issues 3 & 4 lead to building a new pipeline for each chapter.
         *
         * Until I design a GUI for the user to select which stream to export to which codec,
         * current solution is to keep only the audio track which matches the initial purpose
         * of this application */

        // Input
        let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
        filesrc
            .set_property("location", &glib::Value::from(input_path.to_str().unwrap()))
            .unwrap();
        let decodebin = gst::ElementFactory::make("decodebin", None).unwrap();

        self.pipeline.add_many(&[&filesrc, &decodebin]).unwrap();

        filesrc.link(&decodebin).unwrap();
        decodebin.sync_state_with_parent().unwrap();

        // Audio encoder
        let audio_enc = match self.format {
            Format::Flac => gst::ElementFactory::make("flacenc", None).unwrap(),
            Format::Wave => gst::ElementFactory::make("wavenc", None).unwrap(),
            Format::Opus => gst::ElementFactory::make("opusenc", None).unwrap(),
            Format::Vorbis => gst::ElementFactory::make("vorbisenc", None).unwrap(),
            Format::MP3 => gst::ElementFactory::make("lamemp3enc", None).unwrap(),
            _ => panic!(
                "SplitterPipeline::build_pipeline unsupported format: {:?}",
                self.format
            ),
        };

        // Catch events and drop the upstream Tags & TOC
        let audio_enc_sink_pad = audio_enc.get_static_pad("sink").unwrap();
        audio_enc_sink_pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |_pad, probe_info| {
            if let Some(ref data) = probe_info.data {
                if let gst::PadProbeData::Event(ref event) = *data {
                    match event.view() {
                        gst::EventView::Tag(ref _tag) => return gst::PadProbeReturn::Drop,
                        gst::EventView::Toc(ref _toc) => return gst::PadProbeReturn::Drop,
                        _ => (),
                    }
                }
            }
            gst::PadProbeReturn::Ok
        });

        // Some encoders (such as flacenc) starts encoding the preroll buffers when switching to
        // paused mode. When the seek is performed buffers from the new segments are appended
        // to the ones from the preroll. Moreover, flacenc doesn't handle discontinuities.
        // As a workaround, we will drop buffers before the seek. The first buffer with the Discont
        // flag shows that it is possible to seek. The next buffer with the Discont flag corresponds
        // to the first buffer from the target segment

        let (start, end) = self.chapter.get_start_stop_times().unwrap();

        // Note: can't use AtomicBool here as pad probes are multithreaded so the function is Fn
        // not FnMut. See: https://github.com/sdroege/gstreamer-rs/pull/71
        #[allow(clippy::mutex_atomic)]
        let seek_done = Arc::new(Mutex::new(false));
        let pipeline = self.pipeline.clone();
        audio_enc_sink_pad.add_probe(gst::PadProbeType::BUFFER, move |_pad, probe_info| {
            if let Some(ref data) = probe_info.data {
                if let gst::PadProbeData::Buffer(ref buffer) = *data {
                    if buffer.get_flags() & gst::BufferFlags::DISCONT == gst::BufferFlags::DISCONT {
                        let mut seek_done_grp = seek_done.lock().unwrap();
                        if !*seek_done_grp {
                            // First buffer before seek
                            // let's seek and drop buffers until seek start sending new segment
                            let _res = pipeline
                                .seek(
                                    1f64,
                                    gst::SeekFlags::FLUSH | gst::SeekFlags::ACCURATE,
                                    gst::SeekType::Set,
                                    ClockTime::from(start as u64),
                                    gst::SeekType::Set,
                                    ClockTime::from(end as u64),
                                )
                                .map_err(|_| {
                                    // FIXME: feedback to the user using the UI channel
                                    error!("{}", gettext("Failed to intialize the split"));
                                });
                            *seek_done_grp = true;
                        } else {
                            // First Discont buffer after seek => stop dropping buffers
                            return gst::PadProbeReturn::Remove;
                        }
                    }
                }
            }
            gst::PadProbeReturn::Drop
        });

        self.pipeline.add(&audio_enc).unwrap();

        // add a muxer when required
        let (tag_setter, audio_muxer) = match self.format {
            Format::Flac | Format::Wave => (audio_enc.clone(), audio_enc.clone()),
            Format::Opus | Format::Vorbis => {
                let ogg_muxer = gst::ElementFactory::make("oggmux", None).unwrap();
                self.pipeline.add(&ogg_muxer).unwrap();
                audio_enc.link(&ogg_muxer).unwrap();
                (audio_enc.clone(), ogg_muxer)
            }
            Format::MP3 => {
                let id3v2_muxer = gst::ElementFactory::make("id3v2mux", None).unwrap();
                self.pipeline.add(&id3v2_muxer).unwrap();
                audio_enc.link(&id3v2_muxer).unwrap();
                (id3v2_muxer.clone(), id3v2_muxer)
            }
            _ => unimplemented!(
                "SplitterPipeline::build_pipeline for format: {:?}",
                self.format
            ),
        };

        if let Some(tags) = self.chapter.get_tags() {
            let tag_setter = tag_setter.dynamic_cast::<gst::TagSetter>().unwrap();
            tag_setter.merge_tags(&tags, gst::TagMergeMode::ReplaceAll);
        }

        // Output sink
        let outsink = gst::ElementFactory::make("filesink", Some("filesink")).unwrap();
        outsink
            .set_property(
                "location",
                &glib::Value::from(output_path.to_str().unwrap()),
            )
            .unwrap();

        self.pipeline.add(&outsink).unwrap();
        audio_muxer.link(&outsink).unwrap();
        outsink.sync_state_with_parent().unwrap();

        let pipeline_cb = self.pipeline.clone();
        let stream_id = stream_id.to_owned();
        decodebin.connect_pad_added(move |_element, pad| {
            let caps = pad.get_current_caps().unwrap();
            let structure = caps.get_structure(0).unwrap();
            let name = structure.get_name();

            if name.starts_with("audio/")
                && pipeline_cb.get_by_name("audioconvert").is_none()
                && stream_id
                    == pad
                        .get_stream_id()
                        .expect("SplitterPipeline::build_pipeline no stream_id for audio src pad")
            {
                let audio_conv =
                    gst::ElementFactory::make("audioconvert", Some("audioconvert")).unwrap();
                pipeline_cb.add(&audio_conv).unwrap();
                let audio_conv_sink_pad = audio_conv.get_static_pad("sink").unwrap();
                pad.link(&audio_conv_sink_pad).unwrap();
                gst::Element::link_many(&[&audio_conv, &audio_enc]).unwrap();
                audio_conv.sync_state_with_parent().unwrap();
                audio_enc.sync_state_with_parent().unwrap();
            } else {
                let fakesink = gst::ElementFactory::make("fakesink", None).unwrap();
                pipeline_cb.add(&fakesink).unwrap();
                let fakesink_sink_pad = fakesink.get_static_pad("sink").unwrap();
                pad.link(&fakesink_sink_pad).unwrap();
                fakesink.sync_state_with_parent().unwrap();
            }
        });
    }

    pub fn cancel(&self) {
        if self.pipeline.set_state(gst::State::Null).is_err() {
            warn!("could not stop the media");
        }
    }

    // Uses sender to notify the UI controllers
    fn register_bus_inspector(&self, mut sender: async_mpsc::Sender<MediaEvent>) {
        let pipeline = self.pipeline.clone();
        self.pipeline
            .get_bus()
            .unwrap()
            .add_watch(move |_, msg| {
                match msg.view() {
                    gst::MessageView::Eos(..) => {
                        if pipeline.set_state(gst::State::Null).is_err() {
                            sender
                                .try_send(MediaEvent::FailedToExport(gettext(
                                    "Failed to terminate properly. Check the resulting file.",
                                )))
                                .unwrap();
                        }
                        sender.try_send(MediaEvent::Eos).unwrap();
                        return glib::Continue(false);
                    }
                    gst::MessageView::Error(err) => {
                        let _ = sender
                            .try_send(MediaEvent::FailedToExport(err.get_error().to_string()));
                        return glib::Continue(false);
                    }
                    gst::MessageView::AsyncDone(_) => {
                        // Start splitting
                        if pipeline.set_state(gst::State::Playing).is_err() {
                            sender
                                .try_send(MediaEvent::FailedToExport(gettext(
                                    "Failed to start splitting.",
                                )))
                                .unwrap();
                        }
                    }
                    _ => (),
                }

                glib::Continue(true)
            })
            .unwrap();
    }
}
<commit_msg>Split: resample audio when required by encoder<commit_after>use futures::channel::mpsc as async_mpsc;

use gettextrs::gettext;

use gstreamer as gst;
use gstreamer::{prelude::*, ClockTime};

use glib;

use log::{debug, error, info, warn};

use std::{
    path::Path,
    sync::{Arc, Mutex},
};

use metadata::Format;

use super::{MediaEvent, Timestamp};

pub struct SplitterPipeline {
    pipeline: gst::Pipeline,
    format: Format,
    chapter: gst::TocEntry,
}

impl SplitterPipeline {
    pub fn check_requirements(format: Format) -> Result<(), String> {
        match format {
            Format::Flac => {
                gst::ElementFactory::make("flacenc", None)
                    .map(drop)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "flacenc", 1)
                    })
            }
            Format::Wave => {
                gst::ElementFactory::make("wavenc", None)
                    .map(drop)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "wavenc", 1)
                    })
            }
            Format::Opus => {
                gst::ElementFactory::make("opusenc", None)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "opusenc", 1)
                    })
                    .and_then(|_| {
                        gst::ElementFactory::make("oggmux", None)
                            .map(drop)
                            .map_err(|_| {
                                gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                                    .replacen("{element}", "oggmux", 1)
                            })
                    })
            }
            Format::Vorbis => {
                gst::ElementFactory::make("vorbisenc", None)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "vorbisenc", 1)
                    })
                    .and_then(|_| {
                        gst::ElementFactory::make("oggmux", None)
                            .map(drop)
                            .map_err(|_| {
                                gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                                    .replacen("{element}", "oggmux", 1)
                            })
                    })
            }
            Format::MP3 => {
                gst::ElementFactory::make("lamemp3enc", None)
                    .map_err(|_| {
                        gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                            .replacen("{element}", "lamemp3enc", 1)
                    })
                    .and_then(|_| {
                        gst::ElementFactory::make("id3v2mux", None)
                            .map(drop)
                            .map_err(|_| {
                                gettext("Missing `{element}`\ncheck your gst-plugins-good install")
                                    .replacen("{element}", "id3v2mux", 1)
                            })
                    })
            }
            _ => panic!(
                "SplitterPipeline::check_requirements unsupported format: {:?}",
                format
            ),
        }
    }

    pub fn try_new(
        input_path: &Path,
        output_path: &Path,
        stream_id: &str,
        format: Format,
        chapter: gst::TocEntry,
        sender: async_mpsc::Sender<MediaEvent>,
    ) -> Result<SplitterPipeline, String> {
        info!(
            "{}",
            gettext("Splitting {}...").replacen("{}", output_path.to_str().unwrap(), 1)
        );
        debug!("stream id {}", &stream_id);

        let mut this = SplitterPipeline {
            pipeline: gst::Pipeline::new(Some("splitter_pipeline")),
            format,
            chapter,
        };

        this.build_pipeline(input_path, output_path, stream_id);
        this.register_bus_inspector(sender);

        this.pipeline
            .set_state(gst::State::Paused)
            .map(|_| this)
            .map_err(|_| gettext("do you have permission to write the file?"))
    }

    pub fn get_current_ts(&self) -> Option<Timestamp> {
        // `get_current_ts` might be called when the pipeline is not ready yet
        // because we build a new pipeline for each split file.
        // When this happen, the ts position is negative so don't force it to 0
        // because we are most likely not at the begining of the source file.
        let mut position_query = gst::Query::new_position(gst::Format::Time);
        self.pipeline.query(&mut position_query);
        let position = position_query.get_result().get_value();
        if position >= 0 {
            Some(position.into())
        } else {
            None
        }
    }

    fn build_pipeline(&mut self, input_path: &Path, output_path: &Path, stream_id: &str) {
        /* There are multiple showstoppers to implementing something ideal
         * to export splitted chapters with audio and video (and subtitles):
         * 1. matroska-mux drops seek events explicitly (a message states: "discard for now").
         * This means that it is currently not possible to build a pipeline that would allow
         * seeking in a matroska media (using demux to interprete timestamps) and mux back to
         * to matroska. One solution would be to export streams to files and mux back
         * crossing fingers everything remains in sync.
         * 2. nlesrc (from gstreamer-editing-services) allows extracting frames from a starting
         * position for a given duration. However, it is designed to work with single stream
         * medias and decodes to raw formats.
         * 3. filesink can't change the file location without setting the pipeline to Null
         * which also unlinks elements.
         * 4. wavenc doesn't send header after a second seek in segment mode.
         * 5. flacenc can't handle a seek.
         *
         * Issues 3 & 4 lead to building a new pipeline for each chapter.
         *
         * Until I design a GUI for the user to select which stream to export to which codec,
         * current solution is to keep only the audio track which matches the initial purpose
         * of this application */

        // Input
        let filesrc = gst::ElementFactory::make("filesrc", None).unwrap();
        filesrc
            .set_property("location", &glib::Value::from(input_path.to_str().unwrap()))
            .unwrap();
        let decodebin = gst::ElementFactory::make("decodebin", None).unwrap();

        self.pipeline.add_many(&[&filesrc, &decodebin]).unwrap();

        filesrc.link(&decodebin).unwrap();
        decodebin.sync_state_with_parent().unwrap();

        // Audio encoder
        let audio_enc = match self.format {
            Format::Flac => gst::ElementFactory::make("flacenc", None).unwrap(),
            Format::Wave => gst::ElementFactory::make("wavenc", None).unwrap(),
            Format::Opus => gst::ElementFactory::make("opusenc", None).unwrap(),
            Format::Vorbis => gst::ElementFactory::make("vorbisenc", None).unwrap(),
            Format::MP3 => gst::ElementFactory::make("lamemp3enc", None).unwrap(),
            _ => panic!(
                "SplitterPipeline::build_pipeline unsupported format: {:?}",
                self.format
            ),
        };

        // Catch events and drop the upstream Tags & TOC
        let audio_enc_sink_pad = audio_enc.get_static_pad("sink").unwrap();
        audio_enc_sink_pad.add_probe(gst::PadProbeType::EVENT_DOWNSTREAM, |_pad, probe_info| {
            if let Some(ref data) = probe_info.data {
                if let gst::PadProbeData::Event(ref event) = *data {
                    match event.view() {
                        gst::EventView::Tag(ref _tag) => return gst::PadProbeReturn::Drop,
                        gst::EventView::Toc(ref _toc) => return gst::PadProbeReturn::Drop,
                        _ => (),
                    }
                }
            }
            gst::PadProbeReturn::Ok
        });

        // Some encoders (such as flacenc) starts encoding the preroll buffers when switching to
        // paused mode. When the seek is performed buffers from the new segments are appended
        // to the ones from the preroll. Moreover, flacenc doesn't handle discontinuities.
        // As a workaround, we will drop buffers before the seek. The first buffer with the Discont
        // flag shows that it is possible to seek. The next buffer with the Discont flag corresponds
        // to the first buffer from the target segment

        let (start, end) = self.chapter.get_start_stop_times().unwrap();

        // Note: can't use AtomicBool here as pad probes are multithreaded so the function is Fn
        // not FnMut. See: https://github.com/sdroege/gstreamer-rs/pull/71
        #[allow(clippy::mutex_atomic)]
        let seek_done = Arc::new(Mutex::new(false));
        let pipeline = self.pipeline.clone();
        audio_enc_sink_pad.add_probe(gst::PadProbeType::BUFFER, move |_pad, probe_info| {
            if let Some(ref data) = probe_info.data {
                if let gst::PadProbeData::Buffer(ref buffer) = *data {
                    if buffer.get_flags() & gst::BufferFlags::DISCONT == gst::BufferFlags::DISCONT {
                        let mut seek_done_grp = seek_done.lock().unwrap();
                        if !*seek_done_grp {
                            // First buffer before seek
                            // let's seek and drop buffers until seek start sending new segment
                            let _res = pipeline
                                .seek(
                                    1f64,
                                    gst::SeekFlags::FLUSH | gst::SeekFlags::ACCURATE,
                                    gst::SeekType::Set,
                                    ClockTime::from(start as u64),
                                    gst::SeekType::Set,
                                    ClockTime::from(end as u64),
                                )
                                .map_err(|_| {
                                    // FIXME: feedback to the user using the UI channel
                                    error!("{}", gettext("Failed to intialize the split"));
                                });
                            *seek_done_grp = true;
                        } else {
                            // First Discont buffer after seek => stop dropping buffers
                            return gst::PadProbeReturn::Remove;
                        }
                    }
                }
            }
            gst::PadProbeReturn::Drop
        });

        self.pipeline.add(&audio_enc).unwrap();

        // add a muxer when required
        let (tag_setter, audio_muxer) = match self.format {
            Format::Flac | Format::Wave => (audio_enc.clone(), audio_enc.clone()),
            Format::Opus | Format::Vorbis => {
                let ogg_muxer = gst::ElementFactory::make("oggmux", None).unwrap();
                self.pipeline.add(&ogg_muxer).unwrap();
                audio_enc.link(&ogg_muxer).unwrap();
                (audio_enc.clone(), ogg_muxer)
            }
            Format::MP3 => {
                let id3v2_muxer = gst::ElementFactory::make("id3v2mux", None).unwrap();
                self.pipeline.add(&id3v2_muxer).unwrap();
                audio_enc.link(&id3v2_muxer).unwrap();
                (id3v2_muxer.clone(), id3v2_muxer)
            }
            _ => unimplemented!(
                "SplitterPipeline::build_pipeline for format: {:?}",
                self.format
            ),
        };

        if let Some(tags) = self.chapter.get_tags() {
            let tag_setter = tag_setter.dynamic_cast::<gst::TagSetter>().unwrap();
            tag_setter.merge_tags(&tags, gst::TagMergeMode::ReplaceAll);
        }

        // Output sink
        let outsink = gst::ElementFactory::make("filesink", Some("filesink")).unwrap();
        outsink
            .set_property(
                "location",
                &glib::Value::from(output_path.to_str().unwrap()),
            )
            .unwrap();

        self.pipeline.add(&outsink).unwrap();
        audio_muxer.link(&outsink).unwrap();
        outsink.sync_state_with_parent().unwrap();

        let pipeline_cb = self.pipeline.clone();
        let stream_id = stream_id.to_owned();
        decodebin.connect_pad_added(move |_element, pad| {
            let caps = pad.get_current_caps().unwrap();
            let structure = caps.get_structure(0).unwrap();
            let name = structure.get_name();

            if name.starts_with("audio/")
                && pipeline_cb.get_by_name("audioconvert").is_none()
                && stream_id
                    == pad
                        .get_stream_id()
                        .expect("SplitterPipeline::build_pipeline no stream_id for audio src pad")
            {
                let audio_conv =
                    gst::ElementFactory::make("audioconvert", Some("audioconvert")).unwrap();
                pipeline_cb.add(&audio_conv).unwrap();
                let audio_conv_sink_pad = audio_conv.get_static_pad("sink").unwrap();
                pad.link(&audio_conv_sink_pad).unwrap();
                let audio_resample =
                    gst::ElementFactory::make("audioresample", Some("audioresample")).unwrap();
                pipeline_cb.add(&audio_resample).unwrap();
                gst::Element::link_many(&[&audio_conv, &audio_resample, &audio_enc]).unwrap();
                audio_conv.sync_state_with_parent().unwrap();
                audio_resample.sync_state_with_parent().unwrap();
                audio_enc.sync_state_with_parent().unwrap();
            } else {
                let fakesink = gst::ElementFactory::make("fakesink", None).unwrap();
                pipeline_cb.add(&fakesink).unwrap();
                let fakesink_sink_pad = fakesink.get_static_pad("sink").unwrap();
                pad.link(&fakesink_sink_pad).unwrap();
                fakesink.sync_state_with_parent().unwrap();
            }
        });
    }

    pub fn cancel(&self) {
        if self.pipeline.set_state(gst::State::Null).is_err() {
            warn!("could not stop the media");
        }
    }

    // Uses sender to notify the UI controllers
    fn register_bus_inspector(&self, mut sender: async_mpsc::Sender<MediaEvent>) {
        let pipeline = self.pipeline.clone();
        self.pipeline
            .get_bus()
            .unwrap()
            .add_watch(move |_, msg| {
                match msg.view() {
                    gst::MessageView::Eos(..) => {
                        if pipeline.set_state(gst::State::Null).is_err() {
                            sender
                                .try_send(MediaEvent::FailedToExport(gettext(
                                    "Failed to terminate properly. Check the resulting file.",
                                )))
                                .unwrap();
                        }
                        sender.try_send(MediaEvent::Eos).unwrap();
                        return glib::Continue(false);
                    }
                    gst::MessageView::Error(err) => {
                        let _ = sender
                            .try_send(MediaEvent::FailedToExport(err.get_error().to_string()));
                        return glib::Continue(false);
                    }
                    gst::MessageView::AsyncDone(_) => {
                        // Start splitting
                        if pipeline.set_state(gst::State::Playing).is_err() {
                            sender
                                .try_send(MediaEvent::FailedToExport(gettext(
                                    "Failed to start splitting.",
                                )))
                                .unwrap();
                        }
                    }
                    _ => (),
                }

                glib::Continue(true)
            })
            .unwrap();
    }
}
<|endoftext|>


<commit_before>// Functionality that is shared between the cxx_build::bridge entry point and
// the cxxbridge CLI command.

mod error;
mod file;
mod find;
pub(super) mod include;
pub(super) mod out;
mod write;

#[cfg(test)]
mod tests;

use self::error::{format_err, Error, Result};
use self::file::File;
use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::{self, check, Types};
use std::fs;
use std::path::Path;
use syn::Item;

struct Input {
    namespace: Namespace,
    module: Vec<Item>,
}

#[derive(Default)]
pub(super) struct Opt {
    /// Any additional headers to #include
    pub include: Vec<String>,
    /// Whether to set __attribute__((visibility("default")))
    /// or similar annotations on function implementations.
    pub cxx_impl_annotations: Option<String>,
}

pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> Vec<u8> {
    let header = false;
    generate_from_path(path, opt, header)
}

pub(super) fn do_generate_header(path: &Path, opt: Opt) -> Vec<u8> {
    let header = true;
    generate_from_path(path, opt, header)
}

fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec<u8> {
    let source = match fs::read_to_string(path) {
        Ok(source) => source,
        Err(err) => format_err(path, "", Error::Io(err)),
    };
    match generate(&source, opt, header) {
        Ok(out) => out,
        Err(err) => format_err(path, &source, err),
    }
}

fn generate(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
    proc_macro2::fallback::force();
    let ref mut errors = Errors::new();
    let syntax: File = syn::parse_str(source)?;
    let bridge = find::find_bridge_mod(syntax)?;
    let ref namespace = bridge.namespace;
    let ref apis = syntax::parse_items(errors, bridge.module);
    let ref types = Types::collect(errors, apis);
    errors.propagate()?;
    check::typecheck(errors, namespace, apis, types);
    errors.propagate()?;
    let out = write::gen(namespace, apis, types, opt, header);
    Ok(out.content())
}
<commit_msg>Skip past shebang in source file<commit_after>// Functionality that is shared between the cxx_build::bridge entry point and
// the cxxbridge CLI command.

mod error;
mod file;
mod find;
pub(super) mod include;
pub(super) mod out;
mod write;

#[cfg(test)]
mod tests;

use self::error::{format_err, Error, Result};
use self::file::File;
use crate::syntax::namespace::Namespace;
use crate::syntax::report::Errors;
use crate::syntax::{self, check, Types};
use std::fs;
use std::path::Path;
use syn::Item;

struct Input {
    namespace: Namespace,
    module: Vec<Item>,
}

#[derive(Default)]
pub(super) struct Opt {
    /// Any additional headers to #include
    pub include: Vec<String>,
    /// Whether to set __attribute__((visibility("default")))
    /// or similar annotations on function implementations.
    pub cxx_impl_annotations: Option<String>,
}

pub(super) fn do_generate_bridge(path: &Path, opt: Opt) -> Vec<u8> {
    let header = false;
    generate_from_path(path, opt, header)
}

pub(super) fn do_generate_header(path: &Path, opt: Opt) -> Vec<u8> {
    let header = true;
    generate_from_path(path, opt, header)
}

fn generate_from_path(path: &Path, opt: Opt, header: bool) -> Vec<u8> {
    let source = match fs::read_to_string(path) {
        Ok(source) => source,
        Err(err) => format_err(path, "", Error::Io(err)),
    };
    let mut source = source.as_str();
    if source.starts_with("#!") && !source.starts_with("#![") {
        let shebang_end = source.find('\n').unwrap_or(source.len());
        source = &source[shebang_end..];
    }
    match generate(source, opt, header) {
        Ok(out) => out,
        Err(err) => format_err(path, source, err),
    }
}

fn generate(source: &str, opt: Opt, header: bool) -> Result<Vec<u8>> {
    proc_macro2::fallback::force();
    let ref mut errors = Errors::new();
    let syntax: File = syn::parse_str(source)?;
    let bridge = find::find_bridge_mod(syntax)?;
    let ref namespace = bridge.namespace;
    let ref apis = syntax::parse_items(errors, bridge.module);
    let ref types = Types::collect(errors, apis);
    errors.propagate()?;
    check::typecheck(errors, namespace, apis, types);
    errors.propagate()?;
    let out = write::gen(namespace, apis, types, opt, header);
    Ok(out.content())
}
<|endoftext|>


<commit_before>
<commit_msg>I'm not ready to implement gfx-rs yet<commit_after><|endoftext|>


<commit_before>#[feature(globs)];

extern mod extra;
extern mod glfw;
extern mod gl;
extern mod cgmath;
extern mod noise;

use std::cast;
use std::ptr;
use std::str;
use std::vec;
use std::libc;

use gl::types::*;

use cgmath::matrix::Matrix;
use cgmath::matrix::Mat3;
use cgmath::matrix::Mat4;
use cgmath::matrix::ToMat4;
use cgmath::vector::Vector;
use cgmath::vector::Vec3;
use cgmath::vector::Vec4;
use cgmath::angle::rad;

use chunk::Chunk;

#[link(name="GLU")]
extern {}

mod chunk;

static vertex_shader_src : &'static str = r"
#version 110

uniform mat4 transform;

attribute vec3 position;
attribute vec3 normal;

varying vec4 frag_color;

const vec4 obj_diffuse = vec4(0.2, 0.6, 0.2, 1.0);
const vec3 light_direction = vec3(0.408248, -0.816497, 0.408248);
const vec4 light_diffuse = vec4(0.8, 0.8, 0.8, 0.0);
const vec4 light_ambient = vec4(0.2, 0.2, 0.2, 1.0);

void main() {
    gl_Position = transform * vec4(position, 1.0);

    vec4 diffuse_factor
        = max(-dot(normal, light_direction), 0.0) * light_diffuse;
    vec4 ambient_diffuse_factor = diffuse_factor + light_ambient;

    frag_color = ambient_diffuse_factor * obj_diffuse;
}
";

static fragment_shader_src : &'static str = r"
#version 110

varying vec4 frag_color;

void main() {
    gl_FragColor = frag_color;
}
";

pub static WORLD_SIZE: uint = 4;
pub static CHUNK_SIZE: uint = 64;
pub static WORLD_SEED: u32 = 42;

struct GraphicsResources {
    program: GLuint,
    uniform_transform: GLint,
}

#[start]
fn start(argc: int, argv: **u8) -> int {
    std::rt::start_on_main_thread(argc, argv, main)
}

fn main() {
   glfw::set_error_callback(~ErrorContext);

    do glfw::start {
        let mut window_width = 800;
        let mut window_height = 600;

        let window = glfw::Window::create(window_width, window_height, "Hello, I am a window.", glfw::Windowed)
            .expect("Failed to create GLFW window.");

        window.set_cursor_mode(glfw::CursorDisabled);
        window.make_context_current();

        gl::load_with(glfw::get_proc_address);

        gl::Enable(gl::DEPTH_TEST);
        gl::Enable(gl::CULL_FACE);

        glfw::set_swap_interval(1);

        let graphics_resources = load_graphics_resources();

        gl::UseProgram(graphics_resources.program);

        let mut chunks : ~[~Chunk] = ~[];
        for x in range(0, WORLD_SIZE) {
            for z in range(0, WORLD_SIZE) {
                chunks.push(
                    chunk::chunk_gen(&graphics_resources, WORLD_SEED,
                              ((x as int - WORLD_SIZE as int/2) * CHUNK_SIZE as int) as i64,
                              ((z as int - WORLD_SIZE as int/2) * CHUNK_SIZE as int) as i64));
            }
        }

        window.set_key_callback(~KeyContext);

        let (fb_size_port, fb_size_chan): (Port<(u32,u32)>, Chan<(u32,u32)>) = std::comm::Chan::new();
        window.set_framebuffer_size_callback(~FramebufferSizeContext { chan: fb_size_chan });

        let start_time = extra::time::precise_time_ns();
        let mut last_frame_time = start_time;
        let mut num_frames = 0;

        let mut camera_position = Vec3::<f32>::new(0.0f32, 20.0f32, 40.0f32);

        while !window.should_close() {
            glfw::poll_events();

            loop {
                match fb_size_port.try_recv() {
                    Some((w,h)) => {
                        window_width = w;
                        window_height = h;
                    }
                    None => break
                }
            }

            let (cursor_x, cursor_y) = window.get_cursor_pos();
            let camera_angle_y = ((cursor_x * 0.0005) % 1.0) * std::f64::consts::PI * 2.0;
            let camera_angle_x = ((cursor_y * 0.0005) % 1.0) * std::f64::consts::PI * 2.0;

            let mut camera_velocity = Vec3::<f32>::new(0.0f32, 0.0f32, 0.0f32);

            match window.get_key(glfw::KeySpace) {
                glfw::Press => { camera_velocity.y += 1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyLeftControl) {
                glfw::Press => { camera_velocity.y += -1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyS) {
                glfw::Press => { camera_velocity.z += 1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyW) {
                glfw::Press => { camera_velocity.z += -1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyD) {
                glfw::Press => { camera_velocity.x += 1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyA) {
                glfw::Press => { camera_velocity.x += -1.0f32 }
                _ => {}
            }

            gl::Viewport(0,0, window_width as GLint, window_height as GLint);

            gl::ClearColor(0.0, 0.0, 0.0, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);

            let projection = cgmath::projection::perspective(
                rad(1.57f32),
                window_width as f32 / window_height as f32,
                0.1f32, 1000.0f32);

            let camera_translation = Mat4::<f32>::from_cols(
                Vec4::<f32>::unit_x(),
                Vec4::<f32>::unit_y(),
                Vec4::<f32>::unit_z(),
                camera_position.mul_s(-1.0f32).extend(1.0f32));
            let camera_rotation_x = Mat3::<f32>::from_angle_x(rad(camera_angle_x as f32)).to_mat4();
            let camera_rotation_y = Mat3::<f32>::from_angle_y(rad(camera_angle_y as f32)).to_mat4();
            let camera = camera_rotation_x.mul_m(&camera_rotation_y).mul_m(&camera_translation);

            let inv_camera_rotation = Mat3::<f32>::from_euler(rad(-camera_angle_x as f32), rad(-camera_angle_y as f32), rad(0.0f32));
            let absolute_camera_velocity = inv_camera_rotation.mul_v(&camera_velocity).mul_s(0.5f32);
            camera_position.add_self_v(&absolute_camera_velocity);

            for chunk in chunks.iter() {
                let chunk_transform = Mat4::<f32>::from_cols(
                    Vec4::<f32>::unit_x(),
                    Vec4::<f32>::unit_y(),
                    Vec4::<f32>::unit_z(),
                    Vec4::<f32>::new(chunk.x as f32, 0.0f32, chunk.z as f32, 1.0f32));

                let transform = projection.mul_m(&camera).mul_m(&chunk_transform);

                gl::BindVertexArray(chunk.vao);

                unsafe {
                    gl::UniformMatrix4fv(graphics_resources.uniform_transform, 1, gl::FALSE, cast::transmute(&transform));
                    gl::DrawElements(gl::TRIANGLES, chunk.num_elements as i32, gl::UNSIGNED_INT, ptr::null());
                }

                gl::BindVertexArray(0);
            }

            window.swap_buffers();

            check_gl("main loop");

            let cur_time = extra::time::precise_time_ns();
            num_frames += 1;
            if (cur_time - last_frame_time) > (1000*1000*1000) {
                println!("{} frames per second", num_frames);
                num_frames = 0;
                last_frame_time = cur_time;
            }

        }
    }
}

fn load_graphics_resources() -> GraphicsResources {
    let vs = compile_shader(vertex_shader_src.as_bytes(), gl::VERTEX_SHADER);
    let fs = compile_shader(fragment_shader_src.as_bytes(), gl::FRAGMENT_SHADER);
    let program = link_program(vs, fs);

    let uniform_transform = unsafe { "transform".with_c_str(|ptr| gl::GetUniformLocation(program, ptr)) };

    return GraphicsResources {
        program: program,
        uniform_transform: uniform_transform,
    };

}

extern "C" {
    fn gluErrorString(error: GLenum) -> *GLubyte;
}

fn check_gl(message : &str) {
    let err = gl::GetError();
    if (err != gl::NO_ERROR) {
        unsafe {
            let err = std::str::raw::from_c_str(gluErrorString(err) as *i8);
            fail!("GL error {} at {}", err, message);
        }
    }
}

fn compile_shader(src: &[u8], ty: GLenum) -> GLuint {
    let shader = gl::CreateShader(ty);
    unsafe {
        // Attempt to compile the shader
        //transmute is used here because `as` causes ICE
        //wait a sec, is `src` null-terminated properly?
        gl::ShaderSource(shader, 1, std::cast::transmute(std::ptr::to_unsafe_ptr(&src.as_ptr())), ptr::null());
        gl::CompileShader(shader);

        // Get the compile status
        let mut status = gl::FALSE as GLint;
        gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

        // Fail on error
        if status != (gl::TRUE as GLint) {
            let mut len = 0;
            gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
            let mut buf = vec::from_elem(len as uint - 1, 0u8);     // subtract 1 to skip the trailing null character
            gl::GetShaderInfoLog(shader, len, ptr::mut_null(), buf.as_mut_ptr() as *mut GLchar);
            fail!(str::raw::from_utf8(buf).to_owned());
        }
    }
    shader
}

fn link_program(vs: GLuint, fs: GLuint) -> GLuint {
    let program = gl::CreateProgram();
    gl::AttachShader(program, vs);
    gl::AttachShader(program, fs);
    gl::LinkProgram(program);
    unsafe {
        // Get the link status
        let mut status = gl::FALSE as GLint;
        gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);

        // Fail on error
        if status != (gl::TRUE as GLint) {
            let mut len: GLint = 0;
            gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
            let mut buf = vec::from_elem(len as uint - 1, 0u8);     // subtract 1 to skip the trailing null character
            gl::GetProgramInfoLog(program, len, ptr::mut_null(), buf.as_mut_ptr() as *mut GLchar);
            fail!(str::raw::from_utf8(buf).to_owned());
        }
    }
    program
}

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
    fn call(&self, _: glfw::Error, description: ~str) {
        fail!("GLFW Error: {:s}", description);
    }
}

struct KeyContext;
impl glfw::KeyCallback for KeyContext {
    fn call(&self, window: &glfw::Window, key: glfw::Key, _: libc::c_int, action: glfw::Action, _: glfw::Modifiers) {
        match (action, key) {
            (glfw::Press, glfw::KeyEscape) => window.set_should_close(true),
            _ => {}
        }
    }
}

struct FramebufferSizeContext {
    chan: Chan<(u32,u32)>
}
impl glfw::FramebufferSizeCallback for FramebufferSizeContext {
    fn call(&self, _: &glfw::Window, width: i32, height: i32) {
        self.chan.send((width as u32,height as u32));
    }
}
<commit_msg>limit framerate to 60fps<commit_after>#[feature(globs)];

extern mod extra;
extern mod glfw;
extern mod gl;
extern mod cgmath;
extern mod noise;

use std::cast;
use std::ptr;
use std::str;
use std::vec;
use std::libc;
use std::io::Timer;

use gl::types::*;

use cgmath::matrix::Matrix;
use cgmath::matrix::Mat3;
use cgmath::matrix::Mat4;
use cgmath::matrix::ToMat4;
use cgmath::vector::Vector;
use cgmath::vector::Vec3;
use cgmath::vector::Vec4;
use cgmath::angle::rad;

use chunk::Chunk;

#[link(name="GLU")]
extern {}

mod chunk;

static vertex_shader_src : &'static str = r"
#version 110

uniform mat4 transform;

attribute vec3 position;
attribute vec3 normal;

varying vec4 frag_color;

const vec4 obj_diffuse = vec4(0.2, 0.6, 0.2, 1.0);
const vec3 light_direction = vec3(0.408248, -0.816497, 0.408248);
const vec4 light_diffuse = vec4(0.8, 0.8, 0.8, 0.0);
const vec4 light_ambient = vec4(0.2, 0.2, 0.2, 1.0);

void main() {
    gl_Position = transform * vec4(position, 1.0);

    vec4 diffuse_factor
        = max(-dot(normal, light_direction), 0.0) * light_diffuse;
    vec4 ambient_diffuse_factor = diffuse_factor + light_ambient;

    frag_color = ambient_diffuse_factor * obj_diffuse;
}
";

static fragment_shader_src : &'static str = r"
#version 110

varying vec4 frag_color;

void main() {
    gl_FragColor = frag_color;
}
";

pub static WORLD_SIZE: uint = 4;
pub static CHUNK_SIZE: uint = 64;
pub static WORLD_SEED: u32 = 42;

static FRAME_TIME_TARGET_MS : u64 = 16;

struct GraphicsResources {
    program: GLuint,
    uniform_transform: GLint,
}

#[start]
fn start(argc: int, argv: **u8) -> int {
    std::rt::start_on_main_thread(argc, argv, main)
}

fn main() {
   glfw::set_error_callback(~ErrorContext);

    do glfw::start {
        let mut window_width = 800;
        let mut window_height = 600;

        let window = glfw::Window::create(window_width, window_height, "Hello, I am a window.", glfw::Windowed)
            .expect("Failed to create GLFW window.");

        window.set_cursor_mode(glfw::CursorDisabled);
        window.make_context_current();

        gl::load_with(glfw::get_proc_address);

        gl::Enable(gl::DEPTH_TEST);
        gl::Enable(gl::CULL_FACE);

        glfw::set_swap_interval(1);

        let graphics_resources = load_graphics_resources();

        gl::UseProgram(graphics_resources.program);

        let mut chunks : ~[~Chunk] = ~[];
        for x in range(0, WORLD_SIZE) {
            for z in range(0, WORLD_SIZE) {
                chunks.push(
                    chunk::chunk_gen(&graphics_resources, WORLD_SEED,
                              ((x as int - WORLD_SIZE as int/2) * CHUNK_SIZE as int) as i64,
                              ((z as int - WORLD_SIZE as int/2) * CHUNK_SIZE as int) as i64));
            }
        }

        window.set_key_callback(~KeyContext);

        let (fb_size_port, fb_size_chan): (Port<(u32,u32)>, Chan<(u32,u32)>) = std::comm::Chan::new();
        window.set_framebuffer_size_callback(~FramebufferSizeContext { chan: fb_size_chan });

        let start_time = extra::time::precise_time_ns();
        let mut last_frame_time = start_time;
        let mut num_frames = 0;

        let mut camera_position = Vec3::<f32>::new(0.0f32, 20.0f32, 40.0f32);

        let mut timer = Timer::new().unwrap();

        while !window.should_close() {
            let frame_start_time = extra::time::precise_time_ns();

            glfw::poll_events();

            loop {
                match fb_size_port.try_recv() {
                    Some((w,h)) => {
                        window_width = w;
                        window_height = h;
                    }
                    None => break
                }
            }

            let (cursor_x, cursor_y) = window.get_cursor_pos();
            let camera_angle_y = ((cursor_x * 0.0005) % 1.0) * std::f64::consts::PI * 2.0;
            let camera_angle_x = ((cursor_y * 0.0005) % 1.0) * std::f64::consts::PI * 2.0;

            let mut camera_velocity = Vec3::<f32>::new(0.0f32, 0.0f32, 0.0f32);

            match window.get_key(glfw::KeySpace) {
                glfw::Press => { camera_velocity.y += 1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyLeftControl) {
                glfw::Press => { camera_velocity.y += -1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyS) {
                glfw::Press => { camera_velocity.z += 1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyW) {
                glfw::Press => { camera_velocity.z += -1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyD) {
                glfw::Press => { camera_velocity.x += 1.0f32 }
                _ => {}
            }

            match window.get_key(glfw::KeyA) {
                glfw::Press => { camera_velocity.x += -1.0f32 }
                _ => {}
            }

            gl::Viewport(0,0, window_width as GLint, window_height as GLint);

            gl::ClearColor(0.0, 0.0, 0.0, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT);

            let projection = cgmath::projection::perspective(
                rad(1.57f32),
                window_width as f32 / window_height as f32,
                0.1f32, 1000.0f32);

            let camera_translation = Mat4::<f32>::from_cols(
                Vec4::<f32>::unit_x(),
                Vec4::<f32>::unit_y(),
                Vec4::<f32>::unit_z(),
                camera_position.mul_s(-1.0f32).extend(1.0f32));
            let camera_rotation_x = Mat3::<f32>::from_angle_x(rad(camera_angle_x as f32)).to_mat4();
            let camera_rotation_y = Mat3::<f32>::from_angle_y(rad(camera_angle_y as f32)).to_mat4();
            let camera = camera_rotation_x.mul_m(&camera_rotation_y).mul_m(&camera_translation);

            let inv_camera_rotation = Mat3::<f32>::from_euler(rad(-camera_angle_x as f32), rad(-camera_angle_y as f32), rad(0.0f32));
            let absolute_camera_velocity = inv_camera_rotation.mul_v(&camera_velocity).mul_s(0.5f32);
            camera_position.add_self_v(&absolute_camera_velocity);

            for chunk in chunks.iter() {
                let chunk_transform = Mat4::<f32>::from_cols(
                    Vec4::<f32>::unit_x(),
                    Vec4::<f32>::unit_y(),
                    Vec4::<f32>::unit_z(),
                    Vec4::<f32>::new(chunk.x as f32, 0.0f32, chunk.z as f32, 1.0f32));

                let transform = projection.mul_m(&camera).mul_m(&chunk_transform);

                gl::BindVertexArray(chunk.vao);

                unsafe {
                    gl::UniformMatrix4fv(graphics_resources.uniform_transform, 1, gl::FALSE, cast::transmute(&transform));
                    gl::DrawElements(gl::TRIANGLES, chunk.num_elements as i32, gl::UNSIGNED_INT, ptr::null());
                }

                gl::BindVertexArray(0);
            }

            window.swap_buffers();

            check_gl("main loop");

            let cur_time = extra::time::precise_time_ns();
            num_frames += 1;
            if (cur_time - last_frame_time) > (1000*1000*1000) {
                println!("{} frames per second", num_frames);
                num_frames = 0;
                last_frame_time = cur_time;
            }

            let frame_end_time = extra::time::precise_time_ns();
            let frame_time_ms = (frame_end_time - frame_start_time)/(1000*1000);
            if (frame_time_ms < FRAME_TIME_TARGET_MS) {
                timer.sleep(FRAME_TIME_TARGET_MS - frame_time_ms);
            }
        }
    }
}

fn load_graphics_resources() -> GraphicsResources {
    let vs = compile_shader(vertex_shader_src.as_bytes(), gl::VERTEX_SHADER);
    let fs = compile_shader(fragment_shader_src.as_bytes(), gl::FRAGMENT_SHADER);
    let program = link_program(vs, fs);

    let uniform_transform = unsafe { "transform".with_c_str(|ptr| gl::GetUniformLocation(program, ptr)) };

    return GraphicsResources {
        program: program,
        uniform_transform: uniform_transform,
    };

}

extern "C" {
    fn gluErrorString(error: GLenum) -> *GLubyte;
}

fn check_gl(message : &str) {
    let err = gl::GetError();
    if (err != gl::NO_ERROR) {
        unsafe {
            let err = std::str::raw::from_c_str(gluErrorString(err) as *i8);
            fail!("GL error {} at {}", err, message);
        }
    }
}

fn compile_shader(src: &[u8], ty: GLenum) -> GLuint {
    let shader = gl::CreateShader(ty);
    unsafe {
        // Attempt to compile the shader
        //transmute is used here because `as` causes ICE
        //wait a sec, is `src` null-terminated properly?
        gl::ShaderSource(shader, 1, std::cast::transmute(std::ptr::to_unsafe_ptr(&src.as_ptr())), ptr::null());
        gl::CompileShader(shader);

        // Get the compile status
        let mut status = gl::FALSE as GLint;
        gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);

        // Fail on error
        if status != (gl::TRUE as GLint) {
            let mut len = 0;
            gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
            let mut buf = vec::from_elem(len as uint - 1, 0u8);     // subtract 1 to skip the trailing null character
            gl::GetShaderInfoLog(shader, len, ptr::mut_null(), buf.as_mut_ptr() as *mut GLchar);
            fail!(str::raw::from_utf8(buf).to_owned());
        }
    }
    shader
}

fn link_program(vs: GLuint, fs: GLuint) -> GLuint {
    let program = gl::CreateProgram();
    gl::AttachShader(program, vs);
    gl::AttachShader(program, fs);
    gl::LinkProgram(program);
    unsafe {
        // Get the link status
        let mut status = gl::FALSE as GLint;
        gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);

        // Fail on error
        if status != (gl::TRUE as GLint) {
            let mut len: GLint = 0;
            gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
            let mut buf = vec::from_elem(len as uint - 1, 0u8);     // subtract 1 to skip the trailing null character
            gl::GetProgramInfoLog(program, len, ptr::mut_null(), buf.as_mut_ptr() as *mut GLchar);
            fail!(str::raw::from_utf8(buf).to_owned());
        }
    }
    program
}

struct ErrorContext;
impl glfw::ErrorCallback for ErrorContext {
    fn call(&self, _: glfw::Error, description: ~str) {
        fail!("GLFW Error: {:s}", description);
    }
}

struct KeyContext;
impl glfw::KeyCallback for KeyContext {
    fn call(&self, window: &glfw::Window, key: glfw::Key, _: libc::c_int, action: glfw::Action, _: glfw::Modifiers) {
        match (action, key) {
            (glfw::Press, glfw::KeyEscape) => window.set_should_close(true),
            _ => {}
        }
    }
}

struct FramebufferSizeContext {
    chan: Chan<(u32,u32)>
}
impl glfw::FramebufferSizeCallback for FramebufferSizeContext {
    fn call(&self, _: &glfw::Window, width: i32, height: i32) {
        self.chan.send((width as u32,height as u32));
    }
}
<|endoftext|>