<commit_before>#[cfg(feature = "backend_session")]
use std::cell::RefCell;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
use std::sync::{atomic::AtomicBool, Arc};
use std::time::{Instant, SystemTime};

use calloop::{EventSource, Interest, Poll, PostAction, Readiness, Token, TokenFactory};
use drm::control::{connector, crtc, Device as ControlDevice, Event, Mode, ResourceHandles};
use drm::{ClientCapability, Device as BasicDevice, DriverCapability};
use nix::libc::dev_t;
use nix::sys::stat::fstat;

pub(super) mod atomic;
pub(super) mod legacy;
use super::surface::{atomic::AtomicDrmSurface, legacy::LegacyDrmSurface, DrmSurface, DrmSurfaceInternal};
use super::{error::Error, planes, Planes};
use atomic::AtomicDrmDevice;
use legacy::LegacyDrmDevice;

use slog::{error, info, o, trace, warn};

/// An open drm device
#[derive(Debug)]
pub struct DrmDevice<A: AsRawFd + 'static> {
    pub(super) dev_id: dev_t,
    pub(crate) internal: Arc<DrmDeviceInternal<A>>,
    #[cfg(feature = "backend_session")]
    pub(super) links: RefCell<Vec<crate::utils::signaling::SignalToken>>,
    has_universal_planes: bool,
    has_monotonic_timestamps: bool,
    resources: ResourceHandles,
    pub(super) logger: ::slog::Logger,
    token: Option<Token>,
}

impl<A: AsRawFd + 'static> AsRawFd for DrmDevice<A> {
    fn as_raw_fd(&self) -> RawFd {
        match &*self.internal {
            DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
            DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
        }
    }
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDevice<A> {}

#[derive(Debug)]
pub struct FdWrapper<A: AsRawFd + 'static> {
    fd: A,
    pub(super) privileged: bool,
    logger: ::slog::Logger,
}

impl<A: AsRawFd + 'static> AsRawFd for FdWrapper<A> {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}
impl<A: AsRawFd + 'static> BasicDevice for FdWrapper<A> {}
impl<A: AsRawFd + 'static> ControlDevice for FdWrapper<A> {}

impl<A: AsRawFd + 'static> Drop for FdWrapper<A> {
    fn drop(&mut self) {
        info!(self.logger, "Dropping device: {:?}", self.dev_path());
        if self.privileged {
            if let Err(err) = self.release_master_lock() {
                error!(self.logger, "Failed to drop drm master state. Error: {}", err);
            }
        }
    }
}

#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum DrmDeviceInternal<A: AsRawFd + 'static> {
    Atomic(AtomicDrmDevice<A>),
    Legacy(LegacyDrmDevice<A>),
}
impl<A: AsRawFd + 'static> AsRawFd for DrmDeviceInternal<A> {
    fn as_raw_fd(&self) -> RawFd {
        match self {
            DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
            DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
        }
    }
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDeviceInternal<A> {}

impl<A: AsRawFd + 'static> DrmDevice<A> {
    /// Create a new [`DrmDevice`] from an open drm node
    ///
    /// # Arguments
    ///
    /// - `fd` - Open drm node
    /// - `disable_connectors` - Setting this to true will initialize all connectors \
    ///     as disabled on device creation. smithay enables connectors, when attached \
    ///     to a surface, and disables them, when detached. Setting this to `false` \
    ///     requires usage of `drm-rs` to disable unused connectors to prevent them \
    ///     showing garbage, but will also prevent flickering of already turned on \
    ///     connectors (assuming you won't change the resolution).
    /// - `logger` - Optional [`slog::Logger`] to be used by this device.
    ///
    /// # Return
    ///
    /// Returns an error if the file is no valid drm node or the device is not accessible.

    pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
    where
        A: AsRawFd + 'static,
        L: Into<Option<::slog::Logger>>,
    {
        let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm"));
        info!(log, "DrmDevice initializing");

        let dev_id = fstat(fd.as_raw_fd()).map_err(Error::UnableToGetDeviceId)?.st_rdev;
        let active = Arc::new(AtomicBool::new(true));
        let dev = Arc::new({
            let mut dev = FdWrapper {
                fd,
                privileged: false,
                logger: log.clone(),
            };

            // We want to modeset, so we better be the master, if we run via a tty session.
            // This is only needed on older kernels. Newer kernels grant this permission,
            // if no other process is already the *master*. So we skip over this error.
            if dev.acquire_master_lock().is_err() {
                warn!(log, "Unable to become drm master, assuming unprivileged mode");
            } else {
                dev.privileged = true;
            }

            dev
        });

        let has_universal_planes = dev
            .set_client_capability(ClientCapability::UniversalPlanes, true)
            .is_ok();
        let has_monotonic_timestamps = dev
            .get_driver_capability(DriverCapability::MonotonicTimestamp)
            .unwrap_or(0)
            == 1;
        let resources = dev.resource_handles().map_err(|source| Error::Access {
            errmsg: "Error loading resource handles",
            dev: dev.dev_path(),
            source,
        })?;
        let internal = Arc::new(DrmDevice::create_internal(
            dev,
            active,
            disable_connectors,
            log.clone(),
        )?);

        Ok(DrmDevice {
            dev_id,
            internal,
            #[cfg(feature = "backend_session")]
            links: RefCell::new(Vec::new()),
            has_universal_planes,
            has_monotonic_timestamps,
            resources,
            logger: log,
            token: None,
        })
    }

    fn create_internal(
        dev: Arc<FdWrapper<A>>,
        active: Arc<AtomicBool>,
        disable_connectors: bool,
        log: ::slog::Logger,
    ) -> Result<DrmDeviceInternal<A>, Error> {
        let force_legacy = std::env::var("SMITHAY_USE_LEGACY")
            .map(|x| {
                x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
            })
            .unwrap_or(false);

        if force_legacy {
            info!(log, "SMITHAY_USE_LEGACY is set. Forcing LegacyDrmDevice.");
        };

        Ok(
            if !force_legacy && dev.set_client_capability(ClientCapability::Atomic, true).is_ok() {
                DrmDeviceInternal::Atomic(AtomicDrmDevice::new(dev, active, disable_connectors, log)?)
            } else {
                info!(log, "Falling back to LegacyDrmDevice");
                DrmDeviceInternal::Legacy(LegacyDrmDevice::new(dev, active, disable_connectors, log)?)
            },
        )
    }

    /// Returns if the underlying implementation uses atomic-modesetting or not.
    pub fn is_atomic(&self) -> bool {
        match *self.internal {
            DrmDeviceInternal::Atomic(_) => true,
            DrmDeviceInternal::Legacy(_) => false,
        }
    }

    /// Returns a list of crtcs for this device
    pub fn crtcs(&self) -> &[crtc::Handle] {
        self.resources.crtcs()
    }

    /// Returns a set of available planes for a given crtc
    pub fn planes(&self, crtc: &crtc::Handle) -> Result<Planes, Error> {
        planes(self, crtc, self.has_universal_planes)
    }

    /// Creates a new rendering surface.
    ///
    /// # Arguments
    ///
    /// Initialization of surfaces happens through the types provided by
    /// [`drm-rs`](drm).
    ///
    /// - [`crtcs`](drm::control::crtc) represent scanout engines of the device pointing to one framebuffer. \
    ///     Their responsibility is to read the data of the framebuffer and export it into an "Encoder". \
    ///     The number of crtc's represent the number of independent output devices the hardware may handle.
    /// - [`mode`](drm::control::Mode) describes the resolution and rate of images produced by the crtc and \
    ///     has to be compatible with the provided `connectors`.
    /// - [`connectors`](drm::control::connector) - List of connectors driven by the crtc. At least one(!) connector needs to be \
    ///     attached to a crtc in smithay.
    pub fn create_surface(
        &self,
        crtc: crtc::Handle,
        mode: Mode,
        connectors: &[connector::Handle],
    ) -> Result<DrmSurface<A>, Error> {
        if connectors.is_empty() {
            return Err(Error::SurfaceWithoutConnectors(crtc));
        }

        let plane = planes(self, &crtc, self.has_universal_planes)?.primary;
        let info = self.get_plane(plane).map_err(|source| Error::Access {
            errmsg: "Failed to get plane info",
            dev: self.dev_path(),
            source,
        })?;
        let filter = info.possible_crtcs();
        if !self.resources.filter_crtcs(filter).contains(&crtc) {
            return Err(Error::PlaneNotCompatible(crtc, plane));
        }

        let active = match &*self.internal {
            DrmDeviceInternal::Atomic(dev) => dev.active.clone(),
            DrmDeviceInternal::Legacy(dev) => dev.active.clone(),
        };

        let internal = if self.is_atomic() {
            let mapping = match &*self.internal {
                DrmDeviceInternal::Atomic(dev) => dev.prop_mapping.clone(),
                _ => unreachable!(),
            };

            DrmSurfaceInternal::Atomic(AtomicDrmSurface::new(
                self.internal.clone(),
                active,
                crtc,
                plane,
                mapping,
                mode,
                connectors,
                self.logger.clone(),
            )?)
        } else {
            DrmSurfaceInternal::Legacy(LegacyDrmSurface::new(
                self.internal.clone(),
                active,
                crtc,
                mode,
                connectors,
                self.logger.clone(),
            )?)
        };

        Ok(DrmSurface {
            dev_id: self.dev_id,
            crtc,
            primary: plane,
            internal: Arc::new(internal),
            has_universal_planes: self.has_universal_planes,
            #[cfg(feature = "backend_session")]
            links: RefCell::new(Vec::new()),
        })
    }

    /// Returns the device_id of the underlying drm node
    pub fn device_id(&self) -> dev_t {
        self.dev_id
    }
}

/// Trait representing open devices that *may* return a `Path`
pub trait DevPath {
    /// Returns the path of the open device if possible
    fn dev_path(&self) -> Option<PathBuf>;
}

impl<A: AsRawFd> DevPath for A {
    fn dev_path(&self) -> Option<PathBuf> {
        use std::fs;

        fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
    }
}

/// Events that can be generated by a DrmDevice
#[derive(Debug)]
pub enum DrmEvent {
    /// A vblank blank event on the provided crtc has happened
    VBlank(crtc::Handle),
    /// An error happened while processing events
    Error(Error),
}

/// Timing metadata for page-flip events
#[derive(Debug)]
pub struct EventMetadata {
    /// The time the frame flip happend
    pub time: Time,
    /// The sequence number of the frame
    pub sequence: u32,
}

/// Either a realtime or monotonic timestamp
#[derive(Debug)]
pub enum Time {
    /// Monotonic time stamp
    Monotonic(Instant),
    /// Realtime time stamp
    Realtime(SystemTime),
}

