<commit_before>extern crate gfx_gl as gl;
extern crate camera_controllers;
extern crate collada;
extern crate env_logger;
extern crate gfx;
extern crate gfx_debug_draw;
extern crate gfx_device_gl;
extern crate piston;
extern crate sdl2;
extern crate sdl2_window;
extern crate shader_version;
extern crate skeletal_animation;
extern crate vecmath;

use gfx::traits::*;
use gfx_debug_draw::DebugRenderer;

use std::collections::HashMap;

use gl::Gl;

use std::path::Path;
use std::rc::Rc;
use std::cell::RefCell;

use piston::window::{
    WindowSettings,
    OpenGLWindow,
};

use piston::event::{
    events,
    RenderEvent,
    ResizeEvent,
};

use vecmath::mat4_id;

use sdl2_window::Sdl2Window;

use camera_controllers::{
    OrbitZoomCamera,
    OrbitZoomCameraSettings,
    CameraPerspective,
    model_view_projection
};

use skeletal_animation::*;
use collada::document::ColladaDocument;

use piston::input::keyboard::Key;
use piston::input::Button::Keyboard;

fn main() {

    use std::f32::consts::PI;

    env_logger::init().unwrap();

    let (win_width, win_height) = (640, 480);

    let mut window = Sdl2Window::new(
        shader_version::OpenGL::_3_2,
        WindowSettings::new(
            "Animation Viewer".to_string(),
            piston::window::Size { width: 640, height: 480 }
        ).exit_on_esc(true)
    );

    let mut graphics = gfx_device_gl::create(|s| window.get_proc_address(s)).into_graphics();

    let mut frame = gfx::Frame::new(win_width as u16, win_height as u16);

    let window = Rc::new(RefCell::new(window));

    let clear = gfx::ClearData {
        color: [0.3, 0.3, 0.3, 1.0],
        depth: 1.0,
        stencil: 0
    };

    let mut debug_renderer = DebugRenderer::new(&mut graphics, [frame.width as u32, frame.height as u32], 64, None, None).ok().unwrap();

    // TODO - these are (usually) available in the COLLADA file, associated with a <mesh> element in a somewhat convoluted way
    let texture_paths = vec![
        "assets/young_lightskinned_male_diffuse.png",
        "assets/suit01lres_diffuse.png",
        "assets/male02_diffuse_black.png",
        "assets/brown_eye.png",
        "assets/eyebrow009.png",
        "assets/eyelashes01.png",
    ];

    let collada_document = ColladaDocument::from_path(&Path::new("assets/suit_guy.dae")).unwrap();
    let mut skeleton_set = collada_document.get_skeletons().unwrap();
    let skeleton = &skeleton_set[0];

    let mut anim_clips = load_animations("assets/clips.json").unwrap();

    let blend_tree = BlendTreeNode::from_def(
        BlendTreeNodeDef::from_path("assets/walking_blend_tree.json").unwrap(),
        &anim_clips,
    );

    //let mut skinned_renderer = SkinnedRenderer::from_collada(&mut graphics, collada_document, texture_paths).unwrap();

    let rotate_on_z = [
        [PI.cos(), -PI.sin(), 0.0, 0.0],
        [PI.sin(), PI.cos(), 0.0, 0.0],
        [0.0, 0.0, 1.0, 0.0],
        [0.0, 0.0, 0.0, 1.0],
    ];

    let mut params: [f32; 3] = [0.0, 0.0, 0.0];

    let model = mat4_id();
    let mut projection = CameraPerspective {
        fov: 90.0f32,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: (win_width as f32) / (win_height as f32)
    }.projection();

    let mut orbit_zoom_camera: OrbitZoomCamera<f32> = OrbitZoomCamera::new(
        [0.0, 0.0, 0.0],
        OrbitZoomCameraSettings::default()
    );

    // Start event loop

    Gl::load_with(|s| unsafe {
        std::mem::transmute(sdl2::video::gl_get_proc_address(s))
    });

    let mut elapsed_time = 0f64;

    let mut skeleton_toggle = false;
    let mut mesh_toggle = true;
    let mut speed = 1.0;
    let mut label_toggle = false;

    for e in events(window) {

        use piston::event::PressEvent;

        e.resize(|width, height| {
            debug_renderer.resize(width, height);

            // Update frame
            frame.width = width as u16;
            frame.height = height as u16;

            // Update projection matrix
            projection = CameraPerspective {
                fov: 90.0f32,
                near_clip: 0.1,
                far_clip: 1000.0,
                aspect_ratio: (width as f32) / (height as f32)
            }.projection();
        });

        orbit_zoom_camera.event(&e);

        e.press(|button| {
            match button {

                Keyboard(Key::W) => { params[0] -= 0.1; },
                Keyboard(Key::S) => { params[0] += 0.1; },

                Keyboard(Key::A) => { params[1] -= 0.1; },
                Keyboard(Key::D) => { params[1] += 0.1; },

                Keyboard(Key::Q) => { params[2] -= 0.1; },
                Keyboard(Key::E) => { params[2] += 0.1; },

                Keyboard(Key::M) => { mesh_toggle = !mesh_toggle; },
                Keyboard(Key::B) => { skeleton_toggle = !skeleton_toggle },

                Keyboard(Key::P) => { speed *= 1.5; },
                Keyboard(Key::O) => { speed *= 0.75; },
                Keyboard(Key::L) => { label_toggle = !label_toggle; },

                _ => {},
            }
        });

        if let Some(args) = e.render_args() {
            graphics.clear(clear, gfx::COLOR | gfx::DEPTH, &frame);

            let camera_view = orbit_zoom_camera.camera(args.ext_dt).orthogonal();

            let camera_projection = model_view_projection(
                model,
                camera_view,
                projection
            );

            // Draw axes
            debug_renderer.draw_line([0.0, 0.0, 0.0], [5.0, 0.0, 0.0], [1.0, 0.0, 0.0, 1.0]);
            debug_renderer.draw_line([0.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 1.0, 0.0, 1.0]);
            debug_renderer.draw_line([0.0, 0.0, 0.0], [0.0, 0.0, 5.0], [0.0, 0.0, 1.0, 1.0]);

            debug_renderer.draw_text_at_position(
                "X",
                [6.0, 0.0, 0.0],
                [1.0, 0.0, 0.0, 1.0],
            );

            debug_renderer.draw_text_at_position(
                "Y",
                [0.0, 6.0, 0.0],
                [0.0, 1.0, 0.0, 1.0],
            );

            debug_renderer.draw_text_at_position(
                "Z",
                [0.0, 0.0, 6.0],
                [0.0, 0.0, 1.0, 1.0],
            );

            elapsed_time = elapsed_time + (0.01 + 0.02 * params[0] as f64) * speed;

            debug_renderer.draw_text_on_screen(
                &format!("Blend Factor: {}", params[0]),
                [10, 10],
                [1.0, 0.0, 0.0, 1.0],
            );

            debug_renderer.draw_text_on_screen(
                &format!("LR: {}", params[1]),
                [10, 40],
                [1.0, 0.0, 0.0, 1.0],
            );

            debug_renderer.draw_text_on_screen(
                &format!("ROOT: {}", params[2]),
                [10, 80],
                [1.0, 0.0, 0.0, 1.0],
            );

            let mut local_poses = [ SQT { translation: [0.0, 0.0, 0.0], scale: 0.0, rotation: (0.0, [0.0, 0.0, 0.0]) }; 64 ];
            blend_tree.get_output_pose(elapsed_time as f32, &params[..], &mut local_poses[0 .. skeleton.joints.len()]);
            let global_poses = calculate_global_poses(skeleton, &local_poses);

            if mesh_toggle {
                //skinned_renderer.render(&mut graphics, &frame, camera_view, camera_projection, &global_poses);
            }

            if skeleton_toggle {
                draw_skeleton(&skeleton, &global_poses, &mut debug_renderer, label_toggle);
            }

            debug_renderer.render(&mut graphics, &frame, camera_projection);

            graphics.end_frame();
        }
    }
}
<commit_msg>Using AnimationController<commit_after>extern crate gfx_gl as gl;
extern crate camera_controllers;
extern crate collada;
extern crate env_logger;
extern crate gfx;
extern crate gfx_debug_draw;
extern crate gfx_device_gl;
extern crate piston;
extern crate sdl2;
extern crate sdl2_window;
extern crate shader_version;
extern crate skeletal_animation;
extern crate vecmath;

use gfx::traits::*;
use gfx_debug_draw::DebugRenderer;

use gl::Gl;

use std::path::Path;
use std::rc::Rc;
use std::cell::RefCell;

use piston::window::{
    WindowSettings,
    OpenGLWindow,
};

use piston::event::{
    events,
    RenderEvent,
    ResizeEvent,
};

use vecmath::{Matrix4, mat4_id};

use sdl2_window::Sdl2Window;

use camera_controllers::{
    OrbitZoomCamera,
    OrbitZoomCameraSettings,
    CameraPerspective,
    model_view_projection
};

use skeletal_animation::*;
use collada::document::ColladaDocument;

use piston::input::keyboard::Key;
use piston::input::Button::Keyboard;

