<commit_before>#![allow(unused_mut, unused_variables)]

use {Result, Tape, Value, Walue};

/// An anchor.
#[derive(Clone, Debug)]
pub enum Anchor {
    /// Format 1.
    Format1(Anchor1),
    /// Format 2.
    Format2(Anchor2),
    /// Format 3.
    Format3(Anchor3),
}

table! {
    #[doc = "An anchor in format 1."]
    #[derive(Copy)]
    pub Anchor1 { // AnchorFormat1
        format (u16) = { 1 }, // AnchorFormat
        x      (i16), // XCoordinate
        y      (i16), // YCoordinate
    }
}

table! {
    #[doc = "An anchor in format 2."]
    #[derive(Copy)]
    pub Anchor2 { // AnchorFormat2
        format (u16) = { 2 }, // AnchorFormat
        x      (i16), // XCoordinate
        y      (i16), // YCoordinate
        index  (u16), // AnchorPoint
    }
}

table! {
    @position
    #[doc = "An anchor in format 3."]
    pub Anchor3 { // AnchorFormat3
        format          (u16) = { 3 }, // AnchorFormat
        x               (i16), // XCoordinate
        y               (i16), // YCoordinate
        device_x_offset (u16), // XDeviceTable
        device_y_offset (u16), // YDeviceTable

        device_x (Option<Device>) |this, tape, position| {
            jump_take_maybe!(tape, position, this.device_x_offset)
        },

        device_y (Option<Device>) |this, tape, position| {
            jump_take_maybe!(tape, position, this.device_y_offset)
        },
    }
}

table! {
    @define
    #[doc = "A device table."]
    pub Device { // Device
        start_size   (u16     ), // StartSize
        end_size     (u16     ), // EndSize
        delta_format (u16     ), // DeltaFormat
        delta_data   (Vec<u16>), // DeltaValue
    }
}

table! {
    @define
    #[doc = "An entry-exit record."]
    pub Gesture { // EntryExitRecord
        entry_offset (u16   ), // EntryAnchor
        exit_offset  (u16   ), // ExitAnchor
        entry        (Anchor),
        exit         (Anchor),
    }
}

table! {
    @define
    #[doc = "A single value."]
    pub Single { // ValueRecord
        x_placement               (Option<i16>   ), // XPlacement
        y_placement               (Option<i16>   ), // YPlacement
        x_advance                 (Option<i16>   ), // XAdvance
        y_advance                 (Option<i16>   ), // YAdvance
        device_x_placement_offset (Option<u16>   ), // XPlaDevice
        device_y_placement_offset (Option<u16>   ), // YPlaDevice
        device_x_advance_offset   (Option<u16>   ), // XAdvDevice
        device_y_advance_offset   (Option<u16>   ), // YAdvDevice
        device_x_placement        (Option<Device>),
        device_y_placement        (Option<Device>),
        device_x_advance          (Option<Device>),
        device_y_advance          (Option<Device>),
    }
}

table! {
    @define
    #[doc = "A value pair."]
    pub Pair { // PairValueRecord
        value1 (Single), // Value1
        value2 (Single), // Value2
    }
}

table! {
    @define
    #[doc = "A set of value pairs."]
    pub PairSet { // PairSet
        count   (u16      ), // PairValueCount
        records (Vec<Pair>), // PairValueRecord
    }
}

flags! {
    #[doc = "Value flags."]
    pub ValueFlags(u16) {
        0b0000_0000_0000_0001 => has_x_placement,
        0b0000_0000_0000_0010 => has_y_placement,
        0b0000_0000_0000_0100 => has_x_advance,
        0b0000_0000_0000_1000 => has_y_advance,
        0b0000_0000_0001_0000 => has_device_x_placement,
        0b0000_0000_0010_0000 => has_device_y_placement,
        0b0000_0000_0100_0000 => has_device_x_advance,
        0b0000_0000_1000_0000 => has_device_y_advance,
        0b1111_1111_0000_0000 => is_invalid,
    }
}

impl Value for Anchor {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        Ok(match try!(tape.peek::<u16>()) {
            1 => Anchor::Format1(try!(tape.take())),
            2 => Anchor::Format2(try!(tape.take())),
            3 => Anchor::Format3(try!(tape.take())),
            _ => raise!("found an unknown format of the anchor table"),
        })
    }
}

impl Value for Device {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        let start_size = try!(tape.take());
        let end_size = try!(tape.take());
        if start_size > end_size {
            raise!("found a malformed device table");
        }
        let delta_format = try!(tape.take());
        if delta_format == 0 || delta_format > 3 {
            raise!("found an unknow format of the device table");
        }
        let count = (end_size - start_size) as usize + 1;
        let bit_count = (1 << delta_format as usize) * count;
        let short_count = (bit_count + 16 - bit_count % 16) >> 4;
        let delta_data = try!(tape.take_given(short_count));
        Ok(Device {
            start_size: start_size,
            end_size: end_size,
            delta_format: delta_format,
            delta_data: delta_data,
        })
    }
}

impl Walue<u64> for Gesture {
    fn read<T: Tape>(tape: &mut T, position: u64) -> Result<Self> {
        let entry_offset = try!(tape.take());
        let exit_offset = try!(tape.take());
        let entry = jump_take!(@unwrap tape, position, entry_offset);
        let exit = jump_take!(@unwrap tape, position, exit_offset);
        Ok(Gesture {
            entry_offset: entry_offset,
            exit_offset: exit_offset,
            entry: entry,
            exit: exit,
        })
    }
}

impl Walue<(u64, ValueFlags, ValueFlags)> for Pair {
    #[inline]
    fn read<T: Tape>(tape: &mut T, (position, flags1, flags2): (u64, ValueFlags, ValueFlags))
                     -> Result<Self> {

        Ok(Pair {
            value1: try!(tape.take_given((position, flags1))),
            value2: try!(tape.take_given((position, flags2))),
        })
    }
}

impl Walue<(u64, ValueFlags, ValueFlags)> for PairSet {
    fn read<T: Tape>(tape: &mut T, parameter: (u64, ValueFlags, ValueFlags)) -> Result<Self> {
        let count = try!(tape.take());
        let mut records = Vec::with_capacity(count as usize);
        for _ in 0..(count as usize) {
            records.push(try!(tape.take_given(parameter)));
        }
        Ok(PairSet { count: count, records: records })
    }
}

impl Walue<(u64, ValueFlags)> for Single {
    fn read<T: Tape>(tape: &mut T, (position, flags): (u64, ValueFlags)) -> Result<Self> {
        macro_rules! read(
            ($flag:ident) => (if flags.$flag() { Some(try!(tape.take())) } else { None });
        );
        let x_placement = read!(has_x_placement);
        let y_placement = read!(has_y_placement);
        let x_advance = read!(has_x_advance);
        let y_advance = read!(has_y_advance);
        let device_x_placement_offset = read!(has_device_x_placement);
        let device_y_placement_offset = read!(has_device_y_placement);
        let device_x_advance_offset = read!(has_device_x_advance);
        let device_y_advance_offset = read!(has_device_y_advance);
        macro_rules! read(
            ($offset:ident) => (
                match $offset {
                    Some(offset) => Some(jump_take!(@unwrap tape, position, offset)),
                    _ => None,
                }
            );
        );
        let device_x_placement = read!(device_x_placement_offset);
        let device_y_placement = read!(device_y_placement_offset);
        let device_x_advance = read!(device_x_advance_offset);
        let device_y_advance = read!(device_y_advance_offset);
        Ok(Single {
            x_placement: x_placement,
            y_placement: y_placement,
            x_advance: x_advance,
            y_advance: y_advance,
            device_x_placement_offset: device_x_placement_offset,
            device_y_placement_offset: device_y_placement_offset,
            device_x_advance_offset: device_x_advance_offset,
            device_y_advance_offset: device_y_advance_offset,
            device_x_placement: device_x_placement,
            device_y_placement: device_y_placement,
            device_x_advance: device_x_advance,
            device_y_advance: device_y_advance,
        })
    }
}
<commit_msg>Rename a macro<commit_after>#![allow(unused_mut, unused_variables)]

use {Result, Tape, Value, Walue};

/// An anchor.
#[derive(Clone, Debug)]
pub enum Anchor {
    /// Format 1.
    Format1(Anchor1),
    /// Format 2.
    Format2(Anchor2),
    /// Format 3.
    Format3(Anchor3),
}

table! {
    #[doc = "An anchor in format 1."]
    #[derive(Copy)]
    pub Anchor1 { // AnchorFormat1
        format (u16) = { 1 }, // AnchorFormat
        x      (i16), // XCoordinate
        y      (i16), // YCoordinate
    }
}

table! {
    #[doc = "An anchor in format 2."]
    #[derive(Copy)]
    pub Anchor2 { // AnchorFormat2
        format (u16) = { 2 }, // AnchorFormat
        x      (i16), // XCoordinate
        y      (i16), // YCoordinate
        index  (u16), // AnchorPoint
    }
}

table! {
    @position
    #[doc = "An anchor in format 3."]
    pub Anchor3 { // AnchorFormat3
        format          (u16) = { 3 }, // AnchorFormat
        x               (i16), // XCoordinate
        y               (i16), // YCoordinate
        device_x_offset (u16), // XDeviceTable
        device_y_offset (u16), // YDeviceTable

        device_x (Option<Device>) |this, tape, position| {
            jump_take_maybe!(tape, position, this.device_x_offset)
        },

        device_y (Option<Device>) |this, tape, position| {
            jump_take_maybe!(tape, position, this.device_y_offset)
        },
    }
}

table! {
    @define
    #[doc = "A device table."]
    pub Device { // Device
        start_size   (u16     ), // StartSize
        end_size     (u16     ), // EndSize
        delta_format (u16     ), // DeltaFormat
        delta_data   (Vec<u16>), // DeltaValue
    }
}

table! {
    @define
    #[doc = "An entry-exit record."]
    pub Gesture { // EntryExitRecord
        entry_offset (u16   ), // EntryAnchor
        exit_offset  (u16   ), // ExitAnchor
        entry        (Anchor),
        exit         (Anchor),
    }
}

table! {
    @define
    #[doc = "A single value."]
    pub Single { // ValueRecord
        x_placement               (Option<i16>   ), // XPlacement
        y_placement               (Option<i16>   ), // YPlacement
        x_advance                 (Option<i16>   ), // XAdvance
        y_advance                 (Option<i16>   ), // YAdvance
        device_x_placement_offset (Option<u16>   ), // XPlaDevice
        device_y_placement_offset (Option<u16>   ), // YPlaDevice
        device_x_advance_offset   (Option<u16>   ), // XAdvDevice
        device_y_advance_offset   (Option<u16>   ), // YAdvDevice
        device_x_placement        (Option<Device>),
        device_y_placement        (Option<Device>),
        device_x_advance          (Option<Device>),
        device_y_advance          (Option<Device>),
    }
}