impl<A> EventSource for DrmDevice<A>
where
    A: AsRawFd + 'static,
{
    type Event = DrmEvent;
    type Metadata = Option<EventMetadata>;
    type Ret = ();
    type Error = io::Error;

    fn process_events<F>(&mut self, _: Readiness, token: Token, mut callback: F) -> io::Result<PostAction>
    where
        F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
    {
        if Some(token) != self.token {
            return Ok(PostAction::Continue);
        }
        match self.receive_events() {
            Ok(events) => {
                for event in events {
                    if let Event::PageFlip(event) = event {
                        trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc);
                        let metadata = EventMetadata {
                            time: if self.has_monotonic_timestamps {
                                // There is no way to create an Instant, although the underlying type on unix systems
                                // is just libc::timespec, which is literally what drm-rs is getting from the kernel and just converting
                                // into a Duration. So we cheat and initialize a Zero-Instant (because although Instant::ZERO
                                // exists, its private, so you cannot create abitrary Instants). What we really need is a unix-Ext
                                // trait for both SystemTime and Instant to convert from a libc::timespec.
                                //
                                // But this works for now, although it is quite the hack.
                                Time::Monotonic(unsafe { std::mem::zeroed::<Instant>() } + event.duration)
                            } else {
                                Time::Realtime(SystemTime::UNIX_EPOCH + event.duration)
                            },
                            sequence: event.frame,
                        };
                        callback(DrmEvent::VBlank(event.crtc), &mut Some(metadata));
                    } else {
                        trace!(
                            self.logger,
                            "Got a non-page-flip event of device '{:?}'.",
                            self.dev_path()
                        );
                    }
                }
            }
            Err(source) => {
                callback(
                    DrmEvent::Error(Error::Access {
                        errmsg: "Error processing drm events",
                        dev: self.dev_path(),
                        source,
                    }),
                    &mut None,
                );
            }
        }
        Ok(PostAction::Continue)
    }

    fn register(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> calloop::Result<()> {
        self.token = Some(factory.token());
        poll.register(
            self.as_raw_fd(),
            Interest::READ,
            calloop::Mode::Level,
            self.token.unwrap(),
        )
    }

    fn reregister(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> calloop::Result<()> {
        self.token = Some(factory.token());
        poll.reregister(
            self.as_raw_fd(),
            Interest::READ,
            calloop::Mode::Level,
            self.token.unwrap(),
        )
    }

    fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
        self.token = None;
        poll.unregister(self.as_raw_fd())
    }
}
<commit_msg>drm: expose the size of the hardware cursor<commit_after>#[cfg(feature = "backend_session")]
use std::cell::RefCell;
use std::io;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::PathBuf;
use std::sync::{atomic::AtomicBool, Arc};
use std::time::{Instant, SystemTime};

use calloop::{EventSource, Interest, Poll, PostAction, Readiness, Token, TokenFactory};
use drm::control::{connector, crtc, Device as ControlDevice, Event, Mode, ResourceHandles};
use drm::{ClientCapability, Device as BasicDevice, DriverCapability};
use nix::libc::dev_t;
use nix::sys::stat::fstat;

pub(super) mod atomic;
pub(super) mod legacy;
use crate::utils::{Physical, Size};

use super::surface::{atomic::AtomicDrmSurface, legacy::LegacyDrmSurface, DrmSurface, DrmSurfaceInternal};
use super::{error::Error, planes, Planes};
use atomic::AtomicDrmDevice;
use legacy::LegacyDrmDevice;

use slog::{error, info, o, trace, warn};

/// An open drm device
#[derive(Debug)]
pub struct DrmDevice<A: AsRawFd + 'static> {
    pub(super) dev_id: dev_t,
    pub(crate) internal: Arc<DrmDeviceInternal<A>>,
    #[cfg(feature = "backend_session")]
    pub(super) links: RefCell<Vec<crate::utils::signaling::SignalToken>>,
    has_universal_planes: bool,
    has_monotonic_timestamps: bool,
    cursor_size: Size<u32, Physical>,
    resources: ResourceHandles,
    pub(super) logger: ::slog::Logger,
    token: Option<Token>,
}

impl<A: AsRawFd + 'static> AsRawFd for DrmDevice<A> {
    fn as_raw_fd(&self) -> RawFd {
        match &*self.internal {
            DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
            DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
        }
    }
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDevice<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDevice<A> {}

#[derive(Debug)]
pub struct FdWrapper<A: AsRawFd + 'static> {
    fd: A,
    pub(super) privileged: bool,
    logger: ::slog::Logger,
}

impl<A: AsRawFd + 'static> AsRawFd for FdWrapper<A> {
    fn as_raw_fd(&self) -> RawFd {
        self.fd.as_raw_fd()
    }
}
impl<A: AsRawFd + 'static> BasicDevice for FdWrapper<A> {}
impl<A: AsRawFd + 'static> ControlDevice for FdWrapper<A> {}

impl<A: AsRawFd + 'static> Drop for FdWrapper<A> {
    fn drop(&mut self) {
        info!(self.logger, "Dropping device: {:?}", self.dev_path());
        if self.privileged {
            if let Err(err) = self.release_master_lock() {
                error!(self.logger, "Failed to drop drm master state. Error: {}", err);
            }
        }
    }
}

#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum DrmDeviceInternal<A: AsRawFd + 'static> {
    Atomic(AtomicDrmDevice<A>),
    Legacy(LegacyDrmDevice<A>),
}
impl<A: AsRawFd + 'static> AsRawFd for DrmDeviceInternal<A> {
    fn as_raw_fd(&self) -> RawFd {
        match self {
            DrmDeviceInternal::Atomic(dev) => dev.fd.as_raw_fd(),
            DrmDeviceInternal::Legacy(dev) => dev.fd.as_raw_fd(),
        }
    }
}
impl<A: AsRawFd + 'static> BasicDevice for DrmDeviceInternal<A> {}
impl<A: AsRawFd + 'static> ControlDevice for DrmDeviceInternal<A> {}

impl<A: AsRawFd + 'static> DrmDevice<A> {
    /// Create a new [`DrmDevice`] from an open drm node
    ///
    /// # Arguments
    ///
    /// - `fd` - Open drm node
    /// - `disable_connectors` - Setting this to true will initialize all connectors \
    ///     as disabled on device creation. smithay enables connectors, when attached \
    ///     to a surface, and disables them, when detached. Setting this to `false` \
    ///     requires usage of `drm-rs` to disable unused connectors to prevent them \
    ///     showing garbage, but will also prevent flickering of already turned on \
    ///     connectors (assuming you won't change the resolution).
    /// - `logger` - Optional [`slog::Logger`] to be used by this device.
    ///
    /// # Return
    ///
    /// Returns an error if the file is no valid drm node or the device is not accessible.

    pub fn new<L>(fd: A, disable_connectors: bool, logger: L) -> Result<Self, Error>
    where
        A: AsRawFd + 'static,
        L: Into<Option<::slog::Logger>>,
    {
        let log = crate::slog_or_fallback(logger).new(o!("smithay_module" => "backend_drm"));
        info!(log, "DrmDevice initializing");

        let dev_id = fstat(fd.as_raw_fd()).map_err(Error::UnableToGetDeviceId)?.st_rdev;
        let active = Arc::new(AtomicBool::new(true));
        let dev = Arc::new({
            let mut dev = FdWrapper {
                fd,
                privileged: false,
                logger: log.clone(),
            };

            // We want to modeset, so we better be the master, if we run via a tty session.
            // This is only needed on older kernels. Newer kernels grant this permission,
            // if no other process is already the *master*. So we skip over this error.
            if dev.acquire_master_lock().is_err() {
                warn!(log, "Unable to become drm master, assuming unprivileged mode");
            } else {
                dev.privileged = true;
            }

            dev
        });

        let has_universal_planes = dev
            .set_client_capability(ClientCapability::UniversalPlanes, true)
            .is_ok();
        let has_monotonic_timestamps = dev
            .get_driver_capability(DriverCapability::MonotonicTimestamp)
            .unwrap_or(0)
            == 1;
        let cursor_width = dev
            .get_driver_capability(DriverCapability::CursorWidth)
            .unwrap_or(64);
        let cursor_height = dev
            .get_driver_capability(DriverCapability::CursorHeight)
            .unwrap_or(64);
        let cursor_size = Size::from((cursor_width as u32, cursor_height as u32));
        let resources = dev.resource_handles().map_err(|source| Error::Access {
            errmsg: "Error loading resource handles",
            dev: dev.dev_path(),
            source,
        })?;
        let internal = Arc::new(DrmDevice::create_internal(
            dev,
            active,
            disable_connectors,
            log.clone(),
        )?);

        Ok(DrmDevice {
            dev_id,
            internal,
            #[cfg(feature = "backend_session")]
            links: RefCell::new(Vec::new()),
            has_universal_planes,
            has_monotonic_timestamps,
            cursor_size,
            resources,
            logger: log,
            token: None,
        })
    }

    fn create_internal(
        dev: Arc<FdWrapper<A>>,
        active: Arc<AtomicBool>,
        disable_connectors: bool,
        log: ::slog::Logger,
    ) -> Result<DrmDeviceInternal<A>, Error> {
        let force_legacy = std::env::var("SMITHAY_USE_LEGACY")
            .map(|x| {
                x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
            })
            .unwrap_or(false);

        if force_legacy {
            info!(log, "SMITHAY_USE_LEGACY is set. Forcing LegacyDrmDevice.");
        };

        Ok(
            if !force_legacy && dev.set_client_capability(ClientCapability::Atomic, true).is_ok() {
                DrmDeviceInternal::Atomic(AtomicDrmDevice::new(dev, active, disable_connectors, log)?)
            } else {
                info!(log, "Falling back to LegacyDrmDevice");
                DrmDeviceInternal::Legacy(LegacyDrmDevice::new(dev, active, disable_connectors, log)?)
            },
        )
    }

    /// Returns if the underlying implementation uses atomic-modesetting or not.
    pub fn is_atomic(&self) -> bool {
        match *self.internal {
            DrmDeviceInternal::Atomic(_) => true,
            DrmDeviceInternal::Legacy(_) => false,
        }
    }

    /// Returns a list of crtcs for this device
    pub fn crtcs(&self) -> &[crtc::Handle] {
        self.resources.crtcs()
    }

    /// Returns a set of available planes for a given crtc
    pub fn planes(&self, crtc: &crtc::Handle) -> Result<Planes, Error> {
        planes(self, crtc, self.has_universal_planes)
    }

    /// Returns the size of the hardware cursor
    ///
    /// Note: In case of universal planes this is the
    /// maximum size of a buffer that can be used on
    /// the cursor plane.
    pub fn cursor_size(&self) -> Size<u32, Physical> {
        self.cursor_size
    }

    /// Creates a new rendering surface.
    ///
    /// # Arguments
    ///
    /// Initialization of surfaces happens through the types provided by
    /// [`drm-rs`](drm).
    ///
    /// - [`crtcs`](drm::control::crtc) represent scanout engines of the device pointing to one framebuffer. \
    ///     Their responsibility is to read the data of the framebuffer and export it into an "Encoder". \
    ///     The number of crtc's represent the number of independent output devices the hardware may handle.
    /// - [`mode`](drm::control::Mode) describes the resolution and rate of images produced by the crtc and \
    ///     has to be compatible with the provided `connectors`.
    /// - [`connectors`](drm::control::connector) - List of connectors driven by the crtc. At least one(!) connector needs to be \
    ///     attached to a crtc in smithay.
    pub fn create_surface(
        &self,
        crtc: crtc::Handle,
        mode: Mode,
        connectors: &[connector::Handle],
    ) -> Result<DrmSurface<A>, Error> {
        if connectors.is_empty() {
            return Err(Error::SurfaceWithoutConnectors(crtc));
        }

        let plane = planes(self, &crtc, self.has_universal_planes)?.primary;
        let info = self.get_plane(plane).map_err(|source| Error::Access {
            errmsg: "Failed to get plane info",
            dev: self.dev_path(),
            source,
        })?;
        let filter = info.possible_crtcs();
        if !self.resources.filter_crtcs(filter).contains(&crtc) {
            return Err(Error::PlaneNotCompatible(crtc, plane));
        }

        let active = match &*self.internal {
            DrmDeviceInternal::Atomic(dev) => dev.active.clone(),
            DrmDeviceInternal::Legacy(dev) => dev.active.clone(),
        };

        let internal = if self.is_atomic() {
            let mapping = match &*self.internal {
                DrmDeviceInternal::Atomic(dev) => dev.prop_mapping.clone(),
                _ => unreachable!(),
            };

            DrmSurfaceInternal::Atomic(AtomicDrmSurface::new(
                self.internal.clone(),
                active,
                crtc,
                plane,
                mapping,
                mode,
                connectors,
                self.logger.clone(),
            )?)
        } else {
            DrmSurfaceInternal::Legacy(LegacyDrmSurface::new(
                self.internal.clone(),
                active,
                crtc,
                mode,
                connectors,
                self.logger.clone(),
            )?)
        };

        Ok(DrmSurface {
            dev_id: self.dev_id,
            crtc,
            primary: plane,
            internal: Arc::new(internal),
            has_universal_planes: self.has_universal_planes,
            #[cfg(feature = "backend_session")]
            links: RefCell::new(Vec::new()),
        })
    }

    /// Returns the device_id of the underlying drm node
    pub fn device_id(&self) -> dev_t {
        self.dev_id
    }
}

