<commit_before>//! fixed-point numerical types

use std::ops::{Add, AddAssign, Sub, SubAssign};

// shared between Fixed and F2dot14
macro_rules! fixed_impl {
    ($name:ident, $bits:literal, $fract_bits:literal, $ty:ty) => {
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
        #[doc = concat!(stringify!($bits), "-bit signed fixed point number with ", stringify!($fract_bits), " bits of fraction." )]
        pub struct $name($ty);
        impl $name {
            /// Minimum value.
            pub const MIN: Self = Self(<$ty>::MIN);

            /// Maximum value.
            pub const MAX: Self = Self(<$ty>::MAX);

            const INT_MASK: $ty = !0 << $fract_bits;
            const ROUND: $ty = 1 << ($fract_bits - 1);
            const ONE: $ty = 1 << $fract_bits;
            const FRACT_BITS: usize = $fract_bits;

            //TODO: is this actually useful?
            /// Returns the nearest integer value.
            pub fn round(self) -> Self {
                Self(self.0.wrapping_add(Self::ROUND) & Self::INT_MASK)
            }

            /// Returns the absolute value of the number.
            pub fn abs(self) -> Self {
                Self(self.0.abs())
            }

            /// Returns the largest integer less than or equal to the number.
            pub fn floor(self) -> Self {
                Self(self.0 & Self::INT_MASK)
            }

            /// Returns the fractional part of the number.
            pub fn fract(self) -> Self {
                Self(self.0 - self.floor().0)
            }

            /// Wrapping addition.
            pub fn wrapping_add(self, other: Self) -> Self {
                Self(self.0.wrapping_add(other.0))
            }

            /// Saturating addition.
            pub fn saturating_add(self, other: Self) -> Self {
                Self(self.0.saturating_add(other.0))
            }

            /// Wrapping substitution.
            pub fn wrapping_sub(self, other: Self) -> Self {
                Self(self.0.wrapping_sub(other.0))
            }

            /// Saturating substitution.
            pub fn saturating_sub(self, other: Self) -> Self {
                Self(self.0.saturating_sub(other.0))
            }
        }

        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                let int = (self.0 & Self::INT_MASK) >> $fract_bits;
                write!(f, "{}.", int)?;

                let mut fract = (self.0 & !Self::INT_MASK) as u32;
                while fract > 0 {
                    fract *= 10;
                    let val = fract >> $fract_bits;
                    debug_assert!(val < 10);
                    write!(f, "{}", val)?;
                    fract &= ((1 << $fract_bits) - 1);
                }
                Ok(())
            }
        }

        //FIXME: this should respect padding and other format options
        impl std::fmt::Debug for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                write!(f, "{}", self)
            }
        }

        impl Add for $name {
            type Output = Self;
            #[inline(always)]
            fn add(self, other: Self) -> Self {
                // same overflow semantics as std: panic in debug, wrap in release
                Self(self.0 + other.0)
            }
        }

        impl AddAssign for $name {
            fn add_assign(&mut self, other: Self) {
                *self = *self + other;
            }
        }

        impl Sub for $name {
            type Output = Self;
            #[inline(always)]
            fn sub(self, other: Self) -> Self {
                Self(self.0 - other.0)
            }
        }

        impl SubAssign for $name {
            fn sub_assign(&mut self, other: Self) {
                *self = *self - other;
            }
        }
    };
}

/// impl float conversion methods.
///
/// We convert to different float types in order to ensure we can roundtrip
/// without floating point error.
macro_rules! float_conv {
    ($name:ident, $to:ident, $from:ident, $ty:ty) => {
        impl $name {
            #[doc = concat!("Creates a fixed point value from a", stringify!($ty), ".")]
            ///
            /// This operation is lossy; the float will be rounded to the nearest
            /// representable value.
            pub fn $from(x: $ty) -> Self {
                Self((x * Self::ONE as $ty) as _)
            }

            #[doc = concat!("Returns the value as an ", stringify!($ty), ".")]
            ///
            /// This operation is lossless: all representable values can be
            /// round-tripped.
            pub fn $to(self) -> $ty {
                let int = ((self.0 & Self::INT_MASK) >> Self::FRACT_BITS) as $ty;
                let fract = (self.0 & !Self::INT_MASK) as $ty / Self::ONE as $ty;
                int + fract
            }
        }
    };
}

fixed_impl!(F2dot14, 16, 14, i16);
fixed_impl!(Fixed, 32, 16, i32);
float_conv!(F2dot14, to_f32, from_f32, f32);
float_conv!(Fixed, to_f64, from_f64, f64);

#[cfg(test)]
mod tests {
    #![allow(overflowing_literals)] // we want to specify byte values directly
    use super::*;

    #[test]
    fn f2dot14_floats() {
        // Examples from https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types
        assert_eq!(F2dot14(0x7fff), F2dot14::from_f32(1.999939));
        assert_eq!(F2dot14(0x7000), F2dot14::from_f32(1.75));
        assert_eq!(F2dot14(0x0001), F2dot14::from_f32(0.0000610356));
        assert_eq!(F2dot14(0x0000), F2dot14::from_f32(0.0));
        assert_eq!(F2dot14(0xffff), F2dot14::from_f32(-0.000075));
        assert_eq!(F2dot14(0x8000), F2dot14::from_f32(-2.0));
    }

    #[test]
    fn roundtrip_f2dot14() {
        for i in i16::MIN..=i16::MAX {
            let val = F2dot14(i);
            assert_eq!(val, F2dot14::from_f32(val.to_f32()));
        }
    }

    #[test]
    fn round_f2dot14() {
        assert_eq!(F2dot14(0x7000).round(), F2dot14::from_f32(-2.0));
        assert_eq!(F2dot14(0x1F00).round(), F2dot14::from_f32(0.0));
        assert_eq!(F2dot14(0x2000).round(), F2dot14::from_f32(1.0));
    }

    #[test]
    fn round_fixed() {
        //TODO: make good test cases
        assert_eq!(Fixed(0x0001_7FFE).round(), Fixed(0x0001_0000));
        assert_eq!(Fixed(0x0001_7FFF).round(), Fixed(0x0001_0000));
        assert_eq!(Fixed(0x0001_8000).round(), Fixed(0x0002_0000));
    }

    // disabled because it's slow
    //#[test]
    //fn roundtrip_fixed() {
    //for i in i32::MIN..=i32::MAX {
    //let val = Fixed(i);
    //assert_eq!(val, Fixed::from_f64(val.to_f64()));
    //}
    //}

    #[test]
    fn fixed_floats() {
        assert_eq!(Fixed(0x7fff_0000), Fixed::from_f64(32767.));
        assert_eq!(Fixed(0x7000_0001), Fixed::from_f64(28672.00001525879));
        assert_eq!(Fixed(0x0001_0000), Fixed::from_f64(1.0));
        assert_eq!(Fixed(0x0000_0000), Fixed::from_f64(0.0));
        assert_eq!(
            Fixed(i32::from_be_bytes([0xff; 4])),
            Fixed::from_f64(-0.000015259)
        );
        assert_eq!(Fixed(0x7fff_ffff), Fixed::from_f64(32768.0));
    }
}
<commit_msg>Piggyback on float fmt impls<commit_after>//! fixed-point numerical types

use std::ops::{Add, AddAssign, Sub, SubAssign};

// shared between Fixed and F2dot14
macro_rules! fixed_impl {
    ($name:ident, $bits:literal, $fract_bits:literal, $ty:ty) => {
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
        #[doc = concat!(stringify!($bits), "-bit signed fixed point number with ", stringify!($fract_bits), " bits of fraction." )]
        pub struct $name($ty);
        impl $name {
            /// Minimum value.
            pub const MIN: Self = Self(<$ty>::MIN);

            /// Maximum value.
            pub const MAX: Self = Self(<$ty>::MAX);

            /// This type's smallest representable value
            pub const EPSILON: Self = Self(1);

            const INT_MASK: $ty = !0 << $fract_bits;
            const ROUND: $ty = 1 << ($fract_bits - 1);
            const ONE: $ty = 1 << $fract_bits;
            const FRACT_BITS: usize = $fract_bits;

            //TODO: is this actually useful?
            /// Returns the nearest integer value.
            pub fn round(self) -> Self {
                Self(self.0.wrapping_add(Self::ROUND) & Self::INT_MASK)
            }

            /// Returns the absolute value of the number.
            pub fn abs(self) -> Self {
                Self(self.0.abs())
            }

            /// Returns the largest integer less than or equal to the number.
            pub fn floor(self) -> Self {
                Self(self.0 & Self::INT_MASK)
            }

            /// Returns the fractional part of the number.
            pub fn fract(self) -> Self {
                Self(self.0 - self.floor().0)
            }

            /// Wrapping addition.
            pub fn wrapping_add(self, other: Self) -> Self {
                Self(self.0.wrapping_add(other.0))
            }

            /// Saturating addition.
            pub fn saturating_add(self, other: Self) -> Self {
                Self(self.0.saturating_add(other.0))
            }

            /// Wrapping substitution.
            pub fn wrapping_sub(self, other: Self) -> Self {
                Self(self.0.wrapping_sub(other.0))
            }

            /// Saturating substitution.
            pub fn saturating_sub(self, other: Self) -> Self {
                Self(self.0.saturating_sub(other.0))
            }
        }

        impl Add for $name {
            type Output = Self;
            #[inline(always)]
            fn add(self, other: Self) -> Self {
                // same overflow semantics as std: panic in debug, wrap in release
                Self(self.0 + other.0)
            }
        }

        impl AddAssign for $name {
            fn add_assign(&mut self, other: Self) {
                *self = *self + other;
            }
        }

        impl Sub for $name {
            type Output = Self;
            #[inline(always)]
            fn sub(self, other: Self) -> Self {
                Self(self.0 - other.0)
            }
        }

        impl SubAssign for $name {
            fn sub_assign(&mut self, other: Self) {
                *self = *self - other;
            }
        }
    };
}

/// impl float conversion methods.
///
/// We convert to different float types in order to ensure we can roundtrip
/// without floating point error.
macro_rules! float_conv {
    ($name:ident, $to:ident, $from:ident, $ty:ty) => {
        impl $name {
            #[doc = concat!("Creates a fixed point value from a", stringify!($ty), ".")]
            ///
            /// This operation is lossy; the float will be rounded to the nearest
            /// representable value.
            pub fn $from(x: $ty) -> Self {
                Self((x * Self::ONE as $ty).round() as _)
            }

            #[doc = concat!("Returns the value as an ", stringify!($ty), ".")]
            ///
            /// This operation is lossless: all representable values can be
            /// round-tripped.
            pub fn $to(self) -> $ty {
                let int = ((self.0 & Self::INT_MASK) >> Self::FRACT_BITS) as $ty;
                let fract = (self.0 & !Self::INT_MASK) as $ty / Self::ONE as $ty;
                int + fract
            }
        }

        //hack: we can losslessly go to float, so use those fmt impls
        impl std::fmt::Display for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                self.$to().fmt(f)
            }
        }

        impl std::fmt::Debug for $name {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                self.$to().fmt(f)
            }
        }
    };
}

fixed_impl!(F2dot14, 16, 14, i16);
fixed_impl!(Fixed, 32, 16, i32);
float_conv!(F2dot14, to_f32, from_f32, f32);
float_conv!(Fixed, to_f64, from_f64, f64);

#[cfg(test)]
mod tests {
    #![allow(overflowing_literals)] // we want to specify byte values directly
    use super::*;

