<commit_before>/// Provides mostly direct binding to the HTTP API

use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
use std::io::Read;
use std::rc::Rc;

use reqwest;
use reqwest::header::{Authorization, Bearer, Headers};
use serde_json;
use url;

use super::data::*;
use super::error::{TvdbError, TvdbResult};
use data::{EpisodeId, SeriesId};

/// Trait for custom implementations of URL fetching
pub trait RequestClient: Debug {
    fn get_url(&self, url: &str, jwt_token: Option<String>) -> TvdbResult<String>;
}

/// Default implementation of RequestClient
#[derive(Debug)]
pub struct DefaultHttpClient;

impl RequestClient for DefaultHttpClient {
    fn get_url(&self, url: &str, jwt_token: Option<String>) -> TvdbResult<String> {
        // Make request
        let client = reqwest::Client::new();

        let mut headers = Headers::new();
        if let Some(tok) = jwt_token {
            headers.set(Authorization(Bearer { token: tok.into() }));
        }

        let mut resp = client.get(url).headers(headers).send().map_err(|x| {
            TvdbError::CommunicationError {
                reason: format!("Error creating HTTP request: {}", x),
            }
        })?;

        // Check response
        if !resp.status().is_success() {
            return Err(TvdbError::CommunicationError {
                reason: format!(
                    "Unsuccessful HTTP response from url {}: {}",
                    url,
                    resp.status()
                ),
            });
        }

        let mut result = String::new();
        resp.read_to_string(&mut result)
            .map_err(|x| TvdbError::CommunicationError {
                reason: format!("Error reading response: {}", x),
            })?;
        return Ok(result);
    }
}

/// Main interface
#[derive(Debug, Clone)]
pub struct Tvdb<'a> {
    /// Your API key from TheTVDB.com
    pub key: String,
    http_client: Option<&'a RequestClient>,
    jwt_token: RefCell<Option<String>>,
    default_client: Rc<RequestClient>,
}

impl<'a> Tvdb<'a> {
    /// Initalise API with the given API key. A key can be acquired via
    /// the [API Key Registration page](http://thetvdb.com/?tab=apiregister)
    pub fn new<S>(key: S) -> Tvdb<'a>
    where
        S: Into<String>,
    {
        Tvdb {
            key: key.into(),
            http_client: None,
            jwt_token: RefCell::new(None),
            default_client: Rc::new(DefaultHttpClient {}),
        }
    }

    /// Set the JWT session token
    fn set_token(&self, token: String) {
        let mut j = self.jwt_token.borrow_mut();
        *j = Some(token);
    }

    /// Get JWT session token (typically set via `login` method)
    fn get_token(&self) -> Option<String> {
        let j = self.jwt_token.borrow();
        match *j {
            None => None,
            Some(ref t) => Some(format!("{}", *t)),
        }
    }

    /// Authenticate with TheTVDB, storing the JWT token internally for use by
    /// other methods.
    pub fn login(&self) -> TvdbResult<bool> {
        let mut map = HashMap::new();
        map.insert("apikey", &self.key);

        let c = reqwest::Client::new();
        let mut resp = c.post("https://api.thetvdb.com/login")
            .json(&map)
            .send()
            .map_err(|x| TvdbError::CommunicationError {
                reason: format!("{}", x),
            })?;
        let mut result = String::new();
        resp.read_to_string(&mut result)
            .map_err(|x| TvdbError::CommunicationError {
                reason: format!("Error reading response: {}", x),
            })?;

        let deserialized: serde_json::Value = serde_json::from_str(&result)?;
        let tok: String = deserialized["token"].as_str().unwrap().into();
        self.set_token(tok);

        Ok(true)
    }

    /// Sets a custom client (implementation of `RequestClient`) used to
    /// perform HTTP requests
    pub fn set_http_client(&mut self, client: &'a RequestClient) {
        self.http_client = Some::<&'a RequestClient>(client);
    }

    /// Search for series by name or IMDB ID
    /// <https://api.thetvdb.com/swagger#!/Search/get_search_series>
    pub fn search(
        &self,
        name: Option<&str>,
        imdb_id: Option<&str>,
    ) -> TvdbResult<SeriesSearchResult> {
        let dc = self.default_client.as_ref();
        let c = self.http_client.unwrap_or(dc);

        let mut params: HashMap<&str, &str> = HashMap::new();
        if let Some(n) = name {
            params.insert("name", n);
        }
        if let Some(i) = imdb_id {
            params.insert("imdbId", i);
        }

        let search_url = "https://api.thetvdb.com/search/series";
        let url: String = url::Url::parse_with_params(search_url, params)
            .unwrap()
            .as_str()
            .into();
        // Query URL
        let data = c.get_url(&url, self.get_token())?;

        // Parse result
        let result: SeriesSearchResult = serde_json::from_str(&data)?;

        Ok(result)
    }

    fn episode_inner(&self, id: EpisodeId) -> TvdbResult<EpisodeRecordResult> {
        let dc = self.default_client.as_ref();
        let c = self.http_client.unwrap_or(dc);

        // TODO Use `id.language`

        let url = format!("https://api.thetvdb.com/episodes/{id}", id = id.seriesid);
        let data = c.get_url(&url, self.get_token())?;
        // Parse result
        println!("{}", data);
        let result: Result<EpisodeRecordResult, serde_json::Error> = serde_json::from_str(&data);
        return Ok(result?);
    }

    /// Full information about given episode
    /// <https://api.thetvdb.com/swagger#!/Episodes/get_episodes_id>
    pub fn episode<E>(&self, id: E) -> TvdbResult<EpisodeRecordResult>
    where
        E: Into<EpisodeId>,
    {
        self.episode_inner(id.into())
    }

    fn series_episodes_inner(&self, id: SeriesId, page: u32) -> TvdbResult<SeriesEpisodesResult> {
        let dc = self.default_client.as_ref();
        let c = self.http_client.unwrap_or(dc);

        // TODO Use `id.language`

        let url = format!(
            "https://api.thetvdb.com/series/{id}/episodes?page={page}",
            id = id.seriesid,
            page = page
        );
        let data = c.get_url(&url, self.get_token())?;
        // Parse result
        let result: Result<SeriesEpisodesResult, serde_json::Error> = serde_json::from_str(&data);
        return Ok(result?);
    }

    /// All episodes for given series
    pub fn series_episodes<S>(&self, id: S, page: u32) -> TvdbResult<SeriesEpisodesResult>
    where
        S: Into<SeriesId>,
    {
        self.series_episodes_inner(id.into(), page)
    }
}
<commit_msg>Simpler JSON parsing call, remove stray print<commit_after>/// Provides mostly direct binding to the HTTP API

use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt::Debug;
use std::io::Read;
use std::rc::Rc;

use reqwest;
use reqwest::header::{Authorization, Bearer, Headers};
use serde_json;
use url;

use super::data::*;
use super::error::{TvdbError, TvdbResult};
use data::{EpisodeId, SeriesId};

/// Trait for custom implementations of URL fetching
pub trait RequestClient: Debug {
    fn get_url(&self, url: &str, jwt_token: Option<String>) -> TvdbResult<String>;
}

/// Default implementation of RequestClient
#[derive(Debug)]
pub struct DefaultHttpClient;

impl RequestClient for DefaultHttpClient {
    fn get_url(&self, url: &str, jwt_token: Option<String>) -> TvdbResult<String> {
        // Make request
        let client = reqwest::Client::new();

        let mut headers = Headers::new();
        if let Some(tok) = jwt_token {
            headers.set(Authorization(Bearer { token: tok.into() }));
        }

        let mut resp = client.get(url).headers(headers).send().map_err(|x| {
            TvdbError::CommunicationError {
                reason: format!("Error creating HTTP request: {}", x),
            }
        })?;

        // Check response
        if !resp.status().is_success() {
            return Err(TvdbError::CommunicationError {
                reason: format!(
                    "Unsuccessful HTTP response from url {}: {}",
                    url,
                    resp.status()
                ),
            });
        }

        let mut result = String::new();
        resp.read_to_string(&mut result)
            .map_err(|x| TvdbError::CommunicationError {
                reason: format!("Error reading response: {}", x),
            })?;
        return Ok(result);
    }
}

/// Main interface
#[derive(Debug, Clone)]
pub struct Tvdb<'a> {
    /// Your API key from TheTVDB.com
    pub key: String,
    http_client: Option<&'a RequestClient>,
    jwt_token: RefCell<Option<String>>,
    default_client: Rc<RequestClient>,
}

impl<'a> Tvdb<'a> {
    /// Initalise API with the given API key. A key can be acquired via
    /// the [API Key Registration page](http://thetvdb.com/?tab=apiregister)
    pub fn new<S>(key: S) -> Tvdb<'a>
    where
        S: Into<String>,
    {
        Tvdb {
            key: key.into(),
            http_client: None,
            jwt_token: RefCell::new(None),
            default_client: Rc::new(DefaultHttpClient {}),
        }
    }

    /// Set the JWT session token
    fn set_token(&self, token: String) {
        let mut j = self.jwt_token.borrow_mut();
        *j = Some(token);
    }

    /// Get JWT session token (typically set via `login` method)
    fn get_token(&self) -> Option<String> {
        let j = self.jwt_token.borrow();
        match *j {
            None => None,
            Some(ref t) => Some(format!("{}", *t)),
        }
    }

    /// Authenticate with TheTVDB, storing the JWT token internally for use by
    /// other methods.
    pub fn login(&self) -> TvdbResult<bool> {
        let mut map = HashMap::new();
        map.insert("apikey", &self.key);

        let c = reqwest::Client::new();
        let mut resp = c.post("https://api.thetvdb.com/login")
            .json(&map)
            .send()
            .map_err(|x| TvdbError::CommunicationError {
                reason: format!("{}", x),
            })?;
        let mut result = String::new();
        resp.read_to_string(&mut result)
            .map_err(|x| TvdbError::CommunicationError {
                reason: format!("Error reading response: {}", x),
            })?;

        let deserialized: serde_json::Value = serde_json::from_str(&result)?;
        let tok: String = deserialized["token"].as_str().unwrap().into();
        self.set_token(tok);

        Ok(true)
    }

    /// Sets a custom client (implementation of `RequestClient`) used to
    /// perform HTTP requests
    pub fn set_http_client(&mut self, client: &'a RequestClient) {
        self.http_client = Some::<&'a RequestClient>(client);
    }

    /// Search for series by name or IMDB ID
    /// <https://api.thetvdb.com/swagger#!/Search/get_search_series>
    pub fn search(
        &self,
        name: Option<&str>,
        imdb_id: Option<&str>,
    ) -> TvdbResult<SeriesSearchResult> {
        let dc = self.default_client.as_ref();
        let c = self.http_client.unwrap_or(dc);

        let mut params: HashMap<&str, &str> = HashMap::new();
        if let Some(n) = name {
            params.insert("name", n);
        }
        if let Some(i) = imdb_id {
            params.insert("imdbId", i);
        }

        let search_url = "https://api.thetvdb.com/search/series";
        let url: String = url::Url::parse_with_params(search_url, params)
            .unwrap()
            .as_str()
            .into();
        // Query URL
        let data = c.get_url(&url, self.get_token())?;

        // Parse result
        let result: SeriesSearchResult = serde_json::from_str(&data)?;

        Ok(result)
    }

    fn episode_inner(&self, id: EpisodeId) -> TvdbResult<EpisodeRecordResult> {
        let dc = self.default_client.as_ref();
        let c = self.http_client.unwrap_or(dc);

        // TODO Use `id.language`

        let url = format!("https://api.thetvdb.com/episodes/{id}", id = id.seriesid);
        let data = c.get_url(&url, self.get_token())?;
        // Parse result
        let result: EpisodeRecordResult = serde_json::from_str(&data)?;
        return Ok(result);
    }

    /// Full information about given episode
    /// <https://api.thetvdb.com/swagger#!/Episodes/get_episodes_id>
    pub fn episode<E>(&self, id: E) -> TvdbResult<EpisodeRecordResult>
    where
        E: Into<EpisodeId>,
    {
        self.episode_inner(id.into())
    }