fn main() {

    env_logger::init().unwrap();

    let (win_width, win_height) = (640, 480);

    let mut window = Sdl2Window::new(
        shader_version::OpenGL::_3_2,
        WindowSettings::new(
            "Animation Viewer".to_string(),
            piston::window::Size { width: 640, height: 480 }
        ).exit_on_esc(true)
    );

    let mut graphics = gfx_device_gl::create(|s| window.get_proc_address(s)).into_graphics();

    let mut frame = gfx::Frame::new(win_width as u16, win_height as u16);

    let window = Rc::new(RefCell::new(window));

    let clear = gfx::ClearData {
        color: [0.3, 0.3, 0.3, 1.0],
        depth: 1.0,
        stencil: 0
    };

    let mut debug_renderer = DebugRenderer::new(&mut graphics, [frame.width as u32, frame.height as u32], 64, None, None).ok().unwrap();

    // TODO - these are (usually) available in the COLLADA file, associated with a <mesh> element in a somewhat convoluted way
    let texture_paths = vec![
        "assets/young_lightskinned_male_diffuse.png",
        "assets/suit01lres_diffuse.png",
        "assets/male02_diffuse_black.png",
        "assets/brown_eye.png",
        "assets/eyebrow009.png",
        "assets/eyelashes01.png",
    ];

    let skeleton = {
        let collada_document = ColladaDocument::from_path(&Path::new("assets/suit_guy.dae")).unwrap();
        let skeleton_set = collada_document.get_skeletons().unwrap();
        skeleton_set[0].clone()
    };

    let skeleton = Rc::new(RefCell::new(skeleton));

    let anim_clips = load_animations("assets/clips.json").unwrap();

    let blend_tree = BlendTreeNode::from_def(
        BlendTreeNodeDef::from_path("assets/walking_blend_tree.json").unwrap(),
        &anim_clips,
    );

    let mut controller = AnimationController::new(skeleton.clone(), blend_tree);

    //let mut skinned_renderer = SkinnedRenderer::from_collada(&mut graphics, collada_document, texture_paths).unwrap();

    let model = mat4_id();
    let mut projection = CameraPerspective {
        fov: 90.0f32,
        near_clip: 0.1,
        far_clip: 1000.0,
        aspect_ratio: (win_width as f32) / (win_height as f32)
    }.projection();

    let mut orbit_zoom_camera: OrbitZoomCamera<f32> = OrbitZoomCamera::new(
        [0.0, 0.0, 0.0],
        OrbitZoomCameraSettings::default()
    );

    // Start event loop

    Gl::load_with(|s| unsafe {
        std::mem::transmute(sdl2::video::gl_get_proc_address(s))
    });

    let mut elapsed_time = 0f64;

    let mut skeleton_toggle = false;
    let mut mesh_toggle = true;
    let mut speed = 1.0;
    let mut label_toggle = false;

    for e in events(window) {

        use piston::event::PressEvent;

        e.resize(|width, height| {
            debug_renderer.resize(width, height);

            // Update frame
            frame.width = width as u16;
            frame.height = height as u16;

            // Update projection matrix
            projection = CameraPerspective {
                fov: 90.0f32,
                near_clip: 0.1,
                far_clip: 1000.0,
                aspect_ratio: (width as f32) / (height as f32)
            }.projection();
        });

        orbit_zoom_camera.event(&e);

        e.press(|button| {
            match button {

                Keyboard(Key::W) => {
                    let v = controller.get_param(0) + 0.1;
                    controller.set_param(0, v);
                }

                Keyboard(Key::S) => {
                    let v = controller.get_param(0) - 0.1;
                    controller.set_param(0, v);
                }

                Keyboard(Key::D) => {
                    let v = controller.get_param(1) + 0.1;
                    controller.set_param(1, v);
                }

                Keyboard(Key::A) => {
                    let v = controller.get_param(1) - 0.1;
                    controller.set_param(1, v);
                }

                Keyboard(Key::E) => {
                    let v = controller.get_param(2) + 0.1;
                    controller.set_param(2, v);
                }
                Keyboard(Key::Q) => {
                    let v = controller.get_param(2) - 0.1;
                    controller.set_param(2, v);
                }

                Keyboard(Key::M) => { mesh_toggle = !mesh_toggle; },
                Keyboard(Key::B) => { skeleton_toggle = !skeleton_toggle },

                Keyboard(Key::P) => { speed *= 1.5; },
                Keyboard(Key::O) => { speed *= 0.75; },
                Keyboard(Key::L) => { label_toggle = !label_toggle; },

                _ => {},
            }
        });

        if let Some(args) = e.render_args() {
            graphics.clear(clear, gfx::COLOR | gfx::DEPTH, &frame);

            let camera_view = orbit_zoom_camera.camera(args.ext_dt).orthogonal();

            let camera_projection = model_view_projection(
                model,
                camera_view,
                projection
            );

            // Draw axes
            debug_renderer.draw_line([0.0, 0.0, 0.0], [5.0, 0.0, 0.0], [1.0, 0.0, 0.0, 1.0]);
            debug_renderer.draw_line([0.0, 0.0, 0.0], [0.0, 5.0, 0.0], [0.0, 1.0, 0.0, 1.0]);
            debug_renderer.draw_line([0.0, 0.0, 0.0], [0.0, 0.0, 5.0], [0.0, 0.0, 1.0, 1.0]);

            debug_renderer.draw_text_at_position(
                "X",
                [6.0, 0.0, 0.0],
                [1.0, 0.0, 0.0, 1.0],
            );

            debug_renderer.draw_text_at_position(
                "Y",
                [0.0, 6.0, 0.0],
                [0.0, 1.0, 0.0, 1.0],
            );

            debug_renderer.draw_text_at_position(
                "Z",
                [0.0, 0.0, 6.0],
                [0.0, 0.0, 1.0, 1.0],
            );

            elapsed_time = elapsed_time + 0.01 * speed;

            let mut global_poses: [Matrix4<f32>; 64] = [ mat4_id(); 64 ];

            controller.get_output_pose(elapsed_time as f32, &mut global_poses[0 .. skeleton.borrow().joints.len()]);

            if mesh_toggle {
                //skinned_renderer.render(&mut graphics, &frame, camera_view, camera_projection, &global_poses);
            }

            if skeleton_toggle {
                draw_skeleton(skeleton.clone(), &global_poses, &mut debug_renderer, label_toggle);
            }

            debug_renderer.render(&mut graphics, &frame, camera_projection);

            graphics.end_frame();
        }
    }
}
<|endoftext|>


<commit_before>#![feature(unboxed_closures, fn_traits, rustc_private)]

#[macro_use]
extern crate log;

use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;

type Payload = i32;

struct Message {
    payload: Payload,
    sender: Sender<Payload>,
}

trait Actor {
    fn handle(&self, message: Message) -> ();
}

struct ActorHarness<T: Actor> {
    inbox: Receiver<Message>,
    actor: T,
}

impl<T: Actor> ActorHarness<T> {
    fn new(inbox: Receiver<Message>, actor: T) -> ActorHarness<T> {
        ActorHarness {
            inbox: inbox,
            actor: actor,
        }
    }
}

impl<T: Actor> FnOnce<()> for ActorHarness<T> {
    type Output = ();
    extern "rust-call" fn call_once(self, _: ()) -> () {
        // Forever...
        loop {
            // See if we have messages!
            match self.inbox.recv() {
                Ok(m) => self.actor.handle(m),
                // Exit on error
                Err(e) => {
                    error!("[Actor] Error: {:?}", e);
                    break;
                }
            }
        }
    }
}

struct ActorRef {
    outbox: Sender<Message>,
}

impl ActorRef {
    fn new(outbox: Sender<Message>) -> ActorRef {
        ActorRef { outbox: outbox }
    }
}

struct ActorImpl {
}

impl ActorImpl {
    fn new() -> ActorImpl {
        ActorImpl {}
    }
}

impl Actor for ActorImpl {
    fn handle(&self, message: Message) -> () {
        match message {
            // If we're given a 'special' -1 value, exit.
            Message { payload, sender: _ } if payload == -1 => {
                info!("[Actor] Exiting!");
                panic!("Get me out of here!");
            }
            // Otherwise, print and respond to the message!
            Message { payload, sender } => {
                info!("[Actor] Got: {:?}", payload);
                sender.send(payload + 1).unwrap();
            }
        }
    }
}

fn main() {
    // Actor Mailbox
    let (outbox, inbox): (Sender<Message>, Receiver<Message>) = mpsc::channel();

    // Meet the players
    let actor = ActorImpl::new();
    let harness = ActorHarness::new(inbox, actor);
    let r = ActorRef::new(outbox);

    // Create a new thread
    let handle = thread::Builder::new()
        .name("actor".into())
        .spawn(harness)
        .unwrap();

    // Communication channel with actor
    let (sender, receiver): (Sender<Payload>, Receiver<Payload>) = mpsc::channel();
    r.outbox
        .send(Message {
            payload: 0,
            sender: sender.clone(),
        })
        .unwrap();
    let num = receiver.recv().unwrap();
    info!("[Main] Got: {:?}", num);

    // Send another number!
    r.outbox
        .send(Message {
            payload: num + 1,
            sender: sender.clone(),
        })
        .unwrap();
    let num = receiver.recv().unwrap();
    info!("[Main] Got: {:?}", num);

    // Send exit code
    r.outbox
        .send(Message {
            payload: -1,
            sender: sender.clone(),
        })
        .unwrap();

    // Wait for thread to exit
    let res = handle.join();
    info!("[Main] Res: {:?}", res);
}
<commit_msg>ensure that logging is init and not just instantiated by cargo<commit_after>#![feature(unboxed_closures, fn_traits, rustc_private)]

#[macro_use]
extern crate log;
extern crate env_logger;

use std::sync::mpsc::{Sender, Receiver};
use std::sync::mpsc;
use std::thread;

type Payload = i32;

struct Message {
    payload: Payload,
    sender: Sender<Payload>,
}

trait Actor {
    fn handle(&self, message: Message) -> ();
}

struct ActorHarness<T: Actor> {
    inbox: Receiver<Message>,
    actor: T,
}

impl<T: Actor> ActorHarness<T> {
    fn new(inbox: Receiver<Message>, actor: T) -> ActorHarness<T> {
        ActorHarness {
            inbox: inbox,
            actor: actor,
        }
    }
}