    #[test]
    fn f2dot14_floats() {
        // Examples from https://docs.microsoft.com/en-us/typography/opentype/spec/otff#data-types
        assert_eq!(F2dot14(0x7fff), F2dot14::from_f32(1.999939));
        assert_eq!(F2dot14(0x7000), F2dot14::from_f32(1.75));
        assert_eq!(F2dot14(0x0001), F2dot14::from_f32(0.0000610356));
        assert_eq!(F2dot14(0x0000), F2dot14::from_f32(0.0));
        assert_eq!(F2dot14(0xffff), F2dot14::from_f32(-0.000061));
        assert_eq!(F2dot14(0x8000), F2dot14::from_f32(-2.0));
    }

    #[test]
    fn roundtrip_f2dot14() {
        for i in i16::MIN..=i16::MAX {
            let val = F2dot14(i);
            assert_eq!(val, F2dot14::from_f32(val.to_f32()));
        }
    }

    #[test]
    fn round_f2dot14() {
        assert_eq!(F2dot14(0x7000).round(), F2dot14::from_f32(-2.0));
        assert_eq!(F2dot14(0x1F00).round(), F2dot14::from_f32(0.0));
        assert_eq!(F2dot14(0x2000).round(), F2dot14::from_f32(1.0));
    }

    #[test]
    fn round_fixed() {
        //TODO: make good test cases
        assert_eq!(Fixed(0x0001_7FFE).round(), Fixed(0x0001_0000));
        assert_eq!(Fixed(0x0001_7FFF).round(), Fixed(0x0001_0000));
        assert_eq!(Fixed(0x0001_8000).round(), Fixed(0x0002_0000));
    }

    // disabled because it's slow; these were just for my edification anyway
    //#[test]
    //fn roundtrip_fixed() {
    //for i in i32::MIN..=i32::MAX {
    //let val = Fixed(i);
    //assert_eq!(val, Fixed::from_f64(val.to_f64()));
    //}
    //}

    #[test]
    fn fixed_floats() {
        assert_eq!(Fixed(0x7fff_0000), Fixed::from_f64(32767.));
        assert_eq!(Fixed(0x7000_0001), Fixed::from_f64(28672.00001525879));
        assert_eq!(Fixed(0x0001_0000), Fixed::from_f64(1.0));
        assert_eq!(Fixed(0x0000_0000), Fixed::from_f64(0.0));
        assert_eq!(
            Fixed(i32::from_be_bytes([0xff; 4])),
            Fixed::from_f64(-0.000015259)
        );
        assert_eq!(Fixed(0x7fff_ffff), Fixed::from_f64(32768.0));
    }
}
<|endoftext|>


<commit_before>use std::sync::Arc;
use std::collections::{HashMap, VecDeque};
use std::{fs, fmt, path, time, thread};
use std::io::{self, Seek, SeekFrom, Write, Read};
use std::path::PathBuf;

use fs_extra;
use amy;
use sha1;

use torrent::Info;
use util::{hash_to_id, io_err};
use {handle, CONFIG};

const POLL_INT_MS: usize = 1000;
const JOB_TIME_SLICE: u64 = 1;
const EXDEV: i32 = 18;

pub struct Disk {
    poll: amy::Poller,
    ch: handle::Handle<Request, Response>,
    jobs: amy::Receiver<Job>,
    files: FileCache,
    active: VecDeque<Request>,
}

pub struct Location {
    pub file: PathBuf,
    pub offset: u64,
    pub start: usize,
    pub end: usize,
}

#[derive(Debug)]
pub struct Job {
    pub data: Vec<u8>,
    pub path: PathBuf,
}

pub enum Request {
    Write {
        tid: usize,
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        path: Option<String>,
    },
    Read {
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        context: Ctx,
        path: Option<String>,
    },
    Serialize {
        tid: usize,
        data: Vec<u8>,
        hash: [u8; 20],
    },
    Delete {
        tid: usize,
        hash: [u8; 20],
        files: Vec<PathBuf>,
        path: Option<String>,
    },
    Move {
        tid: usize,
        from: String,
        to: String,
        target: String,
    },
    Validate {
        tid: usize,
        info: Arc<Info>,
        path: Option<String>,
        idx: u32,
        invalid: Vec<u32>,
    },
    Shutdown,
}

pub enum Response {
    Read {
        context: Ctx,
        data: Arc<Box<[u8; 16_384]>>,
    },
    ValidationComplete { tid: usize, invalid: Vec<u32> },
    Moved { tid: usize, path: String },
    Error { tid: usize, err: io::Error },
}

pub struct Ctx {
    pub pid: usize,
    pub tid: usize,
    pub idx: u32,
    pub begin: u32,
    pub length: u32,
}

enum JobRes {
    Resp(Response),
    Done,
    Paused(Request),
}

struct FileCache {
    files: HashMap<path::PathBuf, fs::File>,
}

impl Ctx {
    pub fn new(pid: usize, tid: usize, idx: u32, begin: u32, length: u32) -> Ctx {
        Ctx {
            pid,
            tid,
            idx,
            begin,
            length,
        }
    }
}

impl FileCache {
    pub fn new() -> FileCache {
        FileCache { files: HashMap::new() }
    }

    pub fn get_file<F: FnMut(&mut fs::File) -> io::Result<()>>(
        &mut self,
        path: &path::Path,
        mut f: F,
    ) -> io::Result<()> {
        let hit = if let Some(file) = self.files.get_mut(path) {
            f(file)?;
            true
        } else {
            false
        };
        if !hit {
            // TODO: LRU maybe?
            if self.files.len() >= CONFIG.net.max_open_files {
                let removal = self.files.iter().map(|(id, _)| id.clone()).next().unwrap();
                self.files.remove(&removal);
            }
            fs::create_dir_all(path.parent().unwrap())?;
            let mut file = fs::OpenOptions::new()
                .write(true)
                .create(true)
                .read(true)
                .open(path)?;
            f(&mut file)?;
            self.files.insert(path.to_path_buf(), file);
        }
        Ok(())
    }

    pub fn remove_file(&mut self, path: &path::Path) {
        self.files.remove(path);
    }
}

impl Request {
    pub fn write(
        tid: usize,
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        path: Option<String>,
    ) -> Request {
        Request::Write {
            tid,
            data,
            locations,
            path,
        }
    }

    pub fn read(
        context: Ctx,
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        path: Option<String>,
    ) -> Request {
        Request::Read {
            context,
            data,
            locations,
            path,
        }
    }

    pub fn serialize(tid: usize, data: Vec<u8>, hash: [u8; 20]) -> Request {
        Request::Serialize { tid, data, hash }
    }

    pub fn validate(tid: usize, info: Arc<Info>, path: Option<String>) -> Request {
        Request::Validate {
            tid,
            info,
            path,
            idx: 0,
            invalid: Vec::new(),
        }
    }

    pub fn delete(
        tid: usize,
        hash: [u8; 20],
        files: Vec<PathBuf>,
        path: Option<String>,
    ) -> Request {
        Request::Delete {
            tid,
            hash,
            files,
            path,
        }
    }

    pub fn shutdown() -> Request {
        Request::Shutdown
    }

    fn execute(self, fc: &mut FileCache) -> io::Result<JobRes> {
        let sd = &CONFIG.disk.session;
        let dd = &CONFIG.disk.directory;
        match self {
            Request::Write {
                data,
                locations,
                path,
                ..
            } => {
                for loc in locations {
                    let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                    pb.push(&loc.file);
                    fc.get_file(&pb, |f| {
                        f.seek(SeekFrom::Start(loc.offset))?;
                        f.write_all(&data[loc.start..loc.end])?;
                        Ok(())
                    })?;
                }
            }
            Request::Read {
                context,
                mut data,
                locations,
                path,
                ..
            } => {
                for loc in locations {
                    let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                    pb.push(&loc.file);
                    fc.get_file(&pb, |f| {
                        f.seek(SeekFrom::Start(loc.offset))?;
                        f.read_exact(&mut data[loc.start..loc.end])?;
                        Ok(())
                    })?;
                }
                let data = Arc::new(data);
                return Ok(JobRes::Resp(Response::read(context, data)));
            }
            Request::Move {
                tid,
                from,
                to,
                target,
            } => {
                let mut fp = PathBuf::from(&from);
                let mut tp = PathBuf::from(&to);
                fp.push(target.clone());
                tp.push(target);
                match fs::rename(&fp, &tp) {
                    Ok(_) => {}
                    // Cross filesystem move, try to copy then delete
                    Err(ref e) if e.raw_os_error() == Some(EXDEV) => {
                        match fs_extra::dir::copy(&fp, &tp, &fs_extra::dir::CopyOptions::new()) {
                            Ok(_) => {
                                fs::remove_dir_all(&fp)?;
                            }
                            Err(e) => {
                                fs::remove_dir_all(&tp)?;
                                return io_err("Failed to copy directory across filesystems!");
                            }
                        }
                    }
                    Err(e) => return Err(e),
                }
                return Ok(JobRes::Resp(Response::moved(tid, to)));
            }
            Request::Serialize { data, hash, .. } => {
                let mut temp = path::PathBuf::from(sd);
                temp.push(hash_to_id(&hash) + ".temp");
                let mut f = fs::OpenOptions::new().write(true).create(true).open(&temp)?;
                f.write_all(&data)?;
                let mut actual = path::PathBuf::from(sd);
                actual.push(hash_to_id(&hash));
                fs::rename(temp, actual)?;
            }
            Request::Delete { hash, files, path, .. } => {
                let mut spb = path::PathBuf::from(sd);
                spb.push(hash_to_id(&hash));
                fs::remove_file(spb)?;

                for file in files {
                    let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                    pb.push(&file);
                    fc.remove_file(&pb);
                }
            }
            Request::Validate {
                tid,
                info,
                path,
                mut idx,
                mut invalid,
            } => {
                let mut buf = vec![0u8; info.piece_len as usize];
                let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                let mut cf = pb.clone();

                let mut f = fs::OpenOptions::new().read(true).open(&pb);

                let start = time::Instant::now();

                while idx < info.pieces() &&
                    start.elapsed() < time::Duration::from_secs(JOB_TIME_SLICE)
                {
                    let mut valid = true;
                    let mut ctx = sha1::Sha1::new();
                    let locs = info.piece_disk_locs(idx);
                    let mut pos = 0;
                    for loc in locs {
                        if loc.file != cf {
                            pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                            pb.push(&loc.file);
                            f = fs::OpenOptions::new().read(true).open(&pb);
                            cf = loc.file.clone();
                        }
                        // Because this is pausable/resumable, we need to seek to the proper
                        // file position.
                        f.as_mut()
                            .map(|file| file.seek(SeekFrom::Start(loc.offset)))
                            .ok();
                        if let Ok(Ok(amnt)) = f.as_mut().map(|file| file.read(&mut buf[pos..])) {
                            ctx.update(&buf[pos..pos + amnt]);
                            pos += amnt;
                        } else {
                            valid = false;
                        }
                    }
                    let digest = ctx.finish();
                    if !valid || digest.as_ref() != &info.hashes[idx as usize][..] {
                        invalid.push(idx);
                    }

                    idx += 1;
                }
                if idx == info.pieces() {
                    return Ok(JobRes::Resp(Response::validation_complete(tid, invalid)));
                } else {
                    return Ok(JobRes::Paused(Request::Validate {
                        tid,
                        info,
                        path,
                        idx,
                        invalid,
                    }));
                }
            }
            Request::Shutdown => unreachable!(),
        }
        Ok(JobRes::Done)
    }

    pub fn tid(&self) -> usize {
        match *self {
            Request::Serialize { tid, .. } |
            Request::Validate { tid, .. } |
            Request::Delete { tid, .. } |
            Request::Move { tid, .. } |
            Request::Write { tid, .. } => tid,
            Request::Read { ref context, .. } => context.tid,
            Request::Shutdown => unreachable!(),
        }
    }
}

impl fmt::Debug for Request {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "disk::Request")
    }
}