    fn series_episodes_inner(&self, id: SeriesId, page: u32) -> TvdbResult<SeriesEpisodesResult> {
        let dc = self.default_client.as_ref();
        let c = self.http_client.unwrap_or(dc);

        // TODO Use `id.language`

        let url = format!(
            "https://api.thetvdb.com/series/{id}/episodes?page={page}",
            id = id.seriesid,
            page = page
        );
        let data = c.get_url(&url, self.get_token())?;
        // Parse result
        let result: Result<SeriesEpisodesResult, serde_json::Error> = serde_json::from_str(&data);
        return Ok(result?);
    }

    /// All episodes for given series
    pub fn series_episodes<S>(&self, id: S, page: u32) -> TvdbResult<SeriesEpisodesResult>
    where
        S: Into<SeriesId>,
    {
        self.series_episodes_inner(id.into(), page)
    }
}
<|endoftext|>


<commit_before>//! Read and write Elasto Mania replay files.
use std::io::{ Read };
use std::fs::File;
use byteorder::{ ReadBytesExt, LittleEndian };
use super::{ Position, trim_string };

// Magic arbitrary number to signify end of replay file.
const EOR: i32 = 0x00492F75;

/// One frame of replay.
#[derive(Debug, Default, PartialEq)]
pub struct Frame {
    /// Bike position.
    pub bike: Position<f32>,
    /// Left wheel position.
    pub left_wheel: Position<i16>,
    /// Right wheel position.
    pub right_wheel: Position<i16>,
    /// Head position.
    pub head: Position<i16>,
    /// Bike rotation. Range 0..10000.
    pub rotation: i16,
    /// Left wheel rotation. Range 0..255.
    pub left_wheel_rotation: u8,
    /// Right wheel rotation. Range 0..255.
    pub right_wheel_rotation: u8,
    /// Throttle.
    pub throttle: bool,
    /// Right direction. True = right, False = left.
    // TODO: consider making right field = direction and enum with right and left?
    pub right: bool,
    /// Spring sound effect volume.
    pub volume: i16
}

impl Frame {
    /// Returns a new Frame struct with zero-filled values.
    ///
    /// # Examples
    ///
    /// ```
    /// let frame = elma::rec::Frame::new();
    /// ```
    pub fn new() -> Self {
        Frame {
            bike: Position { x: 0_f32, y: 0_f32 },
            left_wheel: Position { x: 0, y: 0 },
            right_wheel: Position { x: 0, y: 0 },
            head: Position { x: 0, y: 0 },
            rotation: 0,
            left_wheel_rotation: 0,
            right_wheel_rotation: 0,
            throttle: false,
            right: false,
            volume: 0
        }
    }
}

#[derive(Debug, Default, PartialEq)]
/// Replay events.
pub struct Event {
    /// Time of event.
    pub time: f64,
    /// Event type.
    pub event_type: EventType
}

#[derive(Debug, PartialEq)]
/// Type of event.
pub enum EventType {
    /// Apple or flower touch, with index of object.
    Touch { index: i16 },
    Turn,
    VoltRight,
    VoltLeft,
    /// Ground touch, for sound effects. Two types; if alternative is true, uses the second type.
    Ground { alternative: bool }
}

impl Default for EventType {
    fn default() -> EventType { EventType::Touch { index: 0 } }
}

impl Event {
    pub fn new() -> Self {
        Event {
            time: 0_f64,
            event_type: EventType::default()
        }
    }
}

/// Replay struct
#[derive(Debug, PartialEq)]
pub struct Replay {
    /// Raw binary data.
    raw: Vec<u8>,
    /// Whether replay is multi-player or not.
    pub multi: bool,
    /// Whether replay is flag-tag or not.
    pub flag_tag: bool,
    /// Random number to link with level file.
    pub link: u32,
    /// Full level filename.
    pub level: String,
    /// Vector with Frame structs.
    pub frames: Vec<Frame>,
    /// Events.
    pub events: Vec<Event>
}

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

impl Replay {
    /// Build a new Replay.
    ///
    /// # Examples
    ///
    /// ```
    /// let rec = elma::rec::Replay::new();
    /// ```
    pub fn new() -> Self {
        Replay {
            raw: vec![],
            multi: false,
            flag_tag: false,
            link: 0,
            level: String::new(),
            frames: vec![],
            events: vec![]
        }
    }

    /// Loads a replay file and returns a Replay struct.
    ///
    /// # Examples
    ///
    /// ```
    /// let rec = elma::rec::Replay::load("tests/test_1.rec");
    /// ```
    pub fn load(filename: &str) -> Self {
        let mut replay = Replay::new();
        let mut file = File::open(filename).unwrap();
        let mut buffer = vec![];
        file.read_to_end(&mut buffer).unwrap();
        replay.raw = buffer;
        replay.parse_replay();
        replay
    }

    /// Parses the raw binary data into Replay struct fields.
    fn parse_replay(&mut self) {
        let mut remaining = self.raw.as_slice();

        // TODO: read bytes 8-12 first to see if multi-player replay, then parse it twice.

        // Frame count.
        let frame_count = remaining.read_i32::<LittleEndian>().unwrap();

        // Some unused value, always 0x83.
        let (_, mut remaining) = remaining.split_at(4);

        // Multi-player replay.
        self.multi = remaining.read_i32::<LittleEndian>().unwrap() > 0;

        // Flag-tag replay.
        self.flag_tag = remaining.read_i32::<LittleEndian>().unwrap() > 0;

        // Level link.
        self.link = remaining.read_u32::<LittleEndian>().unwrap();

        // Level file name, including extension.
        let (level, remaining) = remaining.split_at(12);
        self.level = trim_string(level).unwrap();

        // Unknown, unused.
        let (_, remaining) = remaining.split_at(4);

        // Frames.
        let (mut bike_x, remaining) = remaining.split_at((frame_count*4) as usize);
        let (mut bike_y, remaining) = remaining.split_at((frame_count*4) as usize);
        let (mut left_x, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut left_y, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut right_x, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut right_y, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut head_x, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut head_y, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut rotation, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut left_rotation, remaining) = remaining.split_at((frame_count) as usize);
        let (mut right_rotation, remaining) = remaining.split_at((frame_count) as usize);
        let (mut data, remaining) = remaining.split_at((frame_count) as usize);
        let (mut volume, mut remaining) = remaining.split_at((frame_count*2) as usize);

        for _ in 0..frame_count {
            // Bike X and Y.
            let x = bike_x.read_f32::<LittleEndian>().unwrap();
            let y = bike_y.read_f32::<LittleEndian>().unwrap();
            let bike = Position { x: x, y: y };

            // Left wheel X and Y.
            let x = left_x.read_i16::<LittleEndian>().unwrap();
            let y = left_y.read_i16::<LittleEndian>().unwrap();
            let left_wheel = Position { x: x, y: y };

            // Left wheel X and Y.
            let x = right_x.read_i16::<LittleEndian>().unwrap();
            let y = right_y.read_i16::<LittleEndian>().unwrap();
            let right_wheel = Position { x: x, y: y };

            // Head X and Y.
            let x = head_x.read_i16::<LittleEndian>().unwrap();
            let y = head_y.read_i16::<LittleEndian>().unwrap();
            let head = Position { x: x, y: y };

            // Rotations.
            let rotation = rotation.read_i16::<LittleEndian>().unwrap();
            let left_wheel_rotation = left_rotation.read_u8().unwrap();
            let right_wheel_rotation = right_rotation.read_u8().unwrap();

            // Throttle and turn right.
            let data = data.read_u8().unwrap();
            let throttle = data & 1 != 0;
            let right = data & (1 << 1) != 0;

            // Sound effect volume.
            let volume = volume.read_i16::<LittleEndian>().unwrap();

            self.frames.push(Frame {
                bike: bike,
                left_wheel: left_wheel,
                right_wheel: right_wheel,
                head: head,
                rotation: rotation,
                left_wheel_rotation: left_wheel_rotation,
                right_wheel_rotation: right_wheel_rotation,
                throttle: throttle,
                right: right,
                volume: volume
            });
        }

        // Events.
        let event_count = remaining.read_i32::<LittleEndian>().unwrap();
        for n in 0..event_count {
            // Event time
            let time = remaining.read_f64::<LittleEndian>().unwrap();

            // Event details
            let info = remaining.read_i16::<LittleEndian>().unwrap();
            let event = remaining.read_u8().unwrap();
            // Unknown values
            let _ = remaining.read_u8().unwrap();
            let _ = remaining.read_f32::<LittleEndian>().unwrap();
            let event_type = match event {
                0 => EventType::Touch { index: info },
                1 => EventType::Ground { alternative: false },
                5 => EventType::Turn,
                4 => EventType::Ground { alternative: true },
                6 => EventType::VoltRight,
                7 => EventType::VoltLeft,
                _ => panic!("Unknown event type: {:?}\nin event number: {:?}", event, n)
            };

            self.events.push(Event {
                time: time,
                event_type: event_type
            });
        }

        let expected = remaining.read_i32::<LittleEndian>().unwrap();
        if expected != EOR { panic!("EOR marker mismatch: x0{:x} != x0{:x}", expected, EOR); }

        // TODO: Add multi-player replay parsing.
    }

    /// Save replay as a file.
    pub fn save (&self, _filename: &str) {
        // TODO: save it.
    }
}
<commit_msg>Change wording to be consistent<commit_after>//! Read and write Elasto Mania replay files.
use std::io::{ Read };
use std::fs::File;
use byteorder::{ ReadBytesExt, LittleEndian };
use super::{ Position, trim_string };

// Magic arbitrary number to signify end of replay file.
const EOR: i32 = 0x00492F75;

/// One frame of replay.
#[derive(Debug, Default, PartialEq)]
pub struct Frame {
    /// Bike position.
    pub bike: Position<f32>,
    /// Left wheel position.
    pub left_wheel: Position<i16>,
    /// Right wheel position.
    pub right_wheel: Position<i16>,
    /// Head position.
    pub head: Position<i16>,
    /// Bike rotation. Range 0..10000.
    pub rotation: i16,
    /// Left wheel rotation. Range 0..255.
    pub left_wheel_rotation: u8,
    /// Right wheel rotation. Range 0..255.
    pub right_wheel_rotation: u8,
    /// Throttle.
    pub throttle: bool,
    /// Right direction. True = right, False = left.
    // TODO: consider making right field = direction and enum with right and left?
    pub right: bool,
    /// Spring sound effect volume.
    pub volume: i16
}

impl Frame {
    /// Returns a new Frame struct with zero-filled values.
    ///
    /// # Examples
    ///
    /// ```
    /// let frame = elma::rec::Frame::new();
    /// ```
    pub fn new() -> Self {
        Frame {
            bike: Position { x: 0_f32, y: 0_f32 },
            left_wheel: Position { x: 0, y: 0 },
            right_wheel: Position { x: 0, y: 0 },
            head: Position { x: 0, y: 0 },
            rotation: 0,
            left_wheel_rotation: 0,
            right_wheel_rotation: 0,
            throttle: false,
            right: false,
            volume: 0
        }
    }
}

#[derive(Debug, Default, PartialEq)]
/// Replay events.
pub struct Event {
    /// Time of event.
    pub time: f64,
    /// Event type.
    pub event_type: EventType
}

#[derive(Debug, PartialEq)]
/// Type of event.
pub enum EventType {
    /// Apple or flower touch, with index of object.
    Touch { index: i16 },
    Turn,
    VoltRight,
    VoltLeft,
    /// Ground touch, for sound effects. Two types; if alternative is true, uses the second type.
    Ground { alternative: bool }
}

impl Default for EventType {
    fn default() -> EventType { EventType::Touch { index: 0 } }
}

impl Event {
    pub fn new() -> Self {
        Event {
            time: 0_f64,
            event_type: EventType::default()
        }
    }
}

/// Replay struct
#[derive(Debug, PartialEq)]
pub struct Replay {
    /// Raw binary data.
    raw: Vec<u8>,
    /// Whether replay is multi-player or not.
    pub multi: bool,
    /// Whether replay is flag-tag or not.
    pub flag_tag: bool,
    /// Random number to link with level file.
    pub link: u32,
    /// Full level filename.
    pub level: String,
    /// Vector with Frame structs.
    pub frames: Vec<Frame>,
    /// Events.
    pub events: Vec<Event>
}

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