impl<T: Actor> FnOnce<()> for ActorHarness<T> {
    type Output = ();
    extern "rust-call" fn call_once(self, _: ()) -> () {
        // Forever...
        loop {
            // See if we have messages!
            match self.inbox.recv() {
                Ok(m) => self.actor.handle(m),
                // Exit on error
                Err(e) => {
                    error!("[Actor] Error: {:?}", e);
                    break;
                }
            }
        }
    }
}

struct ActorRef {
    outbox: Sender<Message>,
}

impl ActorRef {
    fn new(outbox: Sender<Message>) -> ActorRef {
        ActorRef { outbox: outbox }
    }
}

struct ActorImpl {
}

impl ActorImpl {
    fn new() -> ActorImpl {
        ActorImpl {}
    }
}

impl Actor for ActorImpl {
    fn handle(&self, message: Message) -> () {
        match message {
            // If we're given a 'special' -1 value, exit.
            Message { payload, sender: _ } if payload == -1 => {
                info!("[Actor] Exiting!");
                panic!("Get me out of here!");
            }
            // Otherwise, print and respond to the message!
            Message { payload, sender } => {
                info!("[Actor] Got: {:?}", payload);
                sender.send(payload + 1).unwrap();
            }
        }
    }
}

fn main() {
    // Init logging
    env_logger::init().unwrap();

    // Actor Mailbox
    let (outbox, inbox): (Sender<Message>, Receiver<Message>) = mpsc::channel();

    // Meet the players
    let actor = ActorImpl::new();
    let harness = ActorHarness::new(inbox, actor);
    let r = ActorRef::new(outbox);

    // Create a new thread
    let handle = thread::Builder::new()
        .name("actor".into())
        .spawn(harness)
        .unwrap();

    // Communication channel with actor
    let (sender, receiver): (Sender<Payload>, Receiver<Payload>) = mpsc::channel();
    r.outbox
        .send(Message {
            payload: 0,
            sender: sender.clone(),
        })
        .unwrap();
    let num = receiver.recv().unwrap();
    info!("[Main] Got: {:?}", num);

    // Send another number!
    r.outbox
        .send(Message {
            payload: num + 1,
            sender: sender.clone(),
        })
        .unwrap();
    let num = receiver.recv().unwrap();
    info!("[Main] Got: {:?}", num);

    // Send exit code
    r.outbox
        .send(Message {
            payload: -1,
            sender: sender.clone(),
        })
        .unwrap();

    // Wait for thread to exit
    let res = handle.join();
    info!("[Main] Res: {:?}", res);
}
<|endoftext|>


<commit_before>extern crate time;
use time::{Timespec, Duration};
use std::thread;
use std::ops::Add;
use std::io::{self, Write, Read};

macro_rules! printfl {
    ($($tt:tt)*) => {{
        use std::io::{self, Write};
        print!($($tt)*);
        io::stdout().flush().ok().expect("flush() fail");
    }}
}

pub struct ProgressBar {
    start_time: Timespec,
    total: usize,
    current: usize,
    is_finish: bool,
    show_bar: bool,
    show_speed: bool,
    show_percent: bool,
    show_counter: bool,
    show_time_left: bool,
}

impl ProgressBar {
    pub fn new(total: usize) -> ProgressBar {
            ProgressBar {
                total: total,
                current: 0,
                start_time: time::get_time(),
                is_finish: false,
                show_bar: true,
                show_speed: false,
                show_percent: true,
                show_counter: true,
                show_time_left: true,
            }
    }

    fn add(&mut self, i: usize) -> usize {
        self.current += i;
        if self.current <= self.total {
            self.draw()
        };
        self.current
    }

    fn draw(&self) {
        let width = 143;    // replace to -> get_tty_size()
        let percent_box;
        let counter_box;
        let mut time_left_box = format!("");
        let speed_box;
        let mut bar_box = "[".to_string();
        let mut out;
        // precent box
        let percent = self.current as f64 / (self.total as f64 / 100f64);
        percent_box = format!(" {:.*} % ", 2, percent);
        // counter box
        counter_box = format!("{} / {} ", self.current, self.total);
        // time left box
        let from_start = time::get_time() - self.start_time;
        let per_entry = from_start / self.current as i32; // Why the hack
        let mut left = per_entry * (self.total - self.current) as i32;
        let sec_nano = Duration::seconds(1).num_nanoseconds().unwrap() as i32;
        left = (left / sec_nano) * sec_nano;
        if left.num_seconds() > 0 {
            time_left_box = format!("{}s", left.num_seconds());
        }
        // NOT WORKING: speed box
        let speed = (from_start / sec_nano) / self.current as i32;
        speed_box = format!("{}/s", speed.num_nanoseconds().unwrap() as f64);
        // bar_box - Add prefix & suffix(2)
        let size = width -
                   (percent_box.to_string() + &counter_box + &time_left_box + &speed_box).len();
        // Test if size > 0
        let curr_count = ((self.current as f64 / self.total as f64) * size as f64).ceil();
        let err_count = size as f64 - curr_count;
        bar_box = bar_box + &std::iter::repeat("=").take(curr_count as usize).collect::<String>();
        if self.current < self.total {
            bar_box = bar_box + ">";
        } else {
            bar_box = bar_box + "=";
        }
        bar_box = bar_box + &std::iter::repeat("-").take(err_count as usize).collect::<String>() +
                  "]";
        out = counter_box.to_string() + &bar_box + &percent_box + &time_left_box;
        // Print
        if out.len() < width {
            let gap = width - out.len();
            out = out + &std::iter::repeat(" ").take(gap as usize).collect::<String>();
        }
        printfl!("\r{}", out);
    }

    fn finish(&mut self) {
        if self.current < self.total {
            self.current = self.total;
            self.draw();
        }
        println!("");
        self.is_finish = true;
    }
}

// Implement io::Writer
impl Write for ProgressBar {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let n = buf.len();
        self.add(n);
        Ok(buf.len())
    }
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

// TODO: Implement io::Reader

fn main() {
    let mut pb = ProgressBar::new(1000);
    for _ in 0..1000 {
        pb.add(1);
        thread::sleep_ms(2);
    }
    pb.finish();
    print!("The end!");

    /*let name = "/usr/share/dict/words";
    let mut file = std::fs::File::open(name).unwrap();
    let bytes = std::fs::metadata(name).unwrap().len() as i64;
    let mut pb = ProgressBar::new(bytes);
    std::io::copy(&mut file, &mut pb).unwrap();
    println!("Done");
    // Create example that use multiWriter and decorateWriter example too
    */
}
<commit_msg>add formatting<commit_after>extern crate time;
use time::{Timespec, Duration};
use std::thread;
use std::ops::Add;
use std::io::{self, Write, Read};

macro_rules! printfl {
    ($($tt:tt)*) => {{
        use std::io::{self, Write};
        print!($($tt)*);
        io::stdout().flush().ok().expect("flush() fail");
    }}
}

static FORMAT: &'static str = "[=>-]";

pub struct ProgressBar {
    start_time: Timespec,
    total: usize,
    current: usize,
    is_finish: bool,
    show_bar: bool,
    show_speed: bool,
    show_percent: bool,
    show_counter: bool,
    show_time_left: bool,

    // Format
    bar_start:  String,
    bar_current: String,
    bar_current_n: String,
    bar_remain: String,
    bar_end: String,
}

impl ProgressBar {
    pub fn new(total: usize) -> ProgressBar {
            let v: Vec<&str> = FORMAT.split("").collect();
            ProgressBar {
                total: total,
                current: 0,
                start_time: time::get_time(),
                is_finish: false,
                show_bar: true,
                show_speed: false,
                show_percent: true,
                show_counter: true,
                show_time_left: true,
                bar_start: v[1].to_string(),
                bar_current: v[2].to_string(),
                bar_current_n: v[3].to_string(),
                bar_remain: v[4].to_string(),
                bar_end: v[5].to_string(),
            }
    }

    fn add(&mut self, i: usize) -> usize {
        self.current += i;
        if self.current <= self.total {
            self.draw()
        };
        self.current
    }

    fn draw(&self) {
        let width = 143;    // replace to -> get_tty_size()
        // start - countersBox
        // middle - barBox
        // end - percentBox + speedBox + timeLeftBox
        let percent_box;
        let counter_box;
        let mut time_left_box = format!("");
        let speed_box;
        let mut bar_box = "[".to_string();
        let mut out;
        // precent box
        let percent = self.current as f64 / (self.total as f64 / 100f64);
        percent_box = format!(" {:.*} % ", 2, percent);
        // counter box
        counter_box = format!("{} / {} ", self.current, self.total);
        // time left box
        let from_start = time::get_time() - self.start_time;
        let per_entry = from_start / self.current as i32; // Why the hack
        let mut left = per_entry * (self.total - self.current) as i32;
        let sec_nano = Duration::seconds(1).num_nanoseconds().unwrap() as i32;
        left = (left / sec_nano) * sec_nano;
        if left.num_seconds() > 0 {
            time_left_box = format!("{}s", left.num_seconds());
        }
        // NOT WORKING: speed box
        let speed = (from_start / sec_nano) / self.current as i32;
        speed_box = format!("{}/s", speed.num_nanoseconds().unwrap() as f64);
        // bar_box - Add prefix & suffix(2)
        let size = width -
                   (percent_box.to_string() + &counter_box + &time_left_box + &speed_box).len();
        // Test if size > 0
        let curr_count = ((self.current as f64 / self.total as f64) * size as f64).ceil();
        let err_count = size as f64 - curr_count;
        bar_box = bar_box + &std::iter::repeat("=").take(curr_count as usize).collect::<String>();
        if self.current < self.total {
            bar_box = bar_box + ">";
        } else {
            bar_box = bar_box + "=";
        }
        bar_box = bar_box + &std::iter::repeat("-").take(err_count as usize).collect::<String>() +
                  "]";
        out = counter_box.to_string() + &bar_box + &percent_box + &time_left_box;
        // Print
        if out.len() < width {
            let gap = width - out.len();
            out = out + &std::iter::repeat(" ").take(gap as usize).collect::<String>();
        }
        printfl!("\r{}", out);
    }