impl Location {
    pub fn new(file: PathBuf, offset: u64, start: u64, end: u64) -> Location {
        Location {
            file,
            offset,
            start: start as usize,
            end: end as usize,
        }
    }
}

impl Response {
    pub fn read(context: Ctx, data: Arc<Box<[u8; 16_384]>>) -> Response {
        Response::Read { context, data }
    }

    pub fn error(tid: usize, err: io::Error) -> Response {
        Response::Error { tid, err }
    }

    pub fn moved(tid: usize, path: String) -> Response {
        Response::Moved { tid, path }
    }

    pub fn validation_complete(tid: usize, invalid: Vec<u32>) -> Response {
        Response::ValidationComplete { tid, invalid }
    }

    pub fn tid(&self) -> usize {
        match *self {
            Response::Read { ref context, .. } => context.tid,
            Response::ValidationComplete { tid, .. } |
            Response::Moved { tid, .. } |
            Response::Error { tid, .. } => tid,
        }
    }
}

impl fmt::Debug for Response {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "disk::Response")
    }
}

impl Disk {
    pub fn new(
        poll: amy::Poller,
        ch: handle::Handle<Request, Response>,
        jobs: amy::Receiver<Job>,
    ) -> Disk {
        Disk {
            poll,
            ch,
            jobs,
            files: FileCache::new(),
            active: VecDeque::new(),
        }
    }

    pub fn run(&mut self) {
        let sd = &CONFIG.disk.session;
        fs::create_dir_all(sd).unwrap();

        loop {
            match self.poll.wait(POLL_INT_MS) {
                Ok(_) => {
                    if self.handle_events() {
                        break;
                    }
                }
                Err(e) => {
                    error!("Failed to poll for events: {:?}", e);
                }
            }
            if !self.active.is_empty() && self.handle_active() {
                break;
            }
        }
    }

    fn handle_active(&mut self) -> bool {
        while let Some(j) = self.active.pop_front() {
            let tid = j.tid();
            match j.execute(&mut self.files) {
                Ok(JobRes::Resp(r)) => {
                    self.ch.send(r).ok();
                }
                Ok(JobRes::Paused(s)) => {
                    self.active.push_front(s);
                }
                Ok(JobRes::Done) => {}
                Err(e) => {
                    self.ch.send(Response::error(tid, e)).ok();
                }
            }
            match self.poll.wait(0) {
                Ok(_) => {
                    if self.handle_events() {
                        return true;
                    }
                }
                Err(e) => {
                    error!("Failed to poll for events: {:?}", e);
                }
            }
        }
        false
    }

    pub fn handle_events(&mut self) -> bool {
        loop {
            match self.ch.recv() {
                Ok(Request::Shutdown) => {
                    return true;
                }
                Ok(r) => {
                    trace!("Handling disk job!");
                    let tid = r.tid();
                    match r.execute(&mut self.files) {
                        Ok(JobRes::Resp(r)) => {
                            self.ch.send(r).ok();
                        }
                        Ok(JobRes::Paused(s)) => {
                            self.active.push_back(s);
                        }
                        Ok(JobRes::Done) => {}
                        Err(e) => {
                            self.ch.send(Response::error(tid, e)).ok();
                        }
                    }
                }
                _ => break,
            }
        }
        while let Ok(j) = self.jobs.try_recv() {
            let mut p = j.path.clone();
            p.set_extension("temp");
            let res = fs::OpenOptions::new()
                .write(true)
                .create(true)
                .open(&p)
                .map(|mut f| f.write(&j.data[..]));
            match res {
                Ok(Ok(_)) => {
                    fs::rename(&p, &j.path).ok();
                }
                Ok(Err(e)) => {
                    error!("Failed to write disk job: {}", e);
                    fs::remove_file(&p).ok();
                }
                Err(e) => {
                    error!("Failed to write disk job: {}", e);
                }
            }
        }
        false
    }
}

pub fn start(
    creg: &mut amy::Registrar,
) -> io::Result<(handle::Handle<Response, Request>, amy::Sender<Job>, thread::JoinHandle<()>)> {
    let poll = amy::Poller::new()?;
    let mut reg = poll.get_registrar()?;
    let (ch, dh) = handle::Handle::new(creg, &mut reg)?;
    let (tx, rx) = reg.channel()?;
    let h = dh.run("disk", move |h| Disk::new(poll, h, rx).run())?;
    Ok((ch, tx, h))
}
<commit_msg>Log disk errors<commit_after>use std::sync::Arc;
use std::collections::{HashMap, VecDeque};
use std::{fs, fmt, path, time, thread};
use std::io::{self, Seek, SeekFrom, Write, Read};
use std::path::PathBuf;

use fs_extra;
use amy;
use sha1;

use torrent::Info;
use util::{hash_to_id, io_err};
use {handle, CONFIG};

const POLL_INT_MS: usize = 1000;
const JOB_TIME_SLICE: u64 = 1;
const EXDEV: i32 = 18;

pub struct Disk {
    poll: amy::Poller,
    ch: handle::Handle<Request, Response>,
    jobs: amy::Receiver<Job>,
    files: FileCache,
    active: VecDeque<Request>,
}

pub struct Location {
    pub file: PathBuf,
    pub offset: u64,
    pub start: usize,
    pub end: usize,
}

#[derive(Debug)]
pub struct Job {
    pub data: Vec<u8>,
    pub path: PathBuf,
}

pub enum Request {
    Write {
        tid: usize,
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        path: Option<String>,
    },
    Read {
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        context: Ctx,
        path: Option<String>,
    },
    Serialize {
        tid: usize,
        data: Vec<u8>,
        hash: [u8; 20],
    },
    Delete {
        tid: usize,
        hash: [u8; 20],
        files: Vec<PathBuf>,
        path: Option<String>,
    },
    Move {
        tid: usize,
        from: String,
        to: String,
        target: String,
    },
    Validate {
        tid: usize,
        info: Arc<Info>,
        path: Option<String>,
        idx: u32,
        invalid: Vec<u32>,
    },
    Shutdown,
}

pub enum Response {
    Read {
        context: Ctx,
        data: Arc<Box<[u8; 16_384]>>,
    },
    ValidationComplete { tid: usize, invalid: Vec<u32> },
    Moved { tid: usize, path: String },
    Error { tid: usize, err: io::Error },
}

pub struct Ctx {
    pub pid: usize,
    pub tid: usize,
    pub idx: u32,
    pub begin: u32,
    pub length: u32,
}

enum JobRes {
    Resp(Response),
    Done,
    Paused(Request),
}

struct FileCache {
    files: HashMap<path::PathBuf, fs::File>,
}

impl Ctx {
    pub fn new(pid: usize, tid: usize, idx: u32, begin: u32, length: u32) -> Ctx {
        Ctx {
            pid,
            tid,
            idx,
            begin,
            length,
        }
    }
}

impl FileCache {
    pub fn new() -> FileCache {
        FileCache { files: HashMap::new() }
    }

    pub fn get_file<F: FnMut(&mut fs::File) -> io::Result<()>>(
        &mut self,
        path: &path::Path,
        mut f: F,
    ) -> io::Result<()> {
        let hit = if let Some(file) = self.files.get_mut(path) {
            f(file)?;
            true
        } else {
            false
        };
        if !hit {
            // TODO: LRU maybe?
            if self.files.len() >= CONFIG.net.max_open_files {
                let removal = self.files.iter().map(|(id, _)| id.clone()).next().unwrap();
                self.files.remove(&removal);
            }
            fs::create_dir_all(path.parent().unwrap())?;
            let mut file = fs::OpenOptions::new()
                .write(true)
                .create(true)
                .read(true)
                .open(path)?;
            f(&mut file)?;
            self.files.insert(path.to_path_buf(), file);
        }
        Ok(())
    }

    pub fn remove_file(&mut self, path: &path::Path) {
        self.files.remove(path);
    }
}

impl Request {
    pub fn write(
        tid: usize,
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        path: Option<String>,
    ) -> Request {
        Request::Write {
            tid,
            data,
            locations,
            path,
        }
    }

    pub fn read(
        context: Ctx,
        data: Box<[u8; 16_384]>,
        locations: Vec<Location>,
        path: Option<String>,
    ) -> Request {
        Request::Read {
            context,
            data,
            locations,
            path,
        }
    }

    pub fn serialize(tid: usize, data: Vec<u8>, hash: [u8; 20]) -> Request {
        Request::Serialize { tid, data, hash }
    }

    pub fn validate(tid: usize, info: Arc<Info>, path: Option<String>) -> Request {
        Request::Validate {
            tid,
            info,
            path,
            idx: 0,
            invalid: Vec::new(),
        }
    }

    pub fn delete(
        tid: usize,
        hash: [u8; 20],
        files: Vec<PathBuf>,
        path: Option<String>,
    ) -> Request {
        Request::Delete {
            tid,
            hash,
            files,
            path,
        }
    }

    pub fn shutdown() -> Request {
        Request::Shutdown
    }

    fn execute(self, fc: &mut FileCache) -> io::Result<JobRes> {
        let sd = &CONFIG.disk.session;
        let dd = &CONFIG.disk.directory;
        match self {
            Request::Write {
                data,
                locations,
                path,
                ..
            } => {
                for loc in locations {
                    let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                    pb.push(&loc.file);
                    fc.get_file(&pb, |f| {
                        f.seek(SeekFrom::Start(loc.offset))?;
                        f.write_all(&data[loc.start..loc.end])?;
                        Ok(())
                    })?;
                }
            }
            Request::Read {
                context,
                mut data,
                locations,
                path,
                ..
            } => {
                for loc in locations {
                    let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                    pb.push(&loc.file);
                    fc.get_file(&pb, |f| {
                        f.seek(SeekFrom::Start(loc.offset))?;
                        f.read_exact(&mut data[loc.start..loc.end])?;
                        Ok(())
                    })?;
                }
                let data = Arc::new(data);
                return Ok(JobRes::Resp(Response::read(context, data)));
            }
            Request::Move {
                tid,
                from,
                to,
                target,
            } => {
                let mut fp = PathBuf::from(&from);
                let mut tp = PathBuf::from(&to);
                fp.push(target.clone());
                tp.push(target);
                match fs::rename(&fp, &tp) {
                    Ok(_) => {}
                    // Cross filesystem move, try to copy then delete
                    Err(ref e) if e.raw_os_error() == Some(EXDEV) => {
                        match fs_extra::dir::copy(&fp, &tp, &fs_extra::dir::CopyOptions::new()) {
                            Ok(_) => {
                                fs::remove_dir_all(&fp)?;
                            }
                            Err(e) => {
                                fs::remove_dir_all(&tp)?;
                                error!("FS copy failed: {:?}", e);
                                return io_err("Failed to copy directory across filesystems!");
                            }
                        }
                    }
                    Err(e) => {
                        error!("FS rename failed: {:?}", e);
                        return Err(e);
                    }
                }
                return Ok(JobRes::Resp(Response::moved(tid, to)));
            }
            Request::Serialize { data, hash, .. } => {
                let mut temp = path::PathBuf::from(sd);
                temp.push(hash_to_id(&hash) + ".temp");
                let mut f = fs::OpenOptions::new().write(true).create(true).open(&temp)?;
                f.write_all(&data)?;
                let mut actual = path::PathBuf::from(sd);
                actual.push(hash_to_id(&hash));
                fs::rename(temp, actual)?;
            }
            Request::Delete { hash, files, path, .. } => {
                let mut spb = path::PathBuf::from(sd);
                spb.push(hash_to_id(&hash));
                fs::remove_file(spb)?;

                for file in files {
                    let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                    pb.push(&file);
                    fc.remove_file(&pb);
                }
            }
            Request::Validate {
                tid,
                info,
                path,
                mut idx,
                mut invalid,
            } => {
                let mut buf = vec![0u8; info.piece_len as usize];
                let mut pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                let mut cf = pb.clone();

                let mut f = fs::OpenOptions::new().read(true).open(&pb);

                let start = time::Instant::now();

                while idx < info.pieces() &&
                    start.elapsed() < time::Duration::from_secs(JOB_TIME_SLICE)
                {
                    let mut valid = true;
                    let mut ctx = sha1::Sha1::new();
                    let locs = info.piece_disk_locs(idx);
                    let mut pos = 0;
                    for loc in locs {
                        if loc.file != cf {
                            pb = path::PathBuf::from(path.as_ref().unwrap_or(dd));
                            pb.push(&loc.file);
                            f = fs::OpenOptions::new().read(true).open(&pb);
                            cf = loc.file.clone();
                        }
                        // Because this is pausable/resumable, we need to seek to the proper
                        // file position.
                        f.as_mut()
                            .map(|file| file.seek(SeekFrom::Start(loc.offset)))
                            .ok();
                        if let Ok(Ok(amnt)) = f.as_mut().map(|file| file.read(&mut buf[pos..])) {
                            ctx.update(&buf[pos..pos + amnt]);
                            pos += amnt;
                        } else {
                            valid = false;
                        }
                    }
                    let digest = ctx.finish();
                    if !valid || digest.as_ref() != &info.hashes[idx as usize][..] {
                        invalid.push(idx);
                    }

                    idx += 1;
                }
                if idx == info.pieces() {
                    return Ok(JobRes::Resp(Response::validation_complete(tid, invalid)));
                } else {
                    return Ok(JobRes::Paused(Request::Validate {
                        tid,
                        info,
                        path,
                        idx,
                        invalid,
                    }));
                }
            }
            Request::Shutdown => unreachable!(),
        }
        Ok(JobRes::Done)
    }