impl Replay {
    /// Return a new Replay struct.
    ///
    /// # Examples
    ///
    /// ```
    /// let rec = elma::rec::Replay::new();
    /// ```
    pub fn new() -> Self {
        Replay {
            raw: vec![],
            multi: false,
            flag_tag: false,
            link: 0,
            level: String::new(),
            frames: vec![],
            events: vec![]
        }
    }

    /// Loads a replay file and returns a Replay struct.
    ///
    /// # Examples
    ///
    /// ```
    /// let rec = elma::rec::Replay::load("tests/test_1.rec");
    /// ```
    pub fn load(filename: &str) -> Self {
        let mut replay = Replay::new();
        let mut file = File::open(filename).unwrap();
        let mut buffer = vec![];
        file.read_to_end(&mut buffer).unwrap();
        replay.raw = buffer;
        replay.parse_replay();
        replay
    }

    /// Parses the raw binary data into Replay struct fields.
    fn parse_replay(&mut self) {
        let mut remaining = self.raw.as_slice();

        // TODO: read bytes 8-12 first to see if multi-player replay, then parse it twice.

        // Frame count.
        let frame_count = remaining.read_i32::<LittleEndian>().unwrap();

        // Some unused value, always 0x83.
        let (_, mut remaining) = remaining.split_at(4);

        // Multi-player replay.
        self.multi = remaining.read_i32::<LittleEndian>().unwrap() > 0;

        // Flag-tag replay.
        self.flag_tag = remaining.read_i32::<LittleEndian>().unwrap() > 0;

        // Level link.
        self.link = remaining.read_u32::<LittleEndian>().unwrap();

        // Level file name, including extension.
        let (level, remaining) = remaining.split_at(12);
        self.level = trim_string(level).unwrap();

        // Unknown, unused.
        let (_, remaining) = remaining.split_at(4);

        // Frames.
        let (mut bike_x, remaining) = remaining.split_at((frame_count*4) as usize);
        let (mut bike_y, remaining) = remaining.split_at((frame_count*4) as usize);
        let (mut left_x, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut left_y, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut right_x, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut right_y, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut head_x, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut head_y, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut rotation, remaining) = remaining.split_at((frame_count*2) as usize);
        let (mut left_rotation, remaining) = remaining.split_at((frame_count) as usize);
        let (mut right_rotation, remaining) = remaining.split_at((frame_count) as usize);
        let (mut data, remaining) = remaining.split_at((frame_count) as usize);
        let (mut volume, mut remaining) = remaining.split_at((frame_count*2) as usize);

        for _ in 0..frame_count {
            // Bike X and Y.
            let x = bike_x.read_f32::<LittleEndian>().unwrap();
            let y = bike_y.read_f32::<LittleEndian>().unwrap();
            let bike = Position { x: x, y: y };

            // Left wheel X and Y.
            let x = left_x.read_i16::<LittleEndian>().unwrap();
            let y = left_y.read_i16::<LittleEndian>().unwrap();
            let left_wheel = Position { x: x, y: y };

            // Left wheel X and Y.
            let x = right_x.read_i16::<LittleEndian>().unwrap();
            let y = right_y.read_i16::<LittleEndian>().unwrap();
            let right_wheel = Position { x: x, y: y };

            // Head X and Y.
            let x = head_x.read_i16::<LittleEndian>().unwrap();
            let y = head_y.read_i16::<LittleEndian>().unwrap();
            let head = Position { x: x, y: y };

            // Rotations.
            let rotation = rotation.read_i16::<LittleEndian>().unwrap();
            let left_wheel_rotation = left_rotation.read_u8().unwrap();
            let right_wheel_rotation = right_rotation.read_u8().unwrap();

            // Throttle and turn right.
            let data = data.read_u8().unwrap();
            let throttle = data & 1 != 0;
            let right = data & (1 << 1) != 0;

            // Sound effect volume.
            let volume = volume.read_i16::<LittleEndian>().unwrap();

            self.frames.push(Frame {
                bike: bike,
                left_wheel: left_wheel,
                right_wheel: right_wheel,
                head: head,
                rotation: rotation,
                left_wheel_rotation: left_wheel_rotation,
                right_wheel_rotation: right_wheel_rotation,
                throttle: throttle,
                right: right,
                volume: volume
            });
        }

        // Events.
        let event_count = remaining.read_i32::<LittleEndian>().unwrap();
        for n in 0..event_count {
            // Event time
            let time = remaining.read_f64::<LittleEndian>().unwrap();

            // Event details
            let info = remaining.read_i16::<LittleEndian>().unwrap();
            let event = remaining.read_u8().unwrap();
            // Unknown values
            let _ = remaining.read_u8().unwrap();
            let _ = remaining.read_f32::<LittleEndian>().unwrap();
            let event_type = match event {
                0 => EventType::Touch { index: info },
                1 => EventType::Ground { alternative: false },
                5 => EventType::Turn,
                4 => EventType::Ground { alternative: true },
                6 => EventType::VoltRight,
                7 => EventType::VoltLeft,
                _ => panic!("Unknown event type: {:?}\nin event number: {:?}", event, n)
            };

            self.events.push(Event {
                time: time,
                event_type: event_type
            });
        }

        let expected = remaining.read_i32::<LittleEndian>().unwrap();
        if expected != EOR { panic!("EOR marker mismatch: x0{:x} != x0{:x}", expected, EOR); }

        // TODO: Add multi-player replay parsing.
    }

    /// Save replay as a file.
    pub fn save (&self, _filename: &str) {
        // TODO: save it.
    }
}
<|endoftext|>


<commit_before>use std::os::getenv;
use std::io::{BufferedReader, IoResult};
use std::io::fs::{File, copy, rename};
use std::io::stdio::{stdout, stderr};
use std::default::Default;

use libc::pid_t;
use collections::treemap::TreeMap;
use argparse::{ArgumentParser, Store, StoreOption, List, StoreTrue, StoreConst};
use argparse::{StoreFalse};

use super::uidmap::write_uid_map;
use super::monitor::Monitor;
use super::env::{Environ, Container};
use super::linux::{ensure_dir, RunOptions, run_container, CPipe};
use super::options::env_options;
use super::build::ensure_container;

use Pid1 = super::linux;

static DEFAULT_PATH: &'static str =
    "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin";

fn read_env_file(path: &Path, env: &mut TreeMap<String, String>)
    -> IoResult<()>
{
    let file = try!(File::open(path).map(|f| BufferedReader::new(f)));
    let mut reader = BufferedReader::new(file);
    for (n, line) in reader.lines().enumerate() {
        let line = try!(line);
        let pair: Vec<&str> = line.as_slice().splitn('=', 1).collect();
        match pair.as_slice() {
            [key, val] => {
                let nkey = key.trim().to_string();
                if !env.contains_key(&nkey) {
                    env.insert(nkey, val.trim().to_string());
                }
            }
            _ => {
                warn!("Invalid line {} in {}",
                    n+1, path.display());
            }
        }
    }
    return Ok(());
}

fn container_environ(container: &Container, env: &mut TreeMap<String, String>)
    -> Result<(), String>
{
    for (k, v) in container.environ.iter() {
        if !env.contains_key(k) {
            env.insert(k.clone(), v.clone());
        }
    }
    match container.environ_file {
        None => {}
        Some(ref suf) => {
            let path = container.container_root.as_ref().unwrap()
                       .join(suf.as_slice().trim_left_chars('/'));
            if path.exists() {
                try!(read_env_file(&path, env).map_err(|e| format!(
                    "Error reading environment file: {}", e)));
            }
        }

    }
    return Ok(());
}

pub fn run_command_line(env: &mut Environ, args: Vec<String>)
    -> Result<int, String>
{
    let mut cname = "devel".to_string();
    let mut no_wrapper = false;
    let mut use_shell = false;
    let mut pid1mode = Pid1::Wait;
    let mut command: Option<String> = None;
    let mut cmdargs: Vec<String> = Vec::new();
    let mut resolv: bool = true;
    {
        let mut ap = ArgumentParser::new();
        ap.refer(&mut cname)
            .add_argument("container", box Store::<String>,
                "A name of the container to build")
            .required();
        ap.refer(&mut command)
            .add_argument("command", box StoreOption::<String>,
                "A command to run inside container")
            .required();
        ap.refer(&mut cmdargs)
            .add_argument("arguments", box List::<String>,
                "Arguments for the command");
        ap.refer(&mut no_wrapper)
            .add_option(["-N", "--no-wrapper"], box StoreTrue,
                "Do not use `command-wrapper` configured for container");
        ap.refer(&mut use_shell)
            .add_option(["-S", "--shell"], box StoreTrue,
                "Run command with `shell` configured for container");
        ap.refer(&mut use_shell)
            .add_option(["-S", "--shell"], box StoreTrue,
                "Run command with `shell` configured for container");
        ap.refer(&mut resolv)
            .add_option(["--no-resolv"], box StoreFalse,
                "Do not copy /etc/resolv.conf");
        ap.refer(&mut pid1mode)
            .add_option(["--wait"], box StoreConst(Pid1::Wait),
                "Spawn a supervisor as pid 1 and wait until target command \
                 exits. But if it spawned any children, they will be killed \
                 at the death of the target command (default)")
            .add_option(["--wait-all-children"],
                box StoreConst(Pid1::WaitAllChildren),
                "Spawn a supervisor as pid 1 and wait until target command \
                 and all of its children are dead. (Dangerous in some cases \
                 as some non useful child process may block container \
                 shutdown).")
            .add_option(["--exec"], box StoreConst(Pid1::Exec),
                "Execute the target command as pid 1. Should not be used \
                 unless the target command is a process manager itself and/or \
                 supposed to work as pid 1.");
        env_options(env, &mut ap);
        ap.stop_on_first_argument(true);
        match ap.parse(args, &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => return Ok(122),
        }
    }
    let mut container = try!(env.get_container(&cname));

    if command.is_none() {
        if container.default_command.is_some() {
            let cmd = container.default_command.as_ref().unwrap();
            cmdargs.push_all(cmd.as_slice());
        } else {
            return Err(format!("No command specified and no default command"));
        }
    } else {
        cmdargs.insert(0, command.unwrap());
        if use_shell {
            cmdargs = container.shell.clone() + cmdargs;
        } else if !no_wrapper && container.command_wrapper.is_some() {
            cmdargs = container.command_wrapper.as_ref().unwrap().clone()+cmdargs;
        }
    }

    let cmd = cmdargs.shift().unwrap();
    try!(ensure_container(env, &mut container));

    let mut monitor = Monitor::new(true);
    let pid = try!(internal_run(env, &container, pid1mode, resolv,
        &env.work_dir, cmd, cmdargs, TreeMap::new()));
    monitor.add("child".to_string(), pid);
    monitor.wait_all();
    return Ok(monitor.get_status());
}