    fn finish(&mut self) {
        if self.current < self.total {
            self.current = self.total;
            self.draw();
        }
        println!("");
        self.is_finish = true;
    }
}

// Implement io::Writer
impl Write for ProgressBar {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        let n = buf.len();
        self.add(n);
        Ok(buf.len())
    }
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

// TODO: Implement io::Reader

fn main() {
    let mut pb = ProgressBar::new(1000);
    for _ in 0..1000 {
        pb.add(1);
        thread::sleep_ms(2);
    }
    pb.finish();
    print!("The end!");

    /*let name = "/usr/share/dict/words";
    let mut file = std::fs::File::open(name).unwrap();
    let bytes = std::fs::metadata(name).unwrap().len() as i64;
    let mut pb = ProgressBar::new(bytes);
    std::io::copy(&mut file, &mut pb).unwrap();
    println!("Done");
    // Create example that use multiWriter and decorateWriter example too
    */
}
<|endoftext|>


<commit_before>#![feature(macro_rules)]
#![feature(phase)]
#![feature(associated_types)]
#[phase(plugin, link)] extern crate log;

extern crate syntax;
extern crate collections;
extern crate core;

#[cfg(not(test))]
use racer::Match;
#[cfg(not(test))]
use racer::util::getline;
#[cfg(not(test))]
use racer::nameres::{do_file_search, do_external_search};
#[cfg(not(test))]
use racer::scopes;

pub mod racer;

#[cfg(not(test))]
fn match_fn(m:Match) {
    let (linenum, charnum) = scopes::point_to_coords2(&m.filepath, m.point).unwrap();
    if m.matchstr.as_slice() == "" {
        panic!("MATCHSTR is empty - waddup?");
    }
    println!("MATCH {},{},{},{},{},{}", m.matchstr,
                                    linenum.to_string(),
                                    charnum.to_string(),
                                    m.filepath.as_str().unwrap(),
                                    m.mtype,
                                    m.contextstr
             );
}

#[cfg(not(test))]
fn complete() {
    let args = std::os::args();
    if args.len() < 3 {
        println!("Provide more arguments!");
        print_usage();
        std::os::set_exit_status(1);
        return;
    }
    match std::os::args().as_slice()[2].as_slice().parse::<uint>() {
        Some(linenum) => {
            // input: linenum, colnum, fname
            if args.len() < 5 {
                println!("Provide more arguments!");
                print_usage();
                std::os::set_exit_status(1);
                return;
            }
            let charnum = std::os::args().as_slice()[3].as_slice().parse::<uint>().unwrap();
            let fname = args.as_slice()[4].as_slice();
            let fpath = Path::new(fname);
            let src = racer::load_file(&fpath);
            let line = getline(&fpath, linenum);
            let (start, pos) = racer::util::expand_ident(line.as_slice(), charnum);
            println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));

            let point = scopes::coords_to_point(&*src, linenum, charnum);
            for m in racer::complete_from_file(&*src, &fpath, point) {
                match_fn(m);
            }
        }
        None => {
            // input: a command line string passed in
            let arg = args.as_slice()[2].as_slice();
            let it = arg.split_str("::");
            let p : Vec<&str> = it.collect();

            for m in do_file_search(p.as_slice()[0], &Path::new(".")) {
                if p.len() == 1 {
                    match_fn(m);
                } else {
                    for m in do_external_search(p.slice_from(1), &m.filepath, m.point, racer::SearchType::StartsWith, racer::Namespace::BothNamespaces) {
                        match_fn(m);
                    }
                }
            }
        }
    }
}

#[cfg(not(test))]
fn prefix() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    if args.len() < 5 {
        println!("Provide more arguments!");
        print_usage();
        std::os::set_exit_status(1);
        return;
    }
    let linenum = args[2].as_slice().parse::<uint>().unwrap();
    let charnum = args[3].as_slice().parse::<uint>().unwrap();
    let fname = args[4].as_slice();

    // print the start, end, and the identifier prefix being matched
    let path = Path::new(fname);
    let line = getline(&path, linenum);
    let (start, pos) = racer::util::expand_ident(line.as_slice(), charnum);
    println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));
}

#[cfg(not(test))]
fn find_definition() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    if args.len() < 5 {
        println!("Provide more arguments!");
        print_usage();
        std::os::set_exit_status(1);
        return;
    }
    let linenum = args[2].as_slice().parse::<uint>().unwrap();
    let charnum = args[3].as_slice().parse::<uint>().unwrap();
    let fname = args[4].as_slice();
    let fpath = Path::new(fname);
    let src = racer::load_file(&fpath);
    let pos = scopes::coords_to_point(&*src, linenum, charnum);

    racer::find_definition(&*src, &fpath, pos).map(match_fn);
}

#[cfg(not(test))]
fn print_usage() {
    let program = std::os::args().as_slice()[0].clone();
    println!("usage: {} complete linenum charnum fname", program);
    println!("or:    {} find-definition linenum charnum fname", program);
    println!("or:    {} complete fullyqualifiedname   (e.g. std::io::)",program);
    println!("or:    {} prefix linenum charnum fname",program);
}

#[cfg(not(test))]
fn main() {
    if std::os::getenv("RUST_SRC_PATH").is_none() {
        println!("RUST_SRC_PATH environment variable must be set");
        std::os::set_exit_status(1);
        return;
    }

    let args_ = std::os::args();
    let args = args_.as_slice();

    if args.len() == 1 {
        print_usage();
        std::os::set_exit_status(1);
        return;
    }

    let command = args[1].as_slice();
    match command.as_slice() {
        "prefix" => prefix(),
        "complete" => complete(),
        "find-definition" => find_definition(),
        "help" => print_usage(),
        _ => {
            println!("Sorry, I didn't understand command {}", command );
            print_usage();
            std::os::set_exit_status(1);
            return;
        }
    }
}
<commit_msg>Deprecated/renamed feature flags<commit_after>#[macro_use] extern crate log;

extern crate syntax;
extern crate collections;
extern crate core;

#[cfg(not(test))]
use racer::Match;
#[cfg(not(test))]
use racer::util::getline;
#[cfg(not(test))]
use racer::nameres::{do_file_search, do_external_search};
#[cfg(not(test))]
use racer::scopes;

pub mod racer;

#[cfg(not(test))]
fn match_fn(m:Match) {
    let (linenum, charnum) = scopes::point_to_coords2(&m.filepath, m.point).unwrap();
    if m.matchstr.as_slice() == "" {
        panic!("MATCHSTR is empty - waddup?");
    }
    println!("MATCH {},{},{},{},{},{}", m.matchstr,
                                    linenum.to_string(),
                                    charnum.to_string(),
                                    m.filepath.as_str().unwrap(),
                                    m.mtype,
                                    m.contextstr
             );
}

#[cfg(not(test))]
fn complete() {
    let args = std::os::args();
    if args.len() < 3 {
        println!("Provide more arguments!");
        print_usage();
        std::os::set_exit_status(1);
        return;
    }
    match std::os::args().as_slice()[2].as_slice().parse::<uint>() {
        Some(linenum) => {
            // input: linenum, colnum, fname
            if args.len() < 5 {
                println!("Provide more arguments!");
                print_usage();
                std::os::set_exit_status(1);
                return;
            }
            let charnum = std::os::args().as_slice()[3].as_slice().parse::<uint>().unwrap();
            let fname = args.as_slice()[4].as_slice();
            let fpath = Path::new(fname);
            let src = racer::load_file(&fpath);
            let line = getline(&fpath, linenum);
            let (start, pos) = racer::util::expand_ident(line.as_slice(), charnum);
            println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));

            let point = scopes::coords_to_point(&*src, linenum, charnum);
            for m in racer::complete_from_file(&*src, &fpath, point) {
                match_fn(m);
            }
        }
        None => {
            // input: a command line string passed in
            let arg = args.as_slice()[2].as_slice();
            let it = arg.split_str("::");
            let p : Vec<&str> = it.collect();

            for m in do_file_search(p.as_slice()[0], &Path::new(".")) {
                if p.len() == 1 {
                    match_fn(m);
                } else {
                    for m in do_external_search(p.slice_from(1), &m.filepath, m.point, racer::SearchType::StartsWith, racer::Namespace::BothNamespaces) {
                        match_fn(m);
                    }
                }
            }
        }
    }
}

#[cfg(not(test))]
fn prefix() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    if args.len() < 5 {
        println!("Provide more arguments!");
        print_usage();
        std::os::set_exit_status(1);
        return;
    }
    let linenum = args[2].as_slice().parse::<uint>().unwrap();
    let charnum = args[3].as_slice().parse::<uint>().unwrap();
    let fname = args[4].as_slice();

    // print the start, end, and the identifier prefix being matched
    let path = Path::new(fname);
    let line = getline(&path, linenum);
    let (start, pos) = racer::util::expand_ident(line.as_slice(), charnum);
    println!("PREFIX {},{},{}", start, pos, line.as_slice().slice(start, pos));
}

#[cfg(not(test))]
fn find_definition() {
    let args_ = std::os::args();
    let args = args_.as_slice();
    if args.len() < 5 {
        println!("Provide more arguments!");
        print_usage();
        std::os::set_exit_status(1);
        return;
    }
    let linenum = args[2].as_slice().parse::<uint>().unwrap();
    let charnum = args[3].as_slice().parse::<uint>().unwrap();
    let fname = args[4].as_slice();
    let fpath = Path::new(fname);
    let src = racer::load_file(&fpath);
    let pos = scopes::coords_to_point(&*src, linenum, charnum);

    racer::find_definition(&*src, &fpath, pos).map(match_fn);
}