/// Trait representing open devices that *may* return a `Path`
pub trait DevPath {
    /// Returns the path of the open device if possible
    fn dev_path(&self) -> Option<PathBuf>;
}

impl<A: AsRawFd> DevPath for A {
    fn dev_path(&self) -> Option<PathBuf> {
        use std::fs;

        fs::read_link(format!("/proc/self/fd/{:?}", self.as_raw_fd())).ok()
    }
}

/// Events that can be generated by a DrmDevice
#[derive(Debug)]
pub enum DrmEvent {
    /// A vblank blank event on the provided crtc has happened
    VBlank(crtc::Handle),
    /// An error happened while processing events
    Error(Error),
}

/// Timing metadata for page-flip events
#[derive(Debug)]
pub struct EventMetadata {
    /// The time the frame flip happend
    pub time: Time,
    /// The sequence number of the frame
    pub sequence: u32,
}

/// Either a realtime or monotonic timestamp
#[derive(Debug)]
pub enum Time {
    /// Monotonic time stamp
    Monotonic(Instant),
    /// Realtime time stamp
    Realtime(SystemTime),
}

impl<A> EventSource for DrmDevice<A>
where
    A: AsRawFd + 'static,
{
    type Event = DrmEvent;
    type Metadata = Option<EventMetadata>;
    type Ret = ();
    type Error = io::Error;

    fn process_events<F>(&mut self, _: Readiness, token: Token, mut callback: F) -> io::Result<PostAction>
    where
        F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
    {
        if Some(token) != self.token {
            return Ok(PostAction::Continue);
        }
        match self.receive_events() {
            Ok(events) => {
                for event in events {
                    if let Event::PageFlip(event) = event {
                        trace!(self.logger, "Got a page-flip event for crtc ({:?})", event.crtc);
                        let metadata = EventMetadata {
                            time: if self.has_monotonic_timestamps {
                                // There is no way to create an Instant, although the underlying type on unix systems
                                // is just libc::timespec, which is literally what drm-rs is getting from the kernel and just converting
                                // into a Duration. So we cheat and initialize a Zero-Instant (because although Instant::ZERO
                                // exists, its private, so you cannot create abitrary Instants). What we really need is a unix-Ext
                                // trait for both SystemTime and Instant to convert from a libc::timespec.
                                //
                                // But this works for now, although it is quite the hack.
                                Time::Monotonic(unsafe { std::mem::zeroed::<Instant>() } + event.duration)
                            } else {
                                Time::Realtime(SystemTime::UNIX_EPOCH + event.duration)
                            },
                            sequence: event.frame,
                        };
                        callback(DrmEvent::VBlank(event.crtc), &mut Some(metadata));
                    } else {
                        trace!(
                            self.logger,
                            "Got a non-page-flip event of device '{:?}'.",
                            self.dev_path()
                        );
                    }
                }
            }
            Err(source) => {
                callback(
                    DrmEvent::Error(Error::Access {
                        errmsg: "Error processing drm events",
                        dev: self.dev_path(),
                        source,
                    }),
                    &mut None,
                );
            }
        }
        Ok(PostAction::Continue)
    }

    fn register(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> calloop::Result<()> {
        self.token = Some(factory.token());
        poll.register(
            self.as_raw_fd(),
            Interest::READ,
            calloop::Mode::Level,
            self.token.unwrap(),
        )
    }

    fn reregister(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> calloop::Result<()> {
        self.token = Some(factory.token());
        poll.reregister(
            self.as_raw_fd(),
            Interest::READ,
            calloop::Mode::Level,
            self.token.unwrap(),
        )
    }

    fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
        self.token = None;
        poll.unregister(self.as_raw_fd())
    }
}
<|endoftext|>


<commit_before>extern crate postgres;
extern crate getopts;

use postgres::{Connection, TlsMode};

use std::env;
use getopts::Options;
use std::process;

extern crate pombase;

use pombase::db::*;
use pombase::web::config::*;
use pombase::web::data_build::*;
use pombase::interpro::parse_interpro;

const PKG_NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn main() {
    print!("{} v{}\n", PKG_NAME, VERSION);

    let args: Vec<String> = env::args().collect();
    let mut opts = Options::new();

    opts.optflag("h", "help", "print this help message");
    opts.optopt("c", "config-file", "Configuration file name", "CONFIG");
    opts.optopt("p", "postgresql-connection-string",
                "PostgresSQL connection string like: postgres://user:pass@host/db_name",
                "CONN_STR");
    opts.optopt("i", "interpro-data-file",
                "The name of the file generated by 'pombase-interpro'", "FILE");
    opts.optopt("d", "output-directory",
                "Destination directory for the output", "DIR");
    opts.optflag("j", "store-json",
                 "optionally create a 'web_json' schema to store the generated JSON in the database");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!("Invalid options\n{}", f)
    };

    let program = args[0].clone();

    if matches.opt_present("help") {
        print_usage(&program, opts);
        process::exit(0);
    }

    if !matches.opt_present("config-file") {
        print!("no -c|--config-file option\n");
        print_usage(&program, opts);
        process::exit(1);
    }
    if !matches.opt_present("postgresql-connection-string") {
        print!("no -p|--postgresql-connection-string option\n");
        print_usage(&program, opts);
        process::exit(1);
    }
    if !matches.opt_present("output-directory") {
        print!("no -d|--output-directory option\n");
        print_usage(&program, opts);
        process::exit(1);
    }

    let config = Config::read(&matches.opt_str("c").unwrap());
    let connection_string = matches.opt_str("p").unwrap();
    let interpro_json = matches.opt_str("i").unwrap();
    let output_dir = matches.opt_str("d").unwrap();

    let conn = Connection::connect(connection_string.as_str(), TlsMode::None).unwrap();

    let raw = Raw::new(&conn);
    let interpro_data = parse_interpro(&config, &interpro_json);
    let web_data_build = WebDataBuild::new(&raw, &interpro_data, &config);
    let web_data = web_data_build.get_web_data();

    match web_data.write(&config, &output_dir) {
        Ok(_) => (),
        Err(e) => {
            panic!("error while writing: {}", e);
        },
    }

    if matches.opt_present("store-json") {
        conn.execute("DROP SCHEMA IF EXISTS web_json CASCADE", &[]).unwrap();
        conn.execute("CREATE SCHEMA web_json", &[]).unwrap();
        conn.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;", &[]).unwrap();
        conn.execute("CREATE TABLE web_json.gene (uniquename TEXT, data JSONB)", &[]).unwrap();
        conn.execute("CREATE TABLE web_json.term (termid TEXT, data JSONB)", &[]).unwrap();
        conn.execute("CREATE TABLE web_json.reference (uniquename TEXT, data JSONB)", &[]).unwrap();

        web_data.store_jsonb(&conn);

        print!("stored results as JSONB using {}\n", &connection_string);
    }
}
<commit_msg>Better message when DB connection fails<commit_after>extern crate postgres;
extern crate getopts;

use postgres::{Connection, TlsMode};

use std::env;
use getopts::Options;
use std::process;

extern crate pombase;

use pombase::db::*;
use pombase::web::config::*;
use pombase::web::data_build::*;
use pombase::interpro::parse_interpro;

const PKG_NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(&brief));
}