    pub fn tid(&self) -> usize {
        match *self {
            Request::Serialize { tid, .. } |
            Request::Validate { tid, .. } |
            Request::Delete { tid, .. } |
            Request::Move { tid, .. } |
            Request::Write { tid, .. } => tid,
            Request::Read { ref context, .. } => context.tid,
            Request::Shutdown => unreachable!(),
        }
    }
}

impl fmt::Debug for Request {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "disk::Request")
    }
}

impl Location {
    pub fn new(file: PathBuf, offset: u64, start: u64, end: u64) -> Location {
        Location {
            file,
            offset,
            start: start as usize,
            end: end as usize,
        }
    }
}

impl Response {
    pub fn read(context: Ctx, data: Arc<Box<[u8; 16_384]>>) -> Response {
        Response::Read { context, data }
    }

    pub fn error(tid: usize, err: io::Error) -> Response {
        Response::Error { tid, err }
    }

    pub fn moved(tid: usize, path: String) -> Response {
        Response::Moved { tid, path }
    }

    pub fn validation_complete(tid: usize, invalid: Vec<u32>) -> Response {
        Response::ValidationComplete { tid, invalid }
    }

    pub fn tid(&self) -> usize {
        match *self {
            Response::Read { ref context, .. } => context.tid,
            Response::ValidationComplete { tid, .. } |
            Response::Moved { tid, .. } |
            Response::Error { tid, .. } => tid,
        }
    }
}

impl fmt::Debug for Response {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "disk::Response")
    }
}

impl Disk {
    pub fn new(
        poll: amy::Poller,
        ch: handle::Handle<Request, Response>,
        jobs: amy::Receiver<Job>,
    ) -> Disk {
        Disk {
            poll,
            ch,
            jobs,
            files: FileCache::new(),
            active: VecDeque::new(),
        }
    }

    pub fn run(&mut self) {
        let sd = &CONFIG.disk.session;
        fs::create_dir_all(sd).unwrap();

        loop {
            match self.poll.wait(POLL_INT_MS) {
                Ok(_) => {
                    if self.handle_events() {
                        break;
                    }
                }
                Err(e) => {
                    error!("Failed to poll for events: {:?}", e);
                }
            }
            if !self.active.is_empty() && self.handle_active() {
                break;
            }
        }
    }

    fn handle_active(&mut self) -> bool {
        while let Some(j) = self.active.pop_front() {
            let tid = j.tid();
            match j.execute(&mut self.files) {
                Ok(JobRes::Resp(r)) => {
                    self.ch.send(r).ok();
                }
                Ok(JobRes::Paused(s)) => {
                    self.active.push_front(s);
                }
                Ok(JobRes::Done) => {}
                Err(e) => {
                    self.ch.send(Response::error(tid, e)).ok();
                }
            }
            match self.poll.wait(0) {
                Ok(_) => {
                    if self.handle_events() {
                        return true;
                    }
                }
                Err(e) => {
                    error!("Failed to poll for events: {:?}", e);
                }
            }
        }
        false
    }

    pub fn handle_events(&mut self) -> bool {
        loop {
            match self.ch.recv() {
                Ok(Request::Shutdown) => {
                    return true;
                }
                Ok(r) => {
                    trace!("Handling disk job!");
                    let tid = r.tid();
                    match r.execute(&mut self.files) {
                        Ok(JobRes::Resp(r)) => {
                            self.ch.send(r).ok();
                        }
                        Ok(JobRes::Paused(s)) => {
                            self.active.push_back(s);
                        }
                        Ok(JobRes::Done) => {}
                        Err(e) => {
                            self.ch.send(Response::error(tid, e)).ok();
                        }
                    }
                }
                _ => break,
            }
        }
        while let Ok(j) = self.jobs.try_recv() {
            let mut p = j.path.clone();
            p.set_extension("temp");
            let res = fs::OpenOptions::new()
                .write(true)
                .create(true)
                .open(&p)
                .map(|mut f| f.write(&j.data[..]));
            match res {
                Ok(Ok(_)) => {
                    fs::rename(&p, &j.path).ok();
                }
                Ok(Err(e)) => {
                    error!("Failed to write disk job: {}", e);
                    fs::remove_file(&p).ok();
                }
                Err(e) => {
                    error!("Failed to write disk job: {}", e);
                }
            }
        }
        false
    }
}

pub fn start(
    creg: &mut amy::Registrar,
) -> io::Result<(handle::Handle<Response, Request>, amy::Sender<Job>, thread::JoinHandle<()>)> {
    let poll = amy::Poller::new()?;
    let mut reg = poll.get_registrar()?;
    let (ch, dh) = handle::Handle::new(creg, &mut reg)?;
    let (tx, rx) = reg.channel()?;
    let h = dh.run("disk", move |h| Disk::new(poll, h, rx).run())?;
    Ok((ch, tx, h))
}
<|endoftext|>


<commit_before>//! Functions used for adding labels to the sounding plot
use app::AppContext;
use config;

use coords::ScreenCoords;

use cairo::{Context, Matrix, FontExtents, FontFace, FontSlant, FontWeight};

//
// TODO: Labeling is clunky. Rewrite this module to make a list of labels and rectangles. If a
// rectangle overlaps another label, it does not get added to the list. Make high priority labels
// first. Fill the rectangles with the background color and then draw the labels.
//

// Set up the font matrix, and set the font, etc.
pub fn prepare_to_label(cr: &Context, ac: &AppContext) {

    // Configure the font.
    let font_face = FontFace::toy_create(config::FONT_NAME, FontSlant::Normal, FontWeight::Bold);
    cr.set_font_face(font_face);

    set_font_size(config::LARGE_FONT_SIZE, cr, ac);
}

// Set the font size by reseting the font matrix
fn set_font_size(size_in_pnts: f64, cr: &Context, ac: &AppContext) {
    let dpi = match ac.get_dpi() {
        None => 72.0,
        Some(value) => value,
    };

    let font_size = size_in_pnts / 72.0 * dpi;
    let (font_size, _) = ac.convert_device_to_screen((font_size, 0.0));

    // Flip the y-coordinate so it displays the font right side up
    cr.set_font_matrix(Matrix {
        xx: 1.0 * font_size,
        yx: 0.0,
        xy: 0.0,
        yy: -1.0 * font_size, // Reflect it to be right side up!
        x0: 0.0,
        y0: 0.0,
    });
}

// Label the pressure, temperatures, etc lines.
pub fn draw_background_labels(cr: &Context, ac: &AppContext) {

    let (screen_x_min, screen_x_max, screen_max_p, screen_y_max) = calculate_label_boundaries(ac);

    // Used for checking overlap later with T
    let max_p_label_right_edge: f64 =
        label_isobars(cr, ac, screen_x_min, screen_x_max, screen_y_max);

    label_isotherms(
        cr,
        ac,
        screen_x_max,
        screen_y_max,
        screen_max_p,
        max_p_label_right_edge,
    );
}

fn calculate_label_boundaries(ac: &AppContext) -> (f64, f64, f64, f64) {

    // Get min/max screen coordinate values
    let (lower_left_screen, upper_right_screen) = ac.bounding_box_in_screen_coords();
    let (mut screen_x_min, _screen_y_min) = lower_left_screen;
    let (screen_x_max, screen_y_max) = upper_right_screen;

    // Get coordinates to keep labels from flowing off the chart.
    let (xmin, _ymin) = ac.convert_xy_to_screen((0.0, 0.0));
    let (_, mut screen_max_p) = ac.convert_screen_to_tp((0.0, 0.0));
    if screen_max_p > config::MAXP {
        screen_max_p = config::MAXP;
    }
    if xmin > screen_x_min {
        screen_x_min = xmin;
    }

    (screen_x_min, screen_x_max, screen_max_p, screen_y_max)
}

fn label_isobars(
    cr: &Context,
    ac: &AppContext,
    screen_x_min: f64,
    screen_x_max: f64,
    screen_y_max: f64,
) -> f64 {

    // Setup label colors
    let rgba = config::ISOBAR_RGBA;
    cr.set_source_rgba(rgba.0, rgba.1, rgba.2, 1.0);

    // For checking label overlap.
    let mut max_p_label_right_edge: f64 = 0.0; // Used for checking overlap later with T
    let mut last_label_y = ::std::f64::MIN; // Used for checking overlap between pressure labels

    for &p in config::ISOBARS.into_iter() {
        // Make the label text
        let label = format!("{}", p);

        // Calculate position of lower left edge for label
        let (_, mut screen_y) = ac.convert_tp_to_screen((0.0, p));
        screen_y += 0.005 * screen_y_max; // Lift off the pressure line slightly

        // Check for vertical overlap.
        let label_extents = cr.text_extents(&label);
        let (label_width, label_height) = (label_extents.width, label_extents.height);
        if screen_y < label_height + last_label_y {
            continue;
        }
        last_label_y = screen_y;

        // Update right edge for checking with T later.
        if label_width > max_p_label_right_edge {
            max_p_label_right_edge = label_width;
        }

        // Draw the label
        cr.move_to(screen_x_min + 0.01 * screen_x_max, screen_y);
        cr.show_text(&label);
    }
    // This is width, add position of left edge to get right edge
    max_p_label_right_edge += screen_x_min + 0.01 * screen_x_max;

    // Use this to make sure temperatures don't overlap
    max_p_label_right_edge
}