pub fn internal_run(env: &Environ, container: &Container,
    pid1mode: Pid1::Pid1Mode, resolv: bool, work_dir: &Path,
    command: String, cmdargs: Vec<String>, runenv: TreeMap<String, String>)
    -> Result<pid_t, String>
{
    let mut runenv = runenv;
    env.populate_environ(&mut runenv);
    try!(container_environ(container, &mut runenv));
    let container_root = container.container_root.as_ref().unwrap();
    info!("Running {}: {} {}", container_root.display(), command, cmdargs);

    let mount_dir = env.project_root.join_many([".vagga", ".mnt"]);
    try!(ensure_dir(&mount_dir));

    if resolv {
        try!(copy(&Path::new("/etc/resolv.conf"),
                  &container_root.join("etc/resolv.conf.tmp"))
            .map_err(|e| format!("Error copying resolv.conf: {}", e)));
        try!(rename(&container_root.join("etc/resolv.conf.tmp"),
                    &container_root.join("etc/resolv.conf"),)
            .map_err(|e| format!("Error copying resolv.conf: {}", e)));
    }

    let home = "HOME".to_string();
    if runenv.find(&home).is_none() {
        // TODO(tailhook) set home to real home if /home is mounted
        runenv.insert(home, "/homeless-shelter".to_string());
    }
    let term = "TERM".to_string();
    if runenv.find(&term).is_none() {
        runenv.insert(term, getenv("TERM").unwrap_or("linux".to_string()));
    }
    let path = "PATH".to_string();
    if runenv.find(&path).is_none() {
        runenv.insert(path, DEFAULT_PATH.to_string());
    }

    let pipe = try!(CPipe::new());
    let mut ropts: RunOptions = Default::default();
    ropts.pid1mode = pid1mode;
    let pid = try!(run_container(&pipe, env,
        container.container_root.as_ref().unwrap(),
        &ropts, work_dir, &command, cmdargs.as_slice(), &runenv));

    try!(write_uid_map(pid, &container.uids, &container.gids));

    try!(pipe.wakeup());
    return Ok(pid);
}
<commit_msg>Make etc dir for resolv.conf<commit_after>use std::os::getenv;
use std::io::{BufferedReader, IoResult, AllPermissions};
use std::io::fs::{File, copy, rename, mkdir};
use std::io::stdio::{stdout, stderr};
use std::default::Default;

use libc::pid_t;
use collections::treemap::TreeMap;
use argparse::{ArgumentParser, Store, StoreOption, List, StoreTrue, StoreConst};
use argparse::{StoreFalse};

use super::uidmap::write_uid_map;
use super::monitor::Monitor;
use super::env::{Environ, Container};
use super::linux::{ensure_dir, RunOptions, run_container, CPipe};
use super::options::env_options;
use super::build::ensure_container;

use Pid1 = super::linux;

static DEFAULT_PATH: &'static str =
    "/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin";

fn read_env_file(path: &Path, env: &mut TreeMap<String, String>)
    -> IoResult<()>
{
    let file = try!(File::open(path).map(|f| BufferedReader::new(f)));
    let mut reader = BufferedReader::new(file);
    for (n, line) in reader.lines().enumerate() {
        let line = try!(line);
        let pair: Vec<&str> = line.as_slice().splitn('=', 1).collect();
        match pair.as_slice() {
            [key, val] => {
                let nkey = key.trim().to_string();
                if !env.contains_key(&nkey) {
                    env.insert(nkey, val.trim().to_string());
                }
            }
            _ => {
                warn!("Invalid line {} in {}",
                    n+1, path.display());
            }
        }
    }
    return Ok(());
}

fn container_environ(container: &Container, env: &mut TreeMap<String, String>)
    -> Result<(), String>
{
    for (k, v) in container.environ.iter() {
        if !env.contains_key(k) {
            env.insert(k.clone(), v.clone());
        }
    }
    match container.environ_file {
        None => {}
        Some(ref suf) => {
            let path = container.container_root.as_ref().unwrap()
                       .join(suf.as_slice().trim_left_chars('/'));
            if path.exists() {
                try!(read_env_file(&path, env).map_err(|e| format!(
                    "Error reading environment file: {}", e)));
            }
        }

    }
    return Ok(());
}

pub fn run_command_line(env: &mut Environ, args: Vec<String>)
    -> Result<int, String>
{
    let mut cname = "devel".to_string();
    let mut no_wrapper = false;
    let mut use_shell = false;
    let mut pid1mode = Pid1::Wait;
    let mut command: Option<String> = None;
    let mut cmdargs: Vec<String> = Vec::new();
    let mut resolv: bool = true;
    {
        let mut ap = ArgumentParser::new();
        ap.refer(&mut cname)
            .add_argument("container", box Store::<String>,
                "A name of the container to build")
            .required();
        ap.refer(&mut command)
            .add_argument("command", box StoreOption::<String>,
                "A command to run inside container")
            .required();
        ap.refer(&mut cmdargs)
            .add_argument("arguments", box List::<String>,
                "Arguments for the command");
        ap.refer(&mut no_wrapper)
            .add_option(["-N", "--no-wrapper"], box StoreTrue,
                "Do not use `command-wrapper` configured for container");
        ap.refer(&mut use_shell)
            .add_option(["-S", "--shell"], box StoreTrue,
                "Run command with `shell` configured for container");
        ap.refer(&mut use_shell)
            .add_option(["-S", "--shell"], box StoreTrue,
                "Run command with `shell` configured for container");
        ap.refer(&mut resolv)
            .add_option(["--no-resolv"], box StoreFalse,
                "Do not copy /etc/resolv.conf");
        ap.refer(&mut pid1mode)
            .add_option(["--wait"], box StoreConst(Pid1::Wait),
                "Spawn a supervisor as pid 1 and wait until target command \
                 exits. But if it spawned any children, they will be killed \
                 at the death of the target command (default)")
            .add_option(["--wait-all-children"],
                box StoreConst(Pid1::WaitAllChildren),
                "Spawn a supervisor as pid 1 and wait until target command \
                 and all of its children are dead. (Dangerous in some cases \
                 as some non useful child process may block container \
                 shutdown).")
            .add_option(["--exec"], box StoreConst(Pid1::Exec),
                "Execute the target command as pid 1. Should not be used \
                 unless the target command is a process manager itself and/or \
                 supposed to work as pid 1.");
        env_options(env, &mut ap);
        ap.stop_on_first_argument(true);
        match ap.parse(args, &mut stdout(), &mut stderr()) {
            Ok(()) => {}
            Err(0) => return Ok(0),
            Err(_) => return Ok(122),
        }
    }
    let mut container = try!(env.get_container(&cname));

    if command.is_none() {
        if container.default_command.is_some() {
            let cmd = container.default_command.as_ref().unwrap();
            cmdargs.push_all(cmd.as_slice());
        } else {
            return Err(format!("No command specified and no default command"));
        }
    } else {
        cmdargs.insert(0, command.unwrap());
        if use_shell {
            cmdargs = container.shell.clone() + cmdargs;
        } else if !no_wrapper && container.command_wrapper.is_some() {
            cmdargs = container.command_wrapper.as_ref().unwrap().clone()+cmdargs;
        }
    }

    let cmd = cmdargs.shift().unwrap();
    try!(ensure_container(env, &mut container));

    let mut monitor = Monitor::new(true);
    let pid = try!(internal_run(env, &container, pid1mode, resolv,
        &env.work_dir, cmd, cmdargs, TreeMap::new()));
    monitor.add("child".to_string(), pid);
    monitor.wait_all();
    return Ok(monitor.get_status());
}

pub fn internal_run(env: &Environ, container: &Container,
    pid1mode: Pid1::Pid1Mode, resolv: bool, work_dir: &Path,
    command: String, cmdargs: Vec<String>, runenv: TreeMap<String, String>)
    -> Result<pid_t, String>
{
    let mut runenv = runenv;
    env.populate_environ(&mut runenv);
    try!(container_environ(container, &mut runenv));
    let container_root = container.container_root.as_ref().unwrap();
    info!("Running {}: {} {}", container_root.display(), command, cmdargs);

    let mount_dir = env.project_root.join_many([".vagga", ".mnt"]);
    try!(ensure_dir(&mount_dir));

    if resolv {
        let etc = container_root.join("etc");
        if !etc.exists() {
            try!(mkdir(&etc, AllPermissions)
                .map_err(|e| format!("Error creating /etc: {}", e)));
        }
        try!(copy(&Path::new("/etc/resolv.conf"),
                  &etc.join("resolv.conf.tmp"))
            .map_err(|e| format!("Error copying resolv.conf: {}", e)));
        try!(rename(&etc.join("resolv.conf.tmp"),
                    &etc.join("resolv.conf"),)
            .map_err(|e| format!("Error copying resolv.conf: {}", e)));
    }

    let home = "HOME".to_string();
    if runenv.find(&home).is_none() {
        // TODO(tailhook) set home to real home if /home is mounted
        runenv.insert(home, "/homeless-shelter".to_string());
    }
    let term = "TERM".to_string();
    if runenv.find(&term).is_none() {
        runenv.insert(term, getenv("TERM").unwrap_or("linux".to_string()));
    }
    let path = "PATH".to_string();
    if runenv.find(&path).is_none() {
        runenv.insert(path, DEFAULT_PATH.to_string());
    }

    let pipe = try!(CPipe::new());
    let mut ropts: RunOptions = Default::default();
    ropts.pid1mode = pid1mode;
    let pid = try!(run_container(&pipe, env,
        container.container_root.as_ref().unwrap(),
        &ropts, work_dir, &command, cmdargs.as_slice(), &runenv));

    try!(write_uid_map(pid, &container.uids, &container.gids));

    try!(pipe.wakeup());
    return Ok(pid);
}
<|endoftext|>


<commit_before>trait StrParseExt {
    fn end_of_start_rest<F1, F2>(&self, is_first: F1, is_rest: F2) -> Option<usize>
        where F1: Fn(char) -> bool,
              F2: Fn(char) -> bool;
}

impl<'a> StrParseExt for &'a str {
    fn end_of_start_rest<F1, F2>(&self, is_first: F1, is_rest: F2) -> Option<usize>
        where F1: Fn(char) -> bool,
              F2: Fn(char) -> bool,
    {
        let mut positions = self.char_indices();

        match positions.next() {
            Some((_, c)) if is_first(c) => (),
            Some((_, _)) => return None,
            None => return None,
        };

        let mut positions = positions.skip_while(|&(_, c)| is_rest(c));
        match positions.next() {
            Some((offset, _)) => Some(offset),
            None => Some(self.len()),
        }
    }
}

pub trait XmlStr {
    /// Find the end of the quoted attribute value, not including the quote
    fn end_of_attribute(&self, quote: &str) -> Option<usize>;
    /// Find the end of the direct character data
    fn end_of_char_data(&self) -> Option<usize>;
    /// Find the end of the CData section, not including the ]]>
    fn end_of_cdata(&self) -> Option<usize>;
    /// Find the end of a run of decimal characters
    fn end_of_decimal_chars(&self) -> Option<usize>;
    /// Find the end of a run of hexidecimal characters
    fn end_of_hex_chars(&self) -> Option<usize>;
    /// Find the end of the comment, not including the -->
    fn end_of_comment(&self) -> Option<usize>;
    /// Find the end of the processing instruction, not including the ?>
    fn end_of_pi_value(&self) -> Option<usize>;
    /// Find the end of the [Name](http://www.w3.org/TR/xml/#NT-Name)
    fn end_of_name(&self) -> Option<usize>;
    /// Find the end of the [NCName](http://www.w3.org/TR/REC-xml-names/#NT-NCName)
    fn end_of_ncname(&self) -> Option<usize>;
    /// Find the end of a run of space characters
    fn end_of_space(&self) -> Option<usize>;
    /// Find the end of the starting tag
    fn end_of_start_tag(&self) -> Option<usize>;
    fn end_of_encoding(&self) -> Option<usize>;
}

impl<'a> XmlStr for &'a str {
    fn end_of_attribute(&self, quote: &str) -> Option<usize> {
        if self.len() == 0 ||
           self.starts_with('&') ||
           self.starts_with('<') ||
           self.starts_with(quote)
        {
            return None;
        }

        let quote_char = quote.chars().next().expect("Cant have null quote");

        self.find(&['&', '<', quote_char][..]).or(Some(self.len()))
    }

    fn end_of_char_data(&self) -> Option<usize> {
        if self.starts_with("<") ||
           self.starts_with("&") ||
           self.starts_with("]]>")
        {
            return None
        }

        let mut indices = self.char_indices();

        loop {
            let mut positions = indices.by_ref().skip_while(|&(_, c)| {
                c != '<' && c != '&' && c != ']'
            });

            match positions.next() {
                None => return Some(self.len()),
                Some((offset, c)) if c == '<' || c == '&' => return Some(offset),
                Some((offset, _)) => {
                    let tail = &self[offset..];
                    if tail.starts_with("]]>") {
                        return Some(offset)
                    } else {
                        // False alarm, resume scanning
                        continue;
                    }
                },
            }
        }
    }