#[cfg(not(test))]
fn print_usage() {
    let program = std::os::args().as_slice()[0].clone();
    println!("usage: {} complete linenum charnum fname", program);
    println!("or:    {} find-definition linenum charnum fname", program);
    println!("or:    {} complete fullyqualifiedname   (e.g. std::io::)",program);
    println!("or:    {} prefix linenum charnum fname",program);
}

#[cfg(not(test))]
fn main() {
    if std::os::getenv("RUST_SRC_PATH").is_none() {
        println!("RUST_SRC_PATH environment variable must be set");
        std::os::set_exit_status(1);
        return;
    }

    let args_ = std::os::args();
    let args = args_.as_slice();

    if args.len() == 1 {
        print_usage();
        std::os::set_exit_status(1);
        return;
    }

    let command = args[1].as_slice();
    match command.as_slice() {
        "prefix" => prefix(),
        "complete" => complete(),
        "find-definition" => find_definition(),
        "help" => print_usage(),
        _ => {
            println!("Sorry, I didn't understand command {}", command );
            print_usage();
            std::os::set_exit_status(1);
            return;
        }
    }
}
<|endoftext|>


<commit_before>//! Muxed. A tmux project manager with no runtime dependencies.
extern crate getopts;
extern crate libc;

use std::os;
use std::io::{println};

mod help;
mod arg_parse;
mod creator {
  mod io { }
}

/// The main execution method.
/// Uses getopts to fetch arguments and pass them to the initializer for
/// inspection.
/// The init method accepts a `Vec<String>` of arguments. If an argument or
/// command does not match valid input print the help screen.
fn main() {
    let args: Vec<String> = os::args();

    let opts = help::opts();
    let maybe_matches = arg_parse::matches(args.tail(), opts.clone());

    if maybe_matches.is_none() {
        help::print_usage(opts);
        return;
    }

    let matches = maybe_matches.unwrap();

    if matches.opt_present("h") || !arg_parse::valid_command(&matches) {
        help::print_usage(opts);
        return;
    } else if matches.opt_present("v") {
        println(format!("{}", "Version: 0.0.0").as_slice());
        return;
    }

    let fragments = &matches.free;

    let command = arg_parse::command(fragments);
    let _value  = arg_parse::value(fragments);

    match command {
//        "new"  => creator::new(value),
        _      => help::print_usage(opts)
    }
}
<commit_msg>Swap the logic so version can easily be printed.<commit_after>//! Muxed. A tmux project manager with no runtime dependencies.
extern crate getopts;
extern crate libc;

use std::os;
use std::io::{println};

mod help;
mod arg_parse;
mod creator {
  mod io { }
}

/// The main execution method.
/// Uses getopts to fetch arguments and pass them to the initializer for
/// inspection.
/// The init method accepts a `Vec<String>` of arguments. If an argument or
/// command does not match valid input print the help screen.
fn main() {
    let args: Vec<String> = os::args();

    let opts = help::opts();
    let maybe_matches = arg_parse::matches(args.tail(), opts.clone());

    if maybe_matches.is_none() {
        help::print_usage(opts);
        return;
    }

    let matches = maybe_matches.unwrap();

    if matches.opt_present("v") {
        println(format!("{}", "Version: 0.0.0").as_slice());
        return;
    } else if matches.opt_present("h") || !arg_parse::valid_command(&matches) {
        help::print_usage(opts);
        return;
    }

    let fragments = &matches.free;

    let command = arg_parse::command(fragments);
    let _value  = arg_parse::value(fragments);

//    match command {
//        "new"  => creator::new(value),
//        _      => help::print_usage(opts)
//    }
}
<|endoftext|>


<commit_before>extern crate time;

use std::io::{Read, Write, Seek};
use std::process::exit;

const HELP_TEXT: &'static str = "netcp (send,receive) ipaddress[:port] filename";
const CALL_SIGN: &'static str = "netcp v0.1";
const MSG_AGREE: &'static str = "AGREE   ";
const MSG_DISAGREE: &'static str = "DISAGREE";
const MSG_FILE: &'static str = "FILE";
const MSG_END: &'static str = "END ";
const TIMEOUT: i64 = 800;

fn main() {
    //get commandline arguments
    let args: Vec<String> = std::env::args().collect();

    //if there is no argument (first arg is always application name), error
    if args.len() == 1 {
        error("No arguments");
    }

    //execute option
    match args[1].as_ref() {
        "help" => {
            println!("{}", HELP_TEXT);
        }

        "send" => {
            //sending needs min. 4 args (appl. name, "send", address, filenames...)
            if args.len() < 4 {
                error("Too few arguments");
            }

            send(&args[2], &args[3..]);
        }

        "receive" => {
            //receiving needs exactly 3 args (appl. name, "receive", address)
            if args.len() != 3 {
                error("Too few or many arguments");
            }

            receive(&args[2]);
        }

        //Unknown parameter
        _ => {
            error("Unknown parameter");
        }
    }
}

fn error(msg: &str) {
    println!("Error: {}.", msg);
    std::process::exit(1);
}

fn receive_data(stream: &mut std::net::TcpStream, buf: &mut [u8]) {
    let mut begin = time::get_time();
    let mut received_bytes: usize = 0;

    while received_bytes < buf.len() && (begin - time::get_time()).num_milliseconds() < TIMEOUT {
        let res = stream.read(&mut buf[received_bytes..]);

        if res.is_err() {
            error("Connection lost");
        }

        let bytes = res.unwrap();
        received_bytes += bytes;

        if bytes != 0 {
            begin = time::get_time();
        }
    }

    if received_bytes < buf.len() {
        error("Connection lost (timeout)");
    }
}

fn send_data(stream: &mut std::net::TcpStream, data: &[u8]) {
    let mut begin = time::get_time();
    let mut sended_bytes: usize = 0;

    while sended_bytes < data.len() && (begin - time::get_time()).num_milliseconds() < TIMEOUT {
        let res = stream.write(&data[sended_bytes..]);

        if res.is_err() {
            error("Connection lost");
        }

        let bytes = res.unwrap();
        sended_bytes += bytes;

        if bytes != 0 {
            begin = time::get_time();
        }
    }

    if sended_bytes < data.len() {
        error("Connection lost (timeout)");
    }
}

fn send_u64(stream: &mut std::net::TcpStream, num: u64) {
    let data = num.to_le();
    let buf: &[u8] = unsafe { std::mem::transmute::<&u64, &[u8; 8]>(&data) };
    send_data(stream, &buf);
}

fn receive_u64(stream: &mut std::net::TcpStream) -> u64 {
    let mut num = 0u64;
    receive_data(stream, unsafe { std::mem::transmute::<&mut u64, &mut [u8; 8]>(&mut num) });
    return u64::from_le(num);
}

fn send_string(stream: &mut std::net::TcpStream, string: &str) {
    let size = string.len() as u64;
    send_u64(stream, size);
    send_data(stream, string.as_bytes());
}

fn receive_string(stream: &mut std::net::TcpStream) -> String {
    let size = receive_u64(stream);
    let mut vec: Vec<u8> = Vec::with_capacity(size as usize);
    unsafe { vec.set_len(size as usize); };
    receive_data(stream, &mut vec[..] );
    let string_res = String::from_utf8(vec);
    match string_res {
        Err(_) => { error("Couldn't convert bytes to string"); }
        _ => {}
    }
    return string_res.unwrap();
}

fn check_agreement(stream: &mut std::net::TcpStream) -> bool {
    let mut vec: Vec<u8> = Vec::with_capacity(MSG_AGREE.len());
    unsafe { vec.set_len(MSG_AGREE.len()); };
    receive_data(stream, &mut vec[..]);

    if compare_byte_array(&vec[..], MSG_AGREE.as_bytes()) {
        return true;
    }
    else if compare_byte_array(&vec[..], MSG_DISAGREE.as_bytes()) {
        return false;
    }
    else {
        error("Invalid protocol");
    }
    return false;
}

fn compare_byte_array(a: &[u8], b: &[u8]) -> bool {
    if a.len() != b.len() {
        return false;
    }

    for i in 0..a.len() {
        if a[i] != b[i] {
            return false;
        }
    }

    return true;
}

fn get_filesize(file: &mut std::fs::File) -> u64 {
    let current = file.seek(std::io::SeekFrom::Current(0)).ok().expect("Error: file seeking failed.");
    let size = file.seek(std::io::SeekFrom::End(0)).ok().expect("Error: file seeking failed.");
    file.seek(std::io::SeekFrom::Start(current)).ok().expect("Error: file seeking failed.");
    return size;
}