table! {
    @define
    #[doc = "A value pair."]
    pub Pair { // PairValueRecord
        value1 (Single), // Value1
        value2 (Single), // Value2
    }
}

table! {
    @define
    #[doc = "A set of value pairs."]
    pub PairSet { // PairSet
        count   (u16      ), // PairValueCount
        records (Vec<Pair>), // PairValueRecord
    }
}

flags! {
    #[doc = "Value flags."]
    pub ValueFlags(u16) {
        0b0000_0000_0000_0001 => has_x_placement,
        0b0000_0000_0000_0010 => has_y_placement,
        0b0000_0000_0000_0100 => has_x_advance,
        0b0000_0000_0000_1000 => has_y_advance,
        0b0000_0000_0001_0000 => has_device_x_placement,
        0b0000_0000_0010_0000 => has_device_y_placement,
        0b0000_0000_0100_0000 => has_device_x_advance,
        0b0000_0000_1000_0000 => has_device_y_advance,
        0b1111_1111_0000_0000 => is_invalid,
    }
}

impl Value for Anchor {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        Ok(match try!(tape.peek::<u16>()) {
            1 => Anchor::Format1(try!(tape.take())),
            2 => Anchor::Format2(try!(tape.take())),
            3 => Anchor::Format3(try!(tape.take())),
            _ => raise!("found an unknown format of the anchor table"),
        })
    }
}

impl Value for Device {
    fn read<T: Tape>(tape: &mut T) -> Result<Self> {
        let start_size = try!(tape.take());
        let end_size = try!(tape.take());
        if start_size > end_size {
            raise!("found a malformed device table");
        }
        let delta_format = try!(tape.take());
        if delta_format == 0 || delta_format > 3 {
            raise!("found an unknow format of the device table");
        }
        let count = (end_size - start_size) as usize + 1;
        let bit_count = (1 << delta_format as usize) * count;
        let short_count = (bit_count + 16 - bit_count % 16) >> 4;
        let delta_data = try!(tape.take_given(short_count));
        Ok(Device {
            start_size: start_size,
            end_size: end_size,
            delta_format: delta_format,
            delta_data: delta_data,
        })
    }
}

impl Walue<u64> for Gesture {
    fn read<T: Tape>(tape: &mut T, position: u64) -> Result<Self> {
        let entry_offset = try!(tape.take());
        let exit_offset = try!(tape.take());
        let entry = jump_take!(@unwrap tape, position, entry_offset);
        let exit = jump_take!(@unwrap tape, position, exit_offset);
        Ok(Gesture {
            entry_offset: entry_offset,
            exit_offset: exit_offset,
            entry: entry,
            exit: exit,
        })
    }
}

impl Walue<(u64, ValueFlags, ValueFlags)> for Pair {
    #[inline]
    fn read<T: Tape>(tape: &mut T, (position, flags1, flags2): (u64, ValueFlags, ValueFlags))
                     -> Result<Self> {

        Ok(Pair {
            value1: try!(tape.take_given((position, flags1))),
            value2: try!(tape.take_given((position, flags2))),
        })
    }
}

impl Walue<(u64, ValueFlags, ValueFlags)> for PairSet {
    fn read<T: Tape>(tape: &mut T, parameter: (u64, ValueFlags, ValueFlags)) -> Result<Self> {
        let count = try!(tape.take());
        let mut records = Vec::with_capacity(count as usize);
        for _ in 0..(count as usize) {
            records.push(try!(tape.take_given(parameter)));
        }
        Ok(PairSet { count: count, records: records })
    }
}

impl Walue<(u64, ValueFlags)> for Single {
    fn read<T: Tape>(tape: &mut T, (position, flags): (u64, ValueFlags)) -> Result<Self> {
        macro_rules! take(
            ($flag:ident) => (if flags.$flag() { Some(try!(tape.take())) } else { None });
        );
        let x_placement = take!(has_x_placement);
        let y_placement = take!(has_y_placement);
        let x_advance = take!(has_x_advance);
        let y_advance = take!(has_y_advance);
        let device_x_placement_offset = take!(has_device_x_placement);
        let device_y_placement_offset = take!(has_device_y_placement);
        let device_x_advance_offset = take!(has_device_x_advance);
        let device_y_advance_offset = take!(has_device_y_advance);
        macro_rules! take(
            ($offset:ident) => (
                match $offset {
                    Some(offset) => Some(jump_take!(@unwrap tape, position, offset)),
                    _ => None,
                }
            );
        );
        let device_x_placement = take!(device_x_placement_offset);
        let device_y_placement = take!(device_y_placement_offset);
        let device_x_advance = take!(device_x_advance_offset);
        let device_y_advance = take!(device_y_advance_offset);
        Ok(Single {
            x_placement: x_placement,
            y_placement: y_placement,
            x_advance: x_advance,
            y_advance: y_advance,
            device_x_placement_offset: device_x_placement_offset,
            device_y_placement_offset: device_y_placement_offset,
            device_x_advance_offset: device_x_advance_offset,
            device_y_advance_offset: device_y_advance_offset,
            device_x_placement: device_x_placement,
            device_y_placement: device_y_placement,
            device_x_advance: device_x_advance,
            device_y_advance: device_y_advance,
        })
    }
}
<|endoftext|>


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

//! Tracking of pending loads in a document.
//! https://html.spec.whatwg.org/multipage/#the-end

use dom::bindings::js::JS;
use dom::document::Document;
use ipc_channel::ipc::IpcSender;
use net_traits::{CoreResourceMsg, FetchResponseMsg, ResourceThreads, IpcSend};
use net_traits::request::RequestInit;
use servo_url::ServoUrl;
use std::thread;

#[derive(Clone, Debug, HeapSizeOf, JSTraceable, PartialEq)]
pub enum LoadType {
    Image(ServoUrl),
    Script(ServoUrl),
    Subframe(ServoUrl),
    Stylesheet(ServoUrl),
    PageSource(ServoUrl),
    Media(ServoUrl),
}

impl LoadType {
    fn url(&self) -> &ServoUrl {
        match *self {
            LoadType::Image(ref url) |
            LoadType::Script(ref url) |
            LoadType::Subframe(ref url) |
            LoadType::Stylesheet(ref url) |
            LoadType::Media(ref url) |
            LoadType::PageSource(ref url) => url,
        }
    }
}

/// Canary value ensuring that manually added blocking loads (ie. ones that weren't
/// created via DocumentLoader::fetch_async) are always removed by the time
/// that the owner is destroyed.
#[derive(HeapSizeOf, JSTraceable)]
#[must_root]
pub struct LoadBlocker {
    /// The document whose load event is blocked by this object existing.
    doc: JS<Document>,
    /// The load that is blocking the document's load event.
    load: Option<LoadType>,
}

impl LoadBlocker {
    /// Mark the document's load event as blocked on this new load.
    pub fn new(doc: &Document, load: LoadType) -> LoadBlocker {
        doc.mut_loader().add_blocking_load(load.clone());
        LoadBlocker {
            doc: JS::from_ref(doc),
            load: Some(load),
        }
    }

    /// Remove this load from the associated document's list of blocking loads.
    pub fn terminate(blocker: &mut Option<LoadBlocker>) {
        if let Some(this) = blocker.as_mut() {
            this.doc.finish_load(this.load.take().unwrap());
        }
        *blocker = None;
    }

    /// Return the url associated with this load.
    pub fn url(&self) -> Option<&ServoUrl> {
        self.load.as_ref().map(LoadType::url)
    }
}

impl Drop for LoadBlocker {
    fn drop(&mut self) {
        if !thread::panicking() {
            debug_assert!(self.load.is_none());
        }
    }
}

#[derive(HeapSizeOf, JSTraceable)]
pub struct DocumentLoader {
    resource_threads: ResourceThreads,
    blocking_loads: Vec<LoadType>,
    events_inhibited: bool,
}

impl DocumentLoader {
    pub fn new(existing: &DocumentLoader) -> DocumentLoader {
        DocumentLoader::new_with_threads(existing.resource_threads.clone(), None)
    }

    pub fn new_with_threads(resource_threads: ResourceThreads,
                            initial_load: Option<ServoUrl>) -> DocumentLoader {
        debug!("Initial blocking load {:?}.", initial_load);
        let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();

        DocumentLoader {
            resource_threads: resource_threads,
            blocking_loads: initial_loads,
            events_inhibited: false,
        }
    }

    /// Add a load to the list of blocking loads.
    fn add_blocking_load(&mut self, load: LoadType) {
        debug!("Adding blocking load {:?} ({}).", load, self.blocking_loads.len());
        self.blocking_loads.push(load);
    }

    /// Initiate a new fetch.
    pub fn fetch_async(&mut self,
                       load: LoadType,
                       request: RequestInit,
                       fetch_target: IpcSender<FetchResponseMsg>) {
        self.add_blocking_load(load);
        self.fetch_async_background(request, fetch_target);
    }

    /// Initiate a new fetch that does not block the document load event.
    pub fn fetch_async_background(&self,
                                  request: RequestInit,
                                  fetch_target: IpcSender<FetchResponseMsg>) {
        self.resource_threads.sender().send(CoreResourceMsg::Fetch(request, fetch_target)).unwrap();
    }

    /// Mark an in-progress network request complete.
    pub fn finish_load(&mut self, load: &LoadType) {
        debug!("Removing blocking load {:?} ({}).", load, self.blocking_loads.len());
        let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == *load);
        self.blocking_loads.remove(idx.expect(&format!("unknown completed load {:?}", load)));
    }

    pub fn is_blocked(&self) -> bool {
        // TODO: Ensure that we report blocked if parsing is still ongoing.
        !self.blocking_loads.is_empty()
    }

    pub fn inhibit_events(&mut self) {
        self.events_inhibited = true;
    }

    pub fn events_inhibited(&self) -> bool {
        self.events_inhibited
    }

    pub fn resource_threads(&self) -> &ResourceThreads {
        &self.resource_threads
    }
}
<commit_msg>fix #18594: Unnecessarily allocating an error string<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Tracking of pending loads in a document.
//! https://html.spec.whatwg.org/multipage/#the-end

use dom::bindings::js::JS;
use dom::document::Document;
use ipc_channel::ipc::IpcSender;
use net_traits::{CoreResourceMsg, FetchResponseMsg, ResourceThreads, IpcSend};
use net_traits::request::RequestInit;
use servo_url::ServoUrl;
use std::thread;

#[derive(Clone, Debug, HeapSizeOf, JSTraceable, PartialEq)]
pub enum LoadType {
    Image(ServoUrl),
    Script(ServoUrl),
    Subframe(ServoUrl),
    Stylesheet(ServoUrl),
    PageSource(ServoUrl),
    Media(ServoUrl),
}