    fn end_of_cdata(&self) -> Option<usize> {
        self.find("]]>")
    }

    fn end_of_decimal_chars(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_decimal_char(),
                               |c| c.is_decimal_char())
    }

    fn end_of_hex_chars(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_hex_char(),
                               |c| c.is_hex_char())
    }

    fn end_of_comment(&self) -> Option<usize> {
        // This deliberately does not include the >. -- is not allowed
        // in a comment, so we can just test the end if it matches the
        // complete close delimiter.
        self.find("--")
    }

    fn end_of_pi_value(&self) -> Option<usize> {
        self.find("?>")
    }

    fn end_of_name(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_name_start_char(), |c| c.is_name_char())
    }

    fn end_of_ncname(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_ncname_start_char(), |c| c.is_ncname_char())
    }

    fn end_of_space(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_space_char(), |c| c.is_space_char())
    }

    fn end_of_start_tag(&self) -> Option<usize> {
        let mut positions = self.char_indices();

        match positions.next() {
            Some((_, c)) if '<' == c => (),
            _ => return None,
        };

        match positions.next() {
            Some((offset, c)) =>
                match c {
                    '?' | '!' | '/' => None,
                    _ => Some(offset),
                },
            None => Some(self.len()),
        }
    }

    fn end_of_encoding(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_encoding_start_char(), |c| c.is_encoding_rest_char())
    }
}

/// Predicates used when parsing an characters in an XML document.
pub trait XmlChar {
    /// Is this a [NameStartChar](http://www.w3.org/TR/xml/#NT-NameStartChar)?
    fn is_name_start_char(self) -> bool;
    /// Is this a [NameChar](http://www.w3.org/TR/xml/#NT-NameChar)?
    fn is_name_char(self) -> bool;
    /// Does this start a [NCName](http://www.w3.org/TR/REC-xml-names/#NT-NCName)?
    fn is_ncname_start_char(self) -> bool;
    /// Is this a component of a [NCName](http://www.w3.org/TR/REC-xml-names/#NT-NCName)?
    fn is_ncname_char(self) -> bool;
    /// Is this an [XML space](http://www.w3.org/TR/xml/#NT-S)?
    fn is_space_char(self) -> bool;
    fn is_decimal_char(self) -> bool;
    fn is_hex_char(self) -> bool;
    fn is_encoding_start_char(self) -> bool;
    fn is_encoding_rest_char(self) -> bool;
}

impl XmlChar for char {
    fn is_name_start_char(self) -> bool {
        self == ':' || self.is_ncname_start_char()
    }

    fn is_name_char(self) -> bool {
        self.is_name_start_char() || self.is_ncname_char()
    }

    fn is_ncname_start_char(self) -> bool {
        match self {
            'A'...'Z'                   |
            '_'                         |
            'a'...'z'                   |
            '\u{0000C0}'...'\u{0000D6}' |
            '\u{0000D8}'...'\u{0000F6}' |
            '\u{0000F8}'...'\u{0002FF}' |
            '\u{000370}'...'\u{00037D}' |
            '\u{00037F}'...'\u{001FFF}' |
            '\u{00200C}'...'\u{00200D}' |
            '\u{002070}'...'\u{00218F}' |
            '\u{002C00}'...'\u{002FEF}' |
            '\u{003001}'...'\u{00D7FF}' |
            '\u{00F900}'...'\u{00FDCF}' |
            '\u{00FDF0}'...'\u{00FFFD}' |
            '\u{010000}'...'\u{0EFFFF}' => true,
            _ => false,
        }
    }

    fn is_ncname_char(self) -> bool {
        if self.is_ncname_start_char() { return true; }
        match self {
            '-'                     |
            '.'                     |
            '0'...'9'               |
            '\u{00B7}'              |
            '\u{0300}'...'\u{036F}' |
            '\u{203F}'...'\u{2040}' => true,
            _ => false
        }
    }

    fn is_space_char(self) -> bool {
        match self {
            '\x20' |
            '\x09' |
            '\x0D' |
            '\x0A' => true,
            _ => false,
        }
    }

    fn is_decimal_char(self) -> bool {
        match self {
            '0'...'9' => true,
            _ => false,
        }
    }

    fn is_hex_char(self) -> bool {
        match self {
            '0'...'9' |
            'a'...'f' |
            'A'...'F' => true,
            _ => false,
        }
    }

    fn is_encoding_start_char(self) -> bool {
        match self {
            'A'...'Z' |
            'a'...'z' => true,
            _ => false,
        }
    }

    fn is_encoding_rest_char(self) -> bool {
        match self {
            'A'...'Z' |
            'a'...'z' |
            '0'...'9' |
            '.' |
            '_' |
            '-' => true,
            _ => false,
        }
    }

}

#[cfg(test)]
mod test {
    use super::XmlStr;

    #[test]
    fn end_of_char_data_leading_ampersand() {
        assert_eq!("&".end_of_char_data(), None);
    }

    #[test]
    fn end_of_char_data_leading_less_than() {
        assert_eq!("<".end_of_char_data(), None);
    }

    #[test]
    fn end_of_char_data_leading_cdata_end() {
        assert_eq!("]]>".end_of_char_data(), None);
    }

    #[test]
    fn end_of_char_data_until_ampersand() {
        assert_eq!("hello&world".end_of_char_data(), Some("hello".len()));
    }

    #[test]
    fn end_of_char_data_until_less_than() {
        assert_eq!("hello<world".end_of_char_data(), Some("hello".len()));
    }

    #[test]
    fn end_of_char_data_until_cdata_end() {
        assert_eq!("hello]]>world".end_of_char_data(), Some("hello".len()));
    }

    #[test]
    fn end_of_char_data_includes_right_square() {
        assert_eq!("hello]world".end_of_char_data(), Some("hello]world".len()));
    }

    #[test]
    fn end_of_char_data_includes_multiple_right_squares() {
        assert_eq!("hello]]world".end_of_char_data(), Some("hello]]world".len()));
    }
}
<commit_msg>Optimize finding the end of character data<commit_after>trait StrParseExt {
    fn end_of_start_rest<F1, F2>(&self, is_first: F1, is_rest: F2) -> Option<usize>
        where F1: Fn(char) -> bool,
              F2: Fn(char) -> bool;
}

impl<'a> StrParseExt for &'a str {
    fn end_of_start_rest<F1, F2>(&self, is_first: F1, is_rest: F2) -> Option<usize>
        where F1: Fn(char) -> bool,
              F2: Fn(char) -> bool,
    {
        let mut positions = self.char_indices();

        match positions.next() {
            Some((_, c)) if is_first(c) => (),
            Some((_, _)) => return None,
            None => return None,
        };

        let mut positions = positions.skip_while(|&(_, c)| is_rest(c));
        match positions.next() {
            Some((offset, _)) => Some(offset),
            None => Some(self.len()),
        }
    }
}

pub trait XmlStr {
    /// Find the end of the quoted attribute value, not including the quote
    fn end_of_attribute(&self, quote: &str) -> Option<usize>;
    /// Find the end of the direct character data
    fn end_of_char_data(&self) -> Option<usize>;
    /// Find the end of the CData section, not including the ]]>
    fn end_of_cdata(&self) -> Option<usize>;
    /// Find the end of a run of decimal characters
    fn end_of_decimal_chars(&self) -> Option<usize>;
    /// Find the end of a run of hexidecimal characters
    fn end_of_hex_chars(&self) -> Option<usize>;
    /// Find the end of the comment, not including the -->
    fn end_of_comment(&self) -> Option<usize>;
    /// Find the end of the processing instruction, not including the ?>
    fn end_of_pi_value(&self) -> Option<usize>;
    /// Find the end of the [Name](http://www.w3.org/TR/xml/#NT-Name)
    fn end_of_name(&self) -> Option<usize>;
    /// Find the end of the [NCName](http://www.w3.org/TR/REC-xml-names/#NT-NCName)
    fn end_of_ncname(&self) -> Option<usize>;
    /// Find the end of a run of space characters
    fn end_of_space(&self) -> Option<usize>;
    /// Find the end of the starting tag
    fn end_of_start_tag(&self) -> Option<usize>;
    fn end_of_encoding(&self) -> Option<usize>;
}

impl<'a> XmlStr for &'a str {
    fn end_of_attribute(&self, quote: &str) -> Option<usize> {
        if self.len() == 0 ||
           self.starts_with('&') ||
           self.starts_with('<') ||
           self.starts_with(quote)
        {
            return None;
        }

        let quote_char = quote.chars().next().expect("Cant have null quote");

        self.find(&['&', '<', quote_char][..]).or(Some(self.len()))
    }

    fn end_of_char_data(&self) -> Option<usize> {
        fn find_end_of_char_data(bytes: &[u8]) -> Option<usize> {
            for (i, &b) in bytes.iter().enumerate() {
                if b == b'<' || b == b'&' { return Some(i) }

                if b == b']' && bytes[i..].starts_with(b"]]>") {
                    return Some(i)
                }
            }
            None
        }

        match find_end_of_char_data(self.as_bytes()) {
            Some(0) => None,
            Some(v) => Some(v),
            None => Some(self.len()),
        }
    }

    fn end_of_cdata(&self) -> Option<usize> {
        self.find("]]>")
    }

    fn end_of_decimal_chars(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_decimal_char(),
                               |c| c.is_decimal_char())
    }

    fn end_of_hex_chars(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_hex_char(),
                               |c| c.is_hex_char())
    }

    fn end_of_comment(&self) -> Option<usize> {
        // This deliberately does not include the >. -- is not allowed
        // in a comment, so we can just test the end if it matches the
        // complete close delimiter.
        self.find("--")
    }

    fn end_of_pi_value(&self) -> Option<usize> {
        self.find("?>")
    }

    fn end_of_name(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_name_start_char(), |c| c.is_name_char())
    }

    fn end_of_ncname(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_ncname_start_char(), |c| c.is_ncname_char())
    }

    fn end_of_space(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_space_char(), |c| c.is_space_char())
    }

    fn end_of_start_tag(&self) -> Option<usize> {
        let mut positions = self.char_indices();

        match positions.next() {
            Some((_, c)) if '<' == c => (),
            _ => return None,
        };

        match positions.next() {
            Some((offset, c)) =>
                match c {
                    '?' | '!' | '/' => None,
                    _ => Some(offset),
                },
            None => Some(self.len()),
        }
    }

    fn end_of_encoding(&self) -> Option<usize> {
        self.end_of_start_rest(|c| c.is_encoding_start_char(), |c| c.is_encoding_rest_char())
    }
}

/// Predicates used when parsing an characters in an XML document.
pub trait XmlChar {
    /// Is this a [NameStartChar](http://www.w3.org/TR/xml/#NT-NameStartChar)?
    fn is_name_start_char(self) -> bool;
    /// Is this a [NameChar](http://www.w3.org/TR/xml/#NT-NameChar)?
    fn is_name_char(self) -> bool;
    /// Does this start a [NCName](http://www.w3.org/TR/REC-xml-names/#NT-NCName)?
    fn is_ncname_start_char(self) -> bool;
    /// Is this a component of a [NCName](http://www.w3.org/TR/REC-xml-names/#NT-NCName)?
    fn is_ncname_char(self) -> bool;
    /// Is this an [XML space](http://www.w3.org/TR/xml/#NT-S)?
    fn is_space_char(self) -> bool;
    fn is_decimal_char(self) -> bool;
    fn is_hex_char(self) -> bool;
    fn is_encoding_start_char(self) -> bool;
    fn is_encoding_rest_char(self) -> bool;
}

impl XmlChar for char {
    fn is_name_start_char(self) -> bool {
        self == ':' || self.is_ncname_start_char()
    }

    fn is_name_char(self) -> bool {
        self.is_name_start_char() || self.is_ncname_char()
    }