fn label_isotherms(
    cr: &Context,
    ac: &AppContext,
    screen_x_max: f64,
    screen_y_max: f64,
    screen_max_p: f64,
    max_p_label_right_edge: f64,
) {
    // Label isotherms
    let rgba = config::ISOTHERM_RGBA;
    let (mut last_label_x_max, mut last_label_x_min) = (0.0, 0.0);
    cr.set_source_rgba(rgba.0, rgba.1, rgba.2, 1.0);
    for &t in config::ISOTHERMS.into_iter() {
        // Make the label text
        let label = format!("{}", t);

        // Calculate position for lower left edge of label
        let (mut screen_x, mut screen_y) = ac.convert_tp_to_screen((t, screen_max_p));
        screen_y += 0.008 * screen_y_max;
        screen_x += 0.008 * screen_y_max;

        // Check for overlap with pressure labels
        if screen_x < max_p_label_right_edge {
            continue;
        }

        // Check for overlap with other T labels
        let extents_width = cr.text_extents(&label).width;
        let (x_min, x_max) = (screen_x, screen_x + extents_width);
        if (x_min > last_label_x_min && x_min < last_label_x_max) ||
            (x_max > last_label_x_min && x_max < last_label_x_max)
        {
            continue;
        }

        last_label_x_min = x_min - 0.008 * screen_x_max;
        last_label_x_max = x_max + 0.008 * screen_x_max;

        // Draw the label
        cr.move_to(screen_x, screen_y);
        cr.show_text(&label);
    }
}

// Add a description box
pub fn draw_legend(cr: &Context, ac: &AppContext) {
    if !ac.plottable() {
        return;
    }

    let upper_left = ac.convert_device_to_screen((5.0, 5.0));
    let font_extents = cr.font_extents();

    let (source_name, valid_time, location) = build_label_strings(ac);

    let (box_width, box_height) =
        calculate_legend_box_size(cr, &font_extents, &source_name, &valid_time, &location);

    draw_legend_rectangle(cr, &upper_left, box_width, box_height);

    draw_legend_text(
        cr,
        &upper_left,
        &font_extents,
        &source_name,
        &valid_time,
        &location,
    );

}

fn build_label_strings(ac: &AppContext) -> (Option<String>, Option<String>, Option<String>) {

    let source_name: Option<String> = ac.get_source_name();
    let mut valid_time: Option<String> = None;
    let mut location: Option<String> = None;
    if let Some(snd) = ac.get_sounding_for_display() {
        // Build the valid time part
        if let Some(vt) = snd.valid_time {
            use chrono::{Datelike, Timelike};
            valid_time = Some(format!(
                "Valid: {:02}/{:02}/{:04} {:02}Z",
                vt.month(),
                vt.day(),
                vt.year(),
                vt.hour()
            ));
        }

        // Build location part.
        if snd.lat.as_option().is_some() || snd.lon.as_option().is_some() ||
            snd.elevation.as_option().is_some()
        {
            location = Some("".to_owned());
            if let Some(ref mut loc) = location {
                if let Some(lat) = snd.lat.as_option() {
                    loc.push_str(&format!("{:.2}", lat));
                }
                if let Some(lon) = snd.lon.as_option() {
                    loc.push_str(&format!(", {:.2}", lon));
                }
                if let Some(el) = snd.elevation.as_option() {
                    loc.push_str(&format!(", {:.0}m ({:.0}ft)", el, el * 3.28084));
                }
            }
        }
    }

    (source_name, valid_time, location)
}

fn calculate_legend_box_size(
    cr: &Context,
    font_extents: &FontExtents,
    source_name: &Option<String>,
    valid_time: &Option<String>,
    location: &Option<String>,
) -> (f64, f64) {

    let mut box_width: f64 = 0.0;
    let mut box_height: f64 = 0.0;

    if let &Some(ref src) = source_name {
        let extents = cr.text_extents(src);
        if extents.width > box_width {
            box_width = extents.width;
        }
        box_height += extents.height;
    }

    if let &Some(ref vt) = valid_time {
        let extents = cr.text_extents(vt);
        if extents.width > box_width {
            box_width = extents.width;
        }
        box_height += extents.height;
        // Add line spacing if previous line was there.
        if source_name.is_some() {
            box_height += font_extents.height - extents.height;
        }
    }

    if let &Some(ref loc) = location {
        let extents = cr.text_extents(loc);
        if extents.width > box_width {
            box_width = extents.width;
        }
        box_height += extents.height;
        // Add line spacing if previous line was there.
        if valid_time.is_some() {
            box_height += font_extents.height - extents.height;
        }
    }

    // Add room for the last line's descent
    box_height += font_extents.descent;

    // Add padding last
    box_height += 2.0 * config::DEFAULT_PADDING;
    box_width += 2.0 * config::DEFAULT_PADDING;

    (box_width, box_height)
}

fn draw_legend_rectangle(cr: &Context, upper_left: &ScreenCoords, width: f64, height: f64) {
    cr.rectangle(upper_left.0, upper_left.1 - height, width, height);

    let rgb = config::ISOBAR_RGBA;
    cr.set_source_rgba(rgb.0, rgb.1, rgb.2, rgb.3);
    cr.set_line_width(cr.device_to_user_distance(3.0, 0.0).0);
    cr.stroke_preserve();
    let rgb = config::BACKGROUND_RGB;
    cr.set_source_rgb(rgb.0, rgb.1, rgb.2);
    cr.fill();
}

fn draw_legend_text(
    cr: &Context,
    upper_left: &ScreenCoords,
    font_extents: &FontExtents,
    source_name: &Option<String>,
    valid_time: &Option<String>,
    location: &Option<String>,
) {
    let rgb = config::ISOBAR_RGBA;
    cr.set_source_rgba(rgb.0, rgb.1, rgb.2, rgb.3);

    // Remember how many lines we have drawn so far for setting position of the next line.
    let mut num_lines_drawn = 0;

    // Get into the initial position
    cr.move_to(
        upper_left.0 + config::DEFAULT_PADDING,
        upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent,
    );

    if let &Some(ref src) = source_name {
        cr.show_text(src);
        num_lines_drawn += 1;
        cr.move_to(
            upper_left.0 + config::DEFAULT_PADDING,
            upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent -
                num_lines_drawn as f64 * font_extents.height,
        );
    }
    if let &Some(ref vt) = valid_time {
        cr.show_text(vt);
        num_lines_drawn += 1;
        cr.move_to(
            upper_left.0 + config::DEFAULT_PADDING,
            upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent -
                num_lines_drawn as f64 * font_extents.height,
        );
    }
    if let &Some(ref loc) = location {
        cr.show_text(loc);
        num_lines_drawn += 1;
        cr.move_to(
            upper_left.0 + config::DEFAULT_PADDING,
            upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent -
                num_lines_drawn as f64 * font_extents.height,
        );
    }
}
<commit_msg>Fix legend bug when app resized.<commit_after>//! Functions used for adding labels to the sounding plot
use app::AppContext;
use config;

use coords::ScreenCoords;

use cairo::{Context, Matrix, FontExtents, FontFace, FontSlant, FontWeight};

//
// TODO: Labeling is clunky. Rewrite this module to make a list of labels and rectangles. If a
// rectangle overlaps another label, it does not get added to the list. Make high priority labels
// first. Fill the rectangles with the background color and then draw the labels.
//

// Set up the font matrix, and set the font, etc.
pub fn prepare_to_label(cr: &Context, ac: &AppContext) {

    // Configure the font.
    let font_face = FontFace::toy_create(config::FONT_NAME, FontSlant::Normal, FontWeight::Bold);
    cr.set_font_face(font_face);

    set_font_size(config::LARGE_FONT_SIZE, cr, ac);
}

// Set the font size by reseting the font matrix
fn set_font_size(size_in_pnts: f64, cr: &Context, ac: &AppContext) {
    let dpi = match ac.get_dpi() {
        None => 72.0,
        Some(value) => value,
    };

    let font_size = size_in_pnts / 72.0 * dpi;
    let (font_size, _) = ac.convert_device_to_screen((font_size, 0.0));

    // Flip the y-coordinate so it displays the font right side up
    cr.set_font_matrix(Matrix {
        xx: 1.0 * font_size,
        yx: 0.0,
        xy: 0.0,
        yy: -1.0 * font_size, // Reflect it to be right side up!
        x0: 0.0,
        y0: 0.0,
    });
}

// Label the pressure, temperatures, etc lines.
pub fn draw_background_labels(cr: &Context, ac: &AppContext) {

    let (screen_x_min, screen_x_max, screen_max_p, screen_y_max) = calculate_label_boundaries(ac);

    // Used for checking overlap later with T
    let max_p_label_right_edge: f64 =
        label_isobars(cr, ac, screen_x_min, screen_x_max, screen_y_max);

    label_isotherms(
        cr,
        ac,
        screen_x_max,
        screen_y_max,
        screen_max_p,
        max_p_label_right_edge,
    );
}

fn calculate_label_boundaries(ac: &AppContext) -> (f64, f64, f64, f64) {

    // Get min/max screen coordinate values
    let (lower_left_screen, upper_right_screen) = ac.bounding_box_in_screen_coords();
    let (mut screen_x_min, _screen_y_min) = lower_left_screen;
    let (screen_x_max, screen_y_max) = upper_right_screen;

    // Get coordinates to keep labels from flowing off the chart.
    let (xmin, _ymin) = ac.convert_xy_to_screen((0.0, 0.0));
    let (_, mut screen_max_p) = ac.convert_screen_to_tp((0.0, 0.0));
    if screen_max_p > config::MAXP {
        screen_max_p = config::MAXP;
    }
    if xmin > screen_x_min {
        screen_x_min = xmin;
    }

    (screen_x_min, screen_x_max, screen_max_p, screen_y_max)
}

fn label_isobars(
    cr: &Context,
    ac: &AppContext,
    screen_x_min: f64,
    screen_x_max: f64,
    screen_y_max: f64,
) -> f64 {

    // Setup label colors
    let rgba = config::ISOBAR_RGBA;
    cr.set_source_rgba(rgba.0, rgba.1, rgba.2, 1.0);

    // For checking label overlap.
    let mut max_p_label_right_edge: f64 = 0.0; // Used for checking overlap later with T
    let mut last_label_y = ::std::f64::MIN; // Used for checking overlap between pressure labels

    for &p in config::ISOBARS.into_iter() {
        // Make the label text
        let label = format!("{}", p);

        // Calculate position of lower left edge for label
        let (_, mut screen_y) = ac.convert_tp_to_screen((0.0, p));
        screen_y += 0.005 * screen_y_max; // Lift off the pressure line slightly

        // Check for vertical overlap.
        let label_extents = cr.text_extents(&label);
        let (label_width, label_height) = (label_extents.width, label_extents.height);
        if screen_y < label_height + last_label_y {
            continue;
        }
        last_label_y = screen_y;

        // Update right edge for checking with T later.
        if label_width > max_p_label_right_edge {
            max_p_label_right_edge = label_width;
        }

        // Draw the label
        cr.move_to(screen_x_min + 0.01 * screen_x_max, screen_y);
        cr.show_text(&label);
    }
    // This is width, add position of left edge to get right edge
    max_p_label_right_edge += screen_x_min + 0.01 * screen_x_max;

    // Use this to make sure temperatures don't overlap
    max_p_label_right_edge
}

fn label_isotherms(
    cr: &Context,
    ac: &AppContext,
    screen_x_max: f64,
    screen_y_max: f64,
    screen_max_p: f64,
    max_p_label_right_edge: f64,
) {
    // Label isotherms
    let rgba = config::ISOTHERM_RGBA;
    let (mut last_label_x_max, mut last_label_x_min) = (0.0, 0.0);
    cr.set_source_rgba(rgba.0, rgba.1, rgba.2, 1.0);
    for &t in config::ISOTHERMS.into_iter() {
        // Make the label text
        let label = format!("{}", t);

        // Calculate position for lower left edge of label
        let (mut screen_x, mut screen_y) = ac.convert_tp_to_screen((t, screen_max_p));
        screen_y += 0.008 * screen_y_max;
        screen_x += 0.008 * screen_y_max;

        // Check for overlap with pressure labels
        if screen_x < max_p_label_right_edge {
            continue;
        }

        // Check for overlap with other T labels
        let extents_width = cr.text_extents(&label).width;
        let (x_min, x_max) = (screen_x, screen_x + extents_width);
        if (x_min > last_label_x_min && x_min < last_label_x_max) ||
            (x_max > last_label_x_min && x_max < last_label_x_max)
        {
            continue;
        }

        last_label_x_min = x_min - 0.008 * screen_x_max;
        last_label_x_max = x_max + 0.008 * screen_x_max;

        // Draw the label
        cr.move_to(screen_x, screen_y);
        cr.show_text(&label);
    }
}