fn main() {
    print!("{} v{}\n", PKG_NAME, VERSION);

    let args: Vec<String> = env::args().collect();
    let mut opts = Options::new();

    opts.optflag("h", "help", "print this help message");
    opts.optopt("c", "config-file", "Configuration file name", "CONFIG");
    opts.optopt("p", "postgresql-connection-string",
                "PostgresSQL connection string like: postgres://user:pass@host/db_name",
                "CONN_STR");
    opts.optopt("i", "interpro-data-file",
                "The name of the file generated by 'pombase-interpro'", "FILE");
    opts.optopt("d", "output-directory",
                "Destination directory for the output", "DIR");
    opts.optflag("j", "store-json",
                 "optionally create a 'web_json' schema to store the generated JSON in the database");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!("Invalid options\n{}", f)
    };

    let program = args[0].clone();

    if matches.opt_present("help") {
        print_usage(&program, opts);
        process::exit(0);
    }

    if !matches.opt_present("config-file") {
        print!("no -c|--config-file option\n");
        print_usage(&program, opts);
        process::exit(1);
    }
    if !matches.opt_present("postgresql-connection-string") {
        print!("no -p|--postgresql-connection-string option\n");
        print_usage(&program, opts);
        process::exit(1);
    }
    if !matches.opt_present("output-directory") {
        print!("no -d|--output-directory option\n");
        print_usage(&program, opts);
        process::exit(1);
    }

    let config = Config::read(&matches.opt_str("c").unwrap());
    let connection_string = matches.opt_str("p").unwrap();
    let interpro_json = matches.opt_str("i").unwrap();
    let output_dir = matches.opt_str("d").unwrap();

    let conn = match Connection::connect(connection_string.as_str(), TlsMode::None) {
        Ok(conn) => conn,
        Err(err) => panic!("failed to connect using: {}, err: {}", connection_string, err)
    };

    let raw = Raw::new(&conn);
    let interpro_data = parse_interpro(&config, &interpro_json);
    let web_data_build = WebDataBuild::new(&raw, &interpro_data, &config);
    let web_data = web_data_build.get_web_data();

    match web_data.write(&config, &output_dir) {
        Ok(_) => (),
        Err(e) => {
            panic!("error while writing: {}", e);
        },
    }

    if matches.opt_present("store-json") {
        conn.execute("DROP SCHEMA IF EXISTS web_json CASCADE", &[]).unwrap();
        conn.execute("CREATE SCHEMA web_json", &[]).unwrap();
        conn.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm;", &[]).unwrap();
        conn.execute("CREATE TABLE web_json.gene (uniquename TEXT, data JSONB)", &[]).unwrap();
        conn.execute("CREATE TABLE web_json.term (termid TEXT, data JSONB)", &[]).unwrap();
        conn.execute("CREATE TABLE web_json.reference (uniquename TEXT, data JSONB)", &[]).unwrap();

        web_data.store_jsonb(&conn);

        print!("stored results as JSONB using {}\n", &connection_string);
    }
}
<|endoftext|>


<commit_before>use std::path::Path;

use async_trait::async_trait;
use indexmap::IndexMap;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json;

use crate::{
    build::BuildStep,
    bundle::{
        layout::{ios::IOsKbdLayer, IOsPlatform},
        KbdgenBundle,
    },
    util::split_keys,
};

#[derive(Serialize, Deserialize)]
pub struct IosInfo {
    name: String,
    locale: String,
    enter: String,
    space: String,
}