impl LoadType {
    fn url(&self) -> &ServoUrl {
        match *self {
            LoadType::Image(ref url) |
            LoadType::Script(ref url) |
            LoadType::Subframe(ref url) |
            LoadType::Stylesheet(ref url) |
            LoadType::Media(ref url) |
            LoadType::PageSource(ref url) => url,
        }
    }
}

/// Canary value ensuring that manually added blocking loads (ie. ones that weren't
/// created via DocumentLoader::fetch_async) are always removed by the time
/// that the owner is destroyed.
#[derive(HeapSizeOf, JSTraceable)]
#[must_root]
pub struct LoadBlocker {
    /// The document whose load event is blocked by this object existing.
    doc: JS<Document>,
    /// The load that is blocking the document's load event.
    load: Option<LoadType>,
}

impl LoadBlocker {
    /// Mark the document's load event as blocked on this new load.
    pub fn new(doc: &Document, load: LoadType) -> LoadBlocker {
        doc.mut_loader().add_blocking_load(load.clone());
        LoadBlocker {
            doc: JS::from_ref(doc),
            load: Some(load),
        }
    }

    /// Remove this load from the associated document's list of blocking loads.
    pub fn terminate(blocker: &mut Option<LoadBlocker>) {
        if let Some(this) = blocker.as_mut() {
            this.doc.finish_load(this.load.take().unwrap());
        }
        *blocker = None;
    }

    /// Return the url associated with this load.
    pub fn url(&self) -> Option<&ServoUrl> {
        self.load.as_ref().map(LoadType::url)
    }
}

impl Drop for LoadBlocker {
    fn drop(&mut self) {
        if !thread::panicking() {
            debug_assert!(self.load.is_none());
        }
    }
}

#[derive(HeapSizeOf, JSTraceable)]
pub struct DocumentLoader {
    resource_threads: ResourceThreads,
    blocking_loads: Vec<LoadType>,
    events_inhibited: bool,
}

impl DocumentLoader {
    pub fn new(existing: &DocumentLoader) -> DocumentLoader {
        DocumentLoader::new_with_threads(existing.resource_threads.clone(), None)
    }

    pub fn new_with_threads(resource_threads: ResourceThreads,
                            initial_load: Option<ServoUrl>) -> DocumentLoader {
        debug!("Initial blocking load {:?}.", initial_load);
        let initial_loads = initial_load.into_iter().map(LoadType::PageSource).collect();

        DocumentLoader {
            resource_threads: resource_threads,
            blocking_loads: initial_loads,
            events_inhibited: false,
        }
    }

    /// Add a load to the list of blocking loads.
    fn add_blocking_load(&mut self, load: LoadType) {
        debug!("Adding blocking load {:?} ({}).", load, self.blocking_loads.len());
        self.blocking_loads.push(load);
    }

    /// Initiate a new fetch.
    pub fn fetch_async(&mut self,
                       load: LoadType,
                       request: RequestInit,
                       fetch_target: IpcSender<FetchResponseMsg>) {
        self.add_blocking_load(load);
        self.fetch_async_background(request, fetch_target);
    }

    /// Initiate a new fetch that does not block the document load event.
    pub fn fetch_async_background(&self,
                                  request: RequestInit,
                                  fetch_target: IpcSender<FetchResponseMsg>) {
        self.resource_threads.sender().send(CoreResourceMsg::Fetch(request, fetch_target)).unwrap();
    }

    /// Mark an in-progress network request complete.
    pub fn finish_load(&mut self, load: &LoadType) {
        debug!("Removing blocking load {:?} ({}).", load, self.blocking_loads.len());
        let idx = self.blocking_loads.iter().position(|unfinished| *unfinished == *load);
        self.blocking_loads.remove(idx.unwrap_or_else(|| panic!("unknown completed load {:?}", load)));
    }

    pub fn is_blocked(&self) -> bool {
        // TODO: Ensure that we report blocked if parsing is still ongoing.
        !self.blocking_loads.is_empty()
    }

    pub fn inhibit_events(&mut self) {
        self.events_inhibited = true;
    }

    pub fn events_inhibited(&self) -> bool {
        self.events_inhibited
    }

    pub fn resource_threads(&self) -> &ResourceThreads {
        &self.resource_threads
    }
}
<|endoftext|>


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

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSAutoCompartment, JSContext, JSObject};
use js::jsapi::JS_GetArrayBufferViewBuffer;
use js::rust::CustomAutoRooterGuard;
use js::rust::wrappers::JS_DetachArrayBuffer;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::cmp::min;
use std::ptr::{self, NonNull};

// This range is defined by the spec.
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
pub const MIN_SAMPLE_RATE: f32 = 8000.;
pub const MAX_SAMPLE_RATE: f32 = 96000.;

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "servo_media"]
    shared_channels: DomRefCell<ServoMediaAudioBuffer>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer {
        let vec = (0..number_of_channels).map(|_| Heap::default()).collect();
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(vec),
            shared_channels: DomRefCell::new(ServoMediaAudioBuffer::new(
                number_of_channels as u8,
                length as usize,
            )),
            sample_rate,
            length,
            duration: length as f64 / sample_rate as f64,
            number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(
        global: &Window,
        number_of_channels: u32,
        length: u32,
        sample_rate: f32,
        initial_data: Option<&[Vec<f32>]>,
    ) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(number_of_channels, length, sample_rate);
        let buffer = reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap);
        buffer.set_channels(initial_data);
        buffer
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer
    pub fn Constructor(
        window: &Window,
        options: &AudioBufferOptions,
    ) -> Fallible<DomRoot<AudioBuffer>> {
        if options.numberOfChannels > MAX_CHANNEL_COUNT ||
            *options.sampleRate < MIN_SAMPLE_RATE ||
            *options.sampleRate > MAX_SAMPLE_RATE
        {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(
            window,
            options.numberOfChannels,
            options.length,
            *options.sampleRate,
            None,
        ))
    }

    #[allow(unsafe_code)]
    pub fn set_channels(&self, initial_data: Option<&[Vec<f32>]>) {
        let global = self.global();
        let cx = global.get_cx();
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        let chans = self.js_channels.borrow_mut();
        for channel in 0..self.number_of_channels {
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            match initial_data {
                Some(data) => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(data[channel as usize].as_slice()),
                            array.handle_mut(),
                        )
                    };
                },
                None => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(&vec![0.; self.length as usize]),
                            array.handle_mut(),
                        )
                    };
                },
            }
            chans[channel as usize].set(array.get());
        }
    }

    #[allow(unsafe_code)]
    unsafe fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Move the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = (*self.shared_channels.borrow_mut()).buffers.remove(i);
            if Float32Array::create(cx, CreateWith::Slice(&shared_channel), array.handle_mut())
                .is_err()
            {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    // https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    let data = array.to_vec();
                    let mut is_shared = false;
                    rooted!(in (cx) let view_buffer =
                        JS_GetArrayBufferViewBuffer(cx, channel.handle(), &mut is_shared));
                    // This buffer is always created unshared
                    debug_assert!(!is_shared);
                    let _ = JS_DetachArrayBuffer(cx, view_buffer.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.borrow_mut()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        self.js_channels.borrow_mut().clear();

        Some((*self.shared_channels.borrow()).clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(
        &self,
        cx: *mut JSContext,
        channel: u32,
    ) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(
            self.js_channels.borrow()[channel as usize].get(),
        ))
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
    #[allow(unsafe_code)]
    fn CopyFromChannel(
        &self,
        mut destination: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if channel_number >= self.number_of_channels || start_in_channel > self.length {
            return Err(Error::IndexSize);
        }

        let bytes_to_copy = min(self.length - start_in_channel, destination.len() as u32) as usize;
        let cx = self.global().get_cx();
        let channel_number = channel_number as usize;
        let offset = start_in_channel as usize;
        let mut dest = Vec::with_capacity(destination.len());

        // We either copy form js_channels or shared_channels.

        let js_channel = self.js_channels.borrow()[channel_number].get();
        if !js_channel.is_null() {
            typedarray!(in(cx) let array: Float32Array = js_channel);
            if let Ok(array) = array {
                let data = unsafe { array.as_slice() };
                dest.extend_from_slice(&data[offset..offset + bytes_to_copy]);
                return Ok(());
            }
        }

        if let Some(shared_channel) = self.shared_channels.borrow().buffers.get(channel_number) {
            dest.extend_from_slice(&shared_channel.as_slice()[offset..offset + bytes_to_copy]);
        }

        unsafe {
            destination.update(&dest);
        }

        Ok(())
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
    #[allow(unsafe_code)]
    fn CopyToChannel(
        &self,
        source: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if channel_number >= self.number_of_channels || start_in_channel > (source.len() as u32) {
            return Err(Error::IndexSize);
        }

        let cx = self.global().get_cx();
        if unsafe { !self.restore_js_channel_data(cx) } {
            return Err(Error::JSFailed);
        }

        let js_channel = self.js_channels.borrow()[channel_number as usize].get();
        if js_channel.is_null() {
            // The array buffer was detached.
            return Err(Error::IndexSize);
        }

        typedarray!(in(cx) let array: Float32Array = js_channel);
        if let Ok(mut array) = array {
            let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize;
            let offset = start_in_channel as usize;
            unsafe {
                array.update(&source.as_slice()[offset..offset + bytes_to_copy]);
            }
        } else {
            return Err(Error::IndexSize);
        }

        Ok(())
    }
}
<commit_msg>Fix AudioBuffer crash. Do not modify number of channels while moving from shared to js channels<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use dom::audionode::MAX_CHANNEL_COUNT;
use dom::bindings::cell::DomRefCell;
use dom::bindings::codegen::Bindings::AudioBufferBinding::{self, AudioBufferMethods, AudioBufferOptions};
use dom::bindings::error::{Error, Fallible};
use dom::bindings::num::Finite;
use dom::bindings::reflector::{DomObject, Reflector, reflect_dom_object};
use dom::bindings::root::DomRoot;
use dom::window::Window;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSAutoCompartment, JSContext, JSObject};
use js::jsapi::JS_GetArrayBufferViewBuffer;
use js::rust::CustomAutoRooterGuard;
use js::rust::wrappers::JS_DetachArrayBuffer;
use js::typedarray::{CreateWith, Float32Array};
use servo_media::audio::buffer_source_node::AudioBuffer as ServoMediaAudioBuffer;
use std::cmp::min;
use std::ptr::{self, NonNull};

// This range is defined by the spec.
// https://webaudio.github.io/web-audio-api/#dom-baseaudiocontext-createbuffer
pub const MIN_SAMPLE_RATE: f32 = 8000.;
pub const MAX_SAMPLE_RATE: f32 = 96000.;

type JSAudioChannel = Heap<*mut JSObject>;

#[dom_struct]
pub struct AudioBuffer {
    reflector_: Reflector,
    js_channels: DomRefCell<Vec<JSAudioChannel>>,
    #[ignore_malloc_size_of = "servo_media"]
    shared_channels: DomRefCell<ServoMediaAudioBuffer>,
    sample_rate: f32,
    length: u32,
    duration: f64,
    number_of_channels: u32,
}

impl AudioBuffer {
    #[allow(unrooted_must_root)]
    #[allow(unsafe_code)]
    pub fn new_inherited(number_of_channels: u32, length: u32, sample_rate: f32) -> AudioBuffer {
        let vec = (0..number_of_channels).map(|_| Heap::default()).collect();
        AudioBuffer {
            reflector_: Reflector::new(),
            js_channels: DomRefCell::new(vec),
            shared_channels: DomRefCell::new(ServoMediaAudioBuffer::new(
                number_of_channels as u8,
                length as usize,
            )),
            sample_rate,
            length,
            duration: length as f64 / sample_rate as f64,
            number_of_channels,
        }
    }

    #[allow(unrooted_must_root)]
    pub fn new(
        global: &Window,
        number_of_channels: u32,
        length: u32,
        sample_rate: f32,
        initial_data: Option<&[Vec<f32>]>,
    ) -> DomRoot<AudioBuffer> {
        let buffer = AudioBuffer::new_inherited(number_of_channels, length, sample_rate);
        let buffer = reflect_dom_object(Box::new(buffer), global, AudioBufferBinding::Wrap);
        buffer.set_channels(initial_data);
        buffer
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-audiobuffer
    pub fn Constructor(
        window: &Window,
        options: &AudioBufferOptions,
    ) -> Fallible<DomRoot<AudioBuffer>> {
        if options.numberOfChannels > MAX_CHANNEL_COUNT ||
            *options.sampleRate < MIN_SAMPLE_RATE ||
            *options.sampleRate > MAX_SAMPLE_RATE
        {
            return Err(Error::NotSupported);
        }
        Ok(AudioBuffer::new(
            window,
            options.numberOfChannels,
            options.length,
            *options.sampleRate,
            None,
        ))
    }

    #[allow(unsafe_code)]
    pub fn set_channels(&self, initial_data: Option<&[Vec<f32>]>) {
        let global = self.global();
        let cx = global.get_cx();
        let _ac = JSAutoCompartment::new(cx, global.reflector().get_jsobject().get());
        let chans = self.js_channels.borrow_mut();
        for channel in 0..self.number_of_channels {
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            match initial_data {
                Some(data) => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(data[channel as usize].as_slice()),
                            array.handle_mut(),
                        )
                    };
                },
                None => {
                    let _ = unsafe {
                        Float32Array::create(
                            cx,
                            CreateWith::Slice(&vec![0.; self.length as usize]),
                            array.handle_mut(),
                        )
                    };
                },
            }
            chans[channel as usize].set(array.get());
        }
    }

    #[allow(unsafe_code)]
    unsafe fn restore_js_channel_data(&self, cx: *mut JSContext) -> bool {
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            if !channel.get().is_null() {
                // Already have data in JS array.
                continue;
            }

            // Move the channel data from shared_channels to js_channels.
            rooted!(in (cx) let mut array = ptr::null_mut::<JSObject>());
            let shared_channel = &(*self.shared_channels.borrow_mut()).buffers[i];
            if Float32Array::create(cx, CreateWith::Slice(shared_channel), array.handle_mut())
                .is_err()
            {
                return false;
            }
            channel.set(array.get());
        }

        true
    }

    // https://webaudio.github.io/web-audio-api/#acquire-the-content
    #[allow(unsafe_code)]
    pub fn acquire_contents(&self) -> Option<ServoMediaAudioBuffer> {
        let cx = self.global().get_cx();
        for (i, channel) in self.js_channels.borrow_mut().iter().enumerate() {
            // Step 1.
            if channel.get().is_null() {
                return None;
            }

            // Step 2.
            let channel_data = unsafe {
                typedarray!(in(cx) let array: Float32Array = channel.get());
                if let Ok(array) = array {
                    let data = array.to_vec();
                    let mut is_shared = false;
                    rooted!(in (cx) let view_buffer =
                        JS_GetArrayBufferViewBuffer(cx, channel.handle(), &mut is_shared));
                    // This buffer is always created unshared
                    debug_assert!(!is_shared);
                    let _ = JS_DetachArrayBuffer(cx, view_buffer.handle());
                    data
                } else {
                    return None;
                }
            };

            channel.set(ptr::null_mut());

            // Step 3.
            (*self.shared_channels.borrow_mut()).buffers[i] = channel_data;

            // Step 4 will complete turning shared_channels
            // data into js_channels ArrayBuffers in restore_js_channel_data.
        }

        Some((*self.shared_channels.borrow()).clone())
    }
}