// Add a description box
pub fn draw_legend(cr: &Context, ac: &AppContext) {
    if !ac.plottable() {
        return;
    }

    let mut upper_left = ac.convert_device_to_screen((5.0, 5.0));
    // Make sure we stay on the x-y coords domain
    let (xmin, ymax) = ac.convert_xy_to_screen((0.0, 1.0));
    if ymax - upper_left.0 < upper_left.1 {
        upper_left.1 = ymax - upper_left.0;
    }

    if xmin + upper_left.0 > upper_left.0 {
        upper_left.0 = xmin + upper_left.0;
    }

    let font_extents = cr.font_extents();

    let (source_name, valid_time, location) = build_label_strings(ac);

    let (box_width, box_height) =
        calculate_legend_box_size(cr, &font_extents, &source_name, &valid_time, &location);

    draw_legend_rectangle(cr, &upper_left, box_width, box_height);

    draw_legend_text(
        cr,
        &upper_left,
        &font_extents,
        &source_name,
        &valid_time,
        &location,
    );

}

fn build_label_strings(ac: &AppContext) -> (Option<String>, Option<String>, Option<String>) {

    let source_name: Option<String> = ac.get_source_name();
    let mut valid_time: Option<String> = None;
    let mut location: Option<String> = None;
    if let Some(snd) = ac.get_sounding_for_display() {
        // Build the valid time part
        if let Some(vt) = snd.valid_time {
            use chrono::{Datelike, Timelike};
            valid_time = Some(format!(
                "Valid: {:02}/{:02}/{:04} {:02}Z",
                vt.month(),
                vt.day(),
                vt.year(),
                vt.hour()
            ));
        }

        // Build location part.
        if snd.lat.as_option().is_some() || snd.lon.as_option().is_some() ||
            snd.elevation.as_option().is_some()
        {
            location = Some("".to_owned());
            if let Some(ref mut loc) = location {
                if let Some(lat) = snd.lat.as_option() {
                    loc.push_str(&format!("{:.2}", lat));
                }
                if let Some(lon) = snd.lon.as_option() {
                    loc.push_str(&format!(", {:.2}", lon));
                }
                if let Some(el) = snd.elevation.as_option() {
                    loc.push_str(&format!(", {:.0}m ({:.0}ft)", el, el * 3.28084));
                }
            }
        }
    }

    (source_name, valid_time, location)
}

fn calculate_legend_box_size(
    cr: &Context,
    font_extents: &FontExtents,
    source_name: &Option<String>,
    valid_time: &Option<String>,
    location: &Option<String>,
) -> (f64, f64) {

    let mut box_width: f64 = 0.0;
    let mut box_height: f64 = 0.0;

    if let &Some(ref src) = source_name {
        let extents = cr.text_extents(src);
        if extents.width > box_width {
            box_width = extents.width;
        }
        box_height += extents.height;
    }

    if let &Some(ref vt) = valid_time {
        let extents = cr.text_extents(vt);
        if extents.width > box_width {
            box_width = extents.width;
        }
        box_height += extents.height;
        // Add line spacing if previous line was there.
        if source_name.is_some() {
            box_height += font_extents.height - extents.height;
        }
    }

    if let &Some(ref loc) = location {
        let extents = cr.text_extents(loc);
        if extents.width > box_width {
            box_width = extents.width;
        }
        box_height += extents.height;
        // Add line spacing if previous line was there.
        if valid_time.is_some() {
            box_height += font_extents.height - extents.height;
        }
    }

    // Add room for the last line's descent
    box_height += font_extents.descent;

    // Add padding last
    box_height += 2.0 * config::DEFAULT_PADDING;
    box_width += 2.0 * config::DEFAULT_PADDING;

    (box_width, box_height)
}

fn draw_legend_rectangle(cr: &Context, upper_left: &ScreenCoords, width: f64, height: f64) {
    cr.rectangle(upper_left.0, upper_left.1 - height, width, height);

    let rgb = config::ISOBAR_RGBA;
    cr.set_source_rgba(rgb.0, rgb.1, rgb.2, rgb.3);
    cr.set_line_width(cr.device_to_user_distance(3.0, 0.0).0);
    cr.stroke_preserve();
    let rgb = config::BACKGROUND_RGB;
    cr.set_source_rgb(rgb.0, rgb.1, rgb.2);
    cr.fill();
}

fn draw_legend_text(
    cr: &Context,
    upper_left: &ScreenCoords,
    font_extents: &FontExtents,
    source_name: &Option<String>,
    valid_time: &Option<String>,
    location: &Option<String>,
) {
    let rgb = config::ISOBAR_RGBA;
    cr.set_source_rgba(rgb.0, rgb.1, rgb.2, rgb.3);

    // Remember how many lines we have drawn so far for setting position of the next line.
    let mut num_lines_drawn = 0;

    // Get into the initial position
    cr.move_to(
        upper_left.0 + config::DEFAULT_PADDING,
        upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent,
    );

    if let &Some(ref src) = source_name {
        cr.show_text(src);
        num_lines_drawn += 1;
        cr.move_to(
            upper_left.0 + config::DEFAULT_PADDING,
            upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent -
                num_lines_drawn as f64 * font_extents.height,
        );
    }
    if let &Some(ref vt) = valid_time {
        cr.show_text(vt);
        num_lines_drawn += 1;
        cr.move_to(
            upper_left.0 + config::DEFAULT_PADDING,
            upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent -
                num_lines_drawn as f64 * font_extents.height,
        );
    }
    if let &Some(ref loc) = location {
        cr.show_text(loc);
        num_lines_drawn += 1;
        cr.move_to(
            upper_left.0 + config::DEFAULT_PADDING,
            upper_left.1 - config::DEFAULT_PADDING - font_extents.ascent -
                num_lines_drawn as f64 * font_extents.height,
        );
    }
}
<|endoftext|>


<commit_before>// This file is part of the uutils coreutils package.
//
// (c) Smigle00 <smigle00@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

#[macro_use]
extern crate uucore;

use platform_info::*;

use clap::{crate_version, App};
use uucore::error::{FromIo, UResult};

static ABOUT: &str = "Display machine architecture";
static SUMMARY: &str = "Determine architecture name for current machine.";

#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    uu_app().get_matches_from(args);

    let uts = PlatformInfo::new().map_err_context(|| "arch: ".to_string())?;
    println!("{}", uts.machine().trim());
    Ok(())
}

pub fn uu_app() -> App<'static, 'static> {
    App::new(executable!())
        .version(crate_version!())
        .about(ABOUT)
        .after_help(SUMMARY)
}
<commit_msg>arch: match GNU error<commit_after>// This file is part of the uutils coreutils package.
//
// (c) Smigle00 <smigle00@gmail.com>
// (c) Jian Zeng <anonymousknight96 AT gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

#[macro_use]
extern crate uucore;

use platform_info::*;

use clap::{crate_version, App};
use uucore::error::{FromIo, UResult};

static ABOUT: &str = "Display machine architecture";
static SUMMARY: &str = "Determine architecture name for current machine.";

#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    uu_app().get_matches_from(args);

    let uts = PlatformInfo::new().map_err_context(|| "cannot get system name".to_string())?;
    println!("{}", uts.machine().trim());
    Ok(())
}

pub fn uu_app() -> App<'static, 'static> {
    App::new(executable!())
        .version(crate_version!())
        .about(ABOUT)
        .after_help(SUMMARY)
}
<|endoftext|>


<commit_before>//! Implementation of Ethernet related Extractors

use net::Packet;

use net::defs::{Rule, EtherType};

use net::conn::filter::Extractor;

use super::defs::Header;

/// Type responsible for ether type extraction
pub struct EtherTypeExtractor;

impl Extractor<EtherType> for EtherTypeExtractor {
    /// Extract the ether type from a rule
    fn from_rule(rule: &Rule) -> Option<EtherType> {
        rule.eth_rule.as_ref().map(|eth_rule| eth_rule.ether_type)
    }

    /// Extract the ether type from a packet
    fn from_packet(pkt: &Packet) -> Option<EtherType> {
        pkt.link_header::<Header>().map(|hdr| hdr.ether_type.as_host())
    }
}
<commit_msg>net/eth: Add definition of type SourceHwExtractor<commit_after>//! Implementation of Ethernet related Extractors

use net::Packet;

use net::defs::{Rule, HwAddr, EtherType};

use net::conn::filter::Extractor;

use super::defs::Header;

/// Type responsible for ether type extraction
pub struct EtherTypeExtractor;

impl Extractor<EtherType> for EtherTypeExtractor {
    /// Extract the ether type from a rule
    fn from_rule(rule: &Rule) -> Option<EtherType> {
        rule.eth_rule.as_ref().map(|eth_rule| eth_rule.ether_type)
    }

    /// Extract the ether type from a packet
    fn from_packet(pkt: &Packet) -> Option<EtherType> {
        pkt.link_header::<Header>().map(|hdr| hdr.ether_type.as_host())
    }
}

/// Type responsible for source hardware extraction
pub struct SourceHwExtractor;

impl Extractor<HwAddr> for SourceHwExtractor {
    /// Extract the source hardware address from a rule
    fn from_rule(_rule: &Rule) -> Option<HwAddr> {
        unimplemented!();
    }

    /// Extract the source hardware address from a packet
    fn from_packet(pkt: &Packet) -> Option<HwAddr> {
        pkt.link_header::<Header>().map(|hdr| hdr.src.clone())
    }
}
<|endoftext|>


<commit_before>extern crate glium;
extern crate image;

use std::collections::HashMap;

use glium::{DisplayBuild, VertexBuffer, Program, DrawParameters, Surface};
use glium::backend::glutin_backend::GlutinFacade;
use glium::texture::Texture2d;
use glium::index::NoIndices;

use std::io::{Read, Cursor};
use std::fs::File;

use super::{Result};

#[derive(Copy, Clone)]
struct Vertex {
    position  : [f32; 2],
    tex_coords: [f32; 2]
}

implement_vertex!(Vertex, position, tex_coords);

impl Vertex{
    #[inline]
    fn new(position: [f32; 2], tex_coords: [f32; 2]) -> Self{
        Vertex{
            position  : position,
            tex_coords: tex_coords,
         }
    }
}

type VertexBuffers = [VertexBuffer<Vertex>; 2];

/// Struct for storing a 2D texture
// NOTE Size: 1 696 bytes
pub struct Texture{
    tex: Texture2d,
    vertex_buffers: VertexBuffers,
}

impl Texture {
    /// Creates a new instance of `Texture` with the given bytes
    fn new(display: &GlutinFacade, bytes: &[u8], width: u32, height: u32) -> Result<Texture>{
        //let (dis_width, dis_height) = display.get_window().unwrap().get_inner_size().unwrap();

        let image = try!(image::load(Cursor::new(bytes),
            image::PNG)).to_rgba();

        let image_dimensions = image.dimensions();
        let image = glium::texture::RawImage2d::from_raw_rgba_reversed(image.into_raw(), image_dimensions);

        let (w, h) = (width as f32 / 2.0, height as f32 / 2.0);

        let v1 = Vertex::new([-w, -h], [0.0, 0.0]);
        let v2 = Vertex::new([ w, -h], [1.0, 0.0]);
        let v3 = Vertex::new([ w,  h], [1.0, 1.0]);
        let v4 = Vertex::new([-w,  h], [0.0, 1.0]);

        Ok(Texture {
            tex: try!(Texture2d::new(display, image)),
            vertex_buffers: [try!(VertexBuffer::new(display, &[v1, v2, v4])), try!(VertexBuffer::new(display, &[v2, v3, v4]))],
        })
    }