pub fn keyboard_entity_from_string(input: String) -> Option<IosButton> {
    let regex = Regex::new(r"^\\s\{([^}:]+)(?::(\d+(?:\.\d+)?))?\}$").expect("valid regex");
    let captures = regex.captures(input.as_str());

    if let Some(captures) = captures {
        if let Some(id) = captures.get(1) {
            let id = format!("_{}", id.as_str());
            if let Some(width) = captures.get(2) {
                Some(IosButton {
                    id: id,
                    width: width.as_str().parse::<f32>().expect("invalid float value"),
                })
            } else {
                Some(IosButton {
                    id: id,
                    width: 1.0f32,
                })
            }
        } else {
            return None;
        }
    } else {
        return None;
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct IosButton {
    id: String,
    width: f32,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum IosKeyMapTypes {
    Character(String),
    Button(IosButton),
}

#[derive(Serialize, Deserialize)]
pub struct IosNormalLayer {
    #[serde(flatten)]
    layer: IndexMap<String, Vec<Vec<IosKeyMapTypes>>>,
}

#[derive(Serialize, Deserialize)]
pub struct IosDeadKeys {
    iphone: IndexMap<String, String>,
    #[serde(rename = "ipad-9in")]
    i_pad_9in: IndexMap<String, String>,
    #[serde(rename = "ipad-12in")]
    i_pad_12in: IndexMap<String, String>,
}

#[derive(Serialize, Deserialize)]
pub struct IosKeyboardDefinitions {
    #[serde(flatten)]
    info: IosInfo,
    longpress: IndexMap<String, Vec<String>>,
    dead_keys: IosDeadKeys,
    transforms: serde_json::value::Value,
    iphone: IosNormalLayer,
    i_pad_9in: IosNormalLayer,
    i_pad_12in: IosNormalLayer,
}

pub fn enum_to_output(layer: &IOsKbdLayer) -> String {
    match layer {
        IOsKbdLayer::Default => "normal",
        IOsKbdLayer::Shift => "shifted",
        IOsKbdLayer::Alt => "alt",
        IOsKbdLayer::AltAndShift => "alt+shift",
        IOsKbdLayer::Symbols1 => "symbols-1",
        IOsKbdLayer::Symbols2 => "symbols-2",
    }
    .to_string()
}

pub fn generate_platform(platform: &IOsPlatform) -> IndexMap<String, Vec<Vec<IosKeyMapTypes>>> {
    let mut layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();
    for (layer_name, layer_key_map) in &platform.layers {
        let layer_name = enum_to_output(layer_name);
        let key_map_rows: Vec<&str> = layer_key_map
            .trim()
            .split("\n")
            .map(|x| x.clone())
            .collect();
        let mut layer_rows: Vec<Vec<IosKeyMapTypes>> = Vec::new();
        for key_map in key_map_rows {
            let key_map = split_keys(key_map);
            let mut new_key_map: Vec<IosKeyMapTypes> = Vec::new();
            for key in key_map {
                if let Some(keyboard_entity) = keyboard_entity_from_string(key.clone()) {
                    new_key_map.push(IosKeyMapTypes::Button(keyboard_entity));
                } else {
                    new_key_map.push(IosKeyMapTypes::Character(key));
                }
            }
            layer_rows.push(new_key_map)
        }
        layers.insert(layer_name, layer_rows);
    }
    layers
}

pub struct GenerateIos;

#[async_trait(?Send)]
impl BuildStep for GenerateIos {
    async fn build(&self, bundle: &KbdgenBundle, output_path: &Path) {
        let keyboard_definitions_file_path = output_path.join("KeyboardDefinitions.json");

        for (language_tag, layout) in &bundle.layouts {
            let mut longpress: IndexMap<String, Vec<String>> = IndexMap::new();
            let mut iphone_layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();
            let mut i_pad_9in_layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();
            let mut i_pad_12in_layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();

            if let Some(ios_target) = &layout.i_os {
                if let Some(layout_longpress) = &layout.longpress {
                    for (key, value) in layout_longpress {
                        longpress.insert(key.clone(), value.clone());
                    }
                }

                if let Some(primary_platform) = &ios_target.primary {
                    iphone_layers.extend(generate_platform(&primary_platform));
                }
                if let Some(i_pad_9in_platform) = &ios_target.i_pad_9in {
                    i_pad_9in_layers.extend(generate_platform(&i_pad_9in_platform));
                }
                if let Some(i_pad_12in_platform) = &ios_target.i_pad_12in {
                    i_pad_12in_layers.extend(generate_platform(&i_pad_12in_platform));
                }

                if let Some(key_names) = &layout.key_names {
                    std::fs::write(
                        output_path.join(keyboard_definitions_file_path.clone()),
                        serde_json::to_string_pretty(&[IosKeyboardDefinitions {
                            info: IosInfo {
                                name: layout
                                    .display_names
                                    .get(language_tag)
                                    .expect("can't evaluate language tag of layout")
                                    .to_string(),
                                locale: language_tag.to_string(),
                                enter: key_names.r#return.to_string(),
                                space: key_names.space.to_string(),
                            },
                            longpress: longpress,
                            dead_keys: IosDeadKeys {
                                iphone: IndexMap::new(),
                                i_pad_9in: IndexMap::new(),
                                i_pad_12in: IndexMap::new(),
                            },
                            transforms: serde_json::value::Value::Null,
                            iphone: IosNormalLayer {
                                layer: iphone_layers,
                            },
                            i_pad_9in: IosNormalLayer {
                                layer: i_pad_9in_layers,
                            },
                            i_pad_12in: IosNormalLayer {
                                layer: i_pad_12in_layers,
                            },
                        }])
                        .unwrap(),
                    )
                    .unwrap();
                }
            }
        }
    }
}
<commit_msg>handle quotation marks from yaml<commit_after>use std::{path::Path, cmp::Ordering, str::Chars};

use async_trait::async_trait;
use indexmap::IndexMap;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json;

use crate::{
    build::BuildStep,
    bundle::{
        layout::{ios::IOsKbdLayer, IOsPlatform},
        KbdgenBundle,
    },
    util::split_keys,
};

#[derive(Serialize, Deserialize)]
pub struct IosInfo {
    name: String,
    locale: String,
    enter: String,
    space: String,
}

pub fn remove_all_occurances(input: String, character: char) -> String {
    input.as_str().chars().filter(|x| x.cmp(&character) != Ordering::Equal).into_iter().collect::<String>()
}

pub fn keyboard_entity_from_string(input: String) -> Option<IosButton> {
    let regex = Regex::new(r"^\\s\{([^}:]+)(?::(\d+(?:\.\d+)?))?\}$").expect("valid regex");
    let captures = regex.captures(input.as_str());

    if let Some(captures) = captures {
        if let Some(id) = captures.get(1) {
            let id = match id.as_str().chars().next().unwrap().cmp(&'\"') {
                Ordering::Equal => remove_all_occurances(id.as_str().to_string(), '\"'),
                _ => format!("_{}", remove_all_occurances(id.as_str().to_string(), '\"'))
            };
            if let Some(width) = captures.get(2) {
                Some(IosButton {
                    id: id,
                    width: width.as_str().parse::<f32>().expect("invalid float value"),
                })
            } else {
                Some(IosButton {
                    id: id,
                    width: 1.0f32,
                })
            }
        } else {
            return None;
        }
    } else {
        return None;
    }
}

#[derive(Serialize, Deserialize, Debug)]
pub struct IosButton {
    id: String,
    width: f32,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum IosKeyMapTypes {
    Character(String),
    Button(IosButton),
}

#[derive(Serialize, Deserialize)]
pub struct IosNormalLayer {
    #[serde(flatten)]
    layer: IndexMap<String, Vec<Vec<IosKeyMapTypes>>>,
}

#[derive(Serialize, Deserialize)]
pub struct IosDeadKeys {
    iphone: IndexMap<String, String>,
    #[serde(rename = "ipad-9in")]
    i_pad_9in: IndexMap<String, String>,
    #[serde(rename = "ipad-12in")]
    i_pad_12in: IndexMap<String, String>,
}

#[derive(Serialize, Deserialize)]
pub struct IosKeyboardDefinitions {
    #[serde(flatten)]
    info: IosInfo,
    longpress: IndexMap<String, Vec<String>>,
    dead_keys: IosDeadKeys,
    transforms: serde_json::value::Value,
    iphone: IosNormalLayer,
    i_pad_9in: IosNormalLayer,
    i_pad_12in: IosNormalLayer,
}

pub fn enum_to_output(layer: &IOsKbdLayer) -> String {
    match layer {
        IOsKbdLayer::Default => "normal",
        IOsKbdLayer::Shift => "shifted",
        IOsKbdLayer::Alt => "alt",
        IOsKbdLayer::AltAndShift => "alt+shift",
        IOsKbdLayer::Symbols1 => "symbols-1",
        IOsKbdLayer::Symbols2 => "symbols-2",
    }
    .to_string()
}

pub fn generate_platform(platform: &IOsPlatform) -> IndexMap<String, Vec<Vec<IosKeyMapTypes>>> {
    let mut layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();
    for (layer_name, layer_key_map) in &platform.layers {
        let layer_name = enum_to_output(layer_name);
        let key_map_rows: Vec<&str> = layer_key_map
            .trim()
            .split("\n")
            .map(|x| x.clone())
            .collect();
        let mut layer_rows: Vec<Vec<IosKeyMapTypes>> = Vec::new();
        for key_map in key_map_rows {
            let key_map = split_keys(key_map);
            let mut new_key_map: Vec<IosKeyMapTypes> = Vec::new();
            for key in key_map {
                if let Some(keyboard_entity) = keyboard_entity_from_string(key.clone()) {
                    new_key_map.push(IosKeyMapTypes::Button(keyboard_entity));
                } else {
                    new_key_map.push(IosKeyMapTypes::Character(key));
                }
            }
            layer_rows.push(new_key_map)
        }
        layers.insert(layer_name, layer_rows);
    }
    layers
}

pub struct GenerateIos;

#[async_trait(?Send)]
impl BuildStep for GenerateIos {
    async fn build(&self, bundle: &KbdgenBundle, output_path: &Path) {
        let keyboard_definitions_file_path = output_path.join("KeyboardDefinitions.json");

        for (language_tag, layout) in &bundle.layouts {
            let mut longpress: IndexMap<String, Vec<String>> = IndexMap::new();
            let mut iphone_layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();
            let mut i_pad_9in_layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();
            let mut i_pad_12in_layers: IndexMap<String, Vec<Vec<IosKeyMapTypes>>> = IndexMap::new();

            if let Some(ios_target) = &layout.i_os {
                if let Some(layout_longpress) = &layout.longpress {
                    for (key, value) in layout_longpress {
                        longpress.insert(key.clone(), value.clone());
                    }
                }

                if let Some(primary_platform) = &ios_target.primary {
                    iphone_layers.extend(generate_platform(&primary_platform));
                }
                if let Some(i_pad_9in_platform) = &ios_target.i_pad_9in {
                    i_pad_9in_layers.extend(generate_platform(&i_pad_9in_platform));
                }
                if let Some(i_pad_12in_platform) = &ios_target.i_pad_12in {
                    i_pad_12in_layers.extend(generate_platform(&i_pad_12in_platform));
                }

                if let Some(key_names) = &layout.key_names {
                    std::fs::write(
                        output_path.join(keyboard_definitions_file_path.clone()),
                        serde_json::to_string_pretty(&[IosKeyboardDefinitions {
                            info: IosInfo {
                                name: layout
                                    .display_names
                                    .get(language_tag)
                                    .expect("can't evaluate language tag of layout")
                                    .to_string(),
                                locale: language_tag.to_string(),
                                enter: key_names.r#return.to_string(),
                                space: key_names.space.to_string(),
                            },
                            longpress: longpress,
                            dead_keys: IosDeadKeys {
                                iphone: IndexMap::new(),
                                i_pad_9in: IndexMap::new(),
                                i_pad_12in: IndexMap::new(),
                            },
                            transforms: serde_json::value::Value::Null,
                            iphone: IosNormalLayer {
                                layer: iphone_layers,
                            },
                            i_pad_9in: IosNormalLayer {
                                layer: i_pad_9in_layers,
                            },
                            i_pad_12in: IosNormalLayer {
                                layer: i_pad_12in_layers,
                            },
                        }])
                        .unwrap(),
                    )
                    .unwrap();
                }
            }
        }
    }
}
<|endoftext|>


<commit_before>use std::{cmp::Ordering, path::Path};

use async_trait::async_trait;
use indexmap::IndexMap;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json;

use crate::{
    build::BuildStep,
    bundle::{
        layout::{ios::IOsKbdLayer, IOsPlatform},
        KbdgenBundle,
    },
    util::split_keys,
};

const REPOSITORY: &str = "repo";
const MODELS: &str = "Keyboard/Models";

#[derive(Serialize, Deserialize)]
pub struct IosInfo {
    name: String,
    locale: String,
    enter: String,
    space: String,
}

/// Removes all occurances of `character` in `input`
pub fn remove_all_occurances(input: String, character: char) -> String {
    input
        .as_str()
        .chars()
        .filter(|x| x.cmp(&character) != Ordering::Equal)
        .into_iter()
        .collect::<String>()
}

pub fn keyboard_component_from_string(input: String) -> Option<IosButton> {
    let regex = Regex::new(r"^\\s\{([^}:]+)(?::(\d+(?:\.\d+)?))?\}$").expect("valid regex");
    let captures = regex.captures(input.as_str());

    if let Some(captures) = captures {
        if let Some(id) = captures.get(1) {
            let id = match id.as_str().chars().next().unwrap().cmp(&'\"') {
                Ordering::Equal => remove_all_occurances(id.as_str().to_string(), '\"'),
                _ => format!("_{}", remove_all_occurances(id.as_str().to_string(), '\"')),
            };
            if let Some(width) = captures.get(2) {
                Some(IosButton {
                    id: id,
                    width: width.as_str().parse::<f32>().expect("invalid float value"),
                })
            } else {
                Some(IosButton {
                    id: id,
                    width: 1.0f32,
                })
            }
        } else {
            return None;
        }
    } else {
        return None;
    }
}

#[derive(Serialize, Deserialize)]
pub struct IosButton {
    id: String,
    width: f32,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum IosKeyMapType {
    Character(String),
    Button(IosButton),
}

#[derive(Serialize, Deserialize)]
pub struct IosPlatform {
    #[serde(flatten)]
    layer: IndexMap<String, Vec<Vec<IosKeyMapType>>>,
}

#[derive(Serialize, Deserialize)]
pub struct IosDeadKeys {
    iphone: IndexMap<String, String>,
    #[serde(rename = "ipad-9in")]
    i_pad_9in: IndexMap<String, String>,
    #[serde(rename = "ipad-12in")]
    i_pad_12in: IndexMap<String, String>,
}

#[derive(Serialize, Deserialize)]
pub struct IosKeyboardDefinitions {
    #[serde(flatten)]
    info: IosInfo,
    longpress: IndexMap<String, Vec<String>>,
    dead_keys: IosDeadKeys,
    transforms: serde_json::value::Value,
    iphone: IosPlatform,
    i_pad_9in: IosPlatform,
    i_pad_12in: IosPlatform,
}

pub fn ios_layer_name(layer: &IOsKbdLayer) -> String {
    match layer {
        IOsKbdLayer::Default => "normal",
        IOsKbdLayer::Shift => "shifted",
        IOsKbdLayer::Alt => "alt",
        IOsKbdLayer::AltAndShift => "alt+shift",
        IOsKbdLayer::Symbols1 => "symbols-1",
        IOsKbdLayer::Symbols2 => "symbols-2",
    }
    .to_string()
}

pub fn generate_platform(platform: &IOsPlatform) -> IndexMap<String, Vec<Vec<IosKeyMapType>>> {
    let mut layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();
    for (layer_name, layer_key_map) in &platform.layers {
        let layer_name = ios_layer_name(layer_name);
        let key_map_rows: Vec<&str> = layer_key_map
            .trim()
            .split("\n")
            .map(|x| x.clone())
            .collect();
        let mut layer_rows: Vec<Vec<IosKeyMapType>> = Vec::new();
        for key_map in key_map_rows {
            let key_map = split_keys(key_map);
            let mut new_key_map: Vec<IosKeyMapType> = Vec::new();
            for key in key_map {
                if let Some(keyboard_entity) = keyboard_component_from_string(key.clone()) {
                    new_key_map.push(IosKeyMapType::Button(keyboard_entity));
                } else {
                    new_key_map.push(IosKeyMapType::Character(key));
                }
            }
            layer_rows.push(new_key_map)
        }
        layers.insert(layer_name, layer_rows);
    }
    layers
}

pub struct GenerateIos;

#[async_trait(?Send)]
impl BuildStep for GenerateIos {
    async fn build(&self, bundle: &KbdgenBundle, output_path: &Path) {
        let repository_path = output_path.join(REPOSITORY);
        let models_path = repository_path.join(MODELS);
        let keyboard_definitions_file_path = models_path.join("KeyboardDefinitions.json");

        for (language_tag, layout) in &bundle.layouts {
            let mut longpress: IndexMap<String, Vec<String>> = IndexMap::new();
            let mut iphone_layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();
            let mut i_pad_9in_layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();
            let mut i_pad_12in_layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();

            if let Some(ios_target) = &layout.i_os {
                if let Some(layout_longpress) = &layout.longpress {
                    for (key, value) in layout_longpress {
                        longpress.insert(key.clone(), value.clone());
                    }
                }

                if let Some(primary_platform) = &ios_target.primary {
                    iphone_layers.extend(generate_platform(&primary_platform));
                }
                if let Some(i_pad_9in_platform) = &ios_target.i_pad_9in {
                    i_pad_9in_layers.extend(generate_platform(&i_pad_9in_platform));
                }
                if let Some(i_pad_12in_platform) = &ios_target.i_pad_12in {
                    i_pad_12in_layers.extend(generate_platform(&i_pad_12in_platform));
                }

                if let Some(key_names) = &layout.key_names {
                    std::fs::write(
                        output_path.join(keyboard_definitions_file_path.clone()),
                        serde_json::to_string_pretty(&[IosKeyboardDefinitions {
                            info: IosInfo {
                                name: layout
                                    .display_names
                                    .get(language_tag)
                                    .expect("can't evaluate language tag of layout")
                                    .to_string(),
                                locale: language_tag.to_string(),
                                enter: key_names.r#return.to_string(),
                                space: key_names.space.to_string(),
                            },
                            longpress: longpress,
                            dead_keys: IosDeadKeys {
                                iphone: IndexMap::new(),
                                i_pad_9in: IndexMap::new(),
                                i_pad_12in: IndexMap::new(),
                            },
                            transforms: serde_json::value::Value::Null,
                            iphone: IosPlatform {
                                layer: iphone_layers,
                            },
                            i_pad_9in: IosPlatform {
                                layer: i_pad_9in_layers,
                            },
                            i_pad_12in: IosPlatform {
                                layer: i_pad_12in_layers,
                            },
                        }])
                        .unwrap(),
                    )
                    .unwrap();
                }
            }
        }
    }
}
<commit_msg>Correct a typo<commit_after>use std::{cmp::Ordering, path::Path};

use async_trait::async_trait;
use indexmap::IndexMap;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json;

use crate::{
    build::BuildStep,
    bundle::{
        layout::{ios::IOsKbdLayer, IOsPlatform},
        KbdgenBundle,
    },
    util::split_keys,
};

const REPOSITORY: &str = "repo";
const MODELS: &str = "Keyboard/Models";

#[derive(Serialize, Deserialize)]
pub struct IosInfo {
    name: String,
    locale: String,
    enter: String,
    space: String,
}

/// Removes all occurrences of `character` in `input`
pub fn remove_all_occurrences(input: String, character: char) -> String {
    input
        .as_str()
        .chars()
        .filter(|x| x.cmp(&character) != Ordering::Equal)
        .into_iter()
        .collect::<String>()
}

pub fn keyboard_component_from_string(input: String) -> Option<IosButton> {
    let regex = Regex::new(r"^\\s\{([^}:]+)(?::(\d+(?:\.\d+)?))?\}$").expect("valid regex");
    let captures = regex.captures(input.as_str());

    if let Some(captures) = captures {
        if let Some(id) = captures.get(1) {
            let id = match id.as_str().chars().next().unwrap().cmp(&'\"') {
                Ordering::Equal => remove_all_occurrences(id.as_str().to_string(), '\"'),
                _ => format!("_{}", remove_all_occurrences(id.as_str().to_string(), '\"')),
            };
            if let Some(width) = captures.get(2) {
                Some(IosButton {
                    id: id,
                    width: width.as_str().parse::<f32>().expect("invalid float value"),
                })
            } else {
                Some(IosButton {
                    id: id,
                    width: 1.0f32,
                })
            }
        } else {
            return None;
        }
    } else {
        return None;
    }
}

#[derive(Serialize, Deserialize)]
pub struct IosButton {
    id: String,
    width: f32,
}

#[derive(Serialize, Deserialize)]
#[serde(untagged)]
pub enum IosKeyMapType {
    Character(String),
    Button(IosButton),
}

#[derive(Serialize, Deserialize)]
pub struct IosPlatform {
    #[serde(flatten)]
    layer: IndexMap<String, Vec<Vec<IosKeyMapType>>>,
}

#[derive(Serialize, Deserialize)]
pub struct IosDeadKeys {
    iphone: IndexMap<String, String>,
    #[serde(rename = "ipad-9in")]
    i_pad_9in: IndexMap<String, String>,
    #[serde(rename = "ipad-12in")]
    i_pad_12in: IndexMap<String, String>,
}

#[derive(Serialize, Deserialize)]
pub struct IosKeyboardDefinitions {
    #[serde(flatten)]
    info: IosInfo,
    longpress: IndexMap<String, Vec<String>>,
    dead_keys: IosDeadKeys,
    transforms: serde_json::value::Value,
    iphone: IosPlatform,
    i_pad_9in: IosPlatform,
    i_pad_12in: IosPlatform,
}

pub fn ios_layer_name(layer: &IOsKbdLayer) -> String {
    match layer {
        IOsKbdLayer::Default => "normal",
        IOsKbdLayer::Shift => "shifted",
        IOsKbdLayer::Alt => "alt",
        IOsKbdLayer::AltAndShift => "alt+shift",
        IOsKbdLayer::Symbols1 => "symbols-1",
        IOsKbdLayer::Symbols2 => "symbols-2",
    }
    .to_string()
}

pub fn generate_platform(platform: &IOsPlatform) -> IndexMap<String, Vec<Vec<IosKeyMapType>>> {
    let mut layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();
    for (layer_name, layer_key_map) in &platform.layers {
        let layer_name = ios_layer_name(layer_name);
        let key_map_rows: Vec<&str> = layer_key_map
            .trim()
            .split("\n")
            .map(|x| x.clone())
            .collect();
        let mut layer_rows: Vec<Vec<IosKeyMapType>> = Vec::new();
        for key_map in key_map_rows {
            let key_map = split_keys(key_map);
            let mut new_key_map: Vec<IosKeyMapType> = Vec::new();
            for key in key_map {
                if let Some(keyboard_entity) = keyboard_component_from_string(key.clone()) {
                    new_key_map.push(IosKeyMapType::Button(keyboard_entity));
                } else {
                    new_key_map.push(IosKeyMapType::Character(key));
                }
            }
            layer_rows.push(new_key_map)
        }
        layers.insert(layer_name, layer_rows);
    }
    layers
}

pub struct GenerateIos;

#[async_trait(?Send)]
impl BuildStep for GenerateIos {
    async fn build(&self, bundle: &KbdgenBundle, output_path: &Path) {
        let repository_path = output_path.join(REPOSITORY);
        let models_path = repository_path.join(MODELS);
        let keyboard_definitions_file_path = models_path.join("KeyboardDefinitions.json");

        for (language_tag, layout) in &bundle.layouts {
            let mut longpress: IndexMap<String, Vec<String>> = IndexMap::new();
            let mut iphone_layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();
            let mut i_pad_9in_layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();
            let mut i_pad_12in_layers: IndexMap<String, Vec<Vec<IosKeyMapType>>> = IndexMap::new();

            if let Some(ios_target) = &layout.i_os {
                if let Some(layout_longpress) = &layout.longpress {
                    for (key, value) in layout_longpress {
                        longpress.insert(key.clone(), value.clone());
                    }
                }

                if let Some(primary_platform) = &ios_target.primary {
                    iphone_layers.extend(generate_platform(&primary_platform));
                }
                if let Some(i_pad_9in_platform) = &ios_target.i_pad_9in {
                    i_pad_9in_layers.extend(generate_platform(&i_pad_9in_platform));
                }
                if let Some(i_pad_12in_platform) = &ios_target.i_pad_12in {
                    i_pad_12in_layers.extend(generate_platform(&i_pad_12in_platform));
                }

                if let Some(key_names) = &layout.key_names {
                    std::fs::write(
                        output_path.join(keyboard_definitions_file_path.clone()),
                        serde_json::to_string_pretty(&[IosKeyboardDefinitions {
                            info: IosInfo {
                                name: layout
                                    .display_names
                                    .get(language_tag)
                                    .expect("can't evaluate language tag of layout")
                                    .to_string(),
                                locale: language_tag.to_string(),
                                enter: key_names.r#return.to_string(),
                                space: key_names.space.to_string(),
                            },
                            longpress: longpress,
                            dead_keys: IosDeadKeys {
                                iphone: IndexMap::new(),
                                i_pad_9in: IndexMap::new(),
                                i_pad_12in: IndexMap::new(),
                            },
                            transforms: serde_json::value::Value::Null,
                            iphone: IosPlatform {
                                layer: iphone_layers,
                            },
                            i_pad_9in: IosPlatform {
                                layer: i_pad_9in_layers,
                            },
                            i_pad_12in: IosPlatform {
                                layer: i_pad_12in_layers,
                            },
                        }])
                        .unwrap(),
                    )
                    .unwrap();
                }
            }
        }
    }
}
<|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 url::Url;
use hyper::method::Method;
use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
use hyper::header::{Headers, ContentType, IfModifiedSince, IfNoneMatch, Accept};
use hyper::header::{IfUnmodifiedSince, IfMatch, IfRange, Location, HeaderView};
use hyper::header::{AcceptLanguage, ContentLanguage};
use hyper::status::StatusCode;
use fetch::cors_cache::{CORSCache, CacheRequestDetails};
use fetch::response::{Response, ResponseType};
use std::ascii::AsciiExt;

/// A [request context](https://fetch.spec.whatwg.org/#concept-request-context)
#[derive(Copy, Clone, PartialEq)]
pub enum Context {
    Audio, Beacon, CSPreport, Download, Embed, Eventsource,
    Favicon, Fetch, Font, Form, Frame, Hyperlink, IFrame, Image,
    ImageSet, Import, Internal, Location, Manifest, Object, Ping,
    Plugin, Prefetch, Script, ServiceWorker, SharedWorker, Subresource,
    Style, Track, Video, Worker, XMLHttpRequest, XSLT
}

/// A [request context frame type](https://fetch.spec.whatwg.org/#concept-request-context-frame-type)
#[derive(Copy, Clone, PartialEq)]
pub enum ContextFrameType {
    Auxiliary,
    TopLevel,
    Nested,
    ContextNone
}

/// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer)
pub enum Referer {
    RefererNone,
    Client,
    RefererUrl(Url)
}

/// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum RequestMode {
    SameOrigin,
    NoCORS,
    CORSMode,
    ForcedPreflightMode
}

/// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum CredentialsMode {
    Omit,
    CredentialsSameOrigin,
    Include
}

/// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum CacheMode {
    Default,
    NoStore,
    Reload,
    NoCache,
    ForceCache,
    OnlyIfCached
}

/// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum RedirectMode {
    Follow,
    Error,
    Manual
}

/// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting)
#[derive(Copy, Clone, PartialEq)]
pub enum ResponseTainting {
    Basic,
    CORSTainting,
    Opaque
}

/// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
pub struct Request {
    pub method: Method,
    pub url: Url,
    pub headers: Headers,
    pub unsafe_request: bool,
    pub body: Option<Vec<u8>>,
    pub preserve_content_codings: bool,
    // pub client: GlobalRef, // XXXManishearth copy over only the relevant fields of the global scope,
                              // not the entire scope to avoid the libscript dependency
    pub is_service_worker_global_scope: bool,
    pub skip_service_worker: bool,
    pub context: Context,
    pub context_frame_type: ContextFrameType,
    pub origin: Option<Url>,
    pub force_origin_header: bool,
    pub same_origin_data: bool,
    pub referer: Referer,
    pub authentication: bool,
    pub sync: bool,
    pub mode: RequestMode,
    pub credentials_mode: CredentialsMode,
    pub use_url_credentials: bool,
    pub cache_mode: CacheMode,
    pub redirect_mode: RedirectMode,
    pub redirect_count: usize,
    pub response_tainting: ResponseTainting,
    pub cache: Option<Box<CORSCache+'static>>
}

impl Request {
    pub fn new(url: Url, context: Context, isServiceWorkerGlobalScope: bool) -> Request {
         Request {
            method: Method::Get,
            url: url,
            headers: Headers::new(),
            unsafe_request: false,
            body: None,
            preserve_content_codings: false,
            is_service_worker_global_scope: isServiceWorkerGlobalScope,
            skip_service_worker: false,
            context: context,
            context_frame_type: ContextFrameType::ContextNone,
            origin: None,
            force_origin_header: false,
            same_origin_data: false,
            referer: Referer::Client,
            authentication: false,
            sync: false,
            mode: RequestMode::NoCORS,
            credentials_mode: CredentialsMode::Omit,
            use_url_credentials: false,
            cache_mode: CacheMode::Default,
            redirect_mode: RedirectMode::Follow,
            redirect_count: 0,
            response_tainting: ResponseTainting::Basic,
            cache: None
        }
    }

    // [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
    pub fn fetch(&mut self, _cors_flag: bool) -> Response {
        // TODO: Implement fetch spec
        Response::network_error()
    }

    /// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
    pub fn basic_fetch(&mut self) -> Response {
        match &*self.url.scheme {
            "about" => match self.url.non_relative_scheme_data() {
                Some(s) if &*s == "blank" => {
                    let mut response = Response::new();
                    response.headers.set(ContentType(Mime(
                        TopLevel::Text, SubLevel::Html,
                        vec![(Attr::Charset, Value::Utf8)])));
                    response
                },
                _ => Response::network_error()
            },
            "http" | "https" => {
                self.http_fetch(false, false, false)
            },
            "blob" | "data" | "file" | "ftp" => {
                // XXXManishearth handle these
                panic!("Unimplemented scheme for Fetch")
            },

            _ => Response::network_error()
        }
    }

    // [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch)
    pub fn http_fetch(&mut self, cors_flag: bool, cors_preflight_flag: bool, authentication_fetch_flag: bool) -> Response {
        // Step 1
        let mut response: Option<Response> = None;
        // Step 2
        if !self.skip_service_worker && !self.is_service_worker_global_scope {
            // TODO: Substep 1 (handle fetch unimplemented)
            // Substep 2
            if let Some(ref res) = response {
                if (res.response_type == ResponseType::Opaque && self.mode != RequestMode::NoCORS) ||
                   res.response_type == ResponseType::Error {
                    return Response::network_error();
                }
            }
        }
        // Step 3
        if response.is_none() {
            // Substep 1
            if cors_preflight_flag {
                let mut method_mismatch = false;
                let mut header_mismatch = false;
                if let Some(ref mut cache) = self.cache {
                    let origin = self.origin.clone().unwrap_or(Url::parse("").unwrap());
                    let url = self.url.clone();
                    let credentials = self.credentials_mode == CredentialsMode::Include;
                    let method_cache_match = cache.match_method(CacheRequestDetails {
                        origin: origin.clone(),
                        destination: url.clone(),
                        credentials: credentials
                    }, self.method.clone());
                    method_mismatch = !method_cache_match && (!is_simple_method(&self.method) ||
                        self.mode == RequestMode::ForcedPreflightMode);
                    header_mismatch = self.headers.iter().any(|view|
                        !cache.match_header(CacheRequestDetails {
                            origin: origin.clone(),
                            destination: url.clone(),
                            credentials: credentials
                        }, view.name()) && !is_simple_header(&view)
                        );
                }
                if method_mismatch || header_mismatch {
                    let preflight_result = self.preflight_fetch();
                    if preflight_result.response_type == ResponseType::Error {
                        return Response::network_error();
                    }
                    response = Some(preflight_result);
                }
            }
            // Substep 2
            self.skip_service_worker = true;
            // Substep 3
            let credentials = match self.credentials_mode {
                CredentialsMode::Include => true,
                CredentialsMode::CredentialsSameOrigin if !cors_flag => true,
                _ => false
            };
            // Substep 4
            if self.cache_mode == CacheMode::Default && is_no_store_cache(&self.headers) {
                self.cache_mode = CacheMode::NoStore;
            }
            // Substep 5
            let fetch_result = self.http_network_or_cache_fetch(credentials, authentication_fetch_flag);
            // Substep 6
            if cors_flag && self.cors_check(&fetch_result).is_err() {
                return Response::network_error();
            }
            response = Some(fetch_result);
        }
        // Step 4
        let mut response = response.unwrap();
        match response.status.unwrap() {
            // Code 304
            StatusCode::NotModified => match self.cache_mode {
                CacheMode::Default | CacheMode::NoCache => {
                    // TODO: Check HTTP cache for request and response entry
                }
                _ => { }
            },
            // Code 301, 302, 303, 307, 308
            StatusCode::MovedPermanently | StatusCode::Found | StatusCode::SeeOther |
            StatusCode::TemporaryRedirect | StatusCode::PermanentRedirect => {
                // Step 1
                if self.redirect_mode == RedirectMode::Error {
                    return Response::network_error();
                }
                // Step 2-4
                if !response.headers.has::<Location>() {
                    return response;
                }
                let location = match response.headers.get::<Location>() {
                    None => return Response::network_error(),
                    Some(location) => location,
                };
                // Step 5
                let locationUrl = Url::parse(location);
                // Step 6
                let locationUrl = match locationUrl {
                    Ok(url) => url,
                    Err(_) => return Response::network_error()
                };
                // Step 7
                if self.redirect_count == 20 {
                    return Response::network_error();
                }
                // Step 8
                self.redirect_count += 1;
                // Step 9
                self.same_origin_data = false;
                // Step 10
                if self.redirect_mode == RedirectMode::Follow {
                    // FIXME: Origin method of the Url crate hasn't been implemented (https://github.com/servo/rust-url/issues/54)
                    // Substep 1
                    // if cors_flag && locationUrl.origin() != self.url.origin() { self.origin = None; }
                    // Substep 2
                    if cors_flag && (!locationUrl.username().unwrap_or("").is_empty() ||
                                      locationUrl.password().is_some()) {
                        return Response::network_error();
                    }
                    // Substep 3
                    if response.status.unwrap() == StatusCode::MovedPermanently ||
                       response.status.unwrap() == StatusCode::SeeOther ||
                       (response.status.unwrap() == StatusCode::Found && self.method == Method::Post) {
                        self.method = Method::Get;
                    }
                    // Substep 4
                    self.url = locationUrl;
                    // Substep 5
                    return self.fetch(cors_flag);
                }
            }
            // Code 401
            StatusCode::Unauthorized => {
                // Step 1
                if !self.authentication || cors_flag {
                    return response;
                }
                // Step 2
                // TODO: Spec says requires testing
                // Step 3
                if !self.use_url_credentials || authentication_fetch_flag {
                    // TODO: Prompt the user for username and password
                }
                return self.http_fetch(cors_flag, cors_preflight_flag, true);
            }
            // Code 407
            StatusCode::ProxyAuthenticationRequired => {
                // Step 1
                // TODO: Spec says requires testing
                // Step 2
                // TODO: Prompt the user for proxy authentication credentials
                // Step 3
                return self.http_fetch(cors_flag, cors_preflight_flag, authentication_fetch_flag);
            }
            _ => { }
        }
        // Step 5
        if authentication_fetch_flag {
            // TODO: Create authentication entry for this request
        }
        // Step 6
        response
    }

    // [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch)
    pub fn http_network_or_cache_fetch(&mut self, _credentials_flag: bool, _authentication_fetch_flag: bool) -> Response {
        // TODO: Implement HTTP network or cache fetch spec
        Response::network_error()
    }

    // [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch)
    pub fn preflight_fetch(&mut self) -> Response {
        // TODO: Implement preflight fetch spec
        Response::network_error()
    }

    // [CORS check](https://fetch.spec.whatwg.org#concept-cors-check)
    pub fn cors_check(&mut self, response: &Response) -> Result<(), ()> {
        // TODO: Implement CORS check spec
        Err(())
    }
}

fn is_no_store_cache(headers: &Headers) -> bool {
    headers.has::<IfModifiedSince>() | headers.has::<IfNoneMatch>() |
    headers.has::<IfUnmodifiedSince>() | headers.has::<IfMatch>() |
    headers.has::<IfRange>()
}

fn is_simple_header(h: &HeaderView) -> bool {
    if h.is::<ContentType>() {
        match h.value() {
            Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) |
            Some(&ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _))) |
            Some(&ContentType(Mime(TopLevel::Multipart, SubLevel::FormData, _))) => true,
            _ => false

        }
    } else {
        h.is::<Accept>() || h.is::<AcceptLanguage>() || h.is::<ContentLanguage>()
    }
}