impl AudioBufferMethods for AudioBuffer {
    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-samplerate
    fn SampleRate(&self) -> Finite<f32> {
        Finite::wrap(self.sample_rate)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-length
    fn Length(&self) -> u32 {
        self.length
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-duration
    fn Duration(&self) -> Finite<f64> {
        Finite::wrap(self.duration)
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-numberofchannels
    fn NumberOfChannels(&self) -> u32 {
        self.number_of_channels
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-getchanneldata
    #[allow(unsafe_code)]
    unsafe fn GetChannelData(
        &self,
        cx: *mut JSContext,
        channel: u32,
    ) -> Fallible<NonNull<JSObject>> {
        if channel >= self.number_of_channels {
            return Err(Error::IndexSize);
        }

        if !self.restore_js_channel_data(cx) {
            return Err(Error::JSFailed);
        }

        Ok(NonNull::new_unchecked(
            self.js_channels.borrow()[channel as usize].get(),
        ))
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copyfromchannel
    #[allow(unsafe_code)]
    fn CopyFromChannel(
        &self,
        mut destination: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if channel_number >= self.number_of_channels || start_in_channel > self.length {
            return Err(Error::IndexSize);
        }

        let bytes_to_copy = min(self.length - start_in_channel, destination.len() as u32) as usize;
        let cx = self.global().get_cx();
        let channel_number = channel_number as usize;
        let offset = start_in_channel as usize;
        let mut dest = Vec::with_capacity(destination.len());

        // We either copy form js_channels or shared_channels.

        let js_channel = self.js_channels.borrow()[channel_number].get();
        if !js_channel.is_null() {
            typedarray!(in(cx) let array: Float32Array = js_channel);
            if let Ok(array) = array {
                let data = unsafe { array.as_slice() };
                dest.extend_from_slice(&data[offset..offset + bytes_to_copy]);
                return Ok(());
            }
        }

        if let Some(shared_channel) = self.shared_channels.borrow().buffers.get(channel_number) {
            dest.extend_from_slice(&shared_channel.as_slice()[offset..offset + bytes_to_copy]);
        }

        unsafe {
            destination.update(&dest);
        }

        Ok(())
    }

    // https://webaudio.github.io/web-audio-api/#dom-audiobuffer-copytochannel
    #[allow(unsafe_code)]
    fn CopyToChannel(
        &self,
        source: CustomAutoRooterGuard<Float32Array>,
        channel_number: u32,
        start_in_channel: u32,
    ) -> Fallible<()> {
        if channel_number >= self.number_of_channels || start_in_channel > (source.len() as u32) {
            return Err(Error::IndexSize);
        }

        let cx = self.global().get_cx();
        if unsafe { !self.restore_js_channel_data(cx) } {
            return Err(Error::JSFailed);
        }

        let js_channel = self.js_channels.borrow()[channel_number as usize].get();
        if js_channel.is_null() {
            // The array buffer was detached.
            return Err(Error::IndexSize);
        }

        typedarray!(in(cx) let array: Float32Array = js_channel);
        if let Ok(mut array) = array {
            let bytes_to_copy = min(self.length - start_in_channel, source.len() as u32) as usize;
            let offset = start_in_channel as usize;
            unsafe {
                array.update(&source.as_slice()[offset..offset + bytes_to_copy]);
            }
        } else {
            return Err(Error::IndexSize);
        }

        Ok(())
    }
}
<|endoftext|>


<commit_before>// cannot use core::prelude and still import StrExt needed for utf16_units
use std::prelude::v1::*;
use core::{ptr, mem, raw};
use core::any::Any;
use core::fmt::Show;
use core::fmt::Writer as FormatWriter;
use core::iter;
use libc::{c_void, c_char};
use core::borrow::IntoCow;

use jni::{jobject, jclass, jmethodID, jfieldID, JNIEnv, jint, jstring, jvalue, JNINativeMethod, JavaVM};
#[cfg(target_word_size = "64")] use jni::jlong;
use jni_constants::*;

use glinit::GLInit;
use drawevent::Events;
use glcommon::MString;
use libc::types::os::arch::posix88::pid_t;

use std;

extern "C" {
    pub fn gettid() -> pid_t;
}

#[macro_use]
mod macros;
pub mod texturethread;
pub mod android_bitmap;
pub mod gldataclasses;
pub mod motionevent;

#[cfg(not(target_word_size = "64"))]
#[allow(non_camel_case_types)]
pub type jpointer = jint;
#[cfg(target_word_size = "64")]
#[allow(non_camel_case_types)]
pub type jpointer = jlong;

struct CaseClass {
    constructor: jmethodID,
    class: jclass,
}

static mut GL_EXCEPTION: CaseClass = CaseClass { constructor: 0 as jmethodID, class: 0 as jclass };

struct GLInitEvents<'a> {
    glinit: GLInit<'a>,
    events: Events<'a>,
    jni_undo_callback: JNIUndoCallback,
    owning_thread: pid_t,
    //lua: LuaInterpolatorState<LuaCallbackType>,
}

#[derive(Copy)]
pub struct JNIUndoCallback {
    callback_obj: jobject,
    callback_method: jmethodID,
}

pub struct JNICallbackClosure<'a> {
    undo_callback: &'a JNIUndoCallback,
    env: *mut JNIEnv,
}

impl JNIUndoCallback {
    pub unsafe fn new(env: *mut JNIEnv, obj: jobject) -> JNIUndoCallback {
        let obj = ((**env).NewGlobalRef)(env, obj);
        let objclass = ((**env).GetObjectClass)(env, obj);
        let method = ((**env).GetMethodID)(env, objclass, cstr!("undoBufferChanged"), cstr!("(I)V"));
        JNIUndoCallback { callback_obj: obj, callback_method: method }
    }

    pub unsafe fn call(&self, env: *mut JNIEnv, new_undo_size: i32) {
        ((**env).CallVoidMethod)(env, self.callback_obj, self.callback_method, new_undo_size as jint);
    }

    pub unsafe fn destroy(self, env: *mut JNIEnv) {
        ((**env).DeleteGlobalRef)(env, self.callback_obj);
    }
    pub fn create_closure(&self, env: *mut JNIEnv) -> JNICallbackClosure {
        JNICallbackClosure { undo_callback: self, env: env }
    }
}

impl CaseClass {
    pub unsafe fn new(env: *mut JNIEnv, name: *const c_char, sig: *const c_char) -> CaseClass {
        let class = ((**env).FindClass)(env, name);
        let constructor = ((**env).GetMethodID)(env, class, cstr!("<init>"), sig);
        let globalclass = ((**env).NewGlobalRef)(env, class);

        CaseClass { constructor: constructor, class: globalclass }
    }
    pub unsafe fn construct(&self, env: *mut JNIEnv, arg: &mut [jvalue]) -> jobject {
        ((**env).NewObjectA)(env, self.class, self.constructor, arg.as_mut_ptr())
    }
    pub unsafe fn destroy(&mut self, env: *mut JNIEnv) {
        ((**env).DeleteGlobalRef)(env, self.class);
    }
}

unsafe fn get_string(env: *mut JNIEnv, string: jstring) -> Option<String> {
    let string = try_opt!(string.as_mut());
    let c = try_opt!(((**env).GetStringChars)(env, string, ptr::null_mut()).as_ref());
    let len = ((**env).GetStringLength)(env, string);
    let strslice: &[u16] = mem::transmute(raw::Slice { data: c, len: len as uint });
    let ruststr = String::from_utf16(strslice);
    ((**env).ReleaseStringChars)(env, string as jstring, strslice.as_ptr());
    match ruststr {
        Ok(s) => Some(s),
        Err(_) => None,
    }
}

unsafe fn get_mstring(env: *mut JNIEnv, string: jstring) -> Option<MString> {
    match get_string(env, string) {
        Some(s) => Some(s.into_cow()),
        None => None,
    }
}

unsafe fn str_to_jstring(env: *mut JNIEnv, s: &str) -> jstring {
    let u16msg: Vec<u16> = s.utf16_units().collect();
    ((**env).NewString)(env, u16msg.as_ptr(), u16msg.len() as i32)
}

pub unsafe fn register_classmethods(env: *mut JNIEnv, classname: *const i8, methods: &[JNINativeMethod]) {
    let class = ((**env).FindClass)(env, classname);
    ((**env).RegisterNatives)(env, class, methods.as_ptr(), methods.len() as i32);
}

fn get_safe_data<'a>(data: jpointer) -> &'a mut GLInitEvents<'a> {
    unsafe {
        let data: &'a mut GLInitEvents<'a> = mem::transmute(data);
        assert_eq!(gettid(), data.owning_thread);
        data
    }
}

#[cfg(not(target_word_size = "64"))]
#[inline(always)]
unsafe fn get_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    ((**env).GetIntField)(env, obj, field)
}
#[cfg(target_word_size = "64")]
#[inline(always)]
unsafe fn get_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    ((**env).GetLongField)(env, obj, field)
}