fn send(address: &String, file_names: &[String]) {
    //1. check file accessibilities
    //2. check address
    //3. connect with client
    //4. send files sequential

    let mut buf = [0u8; 512];

    //1. check file existences
    //get working directory to find correct files
    let work_dir_res = std::env::current_dir();
    match work_dir_res {
        Err(_) => { error("Couldn't find working directory"); },
        _ => {}
    }
    let work_dir = work_dir_res.unwrap();

    //join working directory and file_names to get absolute file addresses in a vector
    let mut file_addrs: Vec<std::path::PathBuf> = Vec::with_capacity(file_names.len());
    for file_name in file_names {
        file_addrs.push(work_dir.join(file_name));
    }

    //open and close files to check accessibility
    for file_addr in &file_addrs {
        if let Err(_) = std::fs::File::open(&file_addr) {
            println!("Error: File doesn't exist or is not accessible ({}).", file_addr.display());
            exit(1);
        }
    }

    //2. check address and bind listener
    let listener_res = std::net::TcpListener::bind(&address[..]);
    match listener_res {
        Err(ref e) => { println!("Error: {}.", e); exit(1); },
        _ => {}
    }
    let listener = listener_res.unwrap();

    //3. connect with client
    print!("Waiting for client..."); let _ = std::io::stdout().flush();
    let stream_res = listener.accept();
    match stream_res {
        Err(ref e) => { println!("Error: {}.", e); exit(1); },
        _ => {}
    }
    let (mut stream, client_addr) = stream_res.unwrap();

    //3. check if client send correct CALL_SIGN
    receive_data(&mut stream, &mut buf[..CALL_SIGN.len()]);

    if compare_byte_array(CALL_SIGN.as_bytes(), &buf[..CALL_SIGN.len()]) == false {
        error("Invalid protocol");
    }

    send_data(&mut stream, MSG_AGREE.as_bytes());

    println!("connected with {}.", client_addr); let _ = std::io::stdout().flush();

    //send files
    for file_addr in &file_addrs {

        let file_res = std::fs::File::open(&file_addr);
        match file_res {
            Err(_) => { error("Couldn't open file") },
            _ => {}
        }
        let mut file = file_res.unwrap();
        let filesize = get_filesize(&mut file);

        send_data(&mut stream, MSG_FILE.as_bytes()); //send file is ready to send
        send_u64(&mut stream, filesize); //send file size as u64

        //send filename
        let filename_os_opt = file_addr.file_name();
        if filename_os_opt.is_none() {
            error("Couldn't convert filename to utf8");
        }
        let filename_opt = filename_os_opt.unwrap().to_str();
        if filename_opt.is_none() {
            error("Couldn't convert filename to utf8");
        }
        send_string(&mut stream, &filename_opt.unwrap());

        print!("Send {}...", &filename_opt.unwrap()); let _ = std::io::stdout().flush();

        //if client send MSG_DISAGREE, continue else send file
        if check_agreement(&mut stream) == false {
            println!("cancelled by client.");
            continue;
        }

        //send file
        let mut i = 0u64;
        while i < (filesize-1) {
            if (filesize - i) >= buf.len() as u64 {
                if let Err(_) = file.read(&mut buf) {
                    error("Couldn't read from file");
                }
                send_data(&mut stream, &buf);
                i += buf.len() as u64;
            }
            else {
                if let Err(_) = file.read(&mut buf[..(filesize - i) as usize]) {
                    error("Couldn't read from file");
                }
                send_data(&mut stream, &buf[..(filesize - i) as usize]);
                i = filesize-1;
            }
        }

        println!("done."); let _ = std::io::stdout().flush();
    }

    //send end
    send_data(&mut stream, MSG_END.as_bytes());
}

fn receive(address: &String) {
    let mut buf = [0u8; 512];

    let work_dir_res = std::env::current_dir();
    match work_dir_res {
        Err(_) => { error("Couldn't find working directory"); },
        _ => {}
    }
    let work_dir = work_dir_res.unwrap();

    let stream_res = std::net::TcpStream::connect(&address[..]);
    match stream_res {
        Err(ref e) => { println!("Error: {}.", e); exit(1); },
        _ => {}
    }
    let mut stream = stream_res.unwrap();

    send_data(&mut stream, CALL_SIGN.as_bytes());

    if check_agreement(&mut stream) == false {
        error("No server found");
    }

    let mut msg_file: Vec<u8> = Vec::with_capacity(MSG_FILE.len());
    unsafe { msg_file.set_len(MSG_FILE.len()); };
    loop {
        receive_data(&mut stream, &mut msg_file[..]);

        if compare_byte_array(&msg_file[..], MSG_END.as_bytes()) {
            break;
        }
        else if compare_byte_array(&msg_file[..], MSG_FILE.as_bytes()) == false {
            error("Invalid protocol");
        }

        let filesize = receive_u64(&mut stream);
        let filename = receive_string(&mut stream);

        let file_res = std::fs::File::create(work_dir.join(&filename));
        match file_res {
            Err(_) => {
                println!("Couldn't create file {}. Skip file transmition.", filename);
                send_data(&mut stream, MSG_DISAGREE.as_bytes());
                continue;
            },
            _ => {}
        }
        let mut file = file_res.unwrap();

        send_data(&mut stream, MSG_AGREE.as_bytes());

        print!("Receive {}...", filename); let _ = std::io::stdout().flush();

        //get file data
        let mut i = 0u64;
        while i < (filesize-1) {
            if (filesize - i) >= buf.len() as u64 {
                receive_data(&mut stream, &mut buf);
                if let Err(_) = file.write(&buf) {
                    error("Couldn't write to file");
                }
                i += buf.len() as u64;
            }
            else {
                receive_data(&mut stream, &mut buf[..(filesize - i) as usize]);
                if let Err(_) = file.write(&buf[..(filesize - i) as usize]) {
                    error("Couldn't write to file");
                }
                i = filesize-1;
            }
        }

        println!("done."); let _ = std::io::stdout().flush();
    }
}
<commit_msg>replaced unnecessary unsafe vector allocations<commit_after>extern crate time;

use std::io::{Read, Write, Seek};
use std::process::exit;

const HELP_TEXT: &'static str = "netcp (send,receive) ipaddress[:port] filename";
const CALL_SIGN: &'static str = "netcp v0.1";
const MSG_AGREE: &'static str = "AGREE   ";
const MSG_DISAGREE: &'static str = "DISAGREE";
const MSG_FILE: &'static str = "FILE";
const MSG_END: &'static str = "END ";
const TIMEOUT: i64 = 800;

fn main() {
    //get commandline arguments
    let args: Vec<String> = std::env::args().collect();

    //if there is no argument (first arg is always application name), error
    if args.len() == 1 {
        error("No arguments");
    }

    //execute option
    match args[1].as_ref() {
        "help" => {
            println!("{}", HELP_TEXT);
        }

        "send" => {
            //sending needs min. 4 args (appl. name, "send", address, filenames...)
            if args.len() < 4 {
                error("Too few arguments");
            }

            send(&args[2], &args[3..]);
        }

        "receive" => {
            //receiving needs exactly 3 args (appl. name, "receive", address)
            if args.len() != 3 {
                error("Too few or many arguments");
            }

            receive(&args[2]);
        }

        //Unknown parameter
        _ => {
            error("Unknown parameter");
        }
    }
}

fn error(msg: &str) {
    println!("Error: {}.", msg);
    std::process::exit(1);
}

fn receive_data(stream: &mut std::net::TcpStream, buf: &mut [u8]) {
    let mut begin = time::get_time();
    let mut received_bytes: usize = 0;

    while received_bytes < buf.len() && (begin - time::get_time()).num_milliseconds() < TIMEOUT {
        let res = stream.read(&mut buf[received_bytes..]);

        if res.is_err() {
            error("Connection lost");
        }

        let bytes = res.unwrap();
        received_bytes += bytes;

        if bytes != 0 {
            begin = time::get_time();
        }
    }

    if received_bytes < buf.len() {
        error("Connection lost (timeout)");
    }
}

fn send_data(stream: &mut std::net::TcpStream, data: &[u8]) {
    let mut begin = time::get_time();
    let mut sended_bytes: usize = 0;

    while sended_bytes < data.len() && (begin - time::get_time()).num_milliseconds() < TIMEOUT {
        let res = stream.write(&data[sended_bytes..]);

        if res.is_err() {
            error("Connection lost");
        }

        let bytes = res.unwrap();
        sended_bytes += bytes;

        if bytes != 0 {
            begin = time::get_time();
        }
    }

    if sended_bytes < data.len() {
        error("Connection lost (timeout)");
    }
}

fn send_u64(stream: &mut std::net::TcpStream, num: u64) {
    let data = num.to_le();
    let buf: &[u8] = unsafe { std::mem::transmute::<&u64, &[u8; 8]>(&data) };
    send_data(stream, &buf);
}

fn receive_u64(stream: &mut std::net::TcpStream) -> u64 {
    let mut num = 0u64;
    receive_data(stream, unsafe { std::mem::transmute::<&mut u64, &mut [u8; 8]>(&mut num) });
    return u64::from_le(num);
}

fn send_string(stream: &mut std::net::TcpStream, string: &str) {
    let size = string.len() as u64;
    send_u64(stream, size);
    send_data(stream, string.as_bytes());
}

fn receive_string(stream: &mut std::net::TcpStream) -> String {
    let size = receive_u64(stream);
    let mut vec = vec![0u8; size as usize];
    receive_data(stream, &mut vec[..] );
    let string_res = String::from_utf8(vec);
    match string_res {
        Err(_) => { error("Couldn't convert bytes to string"); }
        _ => {}
    }
    return string_res.unwrap();
}

fn check_agreement(stream: &mut std::net::TcpStream) -> bool {
    let mut vec = vec![0u8; MSG_AGREE.len()];
    receive_data(stream, &mut vec[..]);

    if compare_byte_array(&vec[..], MSG_AGREE.as_bytes()) {
        return true;
    }
    else if compare_byte_array(&vec[..], MSG_DISAGREE.as_bytes()) {
        return false;
    }
    else {
        error("Invalid protocol");
    }
    return false;
}

fn compare_byte_array(a: &[u8], b: &[u8]) -> bool {
    if a.len() != b.len() {
        return false;
    }

    for i in 0..a.len() {
        if a[i] != b[i] {
            return false;
        }
    }

    return true;
}

fn get_filesize(file: &mut std::fs::File) -> u64 {
    let current = file.seek(std::io::SeekFrom::Current(0)).ok().expect("Error: file seeking failed.");
    let size = file.seek(std::io::SeekFrom::End(0)).ok().expect("Error: file seeking failed.");
    file.seek(std::io::SeekFrom::Start(current)).ok().expect("Error: file seeking failed.");
    return size;
}