fn is_simple_method(m: &Method) -> bool {
    match *m {
        Method::Get | Method::Head | Method::Post => true,
        _ => false
    }
}
<commit_msg>Use snake case in fetch.<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 url::Url;
use hyper::method::Method;
use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
use hyper::header::{Headers, ContentType, IfModifiedSince, IfNoneMatch, Accept};
use hyper::header::{IfUnmodifiedSince, IfMatch, IfRange, Location, HeaderView};
use hyper::header::{AcceptLanguage, ContentLanguage};
use hyper::status::StatusCode;
use fetch::cors_cache::{CORSCache, CacheRequestDetails};
use fetch::response::{Response, ResponseType};
use std::ascii::AsciiExt;

/// A [request context](https://fetch.spec.whatwg.org/#concept-request-context)
#[derive(Copy, Clone, PartialEq)]
pub enum Context {
    Audio, Beacon, CSPreport, Download, Embed, Eventsource,
    Favicon, Fetch, Font, Form, Frame, Hyperlink, IFrame, Image,
    ImageSet, Import, Internal, Location, Manifest, Object, Ping,
    Plugin, Prefetch, Script, ServiceWorker, SharedWorker, Subresource,
    Style, Track, Video, Worker, XMLHttpRequest, XSLT
}

/// A [request context frame type](https://fetch.spec.whatwg.org/#concept-request-context-frame-type)
#[derive(Copy, Clone, PartialEq)]
pub enum ContextFrameType {
    Auxiliary,
    TopLevel,
    Nested,
    ContextNone
}

/// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer)
pub enum Referer {
    RefererNone,
    Client,
    RefererUrl(Url)
}

/// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum RequestMode {
    SameOrigin,
    NoCORS,
    CORSMode,
    ForcedPreflightMode
}

/// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum CredentialsMode {
    Omit,
    CredentialsSameOrigin,
    Include
}

/// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum CacheMode {
    Default,
    NoStore,
    Reload,
    NoCache,
    ForceCache,
    OnlyIfCached
}

/// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode)
#[derive(Copy, Clone, PartialEq)]
pub enum RedirectMode {
    Follow,
    Error,
    Manual
}

/// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting)
#[derive(Copy, Clone, PartialEq)]
pub enum ResponseTainting {
    Basic,
    CORSTainting,
    Opaque
}

/// A [Request](https://fetch.spec.whatwg.org/#requests) as defined by the Fetch spec
pub struct Request {
    pub method: Method,
    pub url: Url,
    pub headers: Headers,
    pub unsafe_request: bool,
    pub body: Option<Vec<u8>>,
    pub preserve_content_codings: bool,
    // pub client: GlobalRef, // XXXManishearth copy over only the relevant fields of the global scope,
                              // not the entire scope to avoid the libscript dependency
    pub is_service_worker_global_scope: bool,
    pub skip_service_worker: bool,
    pub context: Context,
    pub context_frame_type: ContextFrameType,
    pub origin: Option<Url>,
    pub force_origin_header: bool,
    pub same_origin_data: bool,
    pub referer: Referer,
    pub authentication: bool,
    pub sync: bool,
    pub mode: RequestMode,
    pub credentials_mode: CredentialsMode,
    pub use_url_credentials: bool,
    pub cache_mode: CacheMode,
    pub redirect_mode: RedirectMode,
    pub redirect_count: usize,
    pub response_tainting: ResponseTainting,
    pub cache: Option<Box<CORSCache+'static>>
}