    fn is_ncname_start_char(self) -> bool {
        match self {
            'A'...'Z'                   |
            '_'                         |
            'a'...'z'                   |
            '\u{0000C0}'...'\u{0000D6}' |
            '\u{0000D8}'...'\u{0000F6}' |
            '\u{0000F8}'...'\u{0002FF}' |
            '\u{000370}'...'\u{00037D}' |
            '\u{00037F}'...'\u{001FFF}' |
            '\u{00200C}'...'\u{00200D}' |
            '\u{002070}'...'\u{00218F}' |
            '\u{002C00}'...'\u{002FEF}' |
            '\u{003001}'...'\u{00D7FF}' |
            '\u{00F900}'...'\u{00FDCF}' |
            '\u{00FDF0}'...'\u{00FFFD}' |
            '\u{010000}'...'\u{0EFFFF}' => true,
            _ => false,
        }
    }

    fn is_ncname_char(self) -> bool {
        if self.is_ncname_start_char() { return true; }
        match self {
            '-'                     |
            '.'                     |
            '0'...'9'               |
            '\u{00B7}'              |
            '\u{0300}'...'\u{036F}' |
            '\u{203F}'...'\u{2040}' => true,
            _ => false
        }
    }

    fn is_space_char(self) -> bool {
        match self {
            '\x20' |
            '\x09' |
            '\x0D' |
            '\x0A' => true,
            _ => false,
        }
    }

    fn is_decimal_char(self) -> bool {
        match self {
            '0'...'9' => true,
            _ => false,
        }
    }

    fn is_hex_char(self) -> bool {
        match self {
            '0'...'9' |
            'a'...'f' |
            'A'...'F' => true,
            _ => false,
        }
    }

    fn is_encoding_start_char(self) -> bool {
        match self {
            'A'...'Z' |
            'a'...'z' => true,
            _ => false,
        }
    }

    fn is_encoding_rest_char(self) -> bool {
        match self {
            'A'...'Z' |
            'a'...'z' |
            '0'...'9' |
            '.' |
            '_' |
            '-' => true,
            _ => false,
        }
    }

}

#[cfg(test)]
mod test {
    use super::XmlStr;

    #[test]
    fn end_of_char_data_leading_ampersand() {
        assert_eq!("&".end_of_char_data(), None);
    }

    #[test]
    fn end_of_char_data_leading_less_than() {
        assert_eq!("<".end_of_char_data(), None);
    }

    #[test]
    fn end_of_char_data_leading_cdata_end() {
        assert_eq!("]]>".end_of_char_data(), None);
    }

    #[test]
    fn end_of_char_data_until_ampersand() {
        assert_eq!("hello&world".end_of_char_data(), Some("hello".len()));
    }

    #[test]
    fn end_of_char_data_until_less_than() {
        assert_eq!("hello<world".end_of_char_data(), Some("hello".len()));
    }

    #[test]
    fn end_of_char_data_until_cdata_end() {
        assert_eq!("hello]]>world".end_of_char_data(), Some("hello".len()));
    }

    #[test]
    fn end_of_char_data_includes_right_square() {
        assert_eq!("hello]world".end_of_char_data(), Some("hello]world".len()));
    }

    #[test]
    fn end_of_char_data_includes_multiple_right_squares() {
        assert_eq!("hello]]world".end_of_char_data(), Some("hello]]world".len()));
    }
}
<|endoftext|>


<commit_before>#![allow(non_upper_case_globals)]
#![deny(missing_docs)]
// Copyright 2015 Adrien Champion. See the COPYRIGHT file at the top-level
// directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Transition system library.
//!
//! ## Hash consing
//!
//! [`Sym`][sym type] and [`Term`][term type] come from the kinō
//! [`term`][term crate] crate and are hash consed. Remember that one should
//! **never** create more that one [Factory][factory struct] for these. It is
//! thread-safe and can be cloned.
//! [`Context`][context struct] holds one for parsing.
//!
//! ## To do
//!
//! * more clever input consumption in [`Context`][context struct]
//! * less copy in [`Context`][context struct]
//! * more informative parse error (depency checking)
//! * integrate type checking in parser
//!
//! [sym type]: ../term/type.Sym.html (Sym type)
//! [term type]: ../term/type.Term.html (Term type)
//! [term crate]: ../term/index.html (term crate)
//! [factory struct]: ../term/struct.Factory.html (Factory struct)
//! [context struct]: ctxt/struct.Context.html (Context struct)

#[macro_use]
extern crate nom ;
#[macro_use]
extern crate error_chain ;
#[macro_use]
extern crate term ;

use std::sync::Arc ;

/// Errors that can happen during command parsing.
pub mod parse_errors {
  use term::parsing::Spn ;
  error_chain!{
    types {
      Error, ErrorKind, Res, ResExt ;
    }
    errors {
      #[doc = "Legacy error, to remove after transition to spanned things."]
      OldError(e: ::parse::check::Error) {
        description("legacy error")
        display("legacy error: {}", e)
      }
      #[doc = "Parsing error."]
      ParseError(
        span: Spn, blah: String,
        notes: Vec< (Spn, String) >
      ) {
        description("parse error")
        display("parse error `{}`: {}", span, blah)
      }
      #[doc = "IO Error."]
      IoError(e: ::std::io::Error) {
        description("IO error")
        display("IO error: {:?}", e)
      }
    }
  }
}

mod base ;
mod type_check ;
mod parse ;

/// Real types of the elements of a context.
pub mod real_sys {
  pub use base::{
    Sig, Args, Uf, Fun, Prop, Sys, Callable
  } ;
}

/// Parses, type checks and remembers the input.
pub mod ctxt {
  pub use super::base::Callable ;
  pub use super::parse::{
    Res, Context
  } ;
  pub use super::parse::check::Error ;
  pub use type_check::type_check ;
}

pub use base::{ CallSet, PropStatus } ;

pub use parse::Cex ;

/// A signature, a list of types. Used only in `Uf`.
pub type Sig = Arc<base::Sig> ;
/// A list of typed formal parameters.
pub type Args = Arc<base::Args> ;
/// An uninterpreted function.
pub type Uf = Arc<base::Uf> ;
/// A function (actually a macro in SMT-LIB).
pub type Fun = Arc<base::Fun> ;
/// Wraps an (uninterpreted) function.
pub type Callable = Arc<base::Callable> ;
/// A property.
pub type Prop = Arc<base::Prop> ;
/// A transition system.
pub type Sys = Arc<base::Sys> ;

<commit_msg>Updated system's doc<commit_after>#![allow(non_upper_case_globals)]
#![deny(missing_docs)]
// Copyright 2015 Adrien Champion. See the COPYRIGHT file at the top-level
// directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

/*! Transition system library.

## Hash consing

[`Sym`][sym type] and [`Term`][term type] come from the kinō
[`term`][term crate] crate and are hash consed. Remember that one should
**never** create more that one [Factory][factory struct] for these. It is
thread-safe and can be cloned.

[`Context`][context struct] holds one for parsing.

## Thread-safety

All constructions related to systems (`Sys`, `Prop`, `Args`, *etc.*) are
wrapped in an `Arc` and thus are thread-safe.

## To do

* more clever input consumption in [`Context`][context struct]
* less copy in [`Context`][context struct]
* more informative parse error (depency checking)
* spanned parse errors
* integrate type checking in parser
* support for `stdin`-like input

[sym type]: ../term/type.Sym.html (Sym type)
[term type]: ../term/type.Term.html (Term type)
[term crate]: ../term/index.html (term crate)
[factory struct]: ../term/struct.Factory.html (Factory struct)
[context struct]: ctxt/struct.Context.html (Context struct)
*/

#[macro_use]
extern crate nom ;
#[macro_use]
extern crate error_chain ;
#[macro_use]
extern crate term ;

use std::sync::Arc ;

/// Errors that can happen during command parsing.
pub mod parse_errors {
  use term::parsing::Spn ;
  error_chain!{
    types {
      Error, ErrorKind, Res, ResExt ;
    }
    errors {
      #[doc = "Legacy error, to remove after transition to spanned things."]
      OldError(e: ::parse::check::Error) {
        description("legacy error")
        display("legacy error: {}", e)
      }
      #[doc = "Parsing error."]
      ParseError(
        span: Spn, blah: String,
        notes: Vec< (Spn, String) >
      ) {
        description("parse error")
        display("parse error `{}`: {}", span, blah)
      }
      #[doc = "IO Error."]
      IoError(e: ::std::io::Error) {
        description("IO error")
        display("IO error: {:?}", e)
      }
    }
  }
}

mod base ;
mod type_check ;
mod parse ;

/// Real types of the elements of a context.
pub mod real_sys {
  pub use base::{
    Sig, Args, Uf, Fun, Prop, Sys, Callable
  } ;
}

/// Parses, type checks and remembers the input.
pub mod ctxt {
  pub use super::base::Callable ;
  pub use super::parse::{
    Res, Context
  } ;
  pub use super::parse::check::Error ;
  pub use type_check::type_check ;
}

pub use base::{ CallSet, PropStatus } ;

pub use parse::Cex ;

/// A signature, a list of types. Used only in `Uf`.
pub type Sig = Arc<base::Sig> ;
/// A list of typed formal parameters.
pub type Args = Arc<base::Args> ;
/// An uninterpreted function.
pub type Uf = Arc<base::Uf> ;
/// A function (actually a macro in SMT-LIB).
pub type Fun = Arc<base::Fun> ;
/// Wraps an (uninterpreted) function.
pub type Callable = Arc<base::Callable> ;
/// A property.
pub type Prop = Arc<base::Prop> ;
/// A transition system.
pub type Sys = Arc<base::Sys> ;

<|endoftext|>


<commit_before>extern crate std;

use std::io::{self, Read, Write, Seek, SeekFrom};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::collections::HashMap;

use audiotag::{AudioTag, TagResult};

use id3v1;
use id3v2;
use id3v2::frame::{Id, Frame, Encoding, PictureType};
use util;

static DEFAULT_FILE_DISCARD: [&'static str; 11] = [
    "AENC", "ETCO", "EQUA", "MLLT", "POSS",
    "SYLT", "SYTC", "RVAD", "TENC", "TLEN", "TSIZ"
];
static PADDING_BYTES: u32 = 2048;

/// Represents a file on disk which may have an ID3v1 and/or ID3v2 tag in the standard locations.
pub struct FileTags {
    /// The ID3v1 tag (combined with ID3v1.1 and Extended ID3v1 data) stored in the file, if any.
    pub v1: Option<id3v1::Tag>,
    /// The ID3v2 tag stored at the file's start, if any. Does not describe tags which start midway through the file, as in streams.
    pub v2: Option<id3v2::Tag>,
    /// The path, if any, that this file was loaded from.
    pub path: Option<PathBuf>,
    /// Indicates if the path that we are writing to is not the same as the path we read from.
    path_changed: bool,
    /// Indicates if when writing, an ID3v1 tag should be removed.
    remove_v1: bool
}

impl FileTags
{
    /// Create a FileTags structure from pre-parsed tags
    pub fn from_tags(v1: Option<id3v1::Tag>, v2: Option<id3v2::Tag>) -> FileTags
    {
        FileTags {v1: v1, v2: v2, path: None, path_changed: false, remove_v1: false}
    }
    pub fn from_seekable<R: Read+Seek>(reader: &mut R) -> io::Result<FileTags> {
        let v2 = id3v2::read_tag(reader).ok();
        drop(reader.seek(SeekFrom::End(-id3v1::TAG_OFFSET)));
        let mut v1=id3v1::read_tag(reader).ok();
        if let Some(ref mut v1) = v1 {
            drop(reader.seek(SeekFrom::End(-id3v1::TAGPLUS_OFFSET)));
            id3v1::read_xtag(reader, v1);
        }
        Ok(FileTags {v1: v1, v2: v2, path: None, path_changed: false, remove_v1: false, })
    }
}

impl FileTags {
    fn is_candidate(reader: &mut Read, _: Option<FileTags>) -> bool {
        let mut identifier = [0u8; 3];
        drop(reader.read(&mut identifier));
        identifier == *b"ID3"
    }