fn send(address: &String, file_names: &[String]) {
    //1. check file accessibilities
    //2. check address
    //3. connect with client
    //4. send files sequential

    let mut buf = [0u8; 512];

    //1. check file existences
    //get working directory to find correct files
    let work_dir_res = std::env::current_dir();
    match work_dir_res {
        Err(_) => { error("Couldn't find working directory"); },
        _ => {}
    }
    let work_dir = work_dir_res.unwrap();

    //join working directory and file_names to get absolute file addresses in a vector
    let mut file_addrs: Vec<std::path::PathBuf> = Vec::with_capacity(file_names.len());
    for file_name in file_names {
        file_addrs.push(work_dir.join(file_name));
    }

    //open and close files to check accessibility
    for file_addr in &file_addrs {
        if let Err(_) = std::fs::File::open(&file_addr) {
            println!("Error: File doesn't exist or is not accessible ({}).", file_addr.display());
            exit(1);
        }
    }

    //2. check address and bind listener
    let listener_res = std::net::TcpListener::bind(&address[..]);
    match listener_res {
        Err(ref e) => { println!("Error: {}.", e); exit(1); },
        _ => {}
    }
    let listener = listener_res.unwrap();

    //3. connect with client
    print!("Waiting for client..."); let _ = std::io::stdout().flush();
    let stream_res = listener.accept();
    match stream_res {
        Err(ref e) => { println!("Error: {}.", e); exit(1); },
        _ => {}
    }
    let (mut stream, client_addr) = stream_res.unwrap();

    //3. check if client send correct CALL_SIGN
    receive_data(&mut stream, &mut buf[..CALL_SIGN.len()]);

    if compare_byte_array(CALL_SIGN.as_bytes(), &buf[..CALL_SIGN.len()]) == false {
        error("Invalid protocol");
    }

    send_data(&mut stream, MSG_AGREE.as_bytes());

    println!("connected with {}.", client_addr); let _ = std::io::stdout().flush();

    //send files
    for file_addr in &file_addrs {

        let file_res = std::fs::File::open(&file_addr);
        match file_res {
            Err(_) => { error("Couldn't open file") },
            _ => {}
        }
        let mut file = file_res.unwrap();
        let filesize = get_filesize(&mut file);

        send_data(&mut stream, MSG_FILE.as_bytes()); //send file is ready to send
        send_u64(&mut stream, filesize); //send file size as u64

        //send filename
        let filename_os_opt = file_addr.file_name();
        if filename_os_opt.is_none() {
            error("Couldn't convert filename to utf8");
        }
        let filename_opt = filename_os_opt.unwrap().to_str();
        if filename_opt.is_none() {
            error("Couldn't convert filename to utf8");
        }
        send_string(&mut stream, &filename_opt.unwrap());

        print!("Send {}...", &filename_opt.unwrap()); let _ = std::io::stdout().flush();

        //if client send MSG_DISAGREE, continue else send file
        if check_agreement(&mut stream) == false {
            println!("cancelled by client.");
            continue;
        }

        //send file
        let mut i = 0u64;
        while i < (filesize-1) {
            if (filesize - i) >= buf.len() as u64 {
                if let Err(_) = file.read(&mut buf) {
                    error("Couldn't read from file");
                }
                send_data(&mut stream, &buf);
                i += buf.len() as u64;
            }
            else {
                if let Err(_) = file.read(&mut buf[..(filesize - i) as usize]) {
                    error("Couldn't read from file");
                }
                send_data(&mut stream, &buf[..(filesize - i) as usize]);
                i = filesize-1;
            }
        }

        println!("done."); let _ = std::io::stdout().flush();
    }

    //send end
    send_data(&mut stream, MSG_END.as_bytes());
}

fn receive(address: &String) {
    let mut buf = [0u8; 512];

    let work_dir_res = std::env::current_dir();
    match work_dir_res {
        Err(_) => { error("Couldn't find working directory"); },
        _ => {}
    }
    let work_dir = work_dir_res.unwrap();

    let stream_res = std::net::TcpStream::connect(&address[..]);
    match stream_res {
        Err(ref e) => { println!("Error: {}.", e); exit(1); },
        _ => {}
    }
    let mut stream = stream_res.unwrap();

    send_data(&mut stream, CALL_SIGN.as_bytes());

    if check_agreement(&mut stream) == false {
        error("No server found");
    }

    let mut msg_file = vec![0u8; MSG_FILE.len()];
    loop {
        receive_data(&mut stream, &mut msg_file[..]);

        if compare_byte_array(&msg_file[..], MSG_END.as_bytes()) {
            break;
        }
        else if compare_byte_array(&msg_file[..], MSG_FILE.as_bytes()) == false {
            error("Invalid protocol");
        }

        let filesize = receive_u64(&mut stream);
        let filename = receive_string(&mut stream);

        let file_res = std::fs::File::create(work_dir.join(&filename));
        match file_res {
            Err(_) => {
                println!("Couldn't create file {}. Skip file transmition.", filename);
                send_data(&mut stream, MSG_DISAGREE.as_bytes());
                continue;
            },
            _ => {}
        }
        let mut file = file_res.unwrap();

        send_data(&mut stream, MSG_AGREE.as_bytes());

        print!("Receive {}...", filename); let _ = std::io::stdout().flush();

        //get file data
        let mut i = 0u64;
        while i < (filesize-1) {
            if (filesize - i) >= buf.len() as u64 {
                receive_data(&mut stream, &mut buf);
                if let Err(_) = file.write(&buf) {
                    error("Couldn't write to file");
                }
                i += buf.len() as u64;
            }
            else {
                receive_data(&mut stream, &mut buf[..(filesize - i) as usize]);
                if let Err(_) = file.write(&buf[..(filesize - i) as usize]) {
                    error("Couldn't write to file");
                }
                i = filesize-1;
            }
        }

        println!("done."); let _ = std::io::stdout().flush();
    }
}
<|endoftext|>


<commit_before>#![feature(plugin)]
#![plugin(regex_macros)]
#![feature(collections)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate rand;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
//use collections::borrow::Borrow;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(write_to_temp_file(new2));

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        return attributes.into_iter().any(
            |attribute| attribute.name.local.as_slice() == "id" &&
                format!("{}", attribute.value) == id);
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => (&node.children).into_iter()
            .map(|child| find_node_by_id(child, id))
            .filter(|result| result.is_ok())
            .map(|result| result.unwrap())
            .next().ok_or(format!("No node with ID {} found", id)),
    }
}

struct WikipediaMinusWikipediansHandler {
    wiki: Wiki,
    client: Client
}