//#[cfg(target_word_size = "32")]
//#[inline(always)]
//fn set_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    //((**env).SetIntField)(env, obj, field)
//}
//#[cfg(target_word_size = "64")]
//#[inline(always)]
//fn set_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    //((**env).SetLongField)(env, obj, field)
//}

fn on_unwind(msg: &(Any + Send), file: &'static str, line: uint) {
    //use core::fmt::FormatWriter;
    // as far as I know there's no way to identify traits that can be cast to Show at runtime
    if let Some(s) = msg.downcast_ref::<&Show>() {
        loge!("fatal error in {}:{} as &Show: {:?}", file, line, s);
    } else if let Some(s) = msg.downcast_ref::<Box<Show>>() {
        loge!("fatal error in {}:{} as Box<Show>: {:?}", file, line, &**s);
    } else if let Some(s) = msg.downcast_ref::<&str>() {
        loge!("fatal error in {}:{} as &str: {}", file, line, s);
    } else if let Some(s) = msg.downcast_ref::<String>() {
        loge!("fatal error in {}:{} as String: {}", file, line, s);
    //} else if let Some(s) = msg.downcast_ref::<Cow<'static, String, &str>>() {
        //loge!("fatal error in {}:{} as MaybeOwned: {}", file, line, s);
    } else {
        loge!("fatal error in {}:{}: unknown error message type {:?}!", file, line, msg.get_type_id());
        loge!("Printing start:");
        unsafe {
            let mut line = String::new();

            let width = 32;
            let raw::TraitObject { data: msgptr, vtable: _ } = mem::transmute(msg);
            let msgslice: &[u8] = mem::transmute(raw::Slice { data: msgptr as *const (), len: 1000 });
            let poscounter = iter::count(msgptr as u32, width as u32);
            for (chunk, pos) in msgslice.chunks(width).zip(poscounter) {
                {
                    let _ = write!(&mut line, "{:08x}: ", pos);
                    for byte in chunk.iter() {
                        let _ = write!(&mut line, "{} ", byte);
                    }
                }
                loge!("{}", line);
                line.clear();
            }
        }
    }
    // Unwinding always fails, but not before messing up android's crash report backtrace.
    // So, commit suicide before that can happen.
    unsafe {
        let null: *mut u16 = ptr::null_mut();
        *null = 0xdead;
    }
}

#[allow(non_snake_case, unused_variables)]
#[no_mangle]
pub unsafe extern "C" fn JNI_OnLoad(vm: *mut JavaVM, reserved: *mut c_void) -> jint {
    logi!("jni onload!!");
    let mut env: *mut c_void = ptr::null_mut();
    if ((**vm).GetEnv)(vm, (&mut env as *mut *mut c_void), JNI_VERSION_1_6) != JNI_OK {
        return -1;
    }
    let env = env as *mut JNIEnv;
    logi!("got environment!: {:?}", env);

    texturethread::init(env);
    texturethread::init(env);
    android_bitmap::init(env);
    gldataclasses::init(env);
    motionevent::init(env);

    GL_EXCEPTION = CaseClass::new(env, cstr!("com/github/wartman4404/gldraw/GLException"), cstr!("(Ljava/lang/String;)V"));

    //rustrt::init(1, ["rustjni".as_ptr()].as_ptr());
    std::rt::unwind::register(on_unwind);

    logi!("finished jni_onload");
    JNI_VERSION_1_2
}

#[allow(non_snake_case, unused_variables)]
#[no_mangle]
pub unsafe extern "C" fn JNI_OnUnload(vm: *mut JavaVM, reserved: *mut c_void) {
    logi!("jni onload!!");
    let mut env: *mut c_void = ptr::null_mut();
    if ((**vm).GetEnv)(vm, (&mut env as *mut *mut c_void), JNI_VERSION_1_6) != JNI_OK {
        return;
    }
    let env = env as *mut JNIEnv;
    texturethread::destroy(env);
    texturethread::destroy(env);
    android_bitmap::destroy(env);
    gldataclasses::destroy(env);
    motionevent::destroy(env);
    GL_EXCEPTION.destroy(env);
}
<commit_msg>don't crash rustc<commit_after>// cannot use core::prelude and still import StrExt needed for utf16_units
use std::prelude::v1::*;
use core::{ptr, mem, raw};
use core::any::Any;
use core::fmt::Writer as FormatWriter;
use core::iter;
use libc::{c_void, c_char};
use core::borrow::IntoCow;

use jni::{jobject, jclass, jmethodID, jfieldID, JNIEnv, jint, jstring, jvalue, JNINativeMethod, JavaVM};
#[cfg(target_word_size = "64")] use jni::jlong;
use jni_constants::*;

use glinit::GLInit;
use drawevent::Events;
use glcommon::MString;
use libc::types::os::arch::posix88::pid_t;

use std;

extern "C" {
    pub fn gettid() -> pid_t;
}

#[macro_use]
mod macros;
pub mod texturethread;
pub mod android_bitmap;
pub mod gldataclasses;
pub mod motionevent;

#[cfg(not(target_word_size = "64"))]
#[allow(non_camel_case_types)]
pub type jpointer = jint;
#[cfg(target_word_size = "64")]
#[allow(non_camel_case_types)]
pub type jpointer = jlong;

struct CaseClass {
    constructor: jmethodID,
    class: jclass,
}

static mut GL_EXCEPTION: CaseClass = CaseClass { constructor: 0 as jmethodID, class: 0 as jclass };

struct GLInitEvents<'a> {
    glinit: GLInit<'a>,
    events: Events<'a>,
    jni_undo_callback: JNIUndoCallback,
    owning_thread: pid_t,
    //lua: LuaInterpolatorState<LuaCallbackType>,
}

#[derive(Copy)]
pub struct JNIUndoCallback {
    callback_obj: jobject,
    callback_method: jmethodID,
}

pub struct JNICallbackClosure<'a> {
    undo_callback: &'a JNIUndoCallback,
    env: *mut JNIEnv,
}

impl JNIUndoCallback {
    pub unsafe fn new(env: *mut JNIEnv, obj: jobject) -> JNIUndoCallback {
        let obj = ((**env).NewGlobalRef)(env, obj);
        let objclass = ((**env).GetObjectClass)(env, obj);
        let method = ((**env).GetMethodID)(env, objclass, cstr!("undoBufferChanged"), cstr!("(I)V"));
        JNIUndoCallback { callback_obj: obj, callback_method: method }
    }

    pub unsafe fn call(&self, env: *mut JNIEnv, new_undo_size: i32) {
        ((**env).CallVoidMethod)(env, self.callback_obj, self.callback_method, new_undo_size as jint);
    }

    pub unsafe fn destroy(self, env: *mut JNIEnv) {
        ((**env).DeleteGlobalRef)(env, self.callback_obj);
    }
    pub fn create_closure(&self, env: *mut JNIEnv) -> JNICallbackClosure {
        JNICallbackClosure { undo_callback: self, env: env }
    }
}

impl CaseClass {
    pub unsafe fn new(env: *mut JNIEnv, name: *const c_char, sig: *const c_char) -> CaseClass {
        let class = ((**env).FindClass)(env, name);
        let constructor = ((**env).GetMethodID)(env, class, cstr!("<init>"), sig);
        let globalclass = ((**env).NewGlobalRef)(env, class);

        CaseClass { constructor: constructor, class: globalclass }
    }
    pub unsafe fn construct(&self, env: *mut JNIEnv, arg: &mut [jvalue]) -> jobject {
        ((**env).NewObjectA)(env, self.class, self.constructor, arg.as_mut_ptr())
    }
    pub unsafe fn destroy(&mut self, env: *mut JNIEnv) {
        ((**env).DeleteGlobalRef)(env, self.class);
    }
}

unsafe fn get_string(env: *mut JNIEnv, string: jstring) -> Option<String> {
    let string = try_opt!(string.as_mut());
    let c = try_opt!(((**env).GetStringChars)(env, string, ptr::null_mut()).as_ref());
    let len = ((**env).GetStringLength)(env, string);
    let strslice: &[u16] = mem::transmute(raw::Slice { data: c, len: len as uint });
    let ruststr = String::from_utf16(strslice);
    ((**env).ReleaseStringChars)(env, string as jstring, strslice.as_ptr());
    match ruststr {
        Ok(s) => Some(s),
        Err(_) => None,
    }
}