    /// Reads the bytes of a texture from a file and creates a `Texture` instance
    fn new_from_file(display: &GlutinFacade, str_path: &str, width: u32, height: u32) -> Result<Texture>{
        let mut f = try!(File::open(str_path));
        let mut bytes = Vec::new();
        try!(f.read_to_end(&mut bytes));

        Self::new(display, &bytes, width, height)
    }

    /// Gets the array of `VertexBuffer`s
    fn get_vertex_bufffers(&self) -> &VertexBuffers {
        &self.vertex_buffers
    }

    /// Returns a `TextureDrawer` to draw the `Texture1`
    pub fn drawer(&self) -> TextureDrawer{
        TextureDrawer{
            texture: &self,
            rotation   : 0.0,
            translation: (0.0, 0.0),
        }
    }
}

/*
const INDENTIY_MATRIX: [[f32; 4]; 4] = [
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]];
*/

type Textures<'a> = HashMap<&'a str, Texture>;

/// Trait for objects that can be drawn to the screen
pub trait Drawable {
    /// Returns the position on the screen it should be drawn
    fn get_pos(&self) -> (f32, f32);
    /// Returns the rotation it should be drawn with
    fn get_rotation(&self) -> f32;
    /// Returns the `Texture`
    fn get_texture(&self) -> &Texture;
}

/// Functionality for rendering
pub struct Draw<'a> {
    display: GlutinFacade,
    program: Program,
    indices: NoIndices,
    params : DrawParameters<'a>
}

impl<'a> Draw<'a> {
    /// Creates a new `Draw` from a `Display` made using the arguments
    pub fn new(title: &str, width: u32, height: u32) -> Draw<'a> {
        Self::new_from_display(
            glium::glutin::WindowBuilder::new()
                .with_title(title.to_string())
                .with_dimensions(width, height)
                .with_vsync()
                .build_glium().expect("Failed to build the window")
        )
    }

    /// Creates a new `Draw` instance using the given display
    pub fn new_from_display(display: GlutinFacade) -> Draw<'a> {
        let (w, h) = display.get_window().unwrap().get_inner_size().unwrap();
        let (w, h) = (w as f32 / 2.0, h as f32 / 2.0);

        let vertex_shader_src = &r#"
            #version 140

            in vec2 position;
            in vec2 tex_coords;
            out vec2 v_tex_coords;

            uniform mat4 matrix;

            void main() {
                v_tex_coords = tex_coords;

                vec4 pos = matrix * vec4(position, 0.0, 1.0);

                pos.x /= $width;
                pos.y /= $height;

                gl_Position = pos;
            }
        "#.replace("$width", &w.to_string()).replace("$height", &h.to_string());
        let fragment_shader_src = r#"
            #version 140

            in vec2 v_tex_coords;
            out vec4 color;

            uniform sampler2D tex;

            void main() {
                color = texture(tex, v_tex_coords);
            }
        "#;

        let params: DrawParameters = DrawParameters{
            blend : glium::Blend::alpha_blending(),
            .. Default::default()
        };

        Draw{
            program: Program::from_source(&display, vertex_shader_src, fragment_shader_src, None).unwrap(),
            indices: NoIndices(glium::index::PrimitiveType::TrianglesList),
            display: display,
            params : params,
            // halfsize: (w, h),
        }
    }

    /// Returns a `Texture` created from a byte slice
    pub fn load_texture_from_bytes(&self, bytes: &[u8], width: u32, height: u32) -> Result<Texture> {
        Texture::new(&self.display, bytes, width, height)
    }

    /// Returns a `Texture` created from a file
    pub fn load_texture(&self, identifier: &'a str, width: u32, height: u32) -> Result<Texture> {
        Texture::new_from_file(&self.display, &format!("{}.png", identifier), width, height)
    }

    /// Returns a `DrawablesDrawer` for drawing `Drawable`s to the screen
    pub fn draw_drawables<D: Drawable>(&'a self, target: &'a mut glium::Frame) -> DrawablesDrawer<D>{
        DrawablesDrawer{
            drawables: Vec::new(),
            target: target,
            draw: self
        }
    }

    /// Returns the inner `GlutinFacade`
    pub fn get_display(&self) -> &GlutinFacade {
        &self.display
    }
}

/// An interface for drawing `Drawable`s on the screen
#[must_use = "`DrawablesDrawer` does nothing until drawn"]
pub struct DrawablesDrawer<'a, D: 'a + Drawable>{
    drawables: Vec<&'a D>,
    target: &'a mut glium::Frame,
    draw  : &'a Draw<'a>
}

impl<'a, D: Drawable> DrawablesDrawer<'a, D>{
    /// Adds another `Drawble` to be drawn
    pub fn add(mut self, drawable: &'a D) -> Self{
        self.drawables.push(drawable);

        self
    }

    /// Adds an `Iterator` of `Drawable`s to be drawn
    pub fn add_vec<I: IntoIterator<Item = &'a D>>(mut self, drawables: I) -> Self{
        for d in drawables{
            self = self.add(d);
        }

        self
    }

    /// Finally draws the `Drawable`s
    pub fn draw(self) -> Result<()>{
        for drawable in self.drawables{
            let (x, y) = drawable.get_pos();

            try!(
                drawable.get_texture().drawer()
                    .rotate(drawable.get_rotation())
                    .translate(x, y)
                    .draw(self.target, self.draw)
            );
        }

        Ok(())
    }
}

/// An interface for drawing a texture on the screen
#[must_use = "`TextureDrawer` does nothing until drawn"]
pub struct TextureDrawer<'a> {
    translation: (f32, f32),
    rotation   :  f32,
    texture: &'a Texture,
}

impl<'a> TextureDrawer<'a> {
    /// Rotates the texture
    pub fn rotate(self, rotation: f32) -> TextureDrawer<'a> {
        TextureDrawer{
            rotation: rotation,
            ..
            self
        }
    }

    /// Translates the texture
    pub fn translate(self, translate_x: f32, translate_y: f32) -> TextureDrawer<'a> {
        TextureDrawer{
            translation: (translate_x, translate_y),
            ..
            self
        }
    }

    /// Finally draws the texture
    pub fn draw(self, frame: &mut glium::Frame, draw: &Draw) -> Result<()>{
        let (sin, cos)  = self.rotation.sin_cos();
        let (x, y) = self.translation;

        let uniforms = uniform! {
            tex   : &self.texture.tex,
            matrix: [
                [cos, -sin, 0.0, 0.0],
                [sin,  cos, 0.0, 0.0],
                [0.0,  0.0, 1.0, 0.0],
                [  x,    y, 0.0, 1.0],
            ],
        };

        for vertex_buffer in self.texture.get_vertex_bufffers(){
            try!(frame.draw(vertex_buffer, &draw.indices, &draw.program, &uniforms, &draw.params));
        }

        Ok(())
    }
}
<commit_msg>Add include_texture macro<commit_after>extern crate glium;
extern crate image;

use std::collections::HashMap;

use glium::{DisplayBuild, VertexBuffer, Program, DrawParameters, Surface};
use glium::backend::glutin_backend::GlutinFacade;
use glium::texture::Texture2d;
use glium::index::NoIndices;

use std::io::{Read, Cursor};
use std::fs::File;

use super::{Result};

#[derive(Copy, Clone)]
struct Vertex {
    position  : [f32; 2],
    tex_coords: [f32; 2]
}

implement_vertex!(Vertex, position, tex_coords);

impl Vertex{
    #[inline]
    fn new(position: [f32; 2], tex_coords: [f32; 2]) -> Self{
        Vertex{
            position  : position,
            tex_coords: tex_coords,
         }
    }
}

type VertexBuffers = [VertexBuffer<Vertex>; 2];

/// Struct for storing a 2D texture
// NOTE Size: 1 696 bytes
pub struct Texture{
    tex: Texture2d,
    vertex_buffers: VertexBuffers,
}

impl Texture {
    /// Creates a new instance of `Texture` with the given bytes
    fn new(display: &GlutinFacade, bytes: &[u8], width: u32, height: u32) -> Result<Texture>{
        //let (dis_width, dis_height) = display.get_window().unwrap().get_inner_size().unwrap();

        let image = try!(image::load(Cursor::new(bytes),
            image::PNG)).to_rgba();

        let image_dimensions = image.dimensions();
        let image = glium::texture::RawImage2d::from_raw_rgba_reversed(image.into_raw(), image_dimensions);

        let (w, h) = (width as f32 / 2.0, height as f32 / 2.0);

        let v1 = Vertex::new([-w, -h], [0.0, 0.0]);
        let v2 = Vertex::new([ w, -h], [1.0, 0.0]);
        let v3 = Vertex::new([ w,  h], [1.0, 1.0]);
        let v4 = Vertex::new([-w,  h], [0.0, 1.0]);

        Ok(Texture {
            tex: try!(Texture2d::new(display, image)),
            vertex_buffers: [try!(VertexBuffer::new(display, &[v1, v2, v4])), try!(VertexBuffer::new(display, &[v2, v3, v4]))],
        })
    }

    /// Reads the bytes of a texture from a file and creates a `Texture` instance
    fn new_from_file(display: &GlutinFacade, str_path: &str, width: u32, height: u32) -> Result<Texture>{
        let mut f = try!(File::open(str_path));
        let mut bytes = Vec::new();
        try!(f.read_to_end(&mut bytes));

        Self::new(display, &bytes, width, height)
    }

    /// Returns a `TextureDrawer` to draw the `Texture1`
    pub fn drawer(&self) -> TextureDrawer{
        TextureDrawer{
            texture: &self,
            rotation   : 0.0,
            translation: (0.0, 0.0),
        }
    }
}

#[macro_export]
macro_rules! include_texture {
    ($draw:expr, $texture:tt, $w:expr, $h:expr) => {
        $draw.load_texture_from_bytes(include_bytes!($texture), $w, $h)
    };
}

/*
const INDENTIY_MATRIX: [[f32; 4]; 4] = [
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]];
*/

type Textures<'a> = HashMap<&'a str, Texture>;

/// Trait for objects that can be drawn to the screen
pub trait Drawable {
    /// Returns the position on the screen it should be drawn
    fn get_pos(&self) -> (f32, f32);
    /// Returns the rotation it should be drawn with
    fn get_rotation(&self) -> f32;
    /// Returns the `Texture`
    fn get_texture(&self) -> &Texture;
}

/// Functionality for rendering
pub struct Draw<'a> {
    display: GlutinFacade,
    program: Program,
    indices: NoIndices,
    params : DrawParameters<'a>
}

impl<'a> Draw<'a> {
    /// Creates a new `Draw` from a `Display` made using the arguments
    pub fn new(title: &str, width: u32, height: u32) -> Draw<'a> {
        Self::new_from_display(
            glium::glutin::WindowBuilder::new()
                .with_title(title.to_string())
                .with_dimensions(width, height)
                .with_vsync()
                .build_glium().expect("Failed to build the window")
        )
    }

    /// Creates a new `Draw` instance using the given display
    pub fn new_from_display(display: GlutinFacade) -> Draw<'a> {
        let (w, h) = display.get_window().unwrap().get_inner_size().unwrap();
        let (w, h) = (w as f32 / 2.0, h as f32 / 2.0);

        let vertex_shader_src = &r#"
            #version 140

            in vec2 position;
            in vec2 tex_coords;
            out vec2 v_tex_coords;

            uniform mat4 matrix;

            void main() {
                v_tex_coords = tex_coords;

                vec4 pos = matrix * vec4(position, 0.0, 1.0);

                pos.x /= $width;
                pos.y /= $height;

                gl_Position = pos;
            }
        "#.replace("$width", &w.to_string()).replace("$height", &h.to_string());
        let fragment_shader_src = r#"
            #version 140

            in vec2 v_tex_coords;
            out vec4 color;

            uniform sampler2D tex;

            void main() {
                color = texture(tex, v_tex_coords);
            }
        "#;

        let params: DrawParameters = DrawParameters{
            blend : glium::Blend::alpha_blending(),
            .. Default::default()
        };

        Draw{
            program: Program::from_source(&display, vertex_shader_src, fragment_shader_src, None).unwrap(),
            indices: NoIndices(glium::index::PrimitiveType::TrianglesList),
            display: display,
            params : params,
            // halfsize: (w, h),
        }
    }

    /// Returns a `Texture` created from a byte slice
    pub fn load_texture_from_bytes(&self, bytes: &[u8], width: u32, height: u32) -> Result<Texture> {
        Texture::new(&self.display, bytes, width, height)
    }

    /// Returns a `Texture` created from a file
    pub fn load_texture(&self, identifier: &'a str, width: u32, height: u32) -> Result<Texture> {
        Texture::new_from_file(&self.display, &format!("{}.png", identifier), width, height)
    }

    /// Returns a `DrawablesDrawer` for drawing `Drawable`s to the screen
    pub fn draw_drawables<D: Drawable>(&'a self, target: &'a mut glium::Frame) -> DrawablesDrawer<D>{
        DrawablesDrawer{
            drawables: Vec::new(),
            target: target,
            draw: self
        }
    }

    /// Returns the inner `GlutinFacade`
    pub fn get_display(&self) -> &GlutinFacade {
        &self.display
    }
}

/// An interface for drawing `Drawable`s on the screen
#[must_use = "`DrawablesDrawer` does nothing until drawn"]
pub struct DrawablesDrawer<'a, D: 'a + Drawable>{
    drawables: Vec<&'a D>,
    target: &'a mut glium::Frame,
    draw  : &'a Draw<'a>
}

impl<'a, D: Drawable> DrawablesDrawer<'a, D>{
    /// Adds another `Drawble` to be drawn
    pub fn add(mut self, drawable: &'a D) -> Self{
        self.drawables.push(drawable);

        self
    }

    /// Adds an `Iterator` of `Drawable`s to be drawn
    pub fn add_vec<I: IntoIterator<Item = &'a D>>(mut self, drawables: I) -> Self{
        for d in drawables{
            self = self.add(d);
        }

        self
    }

    /// Finally draws the `Drawable`s
    pub fn draw(self) -> Result<()>{
        for drawable in self.drawables{
            let (x, y) = drawable.get_pos();

            try!(
                drawable.get_texture().drawer()
                    .rotate(drawable.get_rotation())
                    .translate(x, y)
                    .draw(self.target, self.draw)
            );
        }

        Ok(())
    }
}

/// An interface for drawing a texture on the screen
#[must_use = "`TextureDrawer` does nothing until drawn"]
pub struct TextureDrawer<'a> {
    translation: (f32, f32),
    rotation   :  f32,
    texture: &'a Texture,
}

impl<'a> TextureDrawer<'a> {
    /// Rotates the texture
    pub fn rotate(self, rotation: f32) -> TextureDrawer<'a> {
        TextureDrawer{
            rotation: rotation,
            ..
            self
        }
    }

    /// Translates the texture
    pub fn translate(self, translate_x: f32, translate_y: f32) -> TextureDrawer<'a> {
        TextureDrawer{
            translation: (translate_x, translate_y),
            ..
            self
        }
    }

    /// Finally draws the texture
    pub fn draw(self, frame: &mut glium::Frame, draw: &Draw) -> Result<()>{
        let (sin, cos)  = self.rotation.sin_cos();
        let (x, y) = self.translation;

        let uniforms = uniform! {
            tex   : &self.texture.tex,
            matrix: [
                [cos, -sin, 0.0, 0.0],
                [sin,  cos, 0.0, 0.0],
                [0.0,  0.0, 1.0, 0.0],
                [  x,    y, 0.0, 1.0],
            ],
        };

        for vertex_buffer in &self.texture.vertex_buffers{
            try!(frame.draw(vertex_buffer, &draw.indices, &draw.program, &uniforms, &draw.params));
        }

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


<commit_before>use crate::msg::diagnostic_msgs::{DiagnosticArray, DiagnosticStatus};
use crate::msg::std_msgs::Header;
use crate::{Level, Status, Task};
use rosrust::{error::Result, Publisher};

pub struct Updater {
    publisher: Publisher<DiagnosticArray>,
    tasks: Vec<Box<dyn Task>>,
    hardware_id: String,
}

impl Updater {
    pub fn new() -> Result<Self> {
        let publisher = rosrust::publish("/diagnostics", 10)?;
        Ok(Self {
            publisher,
            tasks: vec![],
            hardware_id: String::new(),
        })
    }

    #[inline]
    pub fn set_hardware_id(&mut self, hardware_id: impl std::string::ToString) {
        self.hardware_id = hardware_id.to_string();
    }

    #[inline]
    pub fn add_task(&mut self, task: impl Task + 'static) {
        self.tasks.push(Box::new(task))
    }

    #[inline]
    pub fn update(&self) -> Result<()> {
        self.update_with_extra(&[])
    }

    #[inline]
    pub fn update_with_extra<'a>(&'a self, extra_tasks: &[&'a dyn Task]) -> Result<()> {
        self.publish(self.make_update_statuses(extra_tasks))
    }

    #[inline]
    pub fn make_update_statuses<'a>(&self, extra_tasks: &[&'a dyn Task]) -> Vec<DiagnosticStatus> {
        self.map_over_tasks(extra_tasks, |task| self.make_update_status(task))
    }

    pub fn make_update_status(&self, task: &dyn Task) -> DiagnosticStatus {
        let mut status = Status {
            name: task.name().into(),
            hardware_id: self.hardware_id.clone(),
            level: Level::Error,
            message: "No message was set".into(),
            values: vec![],
        };
        task.run(&mut status);
        // TODO: Add support for higher verbosity and unset hardware id warnings
        status.into()
    }

    #[inline]
    pub fn broadcast(&self, level: Level, message: &str) -> Result<()> {
        self.broadcast_with_extra(&[], level, message)
    }

    #[inline]
    pub fn broadcast_with_extra<'a>(
        &'a self,
        extra_tasks: &[&'a dyn Task],
        level: Level,
        message: &str,
    ) -> Result<()> {
        self.publish(self.make_broadcast_statuses(extra_tasks, level, message))
    }

    #[inline]
    pub fn make_broadcast_statuses<'a>(
        &self,
        extra_tasks: &[&'a dyn Task],
        level: Level,
        message: &str,
    ) -> Vec<DiagnosticStatus> {
        self.map_over_tasks(extra_tasks, |task| {
            self.make_broadcast_status_for(task, level, message)
        })
    }

    #[inline]
    pub fn make_broadcast_status_for(
        &self,
        task: &dyn Task,
        level: Level,
        message: &str,
    ) -> DiagnosticStatus {
        Status {
            name: task.name().into(),
            hardware_id: self.hardware_id.clone(),
            level,
            message: message.into(),
            values: vec![],
        }
        .into()
    }

    #[inline]
    pub fn publish(&self, status: Vec<DiagnosticStatus>) -> Result<()> {
        let message = DiagnosticArray {
            header: Header::default(),
            status,
        };
        self.publisher.send(message)
    }

    fn map_over_tasks<'a, F>(
        &self,
        extra_tasks: &[&'a dyn Task],
        handler: F,
    ) -> Vec<DiagnosticStatus>
    where
        F: Fn(&dyn Task) -> DiagnosticStatus,
    {
        Iterator::chain(
            self.tasks.iter().map(|v| &(**v)),
            extra_tasks.iter().cloned(),
        )
        .map(handler)
        .collect()
    }
}
<commit_msg>Default the diagnostic hardware ID to none<commit_after>use crate::msg::diagnostic_msgs::{DiagnosticArray, DiagnosticStatus};
use crate::msg::std_msgs::Header;
use crate::{Level, Status, Task};
use rosrust::{error::Result, Publisher};

pub struct Updater {
    publisher: Publisher<DiagnosticArray>,
    tasks: Vec<Box<dyn Task>>,
    hardware_id: String,
}

impl Updater {
    pub fn new() -> Result<Self> {
        let publisher = rosrust::publish("/diagnostics", 10)?;
        Ok(Self {
            publisher,
            tasks: vec![],
            hardware_id: "none".into(),
        })
    }

    #[inline]
    pub fn set_hardware_id(&mut self, hardware_id: impl std::string::ToString) {
        self.hardware_id = hardware_id.to_string();
    }

    #[inline]
    pub fn add_task(&mut self, task: impl Task + 'static) {
        self.tasks.push(Box::new(task))
    }

    #[inline]
    pub fn update(&self) -> Result<()> {
        self.update_with_extra(&[])
    }

    #[inline]
    pub fn update_with_extra<'a>(&'a self, extra_tasks: &[&'a dyn Task]) -> Result<()> {
        self.publish(self.make_update_statuses(extra_tasks))
    }

    #[inline]
    pub fn make_update_statuses<'a>(&self, extra_tasks: &[&'a dyn Task]) -> Vec<DiagnosticStatus> {
        self.map_over_tasks(extra_tasks, |task| self.make_update_status(task))
    }

    pub fn make_update_status(&self, task: &dyn Task) -> DiagnosticStatus {
        let mut status = Status {
            name: task.name().into(),
            hardware_id: self.hardware_id.clone(),
            level: Level::Error,
            message: "No message was set".into(),
            values: vec![],
        };
        task.run(&mut status);
        // TODO: Add support for higher verbosity and unset hardware id warnings
        status.into()
    }

    #[inline]
    pub fn broadcast(&self, level: Level, message: &str) -> Result<()> {
        self.broadcast_with_extra(&[], level, message)
    }

    #[inline]
    pub fn broadcast_with_extra<'a>(
        &'a self,
        extra_tasks: &[&'a dyn Task],
        level: Level,
        message: &str,
    ) -> Result<()> {
        self.publish(self.make_broadcast_statuses(extra_tasks, level, message))
    }

    #[inline]
    pub fn make_broadcast_statuses<'a>(
        &self,
        extra_tasks: &[&'a dyn Task],
        level: Level,
        message: &str,
    ) -> Vec<DiagnosticStatus> {
        self.map_over_tasks(extra_tasks, |task| {
            self.make_broadcast_status_for(task, level, message)
        })
    }

    #[inline]
    pub fn make_broadcast_status_for(
        &self,
        task: &dyn Task,
        level: Level,
        message: &str,
    ) -> DiagnosticStatus {
        Status {
            name: task.name().into(),
            hardware_id: self.hardware_id.clone(),
            level,
            message: message.into(),
            values: vec![],
        }
        .into()
    }

    #[inline]
    pub fn publish(&self, status: Vec<DiagnosticStatus>) -> Result<()> {
        let message = DiagnosticArray {
            header: Header::default(),
            status,
        };
        self.publisher.send(message)
    }

    fn map_over_tasks<'a, F>(
        &self,
        extra_tasks: &[&'a dyn Task],
        handler: F,
    ) -> Vec<DiagnosticStatus>
    where
        F: Fn(&dyn Task) -> DiagnosticStatus,
    {
        Iterator::chain(
            self.tasks.iter().map(|v| &(**v)),
            extra_tasks.iter().cloned(),
        )
        .map(handler)
        .collect()
    }
}
<|endoftext|>