    fn read_from<'a>(reader: &'a mut Read) -> TagResult<FileTags> {
        //let v2 = id3v2::read_tag(reader).ok();
        //let v1 = id3v1::read_tag(reader).ok();
        //TODO(sp3d): implement this
        Ok(FileTags {v1: None, v2: None, path: None, path_changed: false, remove_v1: false, })
    }

    fn write_to(&mut self, writer: &mut Write) -> TagResult<()> {
        // remove frames which have the flags indicating they should be removed
        match self.v2 {
            Some(ref mut id3v2) => {
                id3v2.frames.retain(|frame| {
                    !(frame.offset != 0
                      && (frame.tag_alter_preservation()
                          || (frame.file_alter_preservation()
                                  || DEFAULT_FILE_DISCARD.contains(&std::str::from_utf8(frame.id.name()).ok().unwrap()))))
                });

                let mut data_cache: HashMap<*const Frame, Vec<u8>> = HashMap::new();
                let mut size = 0;

                for frame in id3v2.frames.iter() {
                    let mut frame_writer = Vec::new();
                    size += try!(frame.write_to(&mut frame_writer));
                    data_cache.insert(frame as *const _, frame_writer);
                }

                id3v2.size = size + PADDING_BYTES;

                try!(writer.write(b"ID3"));
                try!(writer.write(&id3v2.version.to_bytes()));
                try!(writer.write(&[id3v2.flags.to_byte()]));
                try!(writer.write(&util::u32_to_bytes(u32::to_be(util::synchsafe(id3v2.size)))));

                let mut bytes_written = 10;

                for frame in id3v2.frames.iter_mut() {
                    debug!("writing {:?}", frame.id);

                    frame.offset = bytes_written;

                    bytes_written += match data_cache.get(&(frame as *mut _ as *const _)) {
                        Some(data) => {
                            try!(writer.write(&*data));
                            data.len() as u32
                        },
                        None => try!(frame.write_to(writer))
                    }
                }

                id3v2.offset = bytes_written;
                id3v2.modified_offset = id3v2.offset;

                // write padding
                try!(writer.write(&*vec![0; PADDING_BYTES as usize]));
            },
            None => (),
        }
        Ok(())
    }

    fn read_from_path(path: &Path) -> TagResult<FileTags> {
        let mut file = try!(File::open(path));
        let mut tag = try!(FileTags::read_from(&mut file));
        tag.path=Some(path.to_owned());
        Ok(tag)
    }
}
// }}}

// Tests {{{
#[cfg(test)]
mod tests {
    use id3v2::TagFlags;
    use id3v2::TagFlag::*;
    use id3v2::Version::*;

    #[test]
    fn test_flags_to_bytes() {
        let mut flags = TagFlags::new(V4);
        assert_eq!(flags.to_byte(), 0x0);
        flags.set(Unsynchronization, true);
        flags.set(ExtendedHeader, true);
        flags.set(Experimental, true);
        flags.set(Footer, true);
        assert_eq!(flags.to_byte(), 0xF0);
    }
}
// }}}
<commit_msg>remove unused `remove_v1` field from FileTags<commit_after>extern crate std;

use std::io::{self, Read, Write, Seek, SeekFrom};
use std::fs::File;
use std::path::{Path, PathBuf};
use std::collections::HashMap;

use audiotag::{AudioTag, TagResult};

use id3v1;
use id3v2;
use id3v2::frame::{Id, Frame, Encoding, PictureType};
use util;

static DEFAULT_FILE_DISCARD: [&'static str; 11] = [
    "AENC", "ETCO", "EQUA", "MLLT", "POSS",
    "SYLT", "SYTC", "RVAD", "TENC", "TLEN", "TSIZ"
];
static PADDING_BYTES: u32 = 2048;

/// Represents a file on disk which may have an ID3v1 and/or ID3v2 tag in the standard locations.
pub struct FileTags {
    /// The ID3v1 tag (combined with ID3v1.1 and Extended ID3v1 data) stored in the file, if any.
    pub v1: Option<id3v1::Tag>,
    /// The ID3v2 tag stored at the file's start, if any. Does not describe tags which start midway through the file, as in streams.
    pub v2: Option<id3v2::Tag>,
    /// The path, if any, that this file was loaded from.
    pub path: Option<PathBuf>,
    /// Indicates if the path that we are writing to is not the same as the path we read from.
    path_changed: bool,
}

impl FileTags
{
    /// Create a FileTags structure from pre-parsed tags
    pub fn from_tags(v1: Option<id3v1::Tag>, v2: Option<id3v2::Tag>) -> FileTags
    {
        FileTags {v1: v1, v2: v2, path: None, path_changed: false}
    }
    pub fn from_seekable<R: Read+Seek>(reader: &mut R) -> io::Result<FileTags> {
        let v2 = id3v2::read_tag(reader).ok();
        drop(reader.seek(SeekFrom::End(-id3v1::TAG_OFFSET)));
        let mut v1=id3v1::read_tag(reader).ok();
        if let Some(ref mut v1) = v1 {
            drop(reader.seek(SeekFrom::End(-id3v1::TAGPLUS_OFFSET)));
            id3v1::read_xtag(reader, v1);
        }
        Ok(FileTags {v1: v1, v2: v2, path: None, path_changed: false})
    }
}

impl FileTags {
    fn is_candidate(reader: &mut Read, _: Option<FileTags>) -> bool {
        let mut identifier = [0u8; 3];
        drop(reader.read(&mut identifier));
        identifier == *b"ID3"
    }

    fn read_from<'a>(reader: &'a mut Read) -> TagResult<FileTags> {
        //let v2 = id3v2::read_tag(reader).ok();
        //let v1 = id3v1::read_tag(reader).ok();
        //TODO(sp3d): implement this
        Ok(FileTags {v1: None, v2: None, path: None, path_changed: false})
    }

    fn write_to(&mut self, writer: &mut Write) -> TagResult<()> {
        // remove frames which have the flags indicating they should be removed
        match self.v2 {
            Some(ref mut id3v2) => {
                id3v2.frames.retain(|frame| {
                    !(frame.offset != 0
                      && (frame.tag_alter_preservation()
                          || (frame.file_alter_preservation()
                                  || DEFAULT_FILE_DISCARD.contains(&std::str::from_utf8(frame.id.name()).ok().unwrap()))))
                });

                let mut data_cache: HashMap<*const Frame, Vec<u8>> = HashMap::new();
                let mut size = 0;

                for frame in id3v2.frames.iter() {
                    let mut frame_writer = Vec::new();
                    size += try!(frame.write_to(&mut frame_writer));
                    data_cache.insert(frame as *const _, frame_writer);
                }

                id3v2.size = size + PADDING_BYTES;

                try!(writer.write(b"ID3"));
                try!(writer.write(&id3v2.version.to_bytes()));
                try!(writer.write(&[id3v2.flags.to_byte()]));
                try!(writer.write(&util::u32_to_bytes(u32::to_be(util::synchsafe(id3v2.size)))));

                let mut bytes_written = 10;

                for frame in id3v2.frames.iter_mut() {
                    debug!("writing {:?}", frame.id);

                    frame.offset = bytes_written;

                    bytes_written += match data_cache.get(&(frame as *mut _ as *const _)) {
                        Some(data) => {
                            try!(writer.write(&*data));
                            data.len() as u32
                        },
                        None => try!(frame.write_to(writer))
                    }
                }

                id3v2.offset = bytes_written;
                id3v2.modified_offset = id3v2.offset;

                // write padding
                try!(writer.write(&*vec![0; PADDING_BYTES as usize]));
            },
            None => (),
        }
        Ok(())
    }

    fn read_from_path(path: &Path) -> TagResult<FileTags> {
        let mut file = try!(File::open(path));
        let mut tag = try!(FileTags::read_from(&mut file));
        tag.path=Some(path.to_owned());
        Ok(tag)
    }
}
// }}}

// Tests {{{
#[cfg(test)]
mod tests {
    use id3v2::TagFlags;
    use id3v2::TagFlag::*;
    use id3v2::Version::*;

    #[test]
    fn test_flags_to_bytes() {
        let mut flags = TagFlags::new(V4);
        assert_eq!(flags.to_byte(), 0x0);
        flags.set(Unsynchronization, true);
        flags.set(ExtendedHeader, true);
        flags.set(Experimental, true);
        flags.set(Footer, true);
        assert_eq!(flags.to_byte(), 0xF0);
    }
}
// }}}
<|endoftext|>


<commit_before>/// DrawObjectList interns shaders, brushes, and scripts, returning references that can be stored
/// in the event queue.
/// TODO: scripts
/// TODO: serialization
/// TODO: further backing store, caching init objs by sha1 or so
/// TODO: free shaders + textures on gl pause
/// TODO: cleanup, deduplication
///
/// more importantly, switch to a map and return keys, rather than using indices

use core::prelude::*;
use core::mem;
use core::fmt::Show;
use collections::vec::Vec;
use collections::hash::Hash;
use collections::hash::sip::{SipHasher, SipState};
use std::collections::HashMap;
use copyshader::CopyShader;
use gltexture::{PixelFormat, Texture, BrushTexture};
use pointshader::PointShader;
use glcommon::Shader;
use luascript::LuaScript;
use arena::TypedArena;
use glcommon::GLResult;
use glcommon::{FillDefaults, MString};
use log::logi;

/// Holds GL objects that can be inited using the given keys.
/// The list is to avoid having to pass those keys around, and serialize more easily.
/// The arena doesn't relocate its entries, so we can pass back longer-lived pointers,
/// even if it needs some encouragement to do so.
/// There ought to be a better way.
pub struct DrawObjectList<'a, T: 'a, Init: Eq+Hash+'a> {
    map: HashMap<HashReference<'a, Init>, DrawObjectIndex<T>, SipHasher>,
    list: Vec<&'a T>,
    arena: TypedArena<T>,
}

#[deriving(Show)]
pub struct DrawObjectIndex<T>(i32);

impl<T> DrawObjectIndex<T> {
    pub fn error() -> DrawObjectIndex<T> {
        unsafe { mem::transmute(-1i32) }
    }
}

pub type ShaderInitValues = (MString, MString);
pub type BrushInitValues = (PixelFormat, (i32, i32), Vec<u8>);
pub type LuaInitValues = MString;
//pub type ShaderKeyValues = &(String, String);
//pub type BrushKeyValues = (PixelFormat, (i32, i32), Vec<u8>>);
//pub type LuaKeyValues = &String;
pub type ShaderUnfilledValues = (Option<MString>, Option<MString>);
pub type BrushUnfilledValues = BrushInitValues;
pub type LuaUnfilledValues = Option<MString>;
pub type PointShaderIndex = DrawObjectIndex<PointShader>;
pub type CopyShaderIndex = DrawObjectIndex<CopyShader>;
pub type BrushIndex = DrawObjectIndex<Texture>;
pub type LuaIndex = DrawObjectIndex<LuaScript>;

pub trait InitFromCache<Init> {
    fn init(Init) -> Self;
}
pub trait MaybeInitFromCache<Init> {
    fn maybe_init(Init) -> GLResult<Self>;
    fn get_source(&self) -> &Init;
}

// this and the two shader impls were originally a single
// impl<T: Shader> InitFromCache<ShaderInitValues> for Option<T>
// but that counts as the impl for all of Option, not just Option<Shader>
fn _init_copy_shader<T: Shader>(value: (MString, MString)) -> GLResult<T> {
    let (vert, frag) = value;
    Shader::new(vert, frag)
}
impl MaybeInitFromCache<ShaderInitValues> for CopyShader {
    fn maybe_init(value: (MString, MString)) -> GLResult<CopyShader> { _init_copy_shader(value) }
    fn get_source(&self) -> &(MString, MString) { &self.source }
}
impl MaybeInitFromCache<ShaderInitValues> for PointShader {
    fn maybe_init(value: (MString, MString)) -> GLResult<PointShader> { _init_copy_shader(value) }
    fn get_source(&self) -> &(MString, MString) { &self.source }
}
// TODO: use this as the impl for all InitFromCache<Init>
impl MaybeInitFromCache<BrushInitValues> for BrushTexture {
    fn maybe_init(value: BrushInitValues) -> GLResult<BrushTexture> {
        Ok(InitFromCache::init(value))
    }
    fn get_source(&self) -> &BrushInitValues { &self.source }
}