unsafe fn get_mstring(env: *mut JNIEnv, string: jstring) -> Option<MString> {
    match get_string(env, string) {
        Some(s) => Some(s.into_cow()),
        None => None,
    }
}

unsafe fn str_to_jstring(env: *mut JNIEnv, s: &str) -> jstring {
    let u16msg: Vec<u16> = s.utf16_units().collect();
    ((**env).NewString)(env, u16msg.as_ptr(), u16msg.len() as i32)
}

pub unsafe fn register_classmethods(env: *mut JNIEnv, classname: *const i8, methods: &[JNINativeMethod]) {
    let class = ((**env).FindClass)(env, classname);
    ((**env).RegisterNatives)(env, class, methods.as_ptr(), methods.len() as i32);
}

fn get_safe_data<'a>(data: jpointer) -> &'a mut GLInitEvents<'a> {
    unsafe {
        let data: &'a mut GLInitEvents<'a> = mem::transmute(data);
        assert_eq!(gettid(), data.owning_thread);
        data
    }
}

#[cfg(not(target_word_size = "64"))]
#[inline(always)]
unsafe fn get_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    ((**env).GetIntField)(env, obj, field)
}
#[cfg(target_word_size = "64")]
#[inline(always)]
unsafe fn get_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    ((**env).GetLongField)(env, obj, field)
}

//#[cfg(target_word_size = "32")]
//#[inline(always)]
//fn set_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    //((**env).SetIntField)(env, obj, field)
//}
//#[cfg(target_word_size = "64")]
//#[inline(always)]
//fn set_jpointer(env: *mut JNIEnv, obj: jobject, field: jfieldID) -> jpointer {
    //((**env).SetLongField)(env, obj, field)
//}

fn on_unwind(msg: &(Any + Send), file: &'static str, line: uint) {
    //use core::fmt::FormatWriter;
    // as far as I know there's no way to identify traits that can be cast to Show at runtime
    // these cause compiler crashes:
    //if let Some(s) = msg.downcast_ref::<&Show>() {
        //loge!("fatal error in {}:{} as &Show: {:?}", file, line, s);
    //if let Some(s) = msg.downcast_ref::<Box<Show>>() {
        //loge!("fatal error in {}:{} as Box<Show>: {:?}", file, line, &**s);
    if let Some(s) = msg.downcast_ref::<&str>() {
        loge!("fatal error in {}:{} as &str: {}", file, line, s);
    } else if let Some(s) = msg.downcast_ref::<String>() {
        loge!("fatal error in {}:{} as String: {}", file, line, s);
    //} else if let Some(s) = msg.downcast_ref::<Cow<'static, String, &str>>() {
        //loge!("fatal error in {}:{} as MaybeOwned: {}", file, line, s);
    } else {
        loge!("fatal error in {}:{}: unknown error message type {:?}!", file, line, msg.get_type_id());
        loge!("Printing start:");
        unsafe {
            let mut line = String::new();

            let width = 32;
            let raw::TraitObject { data: msgptr, vtable: _ } = mem::transmute(msg);
            let msgslice: &[u8] = mem::transmute(raw::Slice { data: msgptr as *const (), len: 1000 });
            let poscounter = iter::count(msgptr as u32, width as u32);
            for (chunk, pos) in msgslice.chunks(width).zip(poscounter) {
                {
                    let _ = write!(&mut line, "{:08x}: ", pos);
                    for byte in chunk.iter() {
                        let _ = write!(&mut line, "{} ", byte);
                    }
                }
                loge!("{}", line);
                line.clear();
            }
        }
    }
    // Unwinding always fails, but not before messing up android's crash report backtrace.
    // So, commit suicide before that can happen.
    unsafe {
        let null: *mut u16 = ptr::null_mut();
        *null = 0xdead;
    }
}

#[allow(non_snake_case, unused_variables)]
#[no_mangle]
pub unsafe extern "C" fn JNI_OnLoad(vm: *mut JavaVM, reserved: *mut c_void) -> jint {
    logi!("jni onload!!");
    let mut env: *mut c_void = ptr::null_mut();
    if ((**vm).GetEnv)(vm, (&mut env as *mut *mut c_void), JNI_VERSION_1_6) != JNI_OK {
        return -1;
    }
    let env = env as *mut JNIEnv;
    logi!("got environment!: {:?}", env);

    texturethread::init(env);
    texturethread::init(env);
    android_bitmap::init(env);
    gldataclasses::init(env);
    motionevent::init(env);

    GL_EXCEPTION = CaseClass::new(env, cstr!("com/github/wartman4404/gldraw/GLException"), cstr!("(Ljava/lang/String;)V"));

    //rustrt::init(1, ["rustjni".as_ptr()].as_ptr());
    std::rt::unwind::register(on_unwind);

    logi!("finished jni_onload");
    JNI_VERSION_1_2
}

#[allow(non_snake_case, unused_variables)]
#[no_mangle]
pub unsafe extern "C" fn JNI_OnUnload(vm: *mut JavaVM, reserved: *mut c_void) {
    logi!("jni onload!!");
    let mut env: *mut c_void = ptr::null_mut();
    if ((**vm).GetEnv)(vm, (&mut env as *mut *mut c_void), JNI_VERSION_1_6) != JNI_OK {
        return;
    }
    let env = env as *mut JNIEnv;
    texturethread::destroy(env);
    texturethread::destroy(env);
    android_bitmap::destroy(env);
    gldataclasses::destroy(env);
    motionevent::destroy(env);
    GL_EXCEPTION.destroy(env);
}
<|endoftext|>


<commit_before>use error::{Error,Result};
use bookoption::BookOption;

use yaml_rust::{Yaml, YamlLoader};
use std::collections::HashMap;
use std::path::PathBuf;
use std::env;

static OPTIONS:&'static str = "
# Metadata
author:str:Anonymous                # The author of the book
title:str:Untitled                  # The title of the book
lang:str:en                         # The language of the book
subject:str                         # Subject of the book (used for EPUB metadata)
description:str                     # Description of the book (used for EPUB metadata)
cover:path                          # File name of the cover of the book
# Output options
output.epub:path                    # Output file name for EPUB rendering
output.html:path                    # Output file name for HTML rendering
output.html_dir:path                # Output directory name for HTML rendering
output.tex:path                     # Output file name for LaTeX rendering
output.pdf:path                     # Output file name for PDF rendering
output.odt:path                     # Output file name for ODT rendering

# Resources option
resources.base_path:path                 # Path where to find resources (in the source tree). By default, links and images are relative to the Markdown file. If this is set, it will be to this path.
resources.base_path.links:path           # Set base path but only for links. Useless if resources.base_path is set.
resources.base_path.images:path:.        # Set base path but only for images. Useless if resources.base_path is set.
resources.base_path.files:path:.         # Set base path but only for additional files. Useless if resources.base_path is set.
resources.out_path:path:data        # Paths where additional resources should be copied in the EPUB file or HTML directory
resources.files:str                 # Whitespace-separated list of files to embed in e.g. EPUB file

# Misc options
enable_yaml_blocks:bool:false       # Enable inline YAML blocks to override options set in config file
zip.command:str:zip                 # Command to use to zip files (for EPUB/ODT)
numbering:int:1                     # The  maximum heading levels to number (0: no numbering, 1: only chapters, ..., 6: all)
display_toc:bool:false              # If true, display a table of content in the document
toc_name:str:Table of contents      # Name of the table of contents if toc is displayed in line
autoclean:bool:true                 # Toggles cleaning of input markdown (not used for LaTeX)
verbose:bool:false                  # If set to true, print warnings in Markdown processing
side_notes:bool:false               # Display footnotes as side notes in HTML/Epub
temp_dir:path:                      # Path where to create a temporary directory (default: uses result from Rust's std::env::temp_dir())
numbering_template:str:\"{{number}}. {{title}}\" # Format of numbered titles
nb_char:char:' '                    # The non-breaking character to use for autoclean when lang is set to fr

# HTML options
html.template:path                  # Path of an HTML template
html.css:path                       # Path of a stylesheet to use with HTML rendering
html.print_css:path                 # Path of a stylesheet to use with media print in HTML rendering
html.script:path                    # Path of a javascript file
html.display_chapter:bool:true      # Display one chapter at a time (with option to dislay all)

# Multifile HTML options
html_dir.script:path                # Path of a javascript file
html_dir.css:path                   # Path of a CSS template
html_dir.index.html:path            # Path of index.html template
html_dir.chapter.html:path          # Path of a chapter.html template

# EPUB options
epub.version:int:2                  # The EPUB version to generate
epub.css:path                       # Path of a stylesheet to use with EPUB rendering
epub.template:path                  # Path of an epub template for chapter

# LaTeX options
tex.short:bool:false                # If set to true, use article class instead of book and a the default `\\maketitle` command
tex.links_as_footnotes:bool:true    # If set to true, will add foontotes to URL of links in LaTeX/PDF output
tex.command:str:pdflatex            # LaTeX flavour to use for generating PDF
tex.template:path                   # Path of a LaTeX template file

# Deprecated options
base_path:alias:resources.base_path  # Renamed
base_path.links:alias:resources.base_path.links  # Renamed
base_path.images:alias:resources.base_path.images  # Renamed
";

/// Contains the options of a book.
#[derive(Debug)]
pub struct BookOptions {
    options: HashMap<String, BookOption>,
    deprecated: HashMap<String, Option<String>>,
    valid_bools: Vec<&'static str>,
    valid_chars: Vec<&'static str>,
    valid_strings: Vec<&'static str>,
    valid_paths: Vec<&'static str>,
    valid_ints: Vec<&'static str>,

    /// Root path of the book (unnecessary copy :/)
    pub root: PathBuf,
}

impl BookOptions {
    /// Creates a new BookOptions struct from the default compliled string
    pub fn new() -> BookOptions {
        let mut options = BookOptions {
            options: HashMap::new(),
            deprecated: HashMap::new(),
            valid_bools:vec!(),
            valid_chars:vec!(),
            valid_ints:vec!(),
            valid_strings:vec!(),
            valid_paths:vec!(),
            root: PathBuf::new(),
        };

        for (_, key, option_type, default_value) in Self::options_to_vec() {
            if key.is_none() {
                continue;
            }
            let key = key.unwrap();
            match option_type.unwrap() {
                "str" => options.valid_strings.push(key),
                "bool" => options.valid_bools.push(key),
                "int" => options.valid_ints.push(key),
                "char" => options.valid_chars.push(key),
                "path" => options.valid_paths.push(key),
                "alias" => {
                    options.deprecated.insert(key.to_owned(), default_value.map(|s| s.to_owned()));
                    continue;
                }
                _ => panic!(format!("Ill-formatted OPTIONS string: unrecognized type '{}'", option_type.unwrap())),
            }
            if key == "temp_dir" {
                // "temp_dir" has a special default value that depends on the environment
                options.set(key, &env::temp_dir().to_string_lossy()).unwrap();
                continue;
            }
            if let Some(value) = default_value {
                options.set(key, value).unwrap();
            }
        }
        options
    }