impl WikipediaMinusWikipediansHandler {
    fn new(wiki: Wiki, client: Client) -> WikipediaMinusWikipediansHandler {
        WikipediaMinusWikipediansHandler { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut revisions = vec![];
        for revision in try!(self.get_vandalism_reversions(&canonical_title)) {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    info!(concat!("For page \"{}\", restored vandalism ",
                                  "https://{}/w/index.php?title={}&diff=prev&oldid={}"),
                          &title, self.wiki.hostname, &canonical_title, revision.revid);
                    accumulated_contents = merged_contents;
                    revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        let placeholder = format!("WMW_PLACEHOLDER_TEXT_{}", rand::random::<u64>());
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", &placeholder));
        Ok(page_contents_with_placeholder.replace(&placeholder, &body))
    }
}

impl Handler for WikipediaMinusWikipediansHandler {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => Response::with(
                        (iron::status::InternalServerError, "ERROR")),
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: Maybe should be moved to wiki module.
            let mut url = request.url.clone();
            url.host = url::Host::Domain(self.wiki.hostname.clone());
            match self.client.get(&url.into_generic_url().serialize())
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        wikipedia_response.read_to_end(&mut wikipedia_body);

                        let mut response = Response::with(wikipedia_body);
                        response.status = Some(wikipedia_response.status);
                        response.headers = wikipedia_response.headers.clone();
                        info!("Forwarded request for {} to {}", request.url.path.join("/"),
                              self.wiki.hostname);
                        Ok(response)
                    },
                    Err(error) => {
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             format!("ERROR: {}", error)));
                        response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let handler =
        WikipediaMinusWikipediansHandler::new(Wiki::new(wiki.to_string(), Client::new()),
                                              Client::new());
    Iron::new(handler).http(("localhost", port)).unwrap();
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<commit_msg>Log only one line per request with the merged vandalisms.<commit_after>#![feature(plugin)]
#![plugin(regex_macros)]
#![feature(collections)]

extern crate argparse;
extern crate html5ever;
extern crate html5ever_dom_sink;
extern crate hyper;
extern crate iron;
#[macro_use]
extern crate log;
extern crate log4rs;
extern crate rand;
extern crate regex;
extern crate rustc_serialize;
extern crate tempfile;
extern crate tendril;
extern crate url;

/// Helper macro for unwrapping Result values whose E types implement std::fmt::Display. For Ok(),
/// evaluates to the contained value. For Err(), returns early with an Err containing the formatted
/// error.
macro_rules! try_display {
    ($expr:expr, $($format_arg:expr),* ) => (match $expr {
        Ok(val) => val,
        Err(err) => return Err(format!("{}: {}", format!($($format_arg),*), err)),
    })
}

mod json;
mod wiki;

use argparse::ArgumentParser;
use argparse::Store;
//use collections::borrow::Borrow;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;
use std::str::FromStr;

use html5ever::Attribute;
use html5ever::tree_builder::interface::TreeSink;
use html5ever_dom_sink::common::NodeEnum;
use html5ever_dom_sink::rcdom::Handle;
use html5ever_dom_sink::rcdom::RcDom;
use hyper::Client;
use hyper::header::Connection;
use iron::Iron;
use iron::IronResult;
use iron::Request;
use iron::Response;
use iron::headers::ContentType;
use iron::middleware::Handler;
use iron::mime::Mime;
use iron::mime::SubLevel;
use iron::mime::TopLevel;
use tempfile::NamedTempFile;

use wiki::Revision;
use wiki::Wiki;

// TODO: consider doing s/en.wikipedia.org/this app's url/ on the HTML before serving it. This
// currently works fine, but might not over HTTPS.

/// Does a 3-way merge, merging `new1` and `new2` under the assumption that both diverged from
/// `old`. Returns None if the strings do not merge together cleanly.
fn merge(old: &str, new1: &str, new2: &str) -> Result<Option<String>, String> {
    fn write_to_temp_file(contents: &str) -> Result<NamedTempFile, String> {
        let tempfile = try_display!(NamedTempFile::new(), "Error creating temp file");
        let mut file = try_display!(OpenOptions::new().write(true).open(tempfile.path()),
                                    "Error opening temp file for writing");
        try_display!(file.write_all(contents.as_bytes()), "Error writing to temp file");
        try_display!(file.flush(), "Error flushing temp file");
        Ok(tempfile)
    }

    let old_tempfile = try!(write_to_temp_file(old));
    let new1_tempfile = try!(write_to_temp_file(new1));
    let new2_tempfile = try!(write_to_temp_file(new2));

    let mut process = Command::new("diff3");
    process.arg("-m").args(&[new1_tempfile.path(), old_tempfile.path(), new2_tempfile.path()])
        .stdout(Stdio::piped()).stderr(Stdio::null());
    let output = try_display!(process.output(), "Error getting output from diff3 subprocess");
    if output.status.success() {
        Ok(Some(try_display!(String::from_utf8(output.stdout),
                             "Error converting diff3 output to UTF-8")))
    } else {
        Ok(None)
    }
}

fn replace_node_with_placeholder(original_html: &str, div_id: &str, placeholder: &str)
    -> Result<String, String> {
    let html = tendril::StrTendril::from_str(original_html).unwrap();
    let mut dom: RcDom = html5ever::parse(html5ever::one_input(html), Default::default());

    let handle = try!(find_node_by_id(&dom.get_document(), div_id));
    let child_handles =
        (&handle.borrow().children).into_iter().map(|child| child.clone()).collect::<Vec<_>>();
    for child_handle in child_handles {
        dom.remove_from_parent(child_handle);
    }
    dom.append(handle,
               html5ever::tree_builder::interface::NodeOrText::AppendText(
                   tendril::StrTendril::from_str(placeholder).unwrap()));
    let mut serialized: Vec<u8> = vec![];
    html5ever::serialize::serialize(&mut serialized, &dom.document, Default::default());
    Ok(try_display!(String::from_utf8(serialized),
                    "Error converting serialized HTML to UTF-8 string"))
}

fn find_node_by_id(handle: &Handle, id: &str) -> Result<Handle, String> {
    fn has_matching_id(attributes: &Vec<Attribute>, id: &str) -> bool {
        return attributes.into_iter().any(
            |attribute| attribute.name.local.as_slice() == "id" &&
                format!("{}", attribute.value) == id);
    }

    let node = handle.borrow();
    match node.node {
        NodeEnum::Element(_, ref attributes) if has_matching_id(attributes, id) => Ok(handle.clone()),
        _ => (&node.children).into_iter()
            .map(|child| find_node_by_id(child, id))
            .filter(|result| result.is_ok())
            .map(|result| result.unwrap())
            .next().ok_or(format!("No node with ID {} found", id)),
    }
}

struct WikipediaMinusWikipediansHandler {
    wiki: Wiki,
    client: Client
}

impl WikipediaMinusWikipediansHandler {
    fn new(wiki: Wiki, client: Client) -> WikipediaMinusWikipediansHandler {
        WikipediaMinusWikipediansHandler { wiki: wiki, client: client }
    }

    /// Returns a vector of Revisions representing all reversions of vandalism for the page `title`.
    fn get_vandalism_reversions(&self, title: &str) -> Result<Vec<Revision>, String> {
        // TODO: Add a flag to control number of revisions, and experiment with it.
        let revisions = try!(self.wiki.get_revisions(title, 60));
        Ok(revisions.into_iter().filter(|revision| revision.comment.contains("vandal")).collect())
    }

    // TODO: this name is terrible.
    fn get_page_with_vandalism_restored(&self, title: &str) -> Result<String, String> {
        let canonical_title = try!(self.wiki.get_canonical_title(title));
        info!("Canonical page title for \"{}\" is \"{}\"", title, canonical_title);

        let latest_revid = try!(self.wiki.get_latest_revision(&canonical_title)).revid;
        let mut accumulated_contents =
            try!(self.wiki.get_revision_content(&canonical_title, latest_revid));
        let mut merged_revisions = vec![];
        let vandalism_reversions = try!(self.get_vandalism_reversions(&canonical_title));
        for revision in &vandalism_reversions {
            let reverted_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.revid));
            let vandalized_contents =
                try!(self.wiki.get_revision_content(&canonical_title, revision.parentid));
            match merge(&reverted_contents, &vandalized_contents, &accumulated_contents) {
                Ok(Some(merged_contents)) => {
                    accumulated_contents = merged_contents;
                    merged_revisions.push(revision.revid);
                }
                Ok(None) => (),
                Err(msg) => return Err(format!("Error merging revision {} of \"{}\": {}",
                                               revision.revid, title, msg))
            }
        }

        info!("For \"{}\", successfully merged {} of {} possible vandalisms: {:?}", title,
              merged_revisions.len(), vandalism_reversions.len(), merged_revisions);

        let body = try!(self.wiki.parse_wikitext(&canonical_title, &accumulated_contents));
        // Note: "title" rather than "canonical_title", so that redirects look right.
        let current_page_contents = try!(self.wiki.get_current_page_content(&title));
        let placeholder = format!("WMW_PLACEHOLDER_TEXT_{}", rand::random::<u64>());
        let page_contents_with_placeholder =
            try!(replace_node_with_placeholder(
                &current_page_contents, "mw-content-text", &placeholder));
        Ok(page_contents_with_placeholder.replace(&placeholder, &body))
    }
}

impl Handler for WikipediaMinusWikipediansHandler {
    fn handle(&self, request: &mut Request) -> IronResult<Response> {
        if request.url.path.len() == 2 && request.url.path[0] == "wiki" {
            let mut response =
                match self.get_page_with_vandalism_restored(&request.url.path[1]) {
                    Ok(page_contents) => Response::with((iron::status::Ok, page_contents)),
                    // TODO: create an Error type to pass around, so this can distinguish different
                    // types of error (if that would be helpful).
                    // TODO: create a better error page
                    Err(msg) => Response::with(
                        (iron::status::InternalServerError, "ERROR")),
                };
            response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
            Ok(response)
        } else {
            // TODO: should I use an HTTP redirect here instead? Would that work? Would it be desirable?
            // TODO: Maybe should be moved to wiki module.
            let mut url = request.url.clone();
            url.host = url::Host::Domain(self.wiki.hostname.clone());
            match self.client.get(&url.into_generic_url().serialize())
                .header(Connection::close()).send() {
                    Ok(mut wikipedia_response) => {
                        let mut wikipedia_body: Vec<u8> = Vec::new();
                        wikipedia_response.read_to_end(&mut wikipedia_body);

                        let mut response = Response::with(wikipedia_body);
                        response.status = Some(wikipedia_response.status);
                        response.headers = wikipedia_response.headers.clone();
                        info!("Forwarded request for {} to {}", request.url.path.join("/"),
                              self.wiki.hostname);
                        Ok(response)
                    },
                    Err(error) => {
                        let mut response = Response::with(
                            (iron::status::InternalServerError,
                             format!("ERROR: {}", error)));
                        response.headers.set(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])));
                        Ok(response)
                    }
                }
        }
    }
}

fn main() {
    log4rs::init_file("log.toml", Default::default()).unwrap();

    let mut port = 3000;
    let mut wiki = "en.wikipedia.org".to_string();
    {
        let mut parser = ArgumentParser::new();
        parser.set_description("TODO: Usage description");
        parser.refer(&mut port).add_option(&["-p", "--port"], Store, "The port to serve HTTP on.");
        parser.refer(&mut wiki).add_option(&["--wiki"], Store, "The wiki to mirror.");
        parser.parse_args_or_exit();
    }
    let handler =
        WikipediaMinusWikipediansHandler::new(Wiki::new(wiki.to_string(), Client::new()),
                                              Client::new());
    Iron::new(handler).http(("localhost", port)).unwrap();
}

#[cfg(test)]
mod tests {
    use super::{merge, replace_node_with_placeholder};

    #[test]
    fn test_merge_clean() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed.\n";
        let new2 = "First line changed.\n\nSecond line.\n";
        assert_eq!(Some("First line changed.\n\nSecond line changed.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_conflicting() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line changed one way.\n";
        let new2 = "First line changed.\n\nSecond line changed a different way.\n";
        assert_eq!(None, merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_merge_special_characters() {
        let old = "First line.\n\nSecond line.\n";
        let new1 = "First line.\n\nSecond line 𐅃.\n";
        let new2 = "First line さようなら.\n\nSecond line.\n";
        assert_eq!(Some("First line さようなら.\n\nSecond line 𐅃.\n".to_string()),
                   merge(old, new1, new2).unwrap());
    }

    #[test]
    fn test_replace_html_content() {
        let original_html = "

original text

Other text

";
        let expected_html = "

replaced text

Other text

";
        let processed_html = replace_node_with_placeholder(original_html, "mw-content-text", "replaced text").unwrap();
        assert_eq!(expected_html, processed_html);
    }
}
<|endoftext|>