pub fn init_from_defaults<Unfilled, T: MaybeInitFromCache<Init> + FillDefaults<Unfilled, Init, T>, Init>(init: Unfilled) -> GLResult<T> {
    let filled = FillDefaults::fill_defaults(init).val;
    MaybeInitFromCache::<Init>::maybe_init(filled)
}

// FIXME maybe a BrushTexture wrapper?
impl InitFromCache<BrushInitValues> for BrushTexture {
    fn init(value: BrushInitValues) -> BrushTexture {
        let tex = {
            let (format, (w, h), ref pixels) = value;
            Texture::with_image(w, h, Some(pixels.as_slice()), format)
        };
        BrushTexture { texture: tex, source: value }
    }
}

impl MaybeInitFromCache<LuaInitValues> for LuaScript {
    fn maybe_init(value: MString) -> GLResult<LuaScript> {
        LuaScript::new(value)
    }
    fn get_source(&self) -> &MString { &self.source }
}

impl<'a, Unfilled, T: MaybeInitFromCache<Init> + FillDefaults<Unfilled, Init, T>, Init: Hash+Eq+Show> DrawObjectList<'a, T, Init> {
    pub fn new() -> DrawObjectList<'a, T, Init> {
        // the default hasher is keyed off of the task-local rng,
        // which would blow up since we don't have a task
        //let mut rng = rand::weak_rng();
        //let hasher = SipHasher::new_with_keys(rng.next_u64(), rng.next_u64());
        // FIXME weak_rng also blows up? can it not find /dev/urandom?
        let hasher = SipHasher::new_with_keys(0, 0);
        let map = HashMap::with_hasher(hasher);
        DrawObjectList {
            map: map,
            list: Vec::new(),
            arena: TypedArena::new(),
        }
    }

    pub fn push_object(&mut self, init: Unfilled) -> GLResult<DrawObjectIndex<T>> {
        // Can't use map.entry() here as it consumes the key
        let filled = FillDefaults::fill_defaults(init).val;
        let filledref = HashReference { v: unsafe { mem::transmute(&filled) } };
        // see below -- these are safe, we just can't prove it
        if self.map.contains_key(&filledref) {
            Ok(*self.map.get(&filledref).unwrap())
        } else {
            let inited: T = try!(MaybeInitFromCache::<Init>::maybe_init(filled));
            let key = HashReference { v: unsafe { mem::transmute(inited.get_source()) } };
            // ptr's lifetime is limited to &self's, which is fair but not very useful.
            // smart ptrs involve individual allocs but are probably better
            let ptr = self.arena.alloc(inited);
            unsafe {
                self.list.push(mem::transmute(ptr));
            }
            let index = self.list.len() - 1;
            let objindex = DrawObjectIndex(index as i32);
            self.map.insert(key, objindex);
            Ok(objindex)
        }
    }

    pub fn get_object(&self, i: DrawObjectIndex<T>) -> &'a T {
        let DrawObjectIndex(idx) = i;
        self.list[idx as uint]
    }
}

impl<'a, Unfilled, T: InitFromCache<Init> + MaybeInitFromCache<Init> + FillDefaults<Unfilled, Init, T>, Init: Hash+Eq+Show> DrawObjectList<'a, T, Init> {
    pub fn safe_push_object(&mut self, init: Unfilled) -> DrawObjectIndex<T> {
        self.push_object(init).unwrap()
    }
}

struct HashReference<'a, T: Hash+PartialEq+'a> {
    v: &'a T
}

impl<'a, T: Hash+PartialEq+Show> Hash for HashReference<'a, T> {
    fn hash(&self, state: &mut SipState) {
        logi!("hashing '{}'", self.v);
        self.v.hash(state);
        logi!("got '{}'", state.result());
    }
}

impl<'a, T: Hash+PartialEq+Show> PartialEq for HashReference<'a, T> {
    fn eq(&self, other: &HashReference<'a, T>) -> bool {
        let iseq = self.v.eq(other.v);
        logi!("'{}' {} '{}'", self.v, if iseq {"=="} else {"!="}, other.v);
        iseq
    }
}

impl<'a, T: Hash+Eq+Show> Eq for HashReference<'a, T> { }

<commit_msg>use map.entry because that works and is usable now<commit_after>/// DrawObjectList interns shaders, brushes, and scripts, returning references that can be stored
/// in the event queue.
/// TODO: scripts
/// TODO: serialization
/// TODO: further backing store, caching init objs by sha1 or so
/// TODO: free shaders + textures on gl pause
/// TODO: cleanup, deduplication
///
/// more importantly, switch to a map and return keys, rather than using indices

use core::prelude::*;
use core::mem;
use core::fmt::Show;
use collections::vec::Vec;
use collections::hash::Hash;
use collections::hash::sip::{SipHasher, SipState};
use std::collections::HashMap;
use std::collections::hash_map::{Vacant, Occupied};
use copyshader::CopyShader;
use gltexture::{PixelFormat, Texture, BrushTexture};
use pointshader::PointShader;
use glcommon::Shader;
use luascript::LuaScript;
use arena::TypedArena;
use glcommon::GLResult;
use glcommon::{FillDefaults, MString};
use log::logi;

/// Holds GL objects that can be inited using the given keys.
/// The list is to avoid having to pass those keys around, and serialize more easily.
/// The arena doesn't relocate its entries, so we can pass back longer-lived pointers,
/// even if it needs some encouragement to do so.
/// There ought to be a better way.
pub struct DrawObjectList<'a, T: 'a, Init: Eq+Hash+'a> {
    map: HashMap<HashReference<'a, Init>, DrawObjectIndex<T>, SipHasher>,
    list: Vec<&'a T>,
    arena: TypedArena<T>,
}

#[deriving(Show)]
pub struct DrawObjectIndex<T>(i32);

impl<T> DrawObjectIndex<T> {
    pub fn error() -> DrawObjectIndex<T> {
        unsafe { mem::transmute(-1i32) }
    }
}

pub type ShaderInitValues = (MString, MString);
pub type BrushInitValues = (PixelFormat, (i32, i32), Vec<u8>);
pub type LuaInitValues = MString;
//pub type ShaderKeyValues = &(String, String);
//pub type BrushKeyValues = (PixelFormat, (i32, i32), Vec<u8>>);
//pub type LuaKeyValues = &String;
pub type ShaderUnfilledValues = (Option<MString>, Option<MString>);
pub type BrushUnfilledValues = BrushInitValues;
pub type LuaUnfilledValues = Option<MString>;
pub type PointShaderIndex = DrawObjectIndex<PointShader>;
pub type CopyShaderIndex = DrawObjectIndex<CopyShader>;
pub type BrushIndex = DrawObjectIndex<Texture>;
pub type LuaIndex = DrawObjectIndex<LuaScript>;

pub trait InitFromCache<Init> {
    fn init(Init) -> Self;
}
pub trait MaybeInitFromCache<Init> {
    fn maybe_init(Init) -> GLResult<Self>;
    fn get_source(&self) -> &Init;
}

// this and the two shader impls were originally a single
// impl<T: Shader> InitFromCache<ShaderInitValues> for Option<T>
// but that counts as the impl for all of Option, not just Option<Shader>
fn _init_copy_shader<T: Shader>(value: (MString, MString)) -> GLResult<T> {
    let (vert, frag) = value;
    Shader::new(vert, frag)
}
impl MaybeInitFromCache<ShaderInitValues> for CopyShader {
    fn maybe_init(value: (MString, MString)) -> GLResult<CopyShader> { _init_copy_shader(value) }
    fn get_source(&self) -> &(MString, MString) { &self.source }
}
impl MaybeInitFromCache<ShaderInitValues> for PointShader {
    fn maybe_init(value: (MString, MString)) -> GLResult<PointShader> { _init_copy_shader(value) }
    fn get_source(&self) -> &(MString, MString) { &self.source }
}
// TODO: use this as the impl for all InitFromCache<Init>
impl MaybeInitFromCache<BrushInitValues> for BrushTexture {
    fn maybe_init(value: BrushInitValues) -> GLResult<BrushTexture> {
        Ok(InitFromCache::init(value))
    }
    fn get_source(&self) -> &BrushInitValues { &self.source }
}

pub fn init_from_defaults<Unfilled, T: MaybeInitFromCache<Init> + FillDefaults<Unfilled, Init, T>, Init>(init: Unfilled) -> GLResult<T> {
    let filled = FillDefaults::fill_defaults(init).val;
    MaybeInitFromCache::<Init>::maybe_init(filled)
}

// FIXME maybe a BrushTexture wrapper?
impl InitFromCache<BrushInitValues> for BrushTexture {
    fn init(value: BrushInitValues) -> BrushTexture {
        let tex = {
            let (format, (w, h), ref pixels) = value;
            Texture::with_image(w, h, Some(pixels.as_slice()), format)
        };
        BrushTexture { texture: tex, source: value }
    }
}

impl MaybeInitFromCache<LuaInitValues> for LuaScript {
    fn maybe_init(value: MString) -> GLResult<LuaScript> {
        LuaScript::new(value)
    }
    fn get_source(&self) -> &MString { &self.source }
}

impl<'a, Unfilled, T: MaybeInitFromCache<Init> + FillDefaults<Unfilled, Init, T>, Init: Hash+Eq+Show> DrawObjectList<'a, T, Init> {
    pub fn new() -> DrawObjectList<'a, T, Init> {
        // the default hasher is keyed off of the task-local rng,
        // which would blow up since we don't have a task
        //let mut rng = rand::weak_rng();
        //let hasher = SipHasher::new_with_keys(rng.next_u64(), rng.next_u64());
        // FIXME weak_rng also blows up? can it not find /dev/urandom?
        let hasher = SipHasher::new_with_keys(0, 0);
        let map = HashMap::with_hasher(hasher);
        DrawObjectList {
            map: map,
            list: Vec::new(),
            arena: TypedArena::new(),
        }
    }

    pub fn push_object(&mut self, init: Unfilled) -> GLResult<DrawObjectIndex<T>> {
        // Can't use map.entry() here as it consumes the key
        let filled = FillDefaults::fill_defaults(init).val;
        let filledref = HashReference { v: unsafe { mem::transmute(&filled) } };
        // see below -- these are safe, we just can't prove it
        match self.map.entry(filledref) {
            Occupied(entry) => Ok(*entry.get()),
            Vacant(entry) => {
                let inited: T = try!(MaybeInitFromCache::<Init>::maybe_init(filled));
                // ptr's lifetime is limited to &self's, which is fair but not very useful.
                // smart ptrs involve individual allocs but are probably better
                let ptr = self.arena.alloc(inited);
                unsafe {
                    self.list.push(mem::transmute(ptr));
                }
                let index = self.list.len() - 1;
                let objindex = DrawObjectIndex(index as i32);
                entry.set(objindex);
                Ok(objindex)
            }
        }
    }

    pub fn get_object(&self, i: DrawObjectIndex<T>) -> &'a T {
        let DrawObjectIndex(idx) = i;
        self.list[idx as uint]
    }
}

impl<'a, Unfilled, T: InitFromCache<Init> + MaybeInitFromCache<Init> + FillDefaults<Unfilled, Init, T>, Init: Hash+Eq+Show> DrawObjectList<'a, T, Init> {
    pub fn safe_push_object(&mut self, init: Unfilled) -> DrawObjectIndex<T> {
        self.push_object(init).unwrap()
    }
}

struct HashReference<'a, T: Hash+PartialEq+'a> {
    v: &'a T
}

impl<'a, T: Hash+PartialEq+Show> Hash for HashReference<'a, T> {
    fn hash(&self, state: &mut SipState) {
        logi!("hashing '{}'", self.v);
        self.v.hash(state);
        logi!("got '{}'", state.result());
    }
}

impl<'a, T: Hash+PartialEq+Show> PartialEq for HashReference<'a, T> {
    fn eq(&self, other: &HashReference<'a, T>) -> bool {
        let iseq = self.v.eq(other.v);
        logi!("'{}' {} '{}'", self.v, if iseq {"=="} else {"!="}, other.v);
        iseq
    }
}

impl<'a, T: Hash+Eq+Show> Eq for HashReference<'a, T> { }

<|endoftext|>