impl Request {
    pub fn new(url: Url, context: Context, is_service_worker_global_scope: bool) -> Request {
         Request {
            method: Method::Get,
            url: url,
            headers: Headers::new(),
            unsafe_request: false,
            body: None,
            preserve_content_codings: false,
            is_service_worker_global_scope: is_service_worker_global_scope,
            skip_service_worker: false,
            context: context,
            context_frame_type: ContextFrameType::ContextNone,
            origin: None,
            force_origin_header: false,
            same_origin_data: false,
            referer: Referer::Client,
            authentication: false,
            sync: false,
            mode: RequestMode::NoCORS,
            credentials_mode: CredentialsMode::Omit,
            use_url_credentials: false,
            cache_mode: CacheMode::Default,
            redirect_mode: RedirectMode::Follow,
            redirect_count: 0,
            response_tainting: ResponseTainting::Basic,
            cache: None
        }
    }

    // [Fetch](https://fetch.spec.whatwg.org#concept-fetch)
    pub fn fetch(&mut self, _cors_flag: bool) -> Response {
        // TODO: Implement fetch spec
        Response::network_error()
    }

    /// [Basic fetch](https://fetch.spec.whatwg.org#basic-fetch)
    pub fn basic_fetch(&mut self) -> Response {
        match &*self.url.scheme {
            "about" => match self.url.non_relative_scheme_data() {
                Some(s) if &*s == "blank" => {
                    let mut response = Response::new();
                    response.headers.set(ContentType(Mime(
                        TopLevel::Text, SubLevel::Html,
                        vec![(Attr::Charset, Value::Utf8)])));
                    response
                },
                _ => Response::network_error()
            },
            "http" | "https" => {
                self.http_fetch(false, false, false)
            },
            "blob" | "data" | "file" | "ftp" => {
                // XXXManishearth handle these
                panic!("Unimplemented scheme for Fetch")
            },

            _ => Response::network_error()
        }
    }

    // [HTTP fetch](https://fetch.spec.whatwg.org#http-fetch)
    pub fn http_fetch(&mut self, cors_flag: bool, cors_preflight_flag: bool, authentication_fetch_flag: bool) -> Response {
        // Step 1
        let mut response: Option<Response> = None;
        // Step 2
        if !self.skip_service_worker && !self.is_service_worker_global_scope {
            // TODO: Substep 1 (handle fetch unimplemented)
            // Substep 2
            if let Some(ref res) = response {
                if (res.response_type == ResponseType::Opaque && self.mode != RequestMode::NoCORS) ||
                   res.response_type == ResponseType::Error {
                    return Response::network_error();
                }
            }
        }
        // Step 3
        if response.is_none() {
            // Substep 1
            if cors_preflight_flag {
                let mut method_mismatch = false;
                let mut header_mismatch = false;
                if let Some(ref mut cache) = self.cache {
                    let origin = self.origin.clone().unwrap_or(Url::parse("").unwrap());
                    let url = self.url.clone();
                    let credentials = self.credentials_mode == CredentialsMode::Include;
                    let method_cache_match = cache.match_method(CacheRequestDetails {
                        origin: origin.clone(),
                        destination: url.clone(),
                        credentials: credentials
                    }, self.method.clone());
                    method_mismatch = !method_cache_match && (!is_simple_method(&self.method) ||
                        self.mode == RequestMode::ForcedPreflightMode);
                    header_mismatch = self.headers.iter().any(|view|
                        !cache.match_header(CacheRequestDetails {
                            origin: origin.clone(),
                            destination: url.clone(),
                            credentials: credentials
                        }, view.name()) && !is_simple_header(&view)
                        );
                }
                if method_mismatch || header_mismatch {
                    let preflight_result = self.preflight_fetch();
                    if preflight_result.response_type == ResponseType::Error {
                        return Response::network_error();
                    }
                    response = Some(preflight_result);
                }
            }
            // Substep 2
            self.skip_service_worker = true;
            // Substep 3
            let credentials = match self.credentials_mode {
                CredentialsMode::Include => true,
                CredentialsMode::CredentialsSameOrigin if !cors_flag => true,
                _ => false
            };
            // Substep 4
            if self.cache_mode == CacheMode::Default && is_no_store_cache(&self.headers) {
                self.cache_mode = CacheMode::NoStore;
            }
            // Substep 5
            let fetch_result = self.http_network_or_cache_fetch(credentials, authentication_fetch_flag);
            // Substep 6
            if cors_flag && self.cors_check(&fetch_result).is_err() {
                return Response::network_error();
            }
            response = Some(fetch_result);
        }
        // Step 4
        let mut response = response.unwrap();
        match response.status.unwrap() {
            // Code 304
            StatusCode::NotModified => match self.cache_mode {
                CacheMode::Default | CacheMode::NoCache => {
                    // TODO: Check HTTP cache for request and response entry
                }
                _ => { }
            },
            // Code 301, 302, 303, 307, 308
            StatusCode::MovedPermanently | StatusCode::Found | StatusCode::SeeOther |
            StatusCode::TemporaryRedirect | StatusCode::PermanentRedirect => {
                // Step 1
                if self.redirect_mode == RedirectMode::Error {
                    return Response::network_error();
                }
                // Step 2-4
                if !response.headers.has::<Location>() {
                    return response;
                }
                let location = match response.headers.get::<Location>() {
                    None => return Response::network_error(),
                    Some(location) => location,
                };
                // Step 5
                let location_url = Url::parse(location);
                // Step 6
                let location_url = match location_url {
                    Ok(url) => url,
                    Err(_) => return Response::network_error()
                };
                // Step 7
                if self.redirect_count == 20 {
                    return Response::network_error();
                }
                // Step 8
                self.redirect_count += 1;
                // Step 9
                self.same_origin_data = false;
                // Step 10
                if self.redirect_mode == RedirectMode::Follow {
                    // FIXME: Origin method of the Url crate hasn't been implemented (https://github.com/servo/rust-url/issues/54)
                    // Substep 1
                    // if cors_flag && location_url.origin() != self.url.origin() { self.origin = None; }
                    // Substep 2
                    if cors_flag && (!location_url.username().unwrap_or("").is_empty() ||
                                      location_url.password().is_some()) {
                        return Response::network_error();
                    }
                    // Substep 3
                    if response.status.unwrap() == StatusCode::MovedPermanently ||
                       response.status.unwrap() == StatusCode::SeeOther ||
                       (response.status.unwrap() == StatusCode::Found && self.method == Method::Post) {
                        self.method = Method::Get;
                    }
                    // Substep 4
                    self.url = location_url;
                    // Substep 5
                    return self.fetch(cors_flag);
                }
            }
            // Code 401
            StatusCode::Unauthorized => {
                // Step 1
                if !self.authentication || cors_flag {
                    return response;
                }
                // Step 2
                // TODO: Spec says requires testing
                // Step 3
                if !self.use_url_credentials || authentication_fetch_flag {
                    // TODO: Prompt the user for username and password
                }
                return self.http_fetch(cors_flag, cors_preflight_flag, true);
            }
            // Code 407
            StatusCode::ProxyAuthenticationRequired => {
                // Step 1
                // TODO: Spec says requires testing
                // Step 2
                // TODO: Prompt the user for proxy authentication credentials
                // Step 3
                return self.http_fetch(cors_flag, cors_preflight_flag, authentication_fetch_flag);
            }
            _ => { }
        }
        // Step 5
        if authentication_fetch_flag {
            // TODO: Create authentication entry for this request
        }
        // Step 6
        response
    }

    // [HTTP network or cache fetch](https://fetch.spec.whatwg.org#http-network-or-cache-fetch)
    pub fn http_network_or_cache_fetch(&mut self, _credentials_flag: bool, _authentication_fetch_flag: bool) -> Response {
        // TODO: Implement HTTP network or cache fetch spec
        Response::network_error()
    }

    // [CORS preflight fetch](https://fetch.spec.whatwg.org#cors-preflight-fetch)
    pub fn preflight_fetch(&mut self) -> Response {
        // TODO: Implement preflight fetch spec
        Response::network_error()
    }

    // [CORS check](https://fetch.spec.whatwg.org#concept-cors-check)
    pub fn cors_check(&mut self, response: &Response) -> Result<(), ()> {
        // TODO: Implement CORS check spec
        Err(())
    }
}

fn is_no_store_cache(headers: &Headers) -> bool {
    headers.has::<IfModifiedSince>() | headers.has::<IfNoneMatch>() |
    headers.has::<IfUnmodifiedSince>() | headers.has::<IfMatch>() |
    headers.has::<IfRange>()
}

fn is_simple_header(h: &HeaderView) -> bool {
    if h.is::<ContentType>() {
        match h.value() {
            Some(&ContentType(Mime(TopLevel::Text, SubLevel::Plain, _))) |
            Some(&ContentType(Mime(TopLevel::Application, SubLevel::WwwFormUrlEncoded, _))) |
            Some(&ContentType(Mime(TopLevel::Multipart, SubLevel::FormData, _))) => true,
            _ => false

        }
    } else {
        h.is::<Accept>() || h.is::<AcceptLanguage>() || h.is::<ContentLanguage>()
    }
}

fn is_simple_method(m: &Method) -> bool {
    match *m {
        Method::Get | Method::Head | Method::Post => true,
        _ => false
    }
}
<|endoftext|>