    /// Sets an option from a Yaml tuple
    ///
    /// # Arguments
    /// * `key`: the identifier of the option, must be Yaml::String(_)
    /// * `value`: the value of the option
    ///
    /// **Returns** an error if `key` is not a String, not a valid option, of if
    /// the value is not of the right type.
    pub fn set_yaml(&mut self, key: Yaml, value: Yaml) -> Result<Option<BookOption>> {
        let key = if let Yaml::String(key) = key {
            key
        } else {
            return Err(Error::BookOption(format!("Expected a String as a key, found {:?}", key)));
        };

        if self.valid_strings.contains(&key.as_ref()) {
            // value is a string
            if let Yaml::String(value) = value {
                Ok(self.options.insert(key, BookOption::String(value)))
            } else {
                Err(Error::BookOption(format!("Expected a string as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_paths.contains(&key.as_ref()) {
            // value is a path
            if let Yaml::String(value) = value {
                Ok(self.options.insert(key, BookOption::Path(value)))
            } else {
                Err(Error::BookOption(format!("Expected a string as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_chars.contains(&key.as_ref()) {
            // value is a char
            if let Yaml::String(value) = value {
                let chars: Vec<_> = value.chars().collect();
                if chars.len() != 1 {
                    return Err(Error::BookOption(format!("could not parse {} as a char: does not contain exactly one char", &value)));
                }
                Ok(self.options.insert(key.to_owned(), BookOption::Char(chars[0])))
            } else {
                Err(Error::BookOption(format!("Expected a string as value containing a char for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_bools.contains(&key.as_ref()) {
            // value is a bool
            if let Yaml::Boolean(value) = value {
                Ok(self.options.insert(key, BookOption::Bool(value)))
            } else {
                Err(Error::BookOption(format!("Expected a boolean as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_ints.contains(&key.as_ref()) {
            // value is an int
            if let Yaml::Integer(value) = value {
                Ok(self.options.insert(key, BookOption::Int(value as i32)))
            } else {
                Err(Error::BookOption(format!("Expected an integer as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.deprecated.contains_key(&key) {
            let opt = self.deprecated.get(&key).unwrap().clone();
            if let Some(new_key) = opt {
                println!("{} has been deprecated, you should now use {}", &key, &new_key);
                self.set_yaml(Yaml::String(new_key), value)
            } else {
                Err(Error::BookOption(format!("key {} has been deprecated.", &key)))
            }
        } else {
            // key not recognized
            Err(Error::BookOption(format!("Unrecognized key: {}", &key)))
        }
    }

    /// Sets an option
    ///
    /// # Arguments
    /// * `key`: the identifier of the option, e.g.: "author"
    /// * `value`: the value of the option as a string
    ///
    /// **Returns** an error either if `key` is not a valid option or if the
    /// value is not of the right type. An option containing None if key was
    /// not set, and Some(previous_value) if key was already present.
    ///
    /// # Examples
    /// ```
    /// use crowbook::Book;
    /// let mut book = Book::new(&[]);
    /// book.options.set("author", "Joan Doe").unwrap(); // ok
    /// book.options.set("numbering", "2").unwrap(); // sets numbering to chapters and subsections
    /// let result = book.options.set("autor", "John Smith");
    /// assert!(result.is_err()); // error: "author" was mispelled "autor"
    ///
    /// let result = book.options.set("numbering", "foo");
    /// assert!(result.is_err()); // error: numbering must be an int
    /// ```
    pub fn set(&mut self, key: &str, value: &str) -> Result<Option<BookOption>> {
        let result = YamlLoader::load_from_str(value);
        if let Ok(yaml_docs) = result {
            if yaml_docs.len() == 1 {
                let yaml_value = yaml_docs.into_iter().next().unwrap();
                self.set_yaml(Yaml::String(key.to_owned()), yaml_value)
            } else {
                Err(Error::BookOption(format!("value {} for key {} does not contain one and only one YAML value", value, key)))
            }
        } else {
            Err(Error::BookOption(format!("could not parse {} as a valid YAML value", value)))
        }
    }

    /// Gets an option
    pub fn get(&self, key: &str) -> Result<&BookOption> {
        self.options.get(key).ok_or_else(|| Error::InvalidOption(format!("option {} is not present", key)))
    }

    /// Gets a list of path. Only used for resources.files.
    pub fn get_paths_list(&self, key: &str) -> Result<Vec<String>> {
        if key != "resources.files" {
            return Err(Error::BookOption(format!("Can't get {} as a list of files, only valid if key is resources.files", key)));
        }

        let list = try!(try!(self.get(key))
                        .as_str())
            .split_whitespace();
        let mut res = vec!();
        for s in list {
            res.push(s.to_owned());
        }
        Ok(res)
    }

    /// Gets a string option
    pub fn get_str(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_str()
    }

    /// Get a path option
    ///
    /// Adds book's root path before it
    pub fn get_path(&self, key: &str) -> Result<String> {
        let path: &str = try!(try!(self.get(key)).as_path());
        let new_path:PathBuf = self.root.join(path);
        if let Some(path) = new_path.to_str() {
            Ok(path.to_owned())
        } else {
            Err(Error::BookOption(format!("'{}''s path contains invalid UTF-8 code", key)))
        }
    }

    /// Get a path option
    ///
    /// Don't add book's root path before it
    pub fn get_relative_path(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_path()
    }

    /// gets a bool option
    pub fn get_bool(&self, key: &str) -> Result<bool> {
        try!(self.get(key)).as_bool()
    }

    /// gets a char option
    pub fn get_char(&self, key: &str) -> Result<char> {
        try!(self.get(key)).as_char()
    }

    /// gets an int  option
    pub fn get_i32(&self, key: &str) -> Result<i32> {
        try!(self.get(key)).as_i32()
    }

    /// Returns a description of all options valid to pass to a book.
    ///
    /// # arguments
    /// * `md`: whether the output should be formatted in Markdown
    pub fn description(md: bool) -> String {
        let mut out = String::new();
        let mut previous_is_comment = true;
        for (comment, key, o_type, default) in Self::options_to_vec() {
            if key.is_none() {
                if !previous_is_comment {
                    out.push_str("\n");
                    previous_is_comment = true;
                }
                out.push_str(&format!("### {} ###\n", comment));
                continue;
            }
            previous_is_comment = false;
            let o_type = match o_type.unwrap() {
                "bool" => "boolean",
                "int" => "integer",
                "char" => "char",
                "str" => "string",
                "path" => "path",
                "alias" => "DEPRECATED",
                _ => unreachable!()
            };
            let def = if let Some(value) = default {
                value
            } else {
                "not set"
            };
            if md {
                out.push_str(&format!("- **`{}`**
    - **type**: {}
    - **default value**: `{}`
    - {}\n", key.unwrap(), o_type, def, comment));
            } else {
                out.push_str(&format!("- {} (type: {}) (default: {}) {}\n", key.unwrap(), o_type, def,comment));
            }
        }
        out
    }

    /// OPTIONS to a vec of tuples (comment, key, type, default value)
    fn options_to_vec() -> Vec<(&'static str, Option<&'static str>,
                                Option<&'static str>, Option<&'static str>)> {
        let mut out = vec!();
        for line in OPTIONS.lines() {
            let line = line.trim();
            if line.is_empty() {
                continue;
            }
            if line.starts_with('#') {
                out.push((&line[1..], None, None, None));
                continue;
            }
            let v:Vec<_> = line.split('#').collect();
            let content = v[0];
            let comment = v[1];
            let v:Vec<_> = content.split(':').collect();
            let key = Some(v[0].trim());
            let option_type = Some(v[1].trim());
            let default_value = if v.len() > 2 {
                Some(v[2].trim())
            } else {
                None
            };
            out.push((comment, key, option_type, default_value));
        }
        out
    }
}
<commit_msg>Deprecate nb_char<commit_after>use error::{Error,Result};
use bookoption::BookOption;

use yaml_rust::{Yaml, YamlLoader};
use std::collections::HashMap;
use std::path::PathBuf;
use std::env;

static OPTIONS:&'static str = "
# Metadata
author:str:Anonymous                # The author of the book
title:str:Untitled                  # The title of the book
lang:str:en                         # The language of the book
subject:str                         # Subject of the book (used for EPUB metadata)
description:str                     # Description of the book (used for EPUB metadata)
cover:path                          # File name of the cover of the book
# Output options
output.epub:path                    # Output file name for EPUB rendering
output.html:path                    # Output file name for HTML rendering
output.html_dir:path                # Output directory name for HTML rendering
output.tex:path                     # Output file name for LaTeX rendering
output.pdf:path                     # Output file name for PDF rendering
output.odt:path                     # Output file name for ODT rendering

# Resources option
resources.base_path:path                 # Path where to find resources (in the source tree). By default, links and images are relative to the Markdown file. If this is set, it will be to this path.
resources.base_path.links:path           # Set base path but only for links. Useless if resources.base_path is set.
resources.base_path.images:path:.        # Set base path but only for images. Useless if resources.base_path is set.
resources.base_path.files:path:.         # Set base path but only for additional files. Useless if resources.base_path is set.
resources.out_path:path:data        # Paths where additional resources should be copied in the EPUB file or HTML directory
resources.files:str                 # Whitespace-separated list of files to embed in e.g. EPUB file

# Misc options
enable_yaml_blocks:bool:false       # Enable inline YAML blocks to override options set in config file
zip.command:str:zip                 # Command to use to zip files (for EPUB/ODT)
numbering:int:1                     # The  maximum heading levels to number (0: no numbering, 1: only chapters, ..., 6: all)
display_toc:bool:false              # If true, display a table of content in the document
toc_name:str:Table of contents      # Name of the table of contents if toc is displayed in line
autoclean:bool:true                 # Toggles cleaning of input markdown (not used for LaTeX)
verbose:bool:false                  # If set to true, print warnings in Markdown processing
side_notes:bool:false               # Display footnotes as side notes in HTML/Epub
temp_dir:path:                      # Path where to create a temporary directory (default: uses result from Rust's std::env::temp_dir())
numbering_template:str:\"{{number}}. {{title}}\" # Format of numbered titles

# HTML options
html.template:path                  # Path of an HTML template
html.css:path                       # Path of a stylesheet to use with HTML rendering
html.print_css:path                 # Path of a stylesheet to use with media print in HTML rendering
html.script:path                    # Path of a javascript file
html.display_chapter:bool:true      # Display one chapter at a time (with option to dislay all)

# Multifile HTML options
html_dir.script:path                # Path of a javascript file
html_dir.css:path                   # Path of a CSS template
html_dir.index.html:path            # Path of index.html template
html_dir.chapter.html:path          # Path of a chapter.html template

# EPUB options
epub.version:int:2                  # The EPUB version to generate
epub.css:path                       # Path of a stylesheet to use with EPUB rendering
epub.template:path                  # Path of an epub template for chapter

# LaTeX options
tex.short:bool:false                # If set to true, use article class instead of book and a the default `\\maketitle` command
tex.links_as_footnotes:bool:true    # If set to true, will add foontotes to URL of links in LaTeX/PDF output
tex.command:str:pdflatex            # LaTeX flavour to use for generating PDF
tex.template:path                   # Path of a LaTeX template file

# Deprecated options
base_path:alias:resources.base_path  # Renamed
base_path.links:alias:resources.base_path.links  # Renamed
base_path.images:alias:resources.base_path.images  # Renamed
nb_char:alias # Removed
";

/// Contains the options of a book.
#[derive(Debug)]
pub struct BookOptions {
    options: HashMap<String, BookOption>,
    deprecated: HashMap<String, Option<String>>,
    valid_bools: Vec<&'static str>,
    valid_chars: Vec<&'static str>,
    valid_strings: Vec<&'static str>,
    valid_paths: Vec<&'static str>,
    valid_ints: Vec<&'static str>,

    /// Root path of the book (unnecessary copy :/)
    pub root: PathBuf,
}

impl BookOptions {
    /// Creates a new BookOptions struct from the default compliled string
    pub fn new() -> BookOptions {
        let mut options = BookOptions {
            options: HashMap::new(),
            deprecated: HashMap::new(),
            valid_bools:vec!(),
            valid_chars:vec!(),
            valid_ints:vec!(),
            valid_strings:vec!(),
            valid_paths:vec!(),
            root: PathBuf::new(),
        };

        for (_, key, option_type, default_value) in Self::options_to_vec() {
            if key.is_none() {
                continue;
            }
            let key = key.unwrap();
            match option_type.unwrap() {
                "str" => options.valid_strings.push(key),
                "bool" => options.valid_bools.push(key),
                "int" => options.valid_ints.push(key),
                "char" => options.valid_chars.push(key),
                "path" => options.valid_paths.push(key),
                "alias" => {
                    options.deprecated.insert(key.to_owned(), default_value.map(|s| s.to_owned()));
                    continue;
                }
                _ => panic!(format!("Ill-formatted OPTIONS string: unrecognized type '{}'", option_type.unwrap())),
            }
            if key == "temp_dir" {
                // "temp_dir" has a special default value that depends on the environment
                options.set(key, &env::temp_dir().to_string_lossy()).unwrap();
                continue;
            }
            if let Some(value) = default_value {
                options.set(key, value).unwrap();
            }
        }
        options
    }

    /// Sets an option from a Yaml tuple
    ///
    /// # Arguments
    /// * `key`: the identifier of the option, must be Yaml::String(_)
    /// * `value`: the value of the option
    ///
    /// **Returns** an error if `key` is not a String, not a valid option, of if
    /// the value is not of the right type.
    pub fn set_yaml(&mut self, key: Yaml, value: Yaml) -> Result<Option<BookOption>> {
        let key = if let Yaml::String(key) = key {
            key
        } else {
            return Err(Error::BookOption(format!("Expected a String as a key, found {:?}", key)));
        };

        if self.valid_strings.contains(&key.as_ref()) {
            // value is a string
            if let Yaml::String(value) = value {
                Ok(self.options.insert(key, BookOption::String(value)))
            } else {
                Err(Error::BookOption(format!("Expected a string as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_paths.contains(&key.as_ref()) {
            // value is a path
            if let Yaml::String(value) = value {
                Ok(self.options.insert(key, BookOption::Path(value)))
            } else {
                Err(Error::BookOption(format!("Expected a string as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_chars.contains(&key.as_ref()) {
            // value is a char
            if let Yaml::String(value) = value {
                let chars: Vec<_> = value.chars().collect();
                if chars.len() != 1 {
                    return Err(Error::BookOption(format!("could not parse {} as a char: does not contain exactly one char", &value)));
                }
                Ok(self.options.insert(key.to_owned(), BookOption::Char(chars[0])))
            } else {
                Err(Error::BookOption(format!("Expected a string as value containing a char for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_bools.contains(&key.as_ref()) {
            // value is a bool
            if let Yaml::Boolean(value) = value {
                Ok(self.options.insert(key, BookOption::Bool(value)))
            } else {
                Err(Error::BookOption(format!("Expected a boolean as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.valid_ints.contains(&key.as_ref()) {
            // value is an int
            if let Yaml::Integer(value) = value {
                Ok(self.options.insert(key, BookOption::Int(value as i32)))
            } else {
                Err(Error::BookOption(format!("Expected an integer as value for key {}, found {:?}", &key, &value)))
            }
        } else if self.deprecated.contains_key(&key) {
            let opt = self.deprecated.get(&key).unwrap().clone();
            if let Some(new_key) = opt {
                println!("{} has been deprecated, you should now use {}", &key, &new_key);
                self.set_yaml(Yaml::String(new_key), value)
            } else {
                Err(Error::BookOption(format!("key {} has been deprecated.", &key)))
            }
        } else {
            // key not recognized
            Err(Error::BookOption(format!("Unrecognized key: {}", &key)))
        }
    }

    /// Sets an option
    ///
    /// # Arguments
    /// * `key`: the identifier of the option, e.g.: "author"
    /// * `value`: the value of the option as a string
    ///
    /// **Returns** an error either if `key` is not a valid option or if the
    /// value is not of the right type. An option containing None if key was
    /// not set, and Some(previous_value) if key was already present.
    ///
    /// # Examples
    /// ```
    /// use crowbook::Book;
    /// let mut book = Book::new(&[]);
    /// book.options.set("author", "Joan Doe").unwrap(); // ok
    /// book.options.set("numbering", "2").unwrap(); // sets numbering to chapters and subsections
    /// let result = book.options.set("autor", "John Smith");
    /// assert!(result.is_err()); // error: "author" was mispelled "autor"
    ///
    /// let result = book.options.set("numbering", "foo");
    /// assert!(result.is_err()); // error: numbering must be an int
    /// ```
    pub fn set(&mut self, key: &str, value: &str) -> Result<Option<BookOption>> {
        let result = YamlLoader::load_from_str(value);
        if let Ok(yaml_docs) = result {
            if yaml_docs.len() == 1 {
                let yaml_value = yaml_docs.into_iter().next().unwrap();
                self.set_yaml(Yaml::String(key.to_owned()), yaml_value)
            } else {
                Err(Error::BookOption(format!("value {} for key {} does not contain one and only one YAML value", value, key)))
            }
        } else {
            Err(Error::BookOption(format!("could not parse {} as a valid YAML value", value)))
        }
    }

    /// Gets an option
    pub fn get(&self, key: &str) -> Result<&BookOption> {
        self.options.get(key).ok_or_else(|| Error::InvalidOption(format!("option {} is not present", key)))
    }

    /// Gets a list of path. Only used for resources.files.
    pub fn get_paths_list(&self, key: &str) -> Result<Vec<String>> {
        if key != "resources.files" {
            return Err(Error::BookOption(format!("Can't get {} as a list of files, only valid if key is resources.files", key)));
        }

        let list = try!(try!(self.get(key))
                        .as_str())
            .split_whitespace();
        let mut res = vec!();
        for s in list {
            res.push(s.to_owned());
        }
        Ok(res)
    }

    /// Gets a string option
    pub fn get_str(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_str()
    }

    /// Get a path option
    ///
    /// Adds book's root path before it
    pub fn get_path(&self, key: &str) -> Result<String> {
        let path: &str = try!(try!(self.get(key)).as_path());
        let new_path:PathBuf = self.root.join(path);
        if let Some(path) = new_path.to_str() {
            Ok(path.to_owned())
        } else {
            Err(Error::BookOption(format!("'{}''s path contains invalid UTF-8 code", key)))
        }
    }

    /// Get a path option
    ///
    /// Don't add book's root path before it
    pub fn get_relative_path(&self, key: &str) -> Result<&str> {
        try!(self.get(key)).as_path()
    }

    /// gets a bool option
    pub fn get_bool(&self, key: &str) -> Result<bool> {
        try!(self.get(key)).as_bool()
    }

    /// gets a char option
    pub fn get_char(&self, key: &str) -> Result<char> {
        try!(self.get(key)).as_char()
    }

    /// gets an int  option
    pub fn get_i32(&self, key: &str) -> Result<i32> {
        try!(self.get(key)).as_i32()
    }

    /// Returns a description of all options valid to pass to a book.
    ///
    /// # arguments
    /// * `md`: whether the output should be formatted in Markdown
    pub fn description(md: bool) -> String {
        let mut out = String::new();
        let mut previous_is_comment = true;
        for (comment, key, o_type, default) in Self::options_to_vec() {
            if key.is_none() {
                if !previous_is_comment {
                    out.push_str("\n");
                    previous_is_comment = true;
                }
                out.push_str(&format!("### {} ###\n", comment));
                continue;
            }
            previous_is_comment = false;
            let o_type = match o_type.unwrap() {
                "bool" => "boolean",
                "int" => "integer",
                "char" => "char",
                "str" => "string",
                "path" => "path",
                "alias" => "DEPRECATED",
                _ => unreachable!()
            };
            let def = if let Some(value) = default {
                value
            } else {
                "not set"
            };
            if md {
                out.push_str(&format!("- **`{}`**
    - **type**: {}
    - **default value**: `{}`
    - {}\n", key.unwrap(), o_type, def, comment));
            } else {
                out.push_str(&format!("- {} (type: {}) (default: {}) {}\n", key.unwrap(), o_type, def,comment));
            }
        }
        out
    }

    /// OPTIONS to a vec of tuples (comment, key, type, default value)
    fn options_to_vec() -> Vec<(&'static str, Option<&'static str>,
                                Option<&'static str>, Option<&'static str>)> {
        let mut out = vec!();
        for line in OPTIONS.lines() {
            let line = line.trim();
            if line.is_empty() {
                continue;
            }
            if line.starts_with('#') {
                out.push((&line[1..], None, None, None));
                continue;
            }
            let v:Vec<_> = line.split('#').collect();
            let content = v[0];
            let comment = v[1];
            let v:Vec<_> = content.split(':').collect();
            let key = Some(v[0].trim());
            let option_type = Some(v[1].trim());
            let default_value = if v.len() > 2 {
                Some(v[2].trim())
            } else {
                None
            };
            out.push((comment, key, option_type, default_value));
        }
        out
    }
}
<|endoftext|>