<commit_before>// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// This is the backtracking matching engine. It has the same exact capability
// as the full NFA simulation, except it is artificially restricted to small
// regexes on small inputs because of its memory requirements.
//
// In particular, this is a *bounded* backtracking engine. It retains worst
// case linear time by keeping track of the states that is has visited (using a
// bitmap). Namely, once a state is visited, it is never visited again. Since a
// state is keyed by `(instruction index, input index)`, we have that its time
// complexity is `O(mn)` (i.e., linear in the size of the search text).
//
// The backtracking engine can beat out the NFA simulation on small
// regexes/inputs because it doesn't have to keep track of multiple copies of
// the capture groups. In benchmarks, the backtracking engine is roughly twice
// as fast as the full NFA simulation. Note though that its performance doesn't
// scale, even if you're willing to live with the memory requirements. Namely,
// the bitset has to be zeroed on each execution, which becomes quite expensive
// on large bitsets.

use exec::ProgramCache;
use input::{Input, InputAt};
use prog::{Program, InstPtr};
use re_trait::Slot;

type Bits = u32;

const BIT_SIZE: usize = 32;
const MAX_SIZE_BYTES: usize = 256 * (1 << 10); // 256 KB

/// Returns true iff the given regex and input should be executed by this
/// engine with reasonable memory usage.
pub fn should_exec(num_insts: usize, text_len: usize) -> bool {
    // Total memory usage in bytes is determined by:
    //
    //   ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32))
    //
    // The actual limit picked is pretty much a heuristic.
    // See: https://github.com/rust-lang/regex/issues/215
    let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4;
    size <= MAX_SIZE_BYTES
}

/// A backtracking matching engine.
#[derive(Debug)]
pub struct Bounded<'a, 'm, 'r, 's, I> {
    prog: &'r Program,
    input: I,
    matches: &'m mut [bool],
    slots: &'s mut [Slot],
    m: &'a mut Cache,
}

/// Shared cached state between multiple invocations of a backtracking engine
/// in the same thread.
#[derive(Clone, Debug)]
pub struct Cache {
    jobs: Vec<Job>,
    visited: Vec<Bits>,
}

impl Cache {
    /// Create new empty cache for the backtracking engine.
    pub fn new(_prog: &Program) -> Self {
        Cache { jobs: vec![], visited: vec![] }
    }
}

/// A job is an explicit unit of stack space in the backtracking engine.
///
/// The "normal" representation is a single state transition, which corresponds
/// to an NFA state and a character in the input. However, the backtracking
/// engine must keep track of old capture group values. We use the explicit
/// stack to do it.
#[derive(Clone, Copy, Debug)]
enum Job {
    Inst { ip: InstPtr, at: InputAt },
    SaveRestore { slot: usize, old_pos: Option<usize> },
}

impl<'a, 'm, 'r, 's, I: Input> Bounded<'a, 'm, 'r, 's, I> {
    /// Execute the backtracking matching engine.
    ///
    /// If there's a match, `exec` returns `true` and populates the given
    /// captures accordingly.
    pub fn exec(
        prog: &'r Program,
        cache: &ProgramCache,
        matches: &'m mut [bool],
        slots: &'s mut [Slot],
        input: I,
        start: usize,
    ) -> bool {
        let mut cache = cache.borrow_mut();
        let mut cache = &mut cache.backtrack;
        let start = input.at(start);
        let mut b = Bounded {
            prog: prog,
            input: input,
            matches: matches,
            slots: slots,
            m: cache,
        };
        b.exec_(start)
    }

    /// Clears the cache such that the backtracking engine can be executed
    /// on some input of fixed length.
    fn clear(&mut self) {
        // Reset the job memory so that we start fresh.
        self.m.jobs.clear();

        // Now we need to clear the bit state set.
        // We do this by figuring out how much space we need to keep track
        // of the states we've visited.
        // Then we reset all existing allocated space to 0.
        // Finally, we request more space if we need it.
        //
        // This is all a little circuitous, but doing this unsafely
        // doesn't seem to have a measurable impact on performance.
        // (Probably because backtracking is limited to such small
        // inputs/regexes in the first place.)
        let visited_len =
            (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1)
            /
            BIT_SIZE;
        self.m.visited.truncate(visited_len);
        for v in &mut self.m.visited {
            *v = 0;
        }
        if visited_len > self.m.visited.len() {
            let len = self.m.visited.len();
            self.m.visited.reserve_exact(visited_len - len);
            for _ in 0..(visited_len - len) {
                self.m.visited.push(0);
            }
        }
    }

    /// Start backtracking at the given position in the input, but also look
    /// for literal prefixes.
    fn exec_(&mut self, mut at: InputAt) -> bool {
        self.clear();
        // If this is an anchored regex at the beginning of the input, then
        // we're either already done or we only need to try backtracking once.
        if self.prog.is_anchored_start {
            return if !at.is_start() {
                false
            } else {
                self.backtrack(at)
            };
        }
        let mut matched = false;
        loop {
            if !self.prog.prefixes.is_empty() {
                at = match self.input.prefix_at(&self.prog.prefixes, at) {
                    None => break,
                    Some(at) => at,
                };
            }
            matched = self.backtrack(at) || matched;
            if matched && self.prog.matches.len() == 1 {
                return true;
            }
            if at.is_end() {
                break;
            }
            at = self.input.at(at.next_pos());
        }
        matched
    }

    /// The main backtracking loop starting at the given input position.
    fn backtrack(&mut self, start: InputAt) -> bool {
        // N.B. We use an explicit stack to avoid recursion.
        // To avoid excessive pushing and popping, most transitions are handled
        // in the `step` helper function, which only pushes to the stack when
        // there's a capture or a branch.
        let mut matched = false;
        self.m.jobs.push(Job::Inst { ip: 0, at: start });
        while let Some(job) = self.m.jobs.pop() {
            match job {
                Job::Inst { ip, at } => {
                    if self.step(ip, at) {
                        // Only quit if we're matching one regex.
                        // If we're matching a regex set, then mush on and
                        // try to find other matches (if we want them).
                        if self.prog.matches.len() == 1 {
                            return true;
                        }
                        matched = true;
                    }
                }
                Job::SaveRestore { slot, old_pos } => {
                    if slot < self.slots.len() {
                        self.slots[slot] = old_pos;
                    }
                }
            }
        }
        matched
    }

    fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool {
        use prog::Inst::*;
        loop {
            // This loop is an optimization to avoid constantly pushing/popping
            // from the stack. Namely, if we're pushing a job only to run it
            // next, avoid the push and just mutate `ip` (and possibly `at`)
            // in place.
            match self.prog[ip] {
                Match(slot) => {
                    if slot < self.matches.len() {
                        self.matches[slot] = true;
                    }
                    return true;
                }
                Save(ref inst) => {
                    if let Some(&old_pos) = self.slots.get(inst.slot) {
                        // If this path doesn't work out, then we save the old
                        // capture index (if one exists) in an alternate
                        // job. If the next path fails, then the alternate
                        // job is popped and the old capture index is restored.
                        self.m.jobs.push(Job::SaveRestore {
                            slot: inst.slot,
                            old_pos: old_pos,
                        });
                        self.slots[inst.slot] = Some(at.pos());
                    }
                    ip = inst.goto;
                }
                Split(ref inst) => {
                    self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at });
                    ip = inst.goto1;
                }
                EmptyLook(ref inst) => {
                    if self.input.is_empty_match(at, inst) {
                        ip = inst.goto;
                    } else {
                        return false;
                    }
                }
                Char(ref inst) => {
                    if inst.c == at.char() {
                        ip = inst.goto;
                        at = self.input.at(at.next_pos());
                    } else {
                        return false;
                    }
                }
                Ranges(ref inst) => {
                    if inst.matches(at.char()) {
                        ip = inst.goto;
                        at = self.input.at(at.next_pos());
                    } else {
                        return false;
                    }
                }
                Bytes(ref inst) => {
                    if let Some(b) = at.byte() {
                        if inst.matches(b) {
                            ip = inst.goto;
                            at = self.input.at(at.next_pos());
                            continue;
                        }
                    }
                    return false;
                }
            }
            if self.has_visited(ip, at) {
                return false;
            }
        }
    }

    fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool {
        let k = ip * (self.input.len() + 1) + at.pos();
        let k1 = k / BIT_SIZE;
        let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1)));
        if self.m.visited[k1] & k2 == 0 {
            self.m.visited[k1] |= k2;
            false
        } else {
            true
        }
    }
}

fn usize_to_u32(n: usize) -> u32 {
    if (n as u64) > (::std::u32::MAX as u64) {
        panic!("BUG: {} is too big to fit into u32", n)
    }
    n as u32
}
<commit_msg>Auto merge of #328 - seeekr:patch-1, r=BurntSushi<commit_after>// Copyright 2014-2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// This is the backtracking matching engine. It has the same exact capability
// as the full NFA simulation, except it is artificially restricted to small
// regexes on small inputs because of its memory requirements.
//
// In particular, this is a *bounded* backtracking engine. It retains worst
// case linear time by keeping track of the states that it has visited (using a
// bitmap). Namely, once a state is visited, it is never visited again. Since a
// state is keyed by `(instruction index, input index)`, we have that its time
// complexity is `O(mn)` (i.e., linear in the size of the search text).
//
// The backtracking engine can beat out the NFA simulation on small
// regexes/inputs because it doesn't have to keep track of multiple copies of
// the capture groups. In benchmarks, the backtracking engine is roughly twice
// as fast as the full NFA simulation. Note though that its performance doesn't
// scale, even if you're willing to live with the memory requirements. Namely,
// the bitset has to be zeroed on each execution, which becomes quite expensive
// on large bitsets.

use exec::ProgramCache;
use input::{Input, InputAt};
use prog::{Program, InstPtr};
use re_trait::Slot;

type Bits = u32;

const BIT_SIZE: usize = 32;
const MAX_SIZE_BYTES: usize = 256 * (1 << 10); // 256 KB

/// Returns true iff the given regex and input should be executed by this
/// engine with reasonable memory usage.
pub fn should_exec(num_insts: usize, text_len: usize) -> bool {
    // Total memory usage in bytes is determined by:
    //
    //   ((len(insts) * (len(input) + 1) + bits - 1) / bits) * (size_of(u32))
    //
    // The actual limit picked is pretty much a heuristic.
    // See: https://github.com/rust-lang/regex/issues/215
    let size = ((num_insts * (text_len + 1) + BIT_SIZE - 1) / BIT_SIZE) * 4;
    size <= MAX_SIZE_BYTES
}

/// A backtracking matching engine.
#[derive(Debug)]
pub struct Bounded<'a, 'm, 'r, 's, I> {
    prog: &'r Program,
    input: I,
    matches: &'m mut [bool],
    slots: &'s mut [Slot],
    m: &'a mut Cache,
}

/// Shared cached state between multiple invocations of a backtracking engine
/// in the same thread.
#[derive(Clone, Debug)]
pub struct Cache {
    jobs: Vec<Job>,
    visited: Vec<Bits>,
}

impl Cache {
    /// Create new empty cache for the backtracking engine.
    pub fn new(_prog: &Program) -> Self {
        Cache { jobs: vec![], visited: vec![] }
    }
}

/// A job is an explicit unit of stack space in the backtracking engine.
///
/// The "normal" representation is a single state transition, which corresponds
/// to an NFA state and a character in the input. However, the backtracking
/// engine must keep track of old capture group values. We use the explicit
/// stack to do it.
#[derive(Clone, Copy, Debug)]
enum Job {
    Inst { ip: InstPtr, at: InputAt },
    SaveRestore { slot: usize, old_pos: Option<usize> },
}

impl<'a, 'm, 'r, 's, I: Input> Bounded<'a, 'm, 'r, 's, I> {
    /// Execute the backtracking matching engine.
    ///
    /// If there's a match, `exec` returns `true` and populates the given
    /// captures accordingly.
    pub fn exec(
        prog: &'r Program,
        cache: &ProgramCache,
        matches: &'m mut [bool],
        slots: &'s mut [Slot],
        input: I,
        start: usize,
    ) -> bool {
        let mut cache = cache.borrow_mut();
        let mut cache = &mut cache.backtrack;
        let start = input.at(start);
        let mut b = Bounded {
            prog: prog,
            input: input,
            matches: matches,
            slots: slots,
            m: cache,
        };
        b.exec_(start)
    }

    /// Clears the cache such that the backtracking engine can be executed
    /// on some input of fixed length.
    fn clear(&mut self) {
        // Reset the job memory so that we start fresh.
        self.m.jobs.clear();

        // Now we need to clear the bit state set.
        // We do this by figuring out how much space we need to keep track
        // of the states we've visited.
        // Then we reset all existing allocated space to 0.
        // Finally, we request more space if we need it.
        //
        // This is all a little circuitous, but doing this unsafely
        // doesn't seem to have a measurable impact on performance.
        // (Probably because backtracking is limited to such small
        // inputs/regexes in the first place.)
        let visited_len =
            (self.prog.len() * (self.input.len() + 1) + BIT_SIZE - 1)
            /
            BIT_SIZE;
        self.m.visited.truncate(visited_len);
        for v in &mut self.m.visited {
            *v = 0;
        }
        if visited_len > self.m.visited.len() {
            let len = self.m.visited.len();
            self.m.visited.reserve_exact(visited_len - len);
            for _ in 0..(visited_len - len) {
                self.m.visited.push(0);
            }
        }
    }

    /// Start backtracking at the given position in the input, but also look
    /// for literal prefixes.
    fn exec_(&mut self, mut at: InputAt) -> bool {
        self.clear();
        // If this is an anchored regex at the beginning of the input, then
        // we're either already done or we only need to try backtracking once.
        if self.prog.is_anchored_start {
            return if !at.is_start() {
                false
            } else {
                self.backtrack(at)
            };
        }
        let mut matched = false;
        loop {
            if !self.prog.prefixes.is_empty() {
                at = match self.input.prefix_at(&self.prog.prefixes, at) {
                    None => break,
                    Some(at) => at,
                };
            }
            matched = self.backtrack(at) || matched;
            if matched && self.prog.matches.len() == 1 {
                return true;
            }
            if at.is_end() {
                break;
            }
            at = self.input.at(at.next_pos());
        }
        matched
    }

    /// The main backtracking loop starting at the given input position.
    fn backtrack(&mut self, start: InputAt) -> bool {
        // N.B. We use an explicit stack to avoid recursion.
        // To avoid excessive pushing and popping, most transitions are handled
        // in the `step` helper function, which only pushes to the stack when
        // there's a capture or a branch.
        let mut matched = false;
        self.m.jobs.push(Job::Inst { ip: 0, at: start });
        while let Some(job) = self.m.jobs.pop() {
            match job {
                Job::Inst { ip, at } => {
                    if self.step(ip, at) {
                        // Only quit if we're matching one regex.
                        // If we're matching a regex set, then mush on and
                        // try to find other matches (if we want them).
                        if self.prog.matches.len() == 1 {
                            return true;
                        }
                        matched = true;
                    }
                }
                Job::SaveRestore { slot, old_pos } => {
                    if slot < self.slots.len() {
                        self.slots[slot] = old_pos;
                    }
                }
            }
        }
        matched
    }

    fn step(&mut self, mut ip: InstPtr, mut at: InputAt) -> bool {
        use prog::Inst::*;
        loop {
            // This loop is an optimization to avoid constantly pushing/popping
            // from the stack. Namely, if we're pushing a job only to run it
            // next, avoid the push and just mutate `ip` (and possibly `at`)
            // in place.
            match self.prog[ip] {
                Match(slot) => {
                    if slot < self.matches.len() {
                        self.matches[slot] = true;
                    }
                    return true;
                }
                Save(ref inst) => {
                    if let Some(&old_pos) = self.slots.get(inst.slot) {
                        // If this path doesn't work out, then we save the old
                        // capture index (if one exists) in an alternate
                        // job. If the next path fails, then the alternate
                        // job is popped and the old capture index is restored.
                        self.m.jobs.push(Job::SaveRestore {
                            slot: inst.slot,
                            old_pos: old_pos,
                        });
                        self.slots[inst.slot] = Some(at.pos());
                    }
                    ip = inst.goto;
                }
                Split(ref inst) => {
                    self.m.jobs.push(Job::Inst { ip: inst.goto2, at: at });
                    ip = inst.goto1;
                }
                EmptyLook(ref inst) => {
                    if self.input.is_empty_match(at, inst) {
                        ip = inst.goto;
                    } else {
                        return false;
                    }
                }
                Char(ref inst) => {
                    if inst.c == at.char() {
                        ip = inst.goto;
                        at = self.input.at(at.next_pos());
                    } else {
                        return false;
                    }
                }
                Ranges(ref inst) => {
                    if inst.matches(at.char()) {
                        ip = inst.goto;
                        at = self.input.at(at.next_pos());
                    } else {
                        return false;
                    }
                }
                Bytes(ref inst) => {
                    if let Some(b) = at.byte() {
                        if inst.matches(b) {
                            ip = inst.goto;
                            at = self.input.at(at.next_pos());
                            continue;
                        }
                    }
                    return false;
                }
            }
            if self.has_visited(ip, at) {
                return false;
            }
        }
    }

    fn has_visited(&mut self, ip: InstPtr, at: InputAt) -> bool {
        let k = ip * (self.input.len() + 1) + at.pos();
        let k1 = k / BIT_SIZE;
        let k2 = usize_to_u32(1 << (k & (BIT_SIZE - 1)));
        if self.m.visited[k1] & k2 == 0 {
            self.m.visited[k1] |= k2;
            false
        } else {
            true
        }
    }
}

fn usize_to_u32(n: usize) -> u32 {
    if (n as u64) > (::std::u32::MAX as u64) {
        panic!("BUG: {} is too big to fit into u32", n)
    }
    n as u32
}
<|endoftext|>


<commit_before>use std::error;
use std::fmt;
use std::mem;
use std::ptr;
use std::sync::Arc;
use std::sync::Mutex;

use device::Device;
use device::Queue;
use formats::FormatMarker;
use image::Image;
use image::ImagePrototype;
use image::Type2d;
use image::Usage as ImageUsage;
use memory::ChunkProperties;
use memory::ChunkRange;
use memory::MemorySourceChunk;
use swapchain::CompositeAlpha;
use swapchain::PresentMode;
use swapchain::Surface;
use swapchain::SurfaceTransform;
use sync::Fence;
use sync::Semaphore;
use sync::SharingMode;

use check_errors;
use Error;
use OomError;
use Success;
use SynchronizedVulkanObject;
use VulkanObject;
use VulkanPointers;
use vk;

/// Contains the swapping system and the images that can be shown on a surface.
pub struct Swapchain {
    device: Arc<Device>,
    surface: Arc<Surface>,
    swapchain: vk::SwapchainKHR,

    images_semaphores: Mutex<Vec<Option<Arc<Semaphore>>>>,
}

impl Swapchain {
    /// Builds a new swapchain. Allocates images who content can be made visible on a surface.
    ///
    /// See also the `Surface::get_capabilities` function which returns the values that are
    /// supported by the implementation. All the parameters that you pass to `Swapchain::new`
    /// must be supported.
    ///
    /// The `clipped` parameter indicates whether the implementation is allowed to discard
    /// rendering operations that affect regions of the surface which aren't visible. This is
    /// important to take into account if your fragment shader has side-effects or if you want to
    /// read back the content of the image afterwards.
    ///
    /// This function returns the swapchain plus a list of the images that belong to the
    /// swapchain. The order in which the images are returned is important for the
    /// `acquire_next_image` and `present` functions.
    ///
    /// # Panic
    ///
    /// - Panicks if the device and the surface don't belong to the same instance.
    /// - Panicks if `color_attachment` is false in `usage`.
    ///
    pub fn new<F, S>(device: &Arc<Device>, surface: &Arc<Surface>, num_images: u32, format: F,
                     dimensions: [u32; 2], layers: u32, usage: &ImageUsage, sharing: S,
                     transform: SurfaceTransform, alpha: CompositeAlpha, mode: PresentMode,
                     clipped: bool) -> Result<(Arc<Swapchain>, Vec<ImagePrototype<Type2d, F, SwapchainAllocatedChunk>>), OomError>
        where F: FormatMarker + Clone, S: Into<SharingMode>
    {
        Swapchain::new_inner(device, surface, num_images, format, dimensions, layers, usage,
                             sharing, transform, alpha, mode, clipped)
    }

    // TODO:
    //pub fn recreate() { ... }

    fn new_inner<F, S>(device: &Arc<Device>, surface: &Arc<Surface>, num_images: u32, format: F,
                       dimensions: [u32; 2], layers: u32, usage: &ImageUsage, sharing: S,
                       transform: SurfaceTransform, alpha: CompositeAlpha, mode: PresentMode,
                       clipped: bool) -> Result<(Arc<Swapchain>, Vec<ImagePrototype<Type2d, F, SwapchainAllocatedChunk>>), OomError>
        where F: FormatMarker + Clone, S: Into<SharingMode>
    {
        // FIXME: check that the parameters are supported

        // FIXME: check that the device and the surface belong to the same instance
        let vk = device.pointers();

        assert!(usage.color_attachment);
        let usage = usage.to_usage_bits();

        let sharing = sharing.into();

        let swapchain = unsafe {
            let (sh_mode, sh_count, sh_indices) = match sharing {
                SharingMode::Exclusive(id) => (vk::SHARING_MODE_EXCLUSIVE, 0, ptr::null()),
                SharingMode::Concurrent(ref ids) => (vk::SHARING_MODE_CONCURRENT, ids.len() as u32,
                                                     ids.as_ptr()),
            };

            let infos = vk::SwapchainCreateInfoKHR {
                sType: vk::STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
                pNext: ptr::null(),
                flags: 0,   // reserved
                surface: surface.internal_object(),
                minImageCount: num_images,
                imageFormat: format.format() as u32,
                imageColorSpace: vk::COLORSPACE_SRGB_NONLINEAR_KHR,     // only available value
                imageExtent: vk::Extent2D { width: dimensions[0], height: dimensions[1] },
                imageArrayLayers: layers,
                imageUsage: usage,
                imageSharingMode: sh_mode,
                queueFamilyIndexCount: sh_count,
                pQueueFamilyIndices: sh_indices,
                preTransform: transform as u32,
                compositeAlpha: alpha as u32,
                presentMode: mode as u32,
                clipped: if clipped { vk::TRUE } else { vk::FALSE },
                oldSwapchain: 0,      // TODO:
            };

            let mut output = mem::uninitialized();
            try!(check_errors(vk.CreateSwapchainKHR(device.internal_object(), &infos,
                                                    ptr::null(), &mut output)));
            output
        };

        let swapchain = Arc::new(Swapchain {
            device: device.clone(),
            surface: surface.clone(),
            swapchain: swapchain,
            images_semaphores: Mutex::new(Vec::new()),
        });

        let images = unsafe {
            let mut num = 0;
            try!(check_errors(vk.GetSwapchainImagesKHR(device.internal_object(),
                                                       swapchain.swapchain, &mut num,
                                                       ptr::null_mut())));

            let mut images = Vec::with_capacity(num as usize);
            try!(check_errors(vk.GetSwapchainImagesKHR(device.internal_object(),
                                                       swapchain.swapchain, &mut num,
                                                       images.as_mut_ptr())));
            images.set_len(num as usize);
            images
        };

        let images = images.into_iter().enumerate().map(|(id, image)| unsafe {
            let mem = SwapchainAllocatedChunk { swapchain: swapchain.clone(), id: id };
            Image::from_raw_unowned(&device, image, mem, sharing.clone(), usage, format.clone(),
                                    dimensions, (), 1)
        }).collect::<Vec<_>>();

        {
            let mut semaphores = swapchain.images_semaphores.lock().unwrap();
            for _ in 0 .. images.len() {
                semaphores.push(None);
            }
        }

        Ok((swapchain, images))
    }

    /// Tries to take ownership of an image in order to draw on it.
    ///
    /// The function returns the index of the image in the array of images that was returned
    /// when creating the swapchain.
    ///
    /// If you try to draw on an image without acquiring it first, the execution will block. (TODO
    /// behavior may change).
    pub fn acquire_next_image(&self, timeout_ns: u64) -> Result<usize, AcquireError> {
        let vk = self.device.pointers();

        unsafe {
            let semaphore = Semaphore::new(&self.device).unwrap();      // TODO: error

            let mut out = mem::uninitialized();
            let r = try!(check_errors(vk.AcquireNextImageKHR(self.device.internal_object(),
                                                             self.swapchain, timeout_ns,
                                                             semaphore.internal_object(), 0,     // TODO: timeout
                                                             &mut out)));

            let id = match r {
                Success::Success => out as usize,
                Success::Suboptimal => out as usize,        // TODO: give that info to the user
                Success::NotReady => return Err(AcquireError::Timeout),
                Success::Timeout => return Err(AcquireError::Timeout),
                s => panic!("unexpected success value: {:?}", s)
            };

            let mut images_semaphores = self.images_semaphores.lock().unwrap();
            images_semaphores[id] = Some(semaphore);

            Ok(id)
        }
    }

    /// Presents an image on the screen.
    ///
    /// The parameter is the same index as what `acquire_next_image` returned. The image must
    /// have been acquired first.
    ///
    /// The actual behavior depends on the present mode that you passed when creating the
    /// swapchain.
    pub fn present(&self, queue: &Arc<Queue>, index: usize) -> Result<(), OomError> {     // FIXME: wrong error
        let vk = self.device.pointers();

        let wait_semaphore = {
            let mut images_semaphores = self.images_semaphores.lock().unwrap();
            images_semaphores[index].take()
        };

        // FIXME: the semaphore will be destroyed ; need to return it

        unsafe {
            let mut result = mem::uninitialized();

            let index = index as u32;
            let infos = vk::PresentInfoKHR {
                sType: vk::STRUCTURE_TYPE_PRESENT_INFO_KHR,
                pNext: ptr::null(),
                waitSemaphoreCount: if let Some(_) = wait_semaphore { 1 } else { 0 },
                pWaitSemaphores: if let Some(ref sem) = wait_semaphore { &sem.internal_object() } else { ptr::null() },
                swapchainCount: 1,
                pSwapchains: &self.swapchain,
                pImageIndices: &index,
                pResults: &mut result,
            };

            try!(check_errors(vk.QueuePresentKHR(*queue.internal_object_guard(), &infos)));
            try!(check_errors(result));
            Ok(())
        }
    }
}

impl Drop for Swapchain {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            let vk = self.device.pointers();
            vk.DestroySwapchainKHR(self.device.internal_object(), self.swapchain, ptr::null());
        }
    }
}

/// Error that can happen when calling `acquire_next_image`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum AcquireError {
    Timeout,
    SurfaceLost,
    OutOfDate,
}

impl error::Error for AcquireError {
    #[inline]
    fn description(&self) -> &str {
        match *self {
            AcquireError::Timeout => "no image is available for acquiring yet",
            AcquireError::SurfaceLost => "the surface of this swapchain is no longer valid",
            AcquireError::OutOfDate => "the swapchain needs to be recreated",
        }
    }
}

impl fmt::Display for AcquireError {
    #[inline]
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "{}", error::Error::description(self))
    }
}

impl From<Error> for AcquireError {
    #[inline]
    fn from(err: Error) -> AcquireError {
        match err {
            Error::SurfaceLost => AcquireError::SurfaceLost,
            Error::OutOfDate => AcquireError::OutOfDate,
            _ => panic!("unexpected error: {:?}", err)
        }
    }
}

/// "Dummy" object used for images that indicates that they were allocated as part of a swapchain.
pub struct SwapchainAllocatedChunk {
    swapchain: Arc<Swapchain>,
    id: usize,
}

// FIXME: needs correct synchronization as well
unsafe impl MemorySourceChunk for SwapchainAllocatedChunk {
    #[inline]
    fn properties(&self) -> ChunkProperties {
        unreachable!()
    }

    #[inline]
    fn requires_fence(&self) -> bool { false }
    #[inline]
    fn requires_semaphore(&self) -> bool { true }
    #[inline]
    fn may_alias(&self) -> bool { false }

    #[inline]
    unsafe fn gpu_access(&self, _: bool, _: ChunkRange, _: &Arc<Queue>, _: Option<Arc<Fence>>,
                         post_semaphore: Option<Arc<Semaphore>>) -> Option<Arc<Semaphore>>
    {
        assert!(post_semaphore.is_some());
        // FIXME: must also check that image has been acquired
        let mut semaphores = self.swapchain.images_semaphores.lock().unwrap();
        let pre_semaphore = mem::replace(&mut semaphores[self.id], post_semaphore);
        pre_semaphore
    }
}
<commit_msg>Minor change in swapchain<commit_after>use std::error;
use std::fmt;
use std::mem;
use std::ptr;
use std::sync::Arc;
use std::sync::Mutex;

use device::Device;
use device::Queue;
use formats::FormatMarker;
use image::Image;
use image::ImagePrototype;
use image::Type2d;
use image::Usage as ImageUsage;
use memory::ChunkProperties;
use memory::ChunkRange;
use memory::MemorySourceChunk;
use swapchain::CompositeAlpha;
use swapchain::PresentMode;
use swapchain::Surface;
use swapchain::SurfaceTransform;
use sync::Fence;
use sync::Semaphore;
use sync::SharingMode;

use check_errors;
use Error;
use OomError;
use Success;
use SynchronizedVulkanObject;
use VulkanObject;
use VulkanPointers;
use vk;

/// Contains the swapping system and the images that can be shown on a surface.
pub struct Swapchain {
    device: Arc<Device>,
    surface: Arc<Surface>,
    swapchain: vk::SwapchainKHR,

    images_semaphores: Mutex<Vec<Option<Arc<Semaphore>>>>,
}

impl Swapchain {
    /// Builds a new swapchain. Allocates images who content can be made visible on a surface.
    ///
    /// See also the `Surface::get_capabilities` function which returns the values that are
    /// supported by the implementation. All the parameters that you pass to `Swapchain::new`
    /// must be supported.
    ///
    /// The `clipped` parameter indicates whether the implementation is allowed to discard
    /// rendering operations that affect regions of the surface which aren't visible. This is
    /// important to take into account if your fragment shader has side-effects or if you want to
    /// read back the content of the image afterwards.
    ///
    /// This function returns the swapchain plus a list of the images that belong to the
    /// swapchain. The order in which the images are returned is important for the
    /// `acquire_next_image` and `present` functions.
    ///
    /// # Panic
    ///
    /// - Panicks if the device and the surface don't belong to the same instance.
    /// - Panicks if `color_attachment` is false in `usage`.
    ///
    pub fn new<F, S>(device: &Arc<Device>, surface: &Arc<Surface>, num_images: u32, format: F,
                     dimensions: [u32; 2], layers: u32, usage: &ImageUsage, sharing: S,
                     transform: SurfaceTransform, alpha: CompositeAlpha, mode: PresentMode,
                     clipped: bool) -> Result<(Arc<Swapchain>, Vec<ImagePrototype<Type2d, F, SwapchainAllocatedChunk>>), OomError>
        where F: FormatMarker + Clone, S: Into<SharingMode>
    {
        Swapchain::new_inner(device, surface, num_images, format, dimensions, layers, usage,
                             sharing, transform, alpha, mode, clipped)
    }

    // TODO:
    //pub fn recreate() { ... }

    fn new_inner<F, S>(device: &Arc<Device>, surface: &Arc<Surface>, num_images: u32, format: F,
                       dimensions: [u32; 2], layers: u32, usage: &ImageUsage, sharing: S,
                       transform: SurfaceTransform, alpha: CompositeAlpha, mode: PresentMode,
                       clipped: bool) -> Result<(Arc<Swapchain>, Vec<ImagePrototype<Type2d, F, SwapchainAllocatedChunk>>), OomError>
        where F: FormatMarker + Clone, S: Into<SharingMode>
    {
        // FIXME: check that the parameters are supported

        // FIXME: check that the device and the surface belong to the same instance
        let vk = device.pointers();

        assert!(usage.color_attachment);
        let usage = usage.to_usage_bits();

        let sharing = sharing.into();

        let swapchain = unsafe {
            let (sh_mode, sh_count, sh_indices) = match sharing {
                SharingMode::Exclusive(id) => (vk::SHARING_MODE_EXCLUSIVE, 0, ptr::null()),
                SharingMode::Concurrent(ref ids) => (vk::SHARING_MODE_CONCURRENT, ids.len() as u32,
                                                     ids.as_ptr()),
            };

            let infos = vk::SwapchainCreateInfoKHR {
                sType: vk::STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
                pNext: ptr::null(),
                flags: 0,   // reserved
                surface: surface.internal_object(),
                minImageCount: num_images,
                imageFormat: format.format() as u32,
                imageColorSpace: vk::COLORSPACE_SRGB_NONLINEAR_KHR,     // only available value
                imageExtent: vk::Extent2D { width: dimensions[0], height: dimensions[1] },
                imageArrayLayers: layers,
                imageUsage: usage,
                imageSharingMode: sh_mode,
                queueFamilyIndexCount: sh_count,
                pQueueFamilyIndices: sh_indices,
                preTransform: transform as u32,
                compositeAlpha: alpha as u32,
                presentMode: mode as u32,
                clipped: if clipped { vk::TRUE } else { vk::FALSE },
                oldSwapchain: 0,      // TODO:
            };

            let mut output = mem::uninitialized();
            try!(check_errors(vk.CreateSwapchainKHR(device.internal_object(), &infos,
                                                    ptr::null(), &mut output)));
            output
        };

        let swapchain = Arc::new(Swapchain {
            device: device.clone(),
            surface: surface.clone(),
            swapchain: swapchain,
            images_semaphores: Mutex::new(Vec::new()),
        });

        let images = unsafe {
            let mut num = 0;
            try!(check_errors(vk.GetSwapchainImagesKHR(device.internal_object(),
                                                       swapchain.swapchain, &mut num,
                                                       ptr::null_mut())));

            let mut images = Vec::with_capacity(num as usize);
            try!(check_errors(vk.GetSwapchainImagesKHR(device.internal_object(),
                                                       swapchain.swapchain, &mut num,
                                                       images.as_mut_ptr())));
            images.set_len(num as usize);
            images
        };

        let images = images.into_iter().enumerate().map(|(id, image)| unsafe {
            let mem = SwapchainAllocatedChunk { swapchain: swapchain.clone(), id: id };
            Image::from_raw_unowned(&device, image, mem, sharing.clone(), usage, format.clone(),
                                    dimensions, (), 1)
        }).collect::<Vec<_>>();

        {
            let mut semaphores = swapchain.images_semaphores.lock().unwrap();
            for _ in 0 .. images.len() {
                semaphores.push(None);
            }
        }

        Ok((swapchain, images))
    }

    /// Tries to take ownership of an image in order to draw on it.
    ///
    /// The function returns the index of the image in the array of images that was returned
    /// when creating the swapchain.
    ///
    /// If you try to draw on an image without acquiring it first, the execution will block. (TODO
    /// behavior may change).
    pub fn acquire_next_image(&self, timeout_ns: u64) -> Result<usize, AcquireError> {
        let vk = self.device.pointers();

        unsafe {
            let semaphore = Semaphore::new(&self.device).unwrap();      // TODO: error

            let mut out = mem::uninitialized();
            let r = try!(check_errors(vk.AcquireNextImageKHR(self.device.internal_object(),
                                                             self.swapchain, timeout_ns,
                                                             semaphore.internal_object(), 0,     // TODO: timeout
                                                             &mut out)));

            let id = match r {
                Success::Success => out as usize,
                Success::Suboptimal => out as usize,        // TODO: give that info to the user
                Success::NotReady => return Err(AcquireError::Timeout),
                Success::Timeout => return Err(AcquireError::Timeout),
                s => panic!("unexpected success value: {:?}", s)
            };

            let mut images_semaphores = self.images_semaphores.lock().unwrap();
            images_semaphores[id] = Some(semaphore);

            Ok(id)
        }
    }

    /// Presents an image on the screen.
    ///
    /// The parameter is the same index as what `acquire_next_image` returned. The image must
    /// have been acquired first.
    ///
    /// The actual behavior depends on the present mode that you passed when creating the
    /// swapchain.
    pub fn present(&self, queue: &Arc<Queue>, index: usize) -> Result<(), OomError> {     // FIXME: wrong error
        let vk = self.device.pointers();

        let wait_semaphore = {
            let mut images_semaphores = self.images_semaphores.lock().unwrap();
            images_semaphores[index].take()
        };

        // FIXME: the semaphore will be destroyed ; need to return it

        unsafe {
            let mut result = mem::uninitialized();

            let queue = queue.internal_object_guard();

            let index = index as u32;
            let infos = vk::PresentInfoKHR {
                sType: vk::STRUCTURE_TYPE_PRESENT_INFO_KHR,
                pNext: ptr::null(),
                waitSemaphoreCount: if let Some(_) = wait_semaphore { 1 } else { 0 },
                pWaitSemaphores: if let Some(ref sem) = wait_semaphore { &sem.internal_object() } else { ptr::null() },
                swapchainCount: 1,
                pSwapchains: &self.swapchain,
                pImageIndices: &index,
                pResults: &mut result,
            };

            try!(check_errors(vk.QueuePresentKHR(*queue, &infos)));
            try!(check_errors(result));
            Ok(())
        }
    }
}

impl Drop for Swapchain {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            let vk = self.device.pointers();
            vk.DestroySwapchainKHR(self.device.internal_object(), self.swapchain, ptr::null());
        }
    }
}

/// Error that can happen when calling `acquire_next_image`.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum AcquireError {
    Timeout,
    SurfaceLost,
    OutOfDate,
}

impl error::Error for AcquireError {
    #[inline]
    fn description(&self) -> &str {
        match *self {
            AcquireError::Timeout => "no image is available for acquiring yet",
            AcquireError::SurfaceLost => "the surface of this swapchain is no longer valid",
            AcquireError::OutOfDate => "the swapchain needs to be recreated",
        }
    }
}

impl fmt::Display for AcquireError {
    #[inline]
    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(fmt, "{}", error::Error::description(self))
    }
}

impl From<Error> for AcquireError {
    #[inline]
    fn from(err: Error) -> AcquireError {
        match err {
            Error::SurfaceLost => AcquireError::SurfaceLost,
            Error::OutOfDate => AcquireError::OutOfDate,
            _ => panic!("unexpected error: {:?}", err)
        }
    }
}

/// "Dummy" object used for images that indicates that they were allocated as part of a swapchain.
pub struct SwapchainAllocatedChunk {
    swapchain: Arc<Swapchain>,
    id: usize,
}

// FIXME: needs correct synchronization as well
unsafe impl MemorySourceChunk for SwapchainAllocatedChunk {
    #[inline]
    fn properties(&self) -> ChunkProperties {
        unreachable!()
    }

    #[inline]
    fn requires_fence(&self) -> bool { false }
    #[inline]
    fn requires_semaphore(&self) -> bool { true }
    #[inline]
    fn may_alias(&self) -> bool { false }

    #[inline]
    unsafe fn gpu_access(&self, _: bool, _: ChunkRange, _: &Arc<Queue>, _: Option<Arc<Fence>>,
                         post_semaphore: Option<Arc<Semaphore>>) -> Option<Arc<Semaphore>>
    {
        assert!(post_semaphore.is_some());
        // FIXME: must also check that image has been acquired
        let mut semaphores = self.swapchain.images_semaphores.lock().unwrap();
        let pre_semaphore = mem::replace(&mut semaphores[self.id], post_semaphore);
        pre_semaphore
    }
}
<|endoftext|>


<commit_before>use nom::{alphanumeric, eof, line_ending, multispace, space};
use nom::{IResult, Err, ErrorKind, Needed};
use std::str;

use caseless_tag::*;
use parser::{ConditionBase, ConditionExpression, ConditionTree};
use parser::{binary_comparison_operator, binary_logical_operator, unary_comparison_operator};
use parser::{fieldlist, unsigned_number};

use condition::*;

#[derive(Clone, Debug, PartialEq)]
pub struct GroupByClause {
    columns: Vec<String>,
    having: String, // XXX(malte): should this be an arbitrary expr?
}

#[derive(Clone, Debug, PartialEq)]
pub struct LimitClause {
    limit: u64,
    offset: u64,
}

#[derive(Clone, Debug, PartialEq)]
enum OrderType {
    OrderAscending,
    OrderDescending,
}

#[derive(Clone, Debug, PartialEq)]
pub struct OrderClause {
    order_type: OrderType,
    order_cols: Vec<String>, // TODO(malte): can this be an arbitrary expr?
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct SelectStatement {
    table: String,
    distinct: bool,
    fields: Vec<String>,
    where_clause: Option<ConditionExpression>,
    group_by: Option<GroupByClause>,
    order: Option<OrderClause>,
    limit: Option<LimitClause>,
}

/// Parse LIMIT clause
named!(limit_clause<&[u8], LimitClause>,
    chain!(
        caseless_tag!("limit") ~
        space ~
        limit_val: unsigned_number ~
        space? ~
        offset_val: opt!(chain!(
                caseless_tag!("offset") ~
                space ~
                val: unsigned_number,
                || { val }
            )
        ),
    || {
        LimitClause {
            limit: limit_val,
            offset: match offset_val {
                None => 0,
                Some(v) => v,
            },
        }
    })
);

/// Parse WHERE clause of a selection
named!(where_clause<&[u8], ConditionExpression>,
    dbg_dmp!(chain!(
        caseless_tag!("where") ~
        space ~
        cond: condition_expr,
        || { cond }
    ))
);

named!(table_reference<&[u8], &str>,
    chain!(
        table: map_res!(alphanumeric, str::from_utf8) ~
        alias: opt!(
            chain!(
                space ~
                caseless_tag!("as") ~
                space ~
                alias: map_res!(alphanumeric, str::from_utf8),
                || { println!("got alias: {} -> {}", table, alias); alias }
            )
        ),
        || { table }
    )
);

/// Parse rule for a SQL selection query.
/// TODO(malte): support nested queries as selection targets
named!(pub select_statement<&[u8], SelectStatement>,
    dbg_dmp!(chain!(
        caseless_tag!("select") ~
        space ~
        distinct: opt!(caseless_tag!("distinct")) ~
        space? ~
        fields: fieldlist ~
        delimited!(multispace, caseless_tag!("from"), multispace) ~
        table: table_reference ~
        cond: opt!(delimited!(multispace, where_clause, opt!(multispace))) ~
        limit: opt!(delimited!(multispace, limit_clause, opt!(multispace))),
        || {
            SelectStatement {
                table: String::from(table),
                distinct: distinct.is_some(),
                fields: fields.iter().map(|s| String::from(*s)).collect(),
                where_clause: cond,
                group_by: None,
                order: None,
                limit: limit,
            }
        }
    ))
);

named!(pub selection<&[u8], SelectStatement>,
    chain!(
        stmt: select_statement ~
        delimited!(opt!(multispace),
                   alt_complete!(tag!(";") | line_ending),
                   opt!(multispace)
        ),
    || { stmt }
    )
);

mod tests {
    use super::*;
    use parser::{ConditionBase, ConditionExpression, ConditionTree};

    #[test]
    fn simple_select() {
        let qstring = "SELECT id, name FROM users;";

        let res = selection(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("users"),
                       fields: vec!["id".into(), "name".into()],
                       ..Default::default()
                   });
    }

    #[test]
    fn select_all() {
        let qstring = "SELECT * FROM users;";

        let res = selection(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("users"),
                       fields: vec!["ALL".into()],
                       ..Default::default()
                   });
    }

    #[test]
    fn spaces_optional() {
        let qstring = "SELECT id,name FROM users;";

        let res = selection(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("users"),
                       fields: vec!["id".into(), "name".into()],
                       ..Default::default()
                   });
    }

    #[test]
    fn case_sensitivity() {
        let qstring_lc = "select id, name from users;";
        let qstring_uc = "SELECT id, name FROM users;";

        assert_eq!(selection(qstring_lc.as_bytes()).unwrap(),
                   selection(qstring_uc.as_bytes()).unwrap());
    }

    #[test]
    fn termination() {
        let qstring_sem = "select id, name from users;";
        let qstring_linebreak = "select id, name from users\n";

        assert_eq!(selection(qstring_sem.as_bytes()).unwrap(),
                   selection(qstring_linebreak.as_bytes()).unwrap());
    }

    #[test]
    fn where_clause() {
        let qstring = "select * from ContactInfo where email=?\n";

        let res = selection(qstring.as_bytes());

        let expected_where_cond = Some(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("email"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        }));
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("ContactInfo"),
                       fields: vec!["ALL".into()],
                       where_clause: expected_where_cond,
                       ..Default::default()
                   });
    }

    #[test]
    fn limit_clause() {
        let qstring1 = "select * from users limit 10\n";
        let qstring2 = "select * from users limit 10 offset 10\n";

        let expected_lim1 = LimitClause {
            limit: 10,
            offset: 0,
        };
        let expected_lim2 = LimitClause {
            limit: 10,
            offset: 10,
        };

        let res1 = selection(qstring1.as_bytes());
        let res2 = selection(qstring2.as_bytes());
        assert_eq!(res1.unwrap().1.limit, Some(expected_lim1));
        assert_eq!(res2.unwrap().1.limit, Some(expected_lim2));
    }

    #[test]
    fn table_alias() {
        let qstring1 = "select * from PaperTag as t;";
        // let qstring2 = "select * from PaperTag t;";

        let res1 = selection(qstring1.as_bytes());
        assert_eq!(res1.clone().unwrap().1,
                   SelectStatement {
                       table: String::from("PaperTag"),
                       fields: vec!["ALL".into()],
                       ..Default::default()
                   });
        // let res2 = selection(qstring2.as_bytes());
        // assert_eq!(res1.unwrap().1, res2.unwrap().1);
    }

    #[test]
    fn distinct() {
        let qstring = "select distinct tag from PaperTag where paperId=?;";

        let res = selection(qstring.as_bytes());
        let expected_where_cond = Some(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("paperId"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        }));
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("PaperTag"),
                       distinct: true,
                       fields: vec!["tag".into()],
                       where_clause: expected_where_cond,
                       ..Default::default()
                   });
    }

    #[test]
    fn simple_condition_expr() {
        let qstring = "select infoJson from PaperStorage where paperId=? and paperStorageId=? \
                       limit 10;";

        let res = selection(qstring.as_bytes());

        let left_comp = Some(Box::new(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("paperId"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        })));
        let right_comp = Some(Box::new(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("paperStorageId"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        })));
        let expected_where_cond = Some(ConditionExpression::LogicalOp(ConditionTree {
            left: left_comp,
            right: right_comp,
            operator: String::from("and"),
        }));
        println!("{:?}", res);
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("PaperStorage"),
                       fields: vec!["infoJson".into()],
                       where_clause: expected_where_cond,
                       ..Default::default()
                   });
    }
}
<commit_msg>Use semicolon in test case.<commit_after>use nom::{alphanumeric, eof, line_ending, multispace, space};
use nom::{IResult, Err, ErrorKind, Needed};
use std::str;

use caseless_tag::*;
use parser::{ConditionBase, ConditionExpression, ConditionTree};
use parser::{binary_comparison_operator, binary_logical_operator, unary_comparison_operator};
use parser::{fieldlist, unsigned_number};

use condition::*;

#[derive(Clone, Debug, PartialEq)]
pub struct GroupByClause {
    columns: Vec<String>,
    having: String, // XXX(malte): should this be an arbitrary expr?
}

#[derive(Clone, Debug, PartialEq)]
pub struct LimitClause {
    limit: u64,
    offset: u64,
}

#[derive(Clone, Debug, PartialEq)]
enum OrderType {
    OrderAscending,
    OrderDescending,
}

#[derive(Clone, Debug, PartialEq)]
pub struct OrderClause {
    order_type: OrderType,
    order_cols: Vec<String>, // TODO(malte): can this be an arbitrary expr?
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct SelectStatement {
    table: String,
    distinct: bool,
    fields: Vec<String>,
    where_clause: Option<ConditionExpression>,
    group_by: Option<GroupByClause>,
    order: Option<OrderClause>,
    limit: Option<LimitClause>,
}

/// Parse LIMIT clause
named!(limit_clause<&[u8], LimitClause>,
    chain!(
        caseless_tag!("limit") ~
        space ~
        limit_val: unsigned_number ~
        space? ~
        offset_val: opt!(chain!(
                caseless_tag!("offset") ~
                space ~
                val: unsigned_number,
                || { val }
            )
        ),
    || {
        LimitClause {
            limit: limit_val,
            offset: match offset_val {
                None => 0,
                Some(v) => v,
            },
        }
    })
);

/// Parse WHERE clause of a selection
named!(where_clause<&[u8], ConditionExpression>,
    dbg_dmp!(chain!(
        caseless_tag!("where") ~
        space ~
        cond: condition_expr,
        || { cond }
    ))
);

named!(table_reference<&[u8], &str>,
    chain!(
        table: map_res!(alphanumeric, str::from_utf8) ~
        alias: opt!(
            chain!(
                space ~
                caseless_tag!("as") ~
                space ~
                alias: map_res!(alphanumeric, str::from_utf8),
                || { println!("got alias: {} -> {}", table, alias); alias }
            )
        ),
        || { table }
    )
);

/// Parse rule for a SQL selection query.
/// TODO(malte): support nested queries as selection targets
named!(pub select_statement<&[u8], SelectStatement>,
    dbg_dmp!(chain!(
        caseless_tag!("select") ~
        space ~
        distinct: opt!(caseless_tag!("distinct")) ~
        space? ~
        fields: fieldlist ~
        delimited!(multispace, caseless_tag!("from"), multispace) ~
        table: table_reference ~
        cond: opt!(delimited!(multispace, where_clause, opt!(multispace))) ~
        limit: opt!(delimited!(multispace, limit_clause, opt!(multispace))),
        || {
            SelectStatement {
                table: String::from(table),
                distinct: distinct.is_some(),
                fields: fields.iter().map(|s| String::from(*s)).collect(),
                where_clause: cond,
                group_by: None,
                order: None,
                limit: limit,
            }
        }
    ))
);

named!(pub selection<&[u8], SelectStatement>,
    chain!(
        stmt: select_statement ~
        delimited!(opt!(multispace),
                   alt_complete!(tag!(";") | line_ending),
                   opt!(multispace)
        ),
    || { stmt }
    )
);

mod tests {
    use super::*;
    use parser::{ConditionBase, ConditionExpression, ConditionTree};

    #[test]
    fn simple_select() {
        let qstring = "SELECT id, name FROM users;";

        let res = selection(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("users"),
                       fields: vec!["id".into(), "name".into()],
                       ..Default::default()
                   });
    }

    #[test]
    fn select_all() {
        let qstring = "SELECT * FROM users;";

        let res = selection(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("users"),
                       fields: vec!["ALL".into()],
                       ..Default::default()
                   });
    }

    #[test]
    fn spaces_optional() {
        let qstring = "SELECT id,name FROM users;";

        let res = selection(qstring.as_bytes());
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("users"),
                       fields: vec!["id".into(), "name".into()],
                       ..Default::default()
                   });
    }

    #[test]
    fn case_sensitivity() {
        let qstring_lc = "select id, name from users;";
        let qstring_uc = "SELECT id, name FROM users;";

        assert_eq!(selection(qstring_lc.as_bytes()).unwrap(),
                   selection(qstring_uc.as_bytes()).unwrap());
    }

    #[test]
    fn termination() {
        let qstring_sem = "select id, name from users;";
        let qstring_linebreak = "select id, name from users\n";

        assert_eq!(selection(qstring_sem.as_bytes()).unwrap(),
                   selection(qstring_linebreak.as_bytes()).unwrap());
    }

    #[test]
    fn where_clause() {
        let qstring = "select * from ContactInfo where email=?;";

        let res = selection(qstring.as_bytes());

        let expected_where_cond = Some(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("email"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        }));
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("ContactInfo"),
                       fields: vec!["ALL".into()],
                       where_clause: expected_where_cond,
                       ..Default::default()
                   });
    }

    #[test]
    fn limit_clause() {
        let qstring1 = "select * from users limit 10\n";
        let qstring2 = "select * from users limit 10 offset 10\n";

        let expected_lim1 = LimitClause {
            limit: 10,
            offset: 0,
        };
        let expected_lim2 = LimitClause {
            limit: 10,
            offset: 10,
        };

        let res1 = selection(qstring1.as_bytes());
        let res2 = selection(qstring2.as_bytes());
        assert_eq!(res1.unwrap().1.limit, Some(expected_lim1));
        assert_eq!(res2.unwrap().1.limit, Some(expected_lim2));
    }

    #[test]
    fn table_alias() {
        let qstring1 = "select * from PaperTag as t;";
        // let qstring2 = "select * from PaperTag t;";

        let res1 = selection(qstring1.as_bytes());
        assert_eq!(res1.clone().unwrap().1,
                   SelectStatement {
                       table: String::from("PaperTag"),
                       fields: vec!["ALL".into()],
                       ..Default::default()
                   });
        // let res2 = selection(qstring2.as_bytes());
        // assert_eq!(res1.unwrap().1, res2.unwrap().1);
    }

    #[test]
    fn distinct() {
        let qstring = "select distinct tag from PaperTag where paperId=?;";

        let res = selection(qstring.as_bytes());
        let expected_where_cond = Some(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("paperId"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        }));
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("PaperTag"),
                       distinct: true,
                       fields: vec!["tag".into()],
                       where_clause: expected_where_cond,
                       ..Default::default()
                   });
    }

    #[test]
    fn simple_condition_expr() {
        let qstring = "select infoJson from PaperStorage where paperId=? and paperStorageId=? \
                       limit 10;";

        let res = selection(qstring.as_bytes());

        let left_comp = Some(Box::new(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("paperId"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        })));
        let right_comp = Some(Box::new(ConditionExpression::ComparisonOp(ConditionTree {
            left:
                Some(Box::new(ConditionExpression::Expr(ConditionBase::Field(String::from("paperStorageId"))))),
            right: Some(Box::new(ConditionExpression::Expr(ConditionBase::Placeholder))),
            operator: String::from("="),
        })));
        let expected_where_cond = Some(ConditionExpression::LogicalOp(ConditionTree {
            left: left_comp,
            right: right_comp,
            operator: String::from("and"),
        }));
        println!("{:?}", res);
        assert_eq!(res.unwrap().1,
                   SelectStatement {
                       table: String::from("PaperStorage"),
                       fields: vec!["infoJson".into()],
                       where_clause: expected_where_cond,
                       ..Default::default()
                   });
    }
}
<|endoftext|>


<commit_before><commit_msg>Fix integer overflow<commit_after><|endoftext|>


<commit_before>extern crate base64;
extern crate hyper;
extern crate hyper_native_tls;
extern crate pem;
#[macro_use]
extern crate prettytable;
extern crate ring;
extern crate serde_json;
extern crate url;

extern crate ct_submitter;

use std::{env, process};
use std::fs::File;
use std::io::Read;

use ring::digest;

use ct_submitter::{fetch_trusted_ct_logs, submit_cert_to_logs, AddChainRequest};

fn pems_to_chain(data: &str) -> Vec<Vec<u8>> {
    return pem::parse_many(data)
               .into_iter()
               .filter(|p| p.tag == "CERTIFICATE")
               .map(|p| p.contents)
               .collect();
}

fn build_chain_for_cert(http_client: &hyper::Client, cert: &[u8]) -> Vec<Vec<u8>> {
    let body = url::form_urlencoded::Serializer::new(String::new())
        .append_pair("b64cert", &base64::encode(cert))
        .finish();
    let body_bytes = body.as_bytes();
    let response = http_client
        .post("https://crt.sh/gen-add-chain")
        .header(hyper::header::ContentType::form_url_encoded())
        .body(hyper::client::Body::BufBody(body_bytes, body_bytes.len()))
        .send()
        .unwrap();

    let add_chain_request: AddChainRequest = serde_json::from_reader(response).unwrap();
    return add_chain_request
               .chain
               .iter()
               .map(|c| base64::decode(c).unwrap())
               .collect();
}

fn crtsh_url_for_cert(cert: &[u8]) -> String {
    return format!("https://crt.sh?q={}",
                   digest::digest(&digest::SHA256, &cert)
                       .as_ref()
                       .iter()
                       .map(|b| format!("{:02X}", b))
                       .collect::<String>());
}

fn main() {
    if env::args().len() == 1 {
        println!("Usage: {} <cert-or-chain.pem ...>",
                 env::args().next().unwrap());
        process::exit(1);
    }

    let http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_native_tls::NativeTlsClient::new().unwrap())
    );
    // TODO: timeout on the http_client, but for submit_cert_to_logs only, not build_chain
    let logs = fetch_trusted_ct_logs(&http_client);

    for path in env::args().skip(1) {
        println!("Submitting {} ...", path);

        let mut contents = String::new();
        File::open(path)
            .unwrap()
            .read_to_string(&mut contents)
            .unwrap();

        let mut chain = pems_to_chain(&contents);
        if chain.len() == 1 {
            // TODO: There's got to be some way to do this ourselves, instead of using crt.sh as a
            // glorified AIA chaser.
            println!("Only one certificate in chain, using crt.sh to build a full chain ...");
            chain = build_chain_for_cert(&http_client, &chain[0]);
        }
        let scts = submit_cert_to_logs(&http_client, &logs, &chain);

        // TODO: is there a better way to do this hex-encoding?
        println!("Find the cert on crt.sh: {}", crtsh_url_for_cert(&chain[0]));

        let mut table = prettytable::Table::new();
        table.add_row(row!["Log", "SCT"]);
        for (log, sct) in scts {
            table.add_row(row![log.description, base64::encode(&sct.to_raw_bytes())]);
        }
        table.printstd();
        println!();
        println!();
    }

}
<commit_msg>move comment<commit_after>extern crate base64;
extern crate hyper;
extern crate hyper_native_tls;
extern crate pem;
#[macro_use]
extern crate prettytable;
extern crate ring;
extern crate serde_json;
extern crate url;

extern crate ct_submitter;

use std::{env, process};
use std::fs::File;
use std::io::Read;

use ring::digest;

use ct_submitter::{fetch_trusted_ct_logs, submit_cert_to_logs, AddChainRequest};

fn pems_to_chain(data: &str) -> Vec<Vec<u8>> {
    return pem::parse_many(data)
               .into_iter()
               .filter(|p| p.tag == "CERTIFICATE")
               .map(|p| p.contents)
               .collect();
}

fn build_chain_for_cert(http_client: &hyper::Client, cert: &[u8]) -> Vec<Vec<u8>> {
    let body = url::form_urlencoded::Serializer::new(String::new())
        .append_pair("b64cert", &base64::encode(cert))
        .finish();
    let body_bytes = body.as_bytes();
    let response = http_client
        .post("https://crt.sh/gen-add-chain")
        .header(hyper::header::ContentType::form_url_encoded())
        .body(hyper::client::Body::BufBody(body_bytes, body_bytes.len()))
        .send()
        .unwrap();

    let add_chain_request: AddChainRequest = serde_json::from_reader(response).unwrap();
    return add_chain_request
               .chain
               .iter()
               .map(|c| base64::decode(c).unwrap())
               .collect();
}

fn crtsh_url_for_cert(cert: &[u8]) -> String {
    // TODO: is there a better way to do this hex-encoding?
    return format!("https://crt.sh?q={}",
                   digest::digest(&digest::SHA256, &cert)
                       .as_ref()
                       .iter()
                       .map(|b| format!("{:02X}", b))
                       .collect::<String>());
}

fn main() {
    if env::args().len() == 1 {
        println!("Usage: {} <cert-or-chain.pem ...>",
                 env::args().next().unwrap());
        process::exit(1);
    }

    let http_client = hyper::Client::with_connector(
        hyper::net::HttpsConnector::new(hyper_native_tls::NativeTlsClient::new().unwrap())
    );
    // TODO: timeout on the http_client, but for submit_cert_to_logs only, not build_chain
    let logs = fetch_trusted_ct_logs(&http_client);

    for path in env::args().skip(1) {
        println!("Submitting {} ...", path);

        let mut contents = String::new();
        File::open(path)
            .unwrap()
            .read_to_string(&mut contents)
            .unwrap();

        let mut chain = pems_to_chain(&contents);
        if chain.len() == 1 {
            // TODO: There's got to be some way to do this ourselves, instead of using crt.sh as a
            // glorified AIA chaser.
            println!("Only one certificate in chain, using crt.sh to build a full chain ...");
            chain = build_chain_for_cert(&http_client, &chain[0]);
        }
        let scts = submit_cert_to_logs(&http_client, &logs, &chain);

        println!("Find the cert on crt.sh: {}", crtsh_url_for_cert(&chain[0]));
        let mut table = prettytable::Table::new();
        table.add_row(row!["Log", "SCT"]);
        for (log, sct) in scts {
            table.add_row(row![log.description, base64::encode(&sct.to_raw_bytes())]);
        }
        table.printstd();
        println!();
        println!();
    }

}
<|endoftext|>


<commit_before>#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate serde_derive;

extern crate chrono;
#[macro_use]
extern crate hyper;
extern crate hyper_native_tls;
extern crate serde_json;

#[macro_use]
extern crate rocket;
extern crate rocket_contrib;

use std::env;
use hyper::Client;
use hyper::header::Connection;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use std::vec::Vec;
use std::collections::BTreeSet;
use chrono::{NaiveDate as Date, Utc};
use rocket::Outcome;
use chrono::Datelike;
use rocket_contrib::templates::Template;

header! { (XToken, "x-token") => [String] }

static DEBITOOR_TOKEN: &'static str = "DEBITOOR_TOKEN";

fn default_description() -> String {
    "<empty>".to_string()
}

#[derive(Serialize, Deserialize, Debug)]
struct Expense {
    date: String,
    lines: Vec<Line>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
    #[serde(rename = "categoryType")]
    category_type: Option<String>,
    #[serde(rename = "netAmount")]
    net_amount: f64,
    #[serde(default = "default_description")]
    description: String,
    #[serde(rename = "assetDepreciation")]
    #[serde(default = "Vec::new")]
    asset_depreciation: Vec<AssetDepreciation>,
}

#[derive(Serialize, Deserialize, Debug)]
struct AssetDepreciation {
    #[serde(rename = "depreciationCost")]
    depreciation_cost: f64,
    #[serde(rename = "depreciationDate")]
    depreciation_date: Date,
    #[serde(rename = "bookValue")]
    book_value: f64,
}

#[derive(Deserialize, Debug)]
struct AccessToken {
    access_token: String,
}

impl<'a, 'r> rocket::request::FromRequest<'a, 'r> for AccessToken {
    type Error = ();

    fn from_request(
        request: &'a rocket::request::Request<'r>,
    ) -> rocket::request::Outcome<Self, Self::Error> {
        let token = request
            .cookies()
            .get(DEBITOOR_TOKEN)
            .map(|c| c.value().to_owned());

        match token {
            None => Outcome::Forward(()),
            Some(token) => Outcome::Success(AccessToken {
                access_token: token,
            }),
        }
    }
}

#[derive(Deserialize, Debug)]
struct BaseURL {
    base_url: String,
}

impl<'a, 'r> rocket::request::FromRequest<'a, 'r> for BaseURL {
    type Error = ();

    fn from_request(
        request: &'a rocket::request::Request<'r>,
    ) -> rocket::request::Outcome<Self, Self::Error> {
        println!("Header: {:?}", request.headers());

        let schema = request
            .headers()
            .get_one("X-Forwarded-Proto")
            .unwrap_or("http");
        let host = request.headers().get_one("Host").unwrap();

        Outcome::Success(BaseURL {
            base_url: format!("{}://{}", schema, host),
        })
    }
}

fn create_ssl_client() -> Client {
    let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    Client::with_connector(connector)
}

#[get("/?<code>", rank = 1)]
fn check_code(
    mut cookies: rocket::http::Cookies,
    code: String,
    base_url: BaseURL,
) -> rocket::response::Redirect {
    let client_secret = env::var("CLIENT_SECRET").unwrap();

    println!("got code {:?}", code);

    let client = create_ssl_client();

    let body = format!(
        "code={}&client_secret={}&redirect_uri={}",
        code, client_secret, base_url.base_url
    );

    println!("body {}", body);

    let res = client.
        post("https://app.debitoor.com/login/oauth2/access_token").
        //if we keep the connection open the parsing will wait for a minute in between for a timeout
        //don't know why this is, so just disable keep alive for now
        body(body.as_bytes()).
        header(Connection::close()).
        header(hyper::header::ContentType::form_url_encoded()).
        //the access token to authenticate with
        send().unwrap();

    assert_eq!(res.status, hyper::Ok);

    let access_token: AccessToken = serde_json::from_reader(res).unwrap();

    println!("{:?}", access_token);

    //set cookie and redirect
    cookies.add(rocket::http::Cookie::new(
        DEBITOOR_TOKEN.to_owned(),
        access_token.access_token.to_owned(),
    ));
    rocket::response::Redirect::temporary("/")
}

#[allow(unused_variables)]
#[get("/", rank = 2)]
fn index(token: AccessToken) -> rocket::response::Redirect {
    rocket::response::Redirect::temporary(format!("/assets/{}", Utc::now().year()))
}

#[get("/", rank = 3)]
fn redirect_auth() -> rocket::response::Redirect {
    let client_id = env::var("CLIENT_ID").unwrap();

    rocket::response::Redirect::temporary(
        format!(
            "https://app.debitoor.com/login/oauth2/authorize?client_id={}&response_type=code",
            client_id
        ),
    )
}

#[get("/assets/<year>")]
fn asset_list(token: AccessToken, year: i32) -> Template {
    let client = create_ssl_client();

    println!("send request for token {:?}", token);
    let res = client.
        get("https://api.debitoor.com/api/expenses/v4").
        //if we keep the connection open the parsing will wait for a minute in between for a timeout
        //don't know why this is, so just disable keep alive for now
        header(Connection::close()).
        //the access token to authenticate with
        header(XToken(token.access_token.to_owned())).
        send().unwrap();
    assert_eq!(res.status, hyper::Ok);

    println!("create parser");

    let expenses: Vec<Expense> = serde_json::from_reader(res).unwrap();

    #[derive(Serialize, Deserialize, Debug)]
    struct AssetInformation {
        description: String,
        #[serde(rename = "netAmount")]
        net_amount: f64,
        #[serde(rename = "purchasingDate")]
        purchasing_date: Date,
        #[serde(rename = "depreciationCost")]
        depreciation_cost: f64,
        #[serde(rename = "depreciationDate")]
        depreciation_date: Date,
        #[serde(rename = "bookValuePeriodBegin")]
        book_value_period_begin: f64,
        #[serde(rename = "bookValuePeriodEnd")]
        book_value_period_end: f64,
    }

    #[derive(Serialize, Deserialize, Debug)]
    struct Context {
        year: i32,
        asset_information: Vec<AssetInformation>,
        available_years: BTreeSet<i32>,
        total_depreciation_cost: f64,
        total_book_value: f64,
    }

    let mut asset_information: Vec<AssetInformation> = Vec::new();
    let mut available_years: BTreeSet<i32> = BTreeSet::new();

    println!("printing value");

    let mut total_depreciation_cost = 0.0f64;
    let mut total_book_value = 0.0f64;

    for expense in expenses {
        for line in expense.lines {
            let description = line.description.as_str();

            let asset_deprecations = &line.asset_depreciation;

            if asset_deprecations.is_empty() {
                continue;
            }

            //find the first depreciation data, which is the purchase date
            let purchase_info = asset_deprecations
                .iter()
                .fold(None, |min, x| match min {
                    None => Some(x),
                    Some(y) => Some(if x.depreciation_date < y.depreciation_date {
                        x
                    } else {
                        y
                    }),
                })
                .unwrap();

            for asset_depreciation in asset_deprecations {
                available_years.insert(asset_depreciation.depreciation_date.year());
                if asset_depreciation.depreciation_date.year() == year {
                    total_depreciation_cost += asset_depreciation.depreciation_cost;
                    total_book_value += asset_depreciation.book_value;
                    asset_information.push(AssetInformation {
                        description: description.to_string(),
                        net_amount: line.net_amount,
                        purchasing_date: purchase_info.depreciation_date,
                        depreciation_cost: asset_depreciation.depreciation_cost,
                        depreciation_date: asset_depreciation.depreciation_date,
                        book_value_period_begin: asset_depreciation.book_value
                            + asset_depreciation.depreciation_cost,
                        book_value_period_end: asset_depreciation.book_value,
                    });
                }
            }
        }
    }

    println!("Rendering template");

    Template::render(
        "asset_list",
        Context {
            year,
            asset_information,
            available_years,
            total_depreciation_cost,
            total_book_value,
        },
    )
}

fn main() {
    rocket::ignite()
        .attach(Template::fairing())
        .mount("/", routes![index, asset_list, check_code, redirect_auth])
        .launch();
}
<commit_msg>use uri macro<commit_after>#![feature(proc_macro_hygiene, decl_macro)]

#[macro_use]
extern crate serde_derive;

extern crate chrono;
#[macro_use]
extern crate hyper;
extern crate hyper_native_tls;
extern crate serde_json;

#[macro_use]
extern crate rocket;
extern crate rocket_contrib;

use std::env;
use hyper::Client;
use hyper::header::Connection;
use hyper::net::HttpsConnector;
use hyper_native_tls::NativeTlsClient;
use std::vec::Vec;
use std::collections::BTreeSet;
use chrono::{NaiveDate as Date, Utc};
use rocket::Outcome;
use chrono::Datelike;
use rocket_contrib::templates::Template;

header! { (XToken, "x-token") => [String] }

static DEBITOOR_TOKEN: &'static str = "DEBITOOR_TOKEN";

fn default_description() -> String {
    "<empty>".to_string()
}

#[derive(Serialize, Deserialize, Debug)]
struct Expense {
    date: String,
    lines: Vec<Line>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Line {
    #[serde(rename = "categoryType")]
    category_type: Option<String>,
    #[serde(rename = "netAmount")]
    net_amount: f64,
    #[serde(default = "default_description")]
    description: String,
    #[serde(rename = "assetDepreciation")]
    #[serde(default = "Vec::new")]
    asset_depreciation: Vec<AssetDepreciation>,
}

#[derive(Serialize, Deserialize, Debug)]
struct AssetDepreciation {
    #[serde(rename = "depreciationCost")]
    depreciation_cost: f64,
    #[serde(rename = "depreciationDate")]
    depreciation_date: Date,
    #[serde(rename = "bookValue")]
    book_value: f64,
}

#[derive(Deserialize, Debug)]
struct AccessToken {
    access_token: String,
}

impl<'a, 'r> rocket::request::FromRequest<'a, 'r> for AccessToken {
    type Error = ();

    fn from_request(
        request: &'a rocket::request::Request<'r>,
    ) -> rocket::request::Outcome<Self, Self::Error> {
        let token = request
            .cookies()
            .get(DEBITOOR_TOKEN)
            .map(|c| c.value().to_owned());

        match token {
            None => Outcome::Forward(()),
            Some(token) => Outcome::Success(AccessToken {
                access_token: token,
            }),
        }
    }
}

#[derive(Deserialize, Debug)]
struct BaseURL {
    base_url: String,
}

impl<'a, 'r> rocket::request::FromRequest<'a, 'r> for BaseURL {
    type Error = ();

    fn from_request(
        request: &'a rocket::request::Request<'r>,
    ) -> rocket::request::Outcome<Self, Self::Error> {
        println!("Header: {:?}", request.headers());

        let schema = request
            .headers()
            .get_one("X-Forwarded-Proto")
            .unwrap_or("http");
        let host = request.headers().get_one("Host").unwrap();

        Outcome::Success(BaseURL {
            base_url: format!("{}://{}", schema, host),
        })
    }
}

fn create_ssl_client() -> Client {
    let ssl = NativeTlsClient::new().unwrap();
    let connector = HttpsConnector::new(ssl);
    Client::with_connector(connector)
}

#[get("/?<code>", rank = 1)]
fn check_code(
    mut cookies: rocket::http::Cookies,
    code: String,
    base_url: BaseURL,
) -> rocket::response::Redirect {
    let client_secret = env::var("CLIENT_SECRET").unwrap();

    println!("got code {:?}", code);

    let client = create_ssl_client();

    let body = format!(
        "code={}&client_secret={}&redirect_uri={}",
        code, client_secret, base_url.base_url
    );

    println!("body {}", body);

    let res = client.
        post("https://app.debitoor.com/login/oauth2/access_token").
        //if we keep the connection open the parsing will wait for a minute in between for a timeout
        //don't know why this is, so just disable keep alive for now
        body(body.as_bytes()).
        header(Connection::close()).
        header(hyper::header::ContentType::form_url_encoded()).
        //the access token to authenticate with
        send().unwrap();

    assert_eq!(res.status, hyper::Ok);

    let access_token: AccessToken = serde_json::from_reader(res).unwrap();

    println!("{:?}", access_token);

    //set cookie and redirect
    cookies.add(rocket::http::Cookie::new(
        DEBITOOR_TOKEN.to_owned(),
        access_token.access_token.to_owned(),
    ));
    rocket::response::Redirect::temporary("/")
}

#[allow(unused_variables)]
#[get("/", rank = 2)]
fn index(token: AccessToken) -> rocket::response::Redirect {
    rocket::response::Redirect::temporary(uri!(asset_list: year=Utc::now().year()))
}

#[get("/", rank = 3)]
fn redirect_auth() -> rocket::response::Redirect {
    let client_id = env::var("CLIENT_ID").unwrap();

    rocket::response::Redirect::temporary(
        format!(
            "https://app.debitoor.com/login/oauth2/authorize?client_id={}&response_type=code",
            client_id
        ),
    )
}

#[get("/assets/<year>")]
fn asset_list(token: AccessToken, year: i32) -> Template {
    let client = create_ssl_client();

    println!("send request for token {:?}", token);
    let res = client.
        get("https://api.debitoor.com/api/expenses/v4").
        //if we keep the connection open the parsing will wait for a minute in between for a timeout
        //don't know why this is, so just disable keep alive for now
        header(Connection::close()).
        //the access token to authenticate with
        header(XToken(token.access_token.to_owned())).
        send().unwrap();
    assert_eq!(res.status, hyper::Ok);

    println!("create parser");

    let expenses: Vec<Expense> = serde_json::from_reader(res).unwrap();

    #[derive(Serialize, Deserialize, Debug)]
    struct AssetInformation {
        description: String,
        #[serde(rename = "netAmount")]
        net_amount: f64,
        #[serde(rename = "purchasingDate")]
        purchasing_date: Date,
        #[serde(rename = "depreciationCost")]
        depreciation_cost: f64,
        #[serde(rename = "depreciationDate")]
        depreciation_date: Date,
        #[serde(rename = "bookValuePeriodBegin")]
        book_value_period_begin: f64,
        #[serde(rename = "bookValuePeriodEnd")]
        book_value_period_end: f64,
    }

    #[derive(Serialize, Deserialize, Debug)]
    struct Context {
        year: i32,
        asset_information: Vec<AssetInformation>,
        available_years: BTreeSet<i32>,
        total_depreciation_cost: f64,
        total_book_value: f64,
    }

    let mut asset_information: Vec<AssetInformation> = Vec::new();
    let mut available_years: BTreeSet<i32> = BTreeSet::new();

    println!("printing value");

    let mut total_depreciation_cost = 0.0f64;
    let mut total_book_value = 0.0f64;

    for expense in expenses {
        for line in expense.lines {
            let description = line.description.as_str();

            let asset_deprecations = &line.asset_depreciation;

            if asset_deprecations.is_empty() {
                continue;
            }

            //find the first depreciation data, which is the purchase date
            let purchase_info = asset_deprecations
                .iter()
                .fold(None, |min, x| match min {
                    None => Some(x),
                    Some(y) => Some(if x.depreciation_date < y.depreciation_date {
                        x
                    } else {
                        y
                    }),
                })
                .unwrap();

            for asset_depreciation in asset_deprecations {
                available_years.insert(asset_depreciation.depreciation_date.year());
                if asset_depreciation.depreciation_date.year() == year {
                    total_depreciation_cost += asset_depreciation.depreciation_cost;
                    total_book_value += asset_depreciation.book_value;
                    asset_information.push(AssetInformation {
                        description: description.to_string(),
                        net_amount: line.net_amount,
                        purchasing_date: purchase_info.depreciation_date,
                        depreciation_cost: asset_depreciation.depreciation_cost,
                        depreciation_date: asset_depreciation.depreciation_date,
                        book_value_period_begin: asset_depreciation.book_value
                            + asset_depreciation.depreciation_cost,
                        book_value_period_end: asset_depreciation.book_value,
                    });
                }
            }
        }
    }

    println!("Rendering template");

    Template::render(
        "asset_list",
        Context {
            year,
            asset_information,
            available_years,
            total_depreciation_cost,
            total_book_value,
        },
    )
}

fn main() {
    rocket::ignite()
        .attach(Template::fairing())
        .mount("/", routes![index, asset_list, check_code, redirect_auth])
        .launch();
}
<|endoftext|>


<commit_before><commit_msg>Refactor<commit_after><|endoftext|>


<commit_before>// Copyright (c) Carl-Erwin Griffith
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER

extern crate clap;
extern crate unlimited;

//
use std::sync::mpsc::channel;
use std::thread;

use clap::{App, Arg};

use unlimited::core;
use unlimited::core::config::Config;
use unlimited::core::VERSION;
use unlimited::ui;

/// program entry point
/// It parses the command line to build the configuration.
/// Creates the core thread.
/// Nb: the ui is kept in the main thread
fn main() {
    // parse user command line
    let config = parse_command_line();

    // build core/ui communication channels
    let (ui_tx, ui_rx) = channel();
    let (core_tx, core_rx) = channel();
    let ui_name = config.ui_frontend.clone();

    // create core thread
    let ui_tx_clone = ui_tx.clone();
    let core_th = {
        Some(thread::spawn(move || {
            core::start(config, &core_rx, &ui_tx_clone)
        }))
    };

    // run the ui loop in the main thread
    let ui_tx_clone = ui_tx.clone();
    ui::main_loop(ui_name.as_ref(), &ui_rx, &ui_tx_clone, &core_tx);

    // wait for core thread
    if let Some(core_handle) = core_th {
        core_handle.join().unwrap()
    }
}

fn parse_command_line() -> Config {
    let matches = App::new("unlimited")
        .version(VERSION)
        .author("Carl-Erwin Griffith <carl.erwin@gmail.com>")
        .about("unlimited is an experimental editor")
        .args_from_usage("--ui, --ui=[termion|ncurses] 'select user interface frontend'")
        .arg(
            Arg::with_name("FILES")
                .help("list of the files to open")
                .required(false)
                .multiple(true),
        )
        .get_matches();

    let mut ui_frontend = String::new();
    if matches.is_present("ui") {
        ui_frontend = matches.values_of("ui").unwrap().collect::<String>();
    }

    let mut files_list = Vec::new();
    if matches.is_present("FILES") {
        files_list = matches
            .values_of("FILES")
            .unwrap()
            .map(|x| x.to_owned())
            .collect();
    }

    Config {
        files_list,
        ui_frontend,
    }
}
<commit_msg>cosmetics<commit_after>// Copyright (c) Carl-Erwin Griffith
//
// Permission is hereby granted, free of charge, to any
// person obtaining a copy of this software and associated
// documentation files (the "Software"), to deal in the
// Software without restriction, including without
// limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software
// is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice
// shall be included in all copies or substantial portions
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER

extern crate clap;
extern crate unlimited;

//
use std::sync::mpsc::channel;
use std::thread;

use clap::{App, Arg};

use unlimited::core;
use unlimited::core::config::Config;
use unlimited::core::VERSION;
use unlimited::ui;

/// program entry point
/// It parses the command line to build the configuration.
/// Creates the core thread.
/// Nb: the ui is kept in the main thread
fn main() {
    // parse user command line
    let config = parse_command_line();

    // build core/ui communication channels
    let (ui_tx, ui_rx) = channel();
    let (cr_tx, cr_rx) = channel();
    let ui_name = config.ui_frontend.clone();

    // create core thread
    let core_th = {

        let ui_tx_clone = ui_tx.clone();
        Some(thread::spawn(move || {
            core::start(config, &cr_rx, &ui_tx_clone)
        }))
    };

    // run the ui loop in the main thread
    ui::main_loop(ui_name.as_ref(), &ui_rx, &ui_tx, &cr_tx);

    // wait for core thread
    if let Some(core_handle) = core_th {
        core_handle.join().unwrap()
    }
}

fn parse_command_line() -> Config {
    let matches = App::new("unlimited")
        .version(VERSION)
        .author("Carl-Erwin Griffith <carl.erwin@gmail.com>")
        .about("unlimited is an experimental editor")
        .args_from_usage("--ui, --ui=[termion|ncurses] 'select user interface frontend'")
        .arg(
            Arg::with_name("FILES")
                .help("list of the files to open")
                .required(false)
                .multiple(true),
        )
        .get_matches();

    let mut ui_frontend = String::new();
    if matches.is_present("ui") {
        ui_frontend = matches.values_of("ui").unwrap().collect::<String>();
    }

    let mut files_list = Vec::new();
    if matches.is_present("FILES") {
        files_list = matches
            .values_of("FILES")
            .unwrap()
            .map(|x| x.to_owned())
            .collect();
    }

    Config {
        files_list,
        ui_frontend,
    }
}
<|endoftext|>