<commit_before>use std::collections::BTreeMap;
use std::process::{Command, Stdio};
use std::sync::mpsc::channel;
use std::sync::{Mutex, Arc};
use std::time::Duration;
use std::thread;
use std::time::Instant;
use std::io::{BufRead, BufReader};
use std::ffi::OsStr;

use rouille;
use tempdir::TempDir;
use handlebars::Handlebars;
use serde_json::{self, Value};
use regex::Regex;

use cargo_shim::CargoResult;

use build::BuildArgsMatcher;
use error::Error;
use utils::{
    read,
    read_bytes,
    check_if_command_exists
};
use chrome_devtools::{Connection, Reply, ReplyError, ConsoleApiCalledBody, ExceptionThrownBody};

const DEFAULT_TEST_INDEX_HTML: &'static str = r#"
<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" name="viewport" />
    <script>
        var __cargo_web = {};
        var Module = {};
        __cargo_web.status = new Promise( function( resolve ) { Module['onExit'] = resolve; } );
        Module['arguments'] = [{{#each arguments}} "{{{ this }}}", {{/each}}];
    </script>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>
"#;

pub fn test_in_chromium(
    build_matcher: &BuildArgsMatcher,
    build: CargoResult,
    arg_passthrough: &Vec< &OsStr >,
    any_failure: &mut bool
) -> Result< (), Error > {
    let chromium_executable = if cfg!( windows ) && check_if_command_exists( "chrome.exe", None ) {
        "chrome.exe"
    } else if check_if_command_exists( "chromium", None ) {
        "chromium"
    } else if check_if_command_exists( "google-chrome", None ) {
        "google-chrome"
    } else if check_if_command_exists( "google-chrome-stable", None ) {
        "google-chrome-stable"
    } else {
        return Err( Error::EnvironmentError( "you need to have either Chromium or Chrome installed and in your PATH to run the tests!".into() ) );
    };

    let app_js = Arc::new( Mutex::new( String::new() ) );
    let server_app_js = app_js.clone();
    let handlebars = Handlebars::new();
    let mut template_data = BTreeMap::new();
    let arg_passthrough: Vec<_> = arg_passthrough.iter().map( |arg| arg.to_str().unwrap() ).collect();
    template_data.insert( "arguments", arg_passthrough );
    let test_index = handlebars.template_render( DEFAULT_TEST_INDEX_HTML, &template_data ).unwrap();
    let app_wasm: Arc< Mutex< Option< Vec< u8 > > > > = Arc::new( Mutex::new( None ) );
    let wasm_url = Arc::new( Mutex::new( None ) );

    let server_app_wasm = app_wasm.clone();
    let server_wasm_url = wasm_url.clone();

    let server = rouille::Server::new( "localhost:0", move |request| {
        let url = request.url();
        let response = if url == "/" || url == "index.html" {
            rouille::Response::html( test_index.clone() )
        } else if url == "/js/app.js" {
            let data = server_app_js.lock().unwrap().clone();
            rouille::Response::from_data( "application/javascript", data )
        } else {
            match *server_wasm_url.lock().unwrap() {
                Some( ref wasm_url ) if url == *wasm_url => {
                    let data = server_app_wasm.lock().unwrap().as_ref().unwrap().clone();
                    rouille::Response::from_data( "application/wasm", data )
                },
                _ => rouille::Response::empty_404()
            }
        };

        response.with_no_cache()
    }).unwrap();

    let server_address = server.server_addr();
    thread::spawn( move || {
        server.run();
    });

    let artifact = build.artifacts().iter()
        .find( |artifact| artifact.extension().map( |ext| ext == "js" ).unwrap_or( false ) )
        .expect( "internal error: no .js file found" );

    if build_matcher.targeting_wasm() {
        let wasm_artifact = build.artifacts().iter()
            .find( |artifact| artifact.extension().map( |ext| ext == "wasm" ).unwrap_or( false ) )
            .expect( "internal error: no .wasm file found" );

        *wasm_url.lock().unwrap() = Some( format!( "/{}", wasm_artifact.file_name().unwrap().to_str().unwrap() ) );
        *app_wasm.lock().unwrap() = Some( read_bytes( wasm_artifact ).unwrap() );
    }

    *app_js.lock().unwrap() = read( artifact ).unwrap();

    let tmpdir = TempDir::new( "cargo-web-chromium-profile" ).unwrap();
    let tmpdir = tmpdir.path().to_string_lossy();
    let mut command = Command::new( chromium_executable );
    command
        .arg( "--disable-gpu" )
        .arg( "--no-first-run" )
        .arg( "--disable-restore-session-state" )
        .arg( "--no-default-browser-check" )
        .arg( "--disable-java" )
        .arg( "--disable-client-side-phishing-detection" )
        .arg( "--headless" )
        .arg( "--remote-debugging-port=0" )
        .arg( format!( "--user-data-dir={}", tmpdir ) )
        .arg( "about:blank" );

    command
        .stdout( Stdio::null() )
        .stderr( Stdio::piped() )
        .stdin( Stdio::null() );

    debug!( "Launching chromium..." );
    let mut child = command.spawn()
        .map_err( |err| Error::RuntimeError( "cannot launch chromium".into(), err.into() ) )?;

    let stderr = BufReader::new( child.stderr.take().unwrap() );
    let devtools_regex = Regex::new( r"DevTools listening on (ws://[^:]+:\d+)" ).unwrap();
    let (url_tx, url_rx) = channel();
    thread::spawn( move || {
        for line in stderr.lines() {
            let line = match line {
                Ok( line ) => line,
                Err( _ ) => break
            };

            debug!( "Chromium stderr: {:?}", line );
            if let Some( captures ) = devtools_regex.captures( &line ) {
                let url = captures.get( 1 ).unwrap().as_str().to_owned();
                let _ = url_tx.send( url );
                break;
            }
        }
    });

    let url = url_rx.recv_timeout( Duration::from_secs( 10 ) )
        .map_err( |err| Error::RuntimeError( "timeout while waiting for chromium to start".into(), err.into() ) )?;

    debug!( "Chromium in listening on: {}", url );
    let mut connection = Connection::connect( &format!( "{}/json", url ) )
        .map_err( |err| Error::RuntimeError( "devtools connection to chromium failed".into(), err.into() ) )?;

    connection.send_cmd( "Page.enable", Value::Null );
    connection.send_cmd( "Runtime.enable", Value::Null );
    connection.send_cmd(
        "Page.navigate",
        json!({
            "url": format!( "http://localhost:{}", server_address.port() )
        })
    );

    let mut print_counter = 0;
    let mut finished = false;
    let start = Instant::now();
    let time_limit = Duration::from_secs( 60 );
    let mut get_status_req = None;
    loop {
        let elapsed = start.elapsed();
        if elapsed >= time_limit {
            break;
        }
        let remaining = time_limit - elapsed;

        let reply = connection.try_recv( Some( remaining ) );
        let reply = match reply {
            Ok( reply ) => reply,
            Err( ReplyError::Timeout ) => {
                if finished {
                    break;
                } else {
                    continue;
                }
            },
            Err( err ) => {
                return Err( Error::RuntimeError( "error while communicating with chromium".into(), err.into() ) );
            }
        };

        match reply {
            Reply::Result { ref id, ref body } if Some( *id ) == get_status_req => {
                finished = true;
                let status = body.get( "result" ).unwrap().get( "value" ).unwrap().as_u64().unwrap();
                if status != 0 {
                    println_err!( "error: process exited with a status of {}", status );
                    *any_failure = true;
                }
                break;
            },
            Reply::Event { ref method, .. } if method == "Page.frameStoppedLoading" => {
                let id = connection.send_cmd(
                    "Runtime.evaluate",
                    json!({
                        "expression": "__cargo_web.status",
                        "awaitPromise": true
                    })
                );

                get_status_req = Some( id );
            },
            Reply::Event { ref method, ref body } if method == "Runtime.exceptionThrown" => {
                let body: ExceptionThrownBody = serde_json::from_value( body.clone() ).expect( "Failed to parse `Runtime.exceptionThrown` event" );
                println_err!( "error: unhandled exception thrown" );
                if let Some( exception ) = body.exception_details.exception {
                    if let Some( description ) = exception.description {
                        println_err!( "error:     {}", description );
                    }
                }
                if let Some( url ) = body.exception_details.url {
                    println_err!( "error: source: {}:{}:{}", url, body.exception_details.line_number, body.exception_details.column_number );
                }
                // TODO: Better error message.
                *any_failure = true;
                finished = true;
                break;
            },
            Reply::Event { ref method, ref body } if method == "Runtime.consoleAPICalled" => {
                let body: ConsoleApiCalledBody = serde_json::from_value( body.clone() ).unwrap();
                match body.kind.as_str() {
                    "log" | "debug" | "info" | "error" | "warning" => {
                        let mut output = String::new();
                        for arg in body.args {
                            if !output.is_empty() {
                                output.push_str( " " );
                            }

                            if arg.kind == "string" {
                                output.push_str( arg.value.unwrap().as_str().unwrap() );
                            } else {
                                output.push_str( "<" );
                                if let Some( class_name ) = arg.class_name {
                                    output.push_str( &class_name );
                                } else {
                                    output.push_str( &arg.kind );
                                }
                                output.push_str( ">" );
                            }
                        }

                        if build_matcher.targeting_emscripten() {
                            if print_counter == 0 && output.starts_with( "pre-main" ) {
                                continue;
                            } else if print_counter == 1 && output.trim().is_empty() {
                                continue;
                            }
                        }

                        println!( "{}", output );
                        print_counter += 1;
                    },
                    _ => {}
                }
            },
            Reply::Error { ref message, .. } => {
                return Err( Error::RuntimeError( "chromium returned an error".into(), message.clone().into() ) );
            },
            _ => {}
        }
    }

    if !finished {
        println_err!( "error: tests timed out!" );
        *any_failure = true;
    }

    debug!( "Testing finished; waiting for chromium to die..." );
    child.kill().unwrap();
    child.wait().unwrap();

    Ok(())
}
<commit_msg>Disable sandboxing in headless Chromium<commit_after>use std::collections::BTreeMap;
use std::process::{Command, Stdio};
use std::sync::mpsc::channel;
use std::sync::{Mutex, Arc};
use std::time::Duration;
use std::thread;
use std::time::Instant;
use std::io::{BufRead, BufReader};
use std::ffi::OsStr;

use rouille;
use tempdir::TempDir;
use handlebars::Handlebars;
use serde_json::{self, Value};
use regex::Regex;

use cargo_shim::CargoResult;

use build::BuildArgsMatcher;
use error::Error;
use utils::{
    read,
    read_bytes,
    check_if_command_exists
};
use chrome_devtools::{Connection, Reply, ReplyError, ConsoleApiCalledBody, ExceptionThrownBody};

const DEFAULT_TEST_INDEX_HTML: &'static str = r#"
<!DOCTYPE html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=1" name="viewport" />
    <script>
        var __cargo_web = {};
        var Module = {};
        __cargo_web.status = new Promise( function( resolve ) { Module['onExit'] = resolve; } );
        Module['arguments'] = [{{#each arguments}} "{{{ this }}}", {{/each}}];
    </script>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>
"#;

pub fn test_in_chromium(
    build_matcher: &BuildArgsMatcher,
    build: CargoResult,
    arg_passthrough: &Vec< &OsStr >,
    any_failure: &mut bool
) -> Result< (), Error > {
    let chromium_executable = if cfg!( windows ) && check_if_command_exists( "chrome.exe", None ) {
        "chrome.exe"
    } else if check_if_command_exists( "chromium", None ) {
        "chromium"
    } else if check_if_command_exists( "google-chrome", None ) {
        "google-chrome"
    } else if check_if_command_exists( "google-chrome-stable", None ) {
        "google-chrome-stable"
    } else {
        return Err( Error::EnvironmentError( "you need to have either Chromium or Chrome installed and in your PATH to run the tests!".into() ) );
    };

    let app_js = Arc::new( Mutex::new( String::new() ) );
    let server_app_js = app_js.clone();
    let handlebars = Handlebars::new();
    let mut template_data = BTreeMap::new();
    let arg_passthrough: Vec<_> = arg_passthrough.iter().map( |arg| arg.to_str().unwrap() ).collect();
    template_data.insert( "arguments", arg_passthrough );
    let test_index = handlebars.template_render( DEFAULT_TEST_INDEX_HTML, &template_data ).unwrap();
    let app_wasm: Arc< Mutex< Option< Vec< u8 > > > > = Arc::new( Mutex::new( None ) );
    let wasm_url = Arc::new( Mutex::new( None ) );

    let server_app_wasm = app_wasm.clone();
    let server_wasm_url = wasm_url.clone();

    let server = rouille::Server::new( "localhost:0", move |request| {
        let url = request.url();
        let response = if url == "/" || url == "index.html" {
            rouille::Response::html( test_index.clone() )
        } else if url == "/js/app.js" {
            let data = server_app_js.lock().unwrap().clone();
            rouille::Response::from_data( "application/javascript", data )
        } else {
            match *server_wasm_url.lock().unwrap() {
                Some( ref wasm_url ) if url == *wasm_url => {
                    let data = server_app_wasm.lock().unwrap().as_ref().unwrap().clone();
                    rouille::Response::from_data( "application/wasm", data )
                },
                _ => rouille::Response::empty_404()
            }
        };

        response.with_no_cache()
    }).unwrap();

    let server_address = server.server_addr();
    thread::spawn( move || {
        server.run();
    });

    let artifact = build.artifacts().iter()
        .find( |artifact| artifact.extension().map( |ext| ext == "js" ).unwrap_or( false ) )
        .expect( "internal error: no .js file found" );

    if build_matcher.targeting_wasm() {
        let wasm_artifact = build.artifacts().iter()
            .find( |artifact| artifact.extension().map( |ext| ext == "wasm" ).unwrap_or( false ) )
            .expect( "internal error: no .wasm file found" );

        *wasm_url.lock().unwrap() = Some( format!( "/{}", wasm_artifact.file_name().unwrap().to_str().unwrap() ) );
        *app_wasm.lock().unwrap() = Some( read_bytes( wasm_artifact ).unwrap() );
    }

    *app_js.lock().unwrap() = read( artifact ).unwrap();

    let tmpdir = TempDir::new( "cargo-web-chromium-profile" ).unwrap();
    let tmpdir = tmpdir.path().to_string_lossy();
    let mut command = Command::new( chromium_executable );
    command
        .arg( "--disable-gpu" )
        .arg( "--no-first-run" )
        .arg( "--no-sandbox" )
        .arg( "--disable-restore-session-state" )
        .arg( "--no-default-browser-check" )
        .arg( "--disable-java" )
        .arg( "--disable-client-side-phishing-detection" )
        .arg( "--headless" )
        .arg( "--remote-debugging-port=0" )
        .arg( format!( "--user-data-dir={}", tmpdir ) )
        .arg( "about:blank" );

    command
        .stdout( Stdio::null() )
        .stderr( Stdio::piped() )
        .stdin( Stdio::null() );

    debug!( "Launching chromium..." );
    let mut child = command.spawn()
        .map_err( |err| Error::RuntimeError( "cannot launch chromium".into(), err.into() ) )?;

    let stderr = BufReader::new( child.stderr.take().unwrap() );
    let devtools_regex = Regex::new( r"DevTools listening on (ws://[^:]+:\d+)" ).unwrap();
    let (url_tx, url_rx) = channel();
    thread::spawn( move || {
        for line in stderr.lines() {
            let line = match line {
                Ok( line ) => line,
                Err( _ ) => break
            };

            debug!( "Chromium stderr: {:?}", line );
            if let Some( captures ) = devtools_regex.captures( &line ) {
                let url = captures.get( 1 ).unwrap().as_str().to_owned();
                let _ = url_tx.send( url );
                break;
            }
        }
    });

    let url = url_rx.recv_timeout( Duration::from_secs( 10 ) )
        .map_err( |err| Error::RuntimeError( "timeout while waiting for chromium to start".into(), err.into() ) )?;

    debug!( "Chromium in listening on: {}", url );
    let mut connection = Connection::connect( &format!( "{}/json", url ) )
        .map_err( |err| Error::RuntimeError( "devtools connection to chromium failed".into(), err.into() ) )?;

    connection.send_cmd( "Page.enable", Value::Null );
    connection.send_cmd( "Runtime.enable", Value::Null );
    connection.send_cmd(
        "Page.navigate",
        json!({
            "url": format!( "http://localhost:{}", server_address.port() )
        })
    );

    let mut print_counter = 0;
    let mut finished = false;
    let start = Instant::now();
    let time_limit = Duration::from_secs( 60 );
    let mut get_status_req = None;
    loop {
        let elapsed = start.elapsed();
        if elapsed >= time_limit {
            break;
        }
        let remaining = time_limit - elapsed;

        let reply = connection.try_recv( Some( remaining ) );
        let reply = match reply {
            Ok( reply ) => reply,
            Err( ReplyError::Timeout ) => {
                if finished {
                    break;
                } else {
                    continue;
                }
            },
            Err( err ) => {
                return Err( Error::RuntimeError( "error while communicating with chromium".into(), err.into() ) );
            }
        };

        match reply {
            Reply::Result { ref id, ref body } if Some( *id ) == get_status_req => {
                finished = true;
                let status = body.get( "result" ).unwrap().get( "value" ).unwrap().as_u64().unwrap();
                if status != 0 {
                    println_err!( "error: process exited with a status of {}", status );
                    *any_failure = true;
                }
                break;
            },
            Reply::Event { ref method, .. } if method == "Page.frameStoppedLoading" => {
                let id = connection.send_cmd(
                    "Runtime.evaluate",
                    json!({
                        "expression": "__cargo_web.status",
                        "awaitPromise": true
                    })
                );

                get_status_req = Some( id );
            },
            Reply::Event { ref method, ref body } if method == "Runtime.exceptionThrown" => {
                let body: ExceptionThrownBody = serde_json::from_value( body.clone() ).expect( "Failed to parse `Runtime.exceptionThrown` event" );
                println_err!( "error: unhandled exception thrown" );
                if let Some( exception ) = body.exception_details.exception {
                    if let Some( description ) = exception.description {
                        println_err!( "error:     {}", description );
                    }
                }
                if let Some( url ) = body.exception_details.url {
                    println_err!( "error: source: {}:{}:{}", url, body.exception_details.line_number, body.exception_details.column_number );
                }
                // TODO: Better error message.
                *any_failure = true;
                finished = true;
                break;
            },
            Reply::Event { ref method, ref body } if method == "Runtime.consoleAPICalled" => {
                let body: ConsoleApiCalledBody = serde_json::from_value( body.clone() ).unwrap();
                match body.kind.as_str() {
                    "log" | "debug" | "info" | "error" | "warning" => {
                        let mut output = String::new();
                        for arg in body.args {
                            if !output.is_empty() {
                                output.push_str( " " );
                            }

                            if arg.kind == "string" {
                                output.push_str( arg.value.unwrap().as_str().unwrap() );
                            } else {
                                output.push_str( "<" );
                                if let Some( class_name ) = arg.class_name {
                                    output.push_str( &class_name );
                                } else {
                                    output.push_str( &arg.kind );
                                }
                                output.push_str( ">" );
                            }
                        }

                        if build_matcher.targeting_emscripten() {
                            if print_counter == 0 && output.starts_with( "pre-main" ) {
                                continue;
                            } else if print_counter == 1 && output.trim().is_empty() {
                                continue;
                            }
                        }

                        println!( "{}", output );
                        print_counter += 1;
                    },
                    _ => {}
                }
            },
            Reply::Error { ref message, .. } => {
                return Err( Error::RuntimeError( "chromium returned an error".into(), message.clone().into() ) );
            },
            _ => {}
        }
    }

    if !finished {
        println_err!( "error: tests timed out!" );
        *any_failure = true;
    }

    debug!( "Testing finished; waiting for chromium to die..." );
    child.kill().unwrap();
    child.wait().unwrap();

    Ok(())
}
<|endoftext|>


<commit_before>use crate::beacon_chain::BeaconChain;
use libp2p::rpc::HelloMessage;
use libp2p::PeerId;
use slog::{debug, o};
use std::collections::HashMap;
use std::sync::Arc;
use types::{Epoch, Hash256, Slot};

/// The number of slots that we can import blocks ahead of us, before going into full Sync mode.
const SLOT_IMPORT_TOLERANCE: u64 = 100;

/// Keeps track of syncing information for known connected peers.
pub struct PeerSyncInfo {
    latest_finalized_root: Hash256,
    latest_finalized_epoch: Epoch,
    best_root: Hash256,
    best_slot: Slot,
}

/// The current syncing state.
#[derive(PartialEq)]
pub enum SyncState {
    Idle,
    Downloading,
    Stopped,
}

/// Simple Syncing protocol.
//TODO: Decide for HELLO messages whether its better to keep current in RAM or build on the fly
//when asked.
pub struct SimpleSync {
    /// A reference to the underlying beacon chain.
    chain: Arc<BeaconChain>,
    /// A mapping of Peers to their respective PeerSyncInfo.
    known_peers: HashMap<PeerId, PeerSyncInfo>,
    /// The current state of the syncing protocol.
    state: SyncState,
    /// The network id, for quick HELLO RPC message lookup.
    network_id: u8,
    /// The latest epoch of the syncing chain.
    latest_finalized_epoch: Epoch,
    /// The latest block of the syncing chain.
    latest_slot: Slot,
    /// Sync logger.
    log: slog::Logger,
}

impl SimpleSync {
    pub fn new(beacon_chain: Arc<BeaconChain>, log: &slog::Logger) -> Self {
        let state = beacon_chain.get_state();
        let sync_logger = log.new(o!("Service"=> "Sync"));
        SimpleSync {
            chain: beacon_chain.clone(),
            known_peers: HashMap::new(),
            state: SyncState::Idle,
            network_id: beacon_chain.get_spec().network_id,
            latest_finalized_epoch: state.finalized_epoch,
            latest_slot: state.slot - 1, //TODO: Build latest block function into Beacon chain and correct this
            log: sync_logger,
        }
    }

    /// Generates our current state in the form of a HELLO RPC message.
    pub fn generate_hello(&self) -> HelloMessage {
        let state = &self.chain.get_state();
        //TODO: Paul to verify the logic of these fields.
        HelloMessage {
            network_id: self.network_id,
            latest_finalized_root: state.finalized_root,
            latest_finalized_epoch: state.finalized_epoch,
            best_root: state.latest_block_roots[0], //TODO: build correct value as a beacon chain function
            best_slot: state.slot - 1,
        }
    }

    pub fn validate_peer(&mut self, peer_id: PeerId, hello_message: HelloMessage) -> bool {
        // network id must match
        if hello_message.network_id != self.network_id {
            return false;
        }
        // compare latest epoch and finalized root to see if they exist in our chain
        if hello_message.latest_finalized_epoch <= self.latest_finalized_epoch {
            // ensure their finalized root is in our chain
            // TODO: Get the finalized root at hello_message.latest_epoch and ensure they match
            //if (hello_message.latest_finalized_root == self.chain.get_state() {
            //    return false;
            //    }
        }

        // the client is valid, add it to our list of known_peers and request sync if required
        // update peer list if peer already exists
        let peer_info = PeerSyncInfo {
            latest_finalized_root: hello_message.latest_finalized_root,
            latest_finalized_epoch: hello_message.latest_finalized_epoch,
            best_root: hello_message.best_root,
            best_slot: hello_message.best_slot,
        };

        debug!(self.log, "Handshake successful. Peer: {:?}", peer_id);
        self.known_peers.insert(peer_id, peer_info);

        // set state to sync
        if self.state == SyncState::Idle
            && hello_message.best_slot > self.latest_slot + SLOT_IMPORT_TOLERANCE
        {
            self.state = SyncState::Downloading;
            //TODO: Start requesting blocks from known peers. Ideally in batches
        }

        true
    }
}
<commit_msg>Fix issue with merging v0.5.0<commit_after>use crate::beacon_chain::BeaconChain;
use libp2p::rpc::HelloMessage;
use libp2p::PeerId;
use slog::{debug, o};
use std::collections::HashMap;
use std::sync::Arc;
use types::{Epoch, Hash256, Slot};

/// The number of slots that we can import blocks ahead of us, before going into full Sync mode.
const SLOT_IMPORT_TOLERANCE: u64 = 100;

/// Keeps track of syncing information for known connected peers.
pub struct PeerSyncInfo {
    latest_finalized_root: Hash256,
    latest_finalized_epoch: Epoch,
    best_root: Hash256,
    best_slot: Slot,
}

/// The current syncing state.
#[derive(PartialEq)]
pub enum SyncState {
    Idle,
    Downloading,
    Stopped,
}

/// Simple Syncing protocol.
//TODO: Decide for HELLO messages whether its better to keep current in RAM or build on the fly
//when asked.
pub struct SimpleSync {
    /// A reference to the underlying beacon chain.
    chain: Arc<BeaconChain>,
    /// A mapping of Peers to their respective PeerSyncInfo.
    known_peers: HashMap<PeerId, PeerSyncInfo>,
    /// The current state of the syncing protocol.
    state: SyncState,
    /// The network id, for quick HELLO RPC message lookup.
    network_id: u8,
    /// The latest epoch of the syncing chain.
    latest_finalized_epoch: Epoch,
    /// The latest block of the syncing chain.
    latest_slot: Slot,
    /// Sync logger.
    log: slog::Logger,
}

impl SimpleSync {
    pub fn new(beacon_chain: Arc<BeaconChain>, log: &slog::Logger) -> Self {
        let state = beacon_chain.get_state();
        let sync_logger = log.new(o!("Service"=> "Sync"));
        SimpleSync {
            chain: beacon_chain.clone(),
            known_peers: HashMap::new(),
            state: SyncState::Idle,
            network_id: beacon_chain.get_spec().network_id,
            latest_finalized_epoch: state.finalized_epoch,
            latest_slot: state.slot - 1, //TODO: Build latest block function into Beacon chain and correct this
            log: sync_logger,
        }
    }

    /// Generates our current state in the form of a HELLO RPC message.
    pub fn generate_hello(&self) -> HelloMessage {
        let state = &self.chain.get_state();
        //TODO: Paul to verify the logic of these fields.
        HelloMessage {
            network_id: self.network_id,
            latest_finalized_root: state.finalized_root,
            latest_finalized_epoch: state.finalized_epoch,
            best_root: Hash256::zero(), //TODO: build correct value as a beacon chain function
            best_slot: state.slot - 1,
        }
    }

    pub fn validate_peer(&mut self, peer_id: PeerId, hello_message: HelloMessage) -> bool {
        // network id must match
        if hello_message.network_id != self.network_id {
            return false;
        }
        // compare latest epoch and finalized root to see if they exist in our chain
        if hello_message.latest_finalized_epoch <= self.latest_finalized_epoch {
            // ensure their finalized root is in our chain
            // TODO: Get the finalized root at hello_message.latest_epoch and ensure they match
            //if (hello_message.latest_finalized_root == self.chain.get_state() {
            //    return false;
            //    }
        }

        // the client is valid, add it to our list of known_peers and request sync if required
        // update peer list if peer already exists
        let peer_info = PeerSyncInfo {
            latest_finalized_root: hello_message.latest_finalized_root,
            latest_finalized_epoch: hello_message.latest_finalized_epoch,
            best_root: hello_message.best_root,
            best_slot: hello_message.best_slot,
        };

        debug!(self.log, "Handshake successful. Peer: {:?}", peer_id);
        self.known_peers.insert(peer_id, peer_info);

        // set state to sync
        if self.state == SyncState::Idle
            && hello_message.best_slot > self.latest_slot + SLOT_IMPORT_TOLERANCE
        {
            self.state = SyncState::Downloading;
            //TODO: Start requesting blocks from known peers. Ideally in batches
        }

        true
    }
}
<|endoftext|>


<commit_before>#![warn(clippy::all, rust_2018_idioms)]

use cargo_registry::{boot, App, Env};
use std::{
    fs::File,
    sync::{mpsc::channel, Arc, Mutex},
    thread,
    time::Duration,
};

use civet::Server as CivetServer;
use conduit_hyper::Service;
use futures_util::future::FutureExt;
use reqwest::blocking::Client;

#[allow(clippy::large_enum_variant)]
enum Server {
    Civet(CivetServer),
    Hyper(tokio::runtime::Runtime, tokio::task::JoinHandle<()>),
}

use Server::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    env_logger::init();

    let config = cargo_registry::Config::default();
    let client = Client::new();

    let app = App::new(config.clone(), Some(client));
    let app = cargo_registry::build_handler(Arc::new(app));

    // On every server restart, ensure the categories available in the database match
    // the information in *src/categories.toml*.
    let categories_toml = include_str!("../boot/categories.toml");
    boot::categories::sync(categories_toml).unwrap();

    let heroku = dotenv::var("HEROKU").is_ok();
    let fastboot = dotenv::var("USE_FASTBOOT").is_ok();

    let port = if heroku {
        8888
    } else {
        dotenv::var("PORT")
            .ok()
            .and_then(|s| s.parse().ok())
            .unwrap_or(8888)
    };
    let threads = dotenv::var("SERVER_THREADS")
        .map(|s| s.parse().expect("SERVER_THREADS was not a valid number"))
        .unwrap_or_else(|_| {
            if config.env == Env::Development {
                5
            } else {
                50
            }
        });

    let server = if dotenv::var("USE_HYPER").is_ok() {
        use tokio::io::AsyncWriteExt;
        use tokio::signal::unix::{signal, SignalKind};

        println!("Booting with a hyper based server");

        let mut rt = tokio::runtime::Builder::new()
            .threaded_scheduler()
            .enable_all()
            .core_threads(4)
            .max_threads(threads as usize)
            .build()
            .unwrap();

        let handler = Arc::new(conduit_hyper::BlockingHandler::new(app));
        let make_service =
            hyper::service::make_service_fn(move |socket: &hyper::server::conn::AddrStream| {
                let addr = socket.remote_addr();
                let handler = handler.clone();
                async move { Service::from_blocking(handler, addr) }
            });

        let addr = ([127, 0, 0, 1], port).into();
        let server = rt.block_on(async { hyper::Server::bind(&addr).serve(make_service) });

        let mut sig_int = rt.block_on(async { signal(SignalKind::interrupt()) })?;
        let mut sig_term = rt.block_on(async { signal(SignalKind::terminate()) })?;

        let server = server.with_graceful_shutdown(async move {
            // Wait for either signal
            futures_util::select! {
                _ = sig_int.recv().fuse() => (),
                _ = sig_term.recv().fuse() => (),
            };
            let mut stdout = tokio::io::stdout();
            stdout.write_all(b"Starting graceful shutdown\n").await.ok();
        });

        let server = rt.spawn(async { server.await.unwrap() });
        Hyper(rt, server)
    } else {
        println!("Booting with a civet based server");
        let mut cfg = civet::Config::new();
        cfg.port(port).threads(threads).keep_alive(true);
        Civet(CivetServer::start(cfg, app).unwrap())
    };

    println!("listening on port {}", port);

    // Give tokio a chance to spawn the first worker thread
    thread::sleep(Duration::from_millis(10));

    // Creating this file tells heroku to tell nginx that the application is ready
    // to receive traffic.
    if heroku {
        let path = if fastboot {
            "/tmp/backend-initialized"
        } else {
            "/tmp/app-initialized"
        };
        println!("Writing to {}", path);
        File::create(path).unwrap();
    }

    // Block the main thread until the server has shutdown
    match server {
        Hyper(mut rt, server) => {
            rt.block_on(async { server.await.unwrap() });
        }
        Civet(server) => {
            let (tx, rx) = channel::<()>();
            ctrlc_handler(move || tx.send(()).unwrap_or(()));
            rx.recv().unwrap();
            drop(server);
        }
    }

    println!("Server has gracefully shutdown!");
    Ok(())
}

fn ctrlc_handler<F>(f: F)
where
    F: FnOnce() + Send + 'static,
{
    let call_once = Mutex::new(Some(f));

    ctrlc::set_handler(move || {
        if let Some(f) = call_once.lock().unwrap().take() {
            println!("Starting graceful shutdown");
            f();
        } else {
            println!("Already sent signal to start graceful shutdown");
        }
    })
    .unwrap();
}
<commit_msg>Auto merge of #2529 - jtgeibel:fix-blocking-thread-count, r=JohnTitor<commit_after>#![warn(clippy::all, rust_2018_idioms)]

use cargo_registry::{boot, App, Env};
use std::{
    fs::File,
    sync::{mpsc::channel, Arc, Mutex},
    thread,
    time::Duration,
};

use civet::Server as CivetServer;
use conduit_hyper::Service;
use futures_util::future::FutureExt;
use reqwest::blocking::Client;

const CORE_THREADS: usize = 4;

#[allow(clippy::large_enum_variant)]
enum Server {
    Civet(CivetServer),
    Hyper(tokio::runtime::Runtime, tokio::task::JoinHandle<()>),
}

use Server::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize logging
    env_logger::init();

    let config = cargo_registry::Config::default();
    let client = Client::new();

    let app = App::new(config.clone(), Some(client));
    let app = cargo_registry::build_handler(Arc::new(app));

    // On every server restart, ensure the categories available in the database match
    // the information in *src/categories.toml*.
    let categories_toml = include_str!("../boot/categories.toml");
    boot::categories::sync(categories_toml).unwrap();

    let heroku = dotenv::var("HEROKU").is_ok();
    let fastboot = dotenv::var("USE_FASTBOOT").is_ok();

    let port = if heroku {
        8888
    } else {
        dotenv::var("PORT")
            .ok()
            .and_then(|s| s.parse().ok())
            .unwrap_or(8888)
    };
    let threads = dotenv::var("SERVER_THREADS")
        .map(|s| s.parse().expect("SERVER_THREADS was not a valid number"))
        .unwrap_or_else(|_| {
            if config.env == Env::Development {
                5
            } else {
                50
            }
        });

    let server = if dotenv::var("USE_HYPER").is_ok() {
        use tokio::io::AsyncWriteExt;
        use tokio::signal::unix::{signal, SignalKind};

        println!("Booting with a hyper based server");

        let mut rt = tokio::runtime::Builder::new()
            .threaded_scheduler()
            .enable_all()
            .core_threads(CORE_THREADS)
            .max_threads(threads as usize + CORE_THREADS)
            .build()
            .unwrap();

        let handler = Arc::new(conduit_hyper::BlockingHandler::new(app));
        let make_service =
            hyper::service::make_service_fn(move |socket: &hyper::server::conn::AddrStream| {
                let addr = socket.remote_addr();
                let handler = handler.clone();
                async move { Service::from_blocking(handler, addr) }
            });

        let addr = ([127, 0, 0, 1], port).into();
        let server = rt.block_on(async { hyper::Server::bind(&addr).serve(make_service) });

        let mut sig_int = rt.block_on(async { signal(SignalKind::interrupt()) })?;
        let mut sig_term = rt.block_on(async { signal(SignalKind::terminate()) })?;

        let server = server.with_graceful_shutdown(async move {
            // Wait for either signal
            futures_util::select! {
                _ = sig_int.recv().fuse() => (),
                _ = sig_term.recv().fuse() => (),
            };
            let mut stdout = tokio::io::stdout();
            stdout.write_all(b"Starting graceful shutdown\n").await.ok();
        });

        let server = rt.spawn(async { server.await.unwrap() });
        Hyper(rt, server)
    } else {
        println!("Booting with a civet based server");
        let mut cfg = civet::Config::new();
        cfg.port(port).threads(threads).keep_alive(true);
        Civet(CivetServer::start(cfg, app).unwrap())
    };

    println!("listening on port {}", port);

    // Give tokio a chance to spawn the first worker thread
    thread::sleep(Duration::from_millis(10));

    // Creating this file tells heroku to tell nginx that the application is ready
    // to receive traffic.
    if heroku {
        let path = if fastboot {
            "/tmp/backend-initialized"
        } else {
            "/tmp/app-initialized"
        };
        println!("Writing to {}", path);
        File::create(path).unwrap();
    }

    // Block the main thread until the server has shutdown
    match server {
        Hyper(mut rt, server) => {
            rt.block_on(async { server.await.unwrap() });
        }
        Civet(server) => {
            let (tx, rx) = channel::<()>();
            ctrlc_handler(move || tx.send(()).unwrap_or(()));
            rx.recv().unwrap();
            drop(server);
        }
    }

    println!("Server has gracefully shutdown!");
    Ok(())
}

fn ctrlc_handler<F>(f: F)
where
    F: FnOnce() + Send + 'static,
{
    let call_once = Mutex::new(Some(f));

    ctrlc::set_handler(move || {
        if let Some(f) = call_once.lock().unwrap().take() {
            println!("Starting graceful shutdown");
            f();
        } else {
            println!("Already sent signal to start graceful shutdown");
        }
    })
    .unwrap();
}
<|endoftext|>


<commit_before>extern crate magic_sys;
extern crate std;

use bins::error::*;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

pub struct MagicWrapper {
  magic: *const magic_sys::Magic,
  loaded: bool
}

impl Drop for MagicWrapper {
  fn drop(&mut self) {
    unsafe { magic_sys::magic_close(self.magic) }
  }
}

impl MagicWrapper {
  pub fn new(flags: i32, load_defaults: bool) -> Result<Self> {
    let magic = unsafe { magic_sys::magic_open(flags) };
    if magic == std::ptr::null() {
      return Err("libmagic could not create a new magic cookie".into());
    }
    let mut wrapper = MagicWrapper {
      magic: magic,
      loaded: false
    };
    if load_defaults {
      try!(wrapper.load(None));
    }
    Ok(wrapper)
  }

  pub fn load(&mut self, paths: Option<String>) -> Result<()> {
    let ptr = match paths {
      Some(p) => try!(CString::new(p).map_err(|e| e.to_string())).as_ptr(),
      None => std::ptr::null(),
    };
    let load_status = unsafe { magic_sys::magic_load(self.magic, ptr) };
    if load_status != 0 {
      return Err("libmagic could not load default magic database".into());
    }
    self.loaded = true;
    Ok(())
  }

  fn check_loaded(&self) -> Result<()> {
    if !self.loaded {
      return Err("magic database was not loaded".into());
    }
    Ok(())
  }

  fn check_magic_return_value(&self, ptr: *const c_char) -> Result<String> {
    if ptr == std::ptr::null() {
      let error = unsafe { magic_sys::magic_error(self.magic) };
      if error == std::ptr::null() {
        Err("libmagic had an error but didn't think it did".into())
      } else {
        let error = unsafe { CStr::from_ptr(error) };
        Err(format!("libmagic error: {}", error.to_string_lossy().into_owned()).into())
      }
    } else {
      Ok(unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned())
    }
  }

  pub fn magic_buffer(&self, buf: &[u8]) -> Result<String> {
    try!(self.check_loaded());
    let info: *const c_char = unsafe { magic_sys::magic_buffer(self.magic, buf.as_ptr(), buf.len() as u64) };
    self.check_magic_return_value(info)
  }

  // pub fn magic_file<S>(&self, path: S) -> Result<String> where S: Into<String> {
  //   try!(self.check_loaded());
  //   let path = try!(CString::new(path.into()).map_err(|e| e.to_string()));
  //   let info: *const c_char = unsafe { magic_sys::magic_file(self.magic, path.as_ptr()) };
  //   self.check_magic_return_value(info)
  // }
}
<commit_msg>fix(magic): use bit-width for size_t cast<commit_after>extern crate magic_sys;
extern crate std;

use bins::error::*;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

pub struct MagicWrapper {
  magic: *const magic_sys::Magic,
  loaded: bool
}

cfg_if! {
  if #[cfg(target_pointer_width = "64")] {
    type SizeT = u64;
  } else if #[cfg(target_pointer_width = "32")] {
    type SizeT = u32;
  } else {
    // well, that's interesting. default down to 32
    type SizeT = u32;
  }
}

impl Drop for MagicWrapper {
  fn drop(&mut self) {
    unsafe { magic_sys::magic_close(self.magic) }
  }
}

impl MagicWrapper {
  pub fn new(flags: i32, load_defaults: bool) -> Result<Self> {
    let magic = unsafe { magic_sys::magic_open(flags) };
    if magic == std::ptr::null() {
      return Err("libmagic could not create a new magic cookie".into());
    }
    let mut wrapper = MagicWrapper {
      magic: magic,
      loaded: false
    };
    if load_defaults {
      try!(wrapper.load(None));
    }
    Ok(wrapper)
  }

  pub fn load(&mut self, paths: Option<String>) -> Result<()> {
    let ptr = match paths {
      Some(p) => try!(CString::new(p).map_err(|e| e.to_string())).as_ptr(),
      None => std::ptr::null(),
    };
    let load_status = unsafe { magic_sys::magic_load(self.magic, ptr) };
    if load_status != 0 {
      return Err("libmagic could not load default magic database".into());
    }
    self.loaded = true;
    Ok(())
  }

  fn check_loaded(&self) -> Result<()> {
    if !self.loaded {
      return Err("magic database was not loaded".into());
    }
    Ok(())
  }

  fn check_magic_return_value(&self, ptr: *const c_char) -> Result<String> {
    if ptr == std::ptr::null() {
      let error = unsafe { magic_sys::magic_error(self.magic) };
      if error == std::ptr::null() {
        Err("libmagic had an error but didn't think it did".into())
      } else {
        let error = unsafe { CStr::from_ptr(error) };
        Err(format!("libmagic error: {}", error.to_string_lossy().into_owned()).into())
      }
    } else {
      Ok(unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned())
    }
  }

  pub fn magic_buffer(&self, buf: &[u8]) -> Result<String> {
    try!(self.check_loaded());
    let info: *const c_char = unsafe { magic_sys::magic_buffer(self.magic, buf.as_ptr(), buf.len() as SizeT) };
    self.check_magic_return_value(info)
  }

  // pub fn magic_file<S>(&self, path: S) -> Result<String> where S: Into<String> {
  //   try!(self.check_loaded());
  //   let path = try!(CString::new(path.into()).map_err(|e| e.to_string()));
  //   let info: *const c_char = unsafe { magic_sys::magic_file(self.magic, path.as_ptr()) };
  //   self.check_magic_return_value(info)
  // }
}
<|endoftext|>


<commit_before>use bullet::bullets::Bullets;
use bullet::collision::Collision;
use cgmath;
use cgmath::Point2;
use character::controls::CharacterInputState;
use game::constants::{ASPECT_RATIO, BULLET_SPEED};
use gfx;
use gfx_app::{ColorFormat, DepthFormat};
use graphics::{camera::CameraInputState, can_move, Dimensions};
use shaders::{bullet_pipeline, Position, Projection, VertexData};
use specs;
use specs::prelude::{ReadStorage, Read, WriteStorage};
use std::f32;
use graphics::can_move_to_tile;

pub mod bullets;
pub mod collision;

const SHADER_VERT: &[u8] = include_bytes!("../shaders/bullet.v.glsl");
const SHADER_FRAG: &[u8] = include_bytes!("../shaders/bullet.f.glsl");

#[derive(Debug, Clone, PartialEq)]
pub struct BulletDrawable {
  projection: Projection,
  pub position: Position,
  previous_position: Position,
  offset_delta: Position,
  pub movement_direction: Point2<f32>,
  pub status: collision::Collision,
}

impl BulletDrawable {
  pub fn new(position: cgmath::Point2<f32>, movement_direction: Point2<f32>) -> BulletDrawable {
    let view = Dimensions::get_view_matrix();
    BulletDrawable {
      projection: Projection {
        model: view.into(),
        view: view.into(),
        proj: cgmath::perspective(cgmath::Deg(75.0f32), ASPECT_RATIO, 0.1, 4000.0).into(),
      },
      position: Position::new([position.x, position.y]),
      previous_position: Position::new([0.0, 0.0]),
      offset_delta: Position::new([0.0, 0.0]),
      movement_direction,
      status: Collision::Flying,
    }
  }

  pub fn update(&mut self, world_to_clip: &Projection, ci: &CharacterInputState) {
    self.projection = *world_to_clip;

    self.offset_delta =
      if (ci.x_movement - self.previous_position.position[0]).abs() > f32::EPSILON ||
        (ci.y_movement - self.previous_position.position[1]).abs() > f32::EPSILON {
        Position::new([ci.x_movement - self.previous_position.position[0], ci.y_movement - self.previous_position.position[1]])
      } else {
        Position::new([self.offset_delta.position[0], self.offset_delta.position[1]])
      };

    self.previous_position = Position::new([
      ci.x_movement - (self.movement_direction.x * BULLET_SPEED),
      ci.y_movement + (self.movement_direction.y * BULLET_SPEED)]);

    self.position =
      Position::new([
        self.position.position[0] + self.offset_delta.position[0] + (self.movement_direction.x * BULLET_SPEED),
        self.position.position[1] + self.offset_delta.position[1] - (self.movement_direction.y * BULLET_SPEED)
      ]);

    let tile_pos = Position::new([ci.x_movement - self.position.position[0], ci.y_movement - self.position.position[1]]);

    if !can_move(self.position) {
      self.status = Collision::OutOfBounds;
    } else if !can_move_to_tile(tile_pos) {
      self.status = Collision::Hit;
    }
  }
}

pub struct BulletDrawSystem<R: gfx::Resources> {
  bundle: gfx::pso::bundle::Bundle<R, bullet_pipeline::Data<R>>,
}

impl<R: gfx::Resources> BulletDrawSystem<R> {
  pub fn new<F>(factory: &mut F,
                rtv: gfx::handle::RenderTargetView<R, ColorFormat>,
                dsv: gfx::handle::DepthStencilView<R, DepthFormat>) -> BulletDrawSystem<R>
                where F: gfx::Factory<R> {
    use gfx::traits::FactoryExt;

    let vertex_data: [VertexData; 4] = [
        VertexData::new([-1.0, -1.0], [0.0, 1.0]),
        VertexData::new([1.0, -1.0], [1.0, 1.0]),
        VertexData::new([1.0, 1.0], [1.0, 0.0]),
        VertexData::new([-1.0, 1.0], [0.0, 0.0]),
      ];

    let index_data: [u16; 6] = [0, 1, 2, 2, 3, 0];

    let (vertex_buf, slice) = factory.create_vertex_buffer_with_slice(&vertex_data, &index_data[..]);
    let pso = factory
      .create_pipeline_simple(SHADER_VERT,
                              SHADER_FRAG,
                              bullet_pipeline::new())
      .unwrap();

    let pipeline_data = bullet_pipeline::Data {
      vbuf: vertex_buf,
      projection_cb: factory.create_constant_buffer(1),
      position_cb: factory.create_constant_buffer(1),
      out_color: rtv,
      out_depth: dsv,
    };

    BulletDrawSystem {
      bundle: gfx::Bundle::new(slice, pso, pipeline_data),
    }
  }

  pub fn draw<C>(&mut self,
                 drawable: &BulletDrawable,
                 encoder: &mut gfx::Encoder<R, C>)
                 where C: gfx::CommandBuffer<R> {
    encoder.update_constant_buffer(&self.bundle.data.projection_cb, &drawable.projection);
    encoder.update_constant_buffer(&self.bundle.data.position_cb, &drawable.position);
    self.bundle.encode(encoder);
  }
}

#[derive(Debug)]
pub struct PreDrawSystem;

impl PreDrawSystem {
  pub fn new() -> PreDrawSystem {
    PreDrawSystem {}
  }
}

impl<'a> specs::prelude::System<'a> for PreDrawSystem {
  type SystemData = (ReadStorage<'a, CameraInputState>,
                     WriteStorage<'a, Bullets>,
                     ReadStorage<'a, CharacterInputState>,
                     Read<'a, Dimensions>);

  fn run(&mut self, (camera_input, mut bullets, character_input, dim): Self::SystemData) {
    use specs::join::Join;

    for (camera, bs, ci) in (&camera_input, &mut bullets, &character_input).join() {
      let world_to_clip = dim.world_to_projection(camera);

      for b in &mut bs.bullets {
        b.update(&world_to_clip, ci);
      }
    }
  }
}
<commit_msg>Increase bullet size<commit_after>use bullet::bullets::Bullets;
use bullet::collision::Collision;
use cgmath;
use cgmath::Point2;
use character::controls::CharacterInputState;
use game::constants::{ASPECT_RATIO, BULLET_SPEED};
use gfx;
use gfx_app::{ColorFormat, DepthFormat};
use graphics::{camera::CameraInputState, can_move, Dimensions};
use shaders::{bullet_pipeline, Position, Projection, VertexData};
use specs;
use specs::prelude::{ReadStorage, Read, WriteStorage};
use std::f32;
use graphics::can_move_to_tile;

pub mod bullets;
pub mod collision;

const SHADER_VERT: &[u8] = include_bytes!("../shaders/bullet.v.glsl");
const SHADER_FRAG: &[u8] = include_bytes!("../shaders/bullet.f.glsl");

#[derive(Debug, Clone, PartialEq)]
pub struct BulletDrawable {
  projection: Projection,
  pub position: Position,
  previous_position: Position,
  offset_delta: Position,
  pub movement_direction: Point2<f32>,
  pub status: collision::Collision,
}

impl BulletDrawable {
  pub fn new(position: cgmath::Point2<f32>, movement_direction: Point2<f32>) -> BulletDrawable {
    let view = Dimensions::get_view_matrix();
    BulletDrawable {
      projection: Projection {
        model: view.into(),
        view: view.into(),
        proj: cgmath::perspective(cgmath::Deg(75.0f32), ASPECT_RATIO, 0.1, 4000.0).into(),
      },
      position: Position::new([position.x, position.y]),
      previous_position: Position::new([0.0, 0.0]),
      offset_delta: Position::new([0.0, 0.0]),
      movement_direction,
      status: Collision::Flying,
    }
  }

  pub fn update(&mut self, world_to_clip: &Projection, ci: &CharacterInputState) {
    self.projection = *world_to_clip;

    self.offset_delta =
      if (ci.x_movement - self.previous_position.position[0]).abs() > f32::EPSILON ||
        (ci.y_movement - self.previous_position.position[1]).abs() > f32::EPSILON {
        Position::new([ci.x_movement - self.previous_position.position[0], ci.y_movement - self.previous_position.position[1]])
      } else {
        Position::new([self.offset_delta.position[0], self.offset_delta.position[1]])
      };

    self.previous_position = Position::new([
      ci.x_movement - (self.movement_direction.x * BULLET_SPEED),
      ci.y_movement + (self.movement_direction.y * BULLET_SPEED)]);

    self.position =
      Position::new([
        self.position.position[0] + self.offset_delta.position[0] + (self.movement_direction.x * BULLET_SPEED),
        self.position.position[1] + self.offset_delta.position[1] - (self.movement_direction.y * BULLET_SPEED)
      ]);

    let tile_pos = Position::new([ci.x_movement - self.position.position[0], ci.y_movement - self.position.position[1]]);

    if !can_move(self.position) {
      self.status = Collision::OutOfBounds;
    } else if !can_move_to_tile(tile_pos) {
      self.status = Collision::Hit;
    }
  }
}

pub struct BulletDrawSystem<R: gfx::Resources> {
  bundle: gfx::pso::bundle::Bundle<R, bullet_pipeline::Data<R>>,
}

impl<R: gfx::Resources> BulletDrawSystem<R> {
  pub fn new<F>(factory: &mut F,
                rtv: gfx::handle::RenderTargetView<R, ColorFormat>,
                dsv: gfx::handle::DepthStencilView<R, DepthFormat>) -> BulletDrawSystem<R>
                where F: gfx::Factory<R> {
    use gfx::traits::FactoryExt;

    let vertex_data: [VertexData; 4] = [
        VertexData::new([-1.5, -1.5], [0.0, 1.0]),
        VertexData::new([1.5, -1.5], [1.0, 1.0]),
        VertexData::new([1.5, 1.5], [1.0, 0.0]),
        VertexData::new([-1.5, 1.5], [0.0, 0.0]),
      ];

    let index_data: [u16; 6] = [0, 1, 2, 2, 3, 0];

    let (vertex_buf, slice) = factory.create_vertex_buffer_with_slice(&vertex_data, &index_data[..]);
    let pso = factory
      .create_pipeline_simple(SHADER_VERT,
                              SHADER_FRAG,
                              bullet_pipeline::new())
      .unwrap();

    let pipeline_data = bullet_pipeline::Data {
      vbuf: vertex_buf,
      projection_cb: factory.create_constant_buffer(1),
      position_cb: factory.create_constant_buffer(1),
      out_color: rtv,
      out_depth: dsv,
    };

    BulletDrawSystem {
      bundle: gfx::Bundle::new(slice, pso, pipeline_data),
    }
  }

  pub fn draw<C>(&mut self,
                 drawable: &BulletDrawable,
                 encoder: &mut gfx::Encoder<R, C>)
                 where C: gfx::CommandBuffer<R> {
    encoder.update_constant_buffer(&self.bundle.data.projection_cb, &drawable.projection);
    encoder.update_constant_buffer(&self.bundle.data.position_cb, &drawable.position);
    self.bundle.encode(encoder);
  }
}

#[derive(Debug)]
pub struct PreDrawSystem;

impl PreDrawSystem {
  pub fn new() -> PreDrawSystem {
    PreDrawSystem {}
  }
}

impl<'a> specs::prelude::System<'a> for PreDrawSystem {
  type SystemData = (ReadStorage<'a, CameraInputState>,
                     WriteStorage<'a, Bullets>,
                     ReadStorage<'a, CharacterInputState>,
                     Read<'a, Dimensions>);

  fn run(&mut self, (camera_input, mut bullets, character_input, dim): Self::SystemData) {
    use specs::join::Join;

    for (camera, bs, ci) in (&camera_input, &mut bullets, &character_input).join() {
      let world_to_clip = dim.world_to_projection(camera);

      for b in &mut bs.bullets {
        b.update(&world_to_clip, ci);
      }
    }
  }
}
<|endoftext|>


<commit_before>#[desc = "C64 emulator"];
#[author = "Andreas Neuhaus <zargony@zargony.com>"];
#[crate_type = "bin"];

pub mod addressable;
pub mod memory;
pub mod mos6510;
pub mod ram;
pub mod rom;
<commit_msg>Don't reexport modules in crate file<commit_after>#[desc = "C64 emulator"];
#[author = "Andreas Neuhaus <zargony@zargony.com>"];
#[crate_type = "bin"];

mod addressable;
mod memory;
mod mos6510;
mod ram;
mod rom;
<|endoftext|>


<commit_before>extern crate generic_array;
extern crate libc;
extern crate sha2;
extern crate xattr;

use std::ffi::{OsStr, OsString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::RawFd;
use std::path::{Component, Path, PathBuf};

use self::generic_array::GenericArray;
use self::generic_array::typenum::U64;
use self::sha2::{Sha512, Digest};
use self::xattr::FileExt;

use catfs::error;
use catfs::error::RError;
use catfs::rlibc;
use catfs::rlibc::File;

pub struct Handle {
    src_file: File,
    cache_file: File,
    dirty: bool,
}

// no-op to workaround the fact that we send the entire CatFS at start
// time, but we never send anything. Could have used Unique but that
// bounds us to rust nightly
unsafe impl Send for Handle {}

fn make_rdwr(f: &mut u32) {
    *f = (*f & !rlibc::O_ACCMODE) | rlibc::O_RDWR;
}

fn maybe_unlinkat(dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
    if let Err(e) = rlibc::unlinkat(dir, path, 0) {
        if !error::is_enoent(&e) {
            return Err(e);
        }
    }
    return Ok(());
}

pub fn mkdirat_all(dir: RawFd, path: &AsRef<Path>, mode: u32) -> io::Result<()> {
    let mut p = PathBuf::new();

    for c in path.as_ref().components() {
        if let Component::Normal(n) = c {
            p.push(n);

            if let Err(e) = rlibc::mkdirat(dir, &p, mode) {
                if e.raw_os_error().unwrap() != libc::EEXIST {
                    return Err(e);
                }
            }
        }
    }

    return Ok(());
}

impl Handle {
    pub fn create(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        mode: u32,
    ) -> error::Result<Handle> {
        // need to read the cache file for writeback
        let mut cache_flags = flags.clone();
        if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut cache_flags);
        }
        //debug!("create {:b} {:b} {:#o}", flags, cache_flags, mode);

        if let Some(parent) = path.as_ref().parent() {
            mkdirat_all(cache_dir, &parent, 0o777)?;
        }

        let src_file = File::openat(src_dir, path, flags, mode)?;
        // we are able to create the src file, then the cache file
        // shouldn't be here, but it could be because of bug/crash,
        // so unlink it first
        maybe_unlinkat(cache_dir, path)?;

        return Ok(Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, mode)?,
            dirty: true,
        });
    }

    pub fn open(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        cache_valid_if_present: bool,
    ) -> error::Result<Handle> {
        // even if file is open for write only, I still need to be
        // able to read the src for read-modify-write
        let mut flags = flags;
        if (flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut flags);
        }

        let valid = Handle::validate_cache(src_dir, cache_dir, &path, cache_valid_if_present)?;
        debug!(
            "{:?} {} a valid cache file",
            path.as_ref(),
            if valid { "is" } else { "is not" },
        );
        let mut cache_flags = flags.clone();

        if !valid {
            // mkdir the parents
            if let Some(parent) = path.as_ref().parent() {
                mkdirat_all(cache_dir, &parent, 0o777)?;
            }
            // need to cache this file so need to open it for write
            cache_flags |= rlibc::O_CREAT;
            if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
                make_rdwr(&mut cache_flags);
            }
        }

        let src_file: File;
        if valid && (flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
            src_file = Default::default();
        } else {
            src_file = File::openat(src_dir, path, flags, 0o666)?;
        }

        let handle = Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, 0o666)?,
            dirty: false,
        };

        if !valid && (flags & rlibc::O_TRUNC) == 0 {
            debug!("read ahead {:?}", path.as_ref());
            handle.copy(true)?;
        }

        return Ok(handle);
    }

    // the equivalent of:
    // getfattr -e hex --match=.* -d $f 2>/dev/null | grep =;
    // /usr/bin/stat -t --printf "%Y\n%s\n" $f
    // note that mtime is printed first and then size
    pub fn src_str_to_checksum(f: &File) -> error::Result<OsString> {
        let mut s = OsString::new();
        match f.list_xattr() {
            Ok(attrs) => {
                for x in attrs {
                    if let Some(v) = f.get_xattr(&x)? {
                        s.push(x);
                        s.push(OsStr::new("="));
                        s.push("0x");
                        for b in v {
                            s.push(format!("{:x}", b));
                        }
                        s.push("\n");
                    }
                }
            }
            Err(e) => {
                if e.raw_os_error().unwrap() != libc::ENOTSUP {
                    return Err(RError::from(e));
                }
            }
        }

        let st = f.stat()?;
        s.push(format!("{}\n", st.st_mtime));
        s.push(format!("{}\n", st.st_size));
        return Ok(s);
    }

    fn src_chksum(f: &File) -> error::Result<GenericArray<u8, U64>> {
        let s = Handle::src_str_to_checksum(f)?;
        let mut h = Sha512::default();
        h.input(s.as_bytes());
        return Ok(h.result());
    }

    fn set_pristine(&self, pristine: bool) -> error::Result<()> {
        if pristine {
            self.cache_file.set_xattr(
                "user.catfs.src_chksum",
                Handle::src_chksum(&self.src_file)?
                    .as_slice(),
            )?;
        } else {
            self.cache_file.remove_xattr("user.catfs.src_chksum")?;
        }
        return Ok(());
    }

    fn is_pristine(src_file: &File, cache_file: &File) -> error::Result<bool> {
        if let Some(v) = cache_file.get_xattr("user.catfs.src_chksum")? {
            return Ok(v == Handle::src_chksum(src_file)?.as_slice());
        }

        return Ok(false);
    }

    pub fn unlink(src_dir: RawFd, cache_dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
        maybe_unlinkat(cache_dir, path)?;
        return rlibc::unlinkat(src_dir, path, 0);
    }

    fn validate_cache(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        cache_valid_if_present: bool,
    ) -> error::Result<bool> {
        match File::openat(src_dir, path, rlibc::O_RDONLY, 0) {
            Ok(mut src_file) => {
                match File::openat(cache_dir, path, rlibc::O_RDONLY, 0) {
                    Ok(mut cache_file) => {
                        let valid: bool;
                        if cache_valid_if_present || Handle::is_pristine(&src_file, &cache_file)? {
                            valid = true;
                        } else {
                            valid = false;
                            rlibc::unlinkat(cache_dir, path, 0)?;
                        }
                        src_file.close()?;
                        cache_file.close()?;
                        return Ok(valid);
                    }
                    Err(e) => {
                        src_file.close()?;
                        if error::try_enoent(e)? {
                            return Ok(false);
                        }
                    }
                }
            }
            Err(e) => {
                if error::try_enoent(e)? {
                    // the source file doesn't exist, the cache file shouldn't either
                    maybe_unlinkat(cache_dir, path)?;
                }
            }
        }

        return Ok(false);
    }

    pub fn read(&self, offset: u64, buf: &mut [u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_read: usize = 0;

        while bytes_read < nwant {
            match self.cache_file.read_at(
                &mut buf[bytes_read..],
                offset + (bytes_read as u64),
            ) {
                Ok(nread) => {
                    if nread == 0 {
                        return Ok(bytes_read);
                    }
                    bytes_read += nread;
                }
                Err(e) => {
                    if bytes_read > 0 {
                        return Ok(bytes_read);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        return Ok(bytes_read);
    }

    pub fn write(&mut self, offset: u64, buf: &[u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_written: usize = 0;

        if !self.dirty {
            // assumes that the metadata will hit the disk before the
            // incoming data will, and not flushing
            self.set_pristine(false)?;
        }

        while bytes_written < nwant {
            match self.cache_file.write_at(
                &buf[bytes_written..],
                offset + (bytes_written as u64),
            ) {
                Ok(nwritten) => {
                    if nwritten == 0 {
                        return Ok(bytes_written);
                    }
                    bytes_written += nwritten;
                }
                Err(e) => {
                    if bytes_written > 0 {
                        return Ok(bytes_written);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        if bytes_written != 0 {
            self.dirty = true;
        }

        return Ok(bytes_written);
    }

    pub fn flush(&mut self) -> error::Result<(bool)> {
        let mut flushed_to_src = false;
        if self.dirty {
            self.copy(false)?;
            self.dirty = false;
            self.cache_file.flush()?;
            // if file was opened for read only and cache is valid we
            // might not have opened src_file
            if self.src_file.valid() {
                self.src_file.flush()?;
                flushed_to_src = true;
            }
        }
        return Ok(flushed_to_src);
    }

    fn copy_user(rh: &File, wh: &File) -> error::Result<()> {
        let mut buf = [0u8; 32 * 1024];
        let mut offset = 0;
        loop {
            let nread = rh.read_at(&mut buf, offset)?;
            if nread == 0 {
                break;
            }
            wh.write_at(&buf[..nread], offset)?;
            offset += nread as u64;
        }
        return Ok(());
    }

    fn copy_splice(rh: &File, wh: &File) -> error::Result<()> {
        let (pin, pout) = rlibc::pipe()?;

        let mut offset = 0;
        loop {
            let nread = rlibc::splice(rh.as_raw_fd(), offset, pout, -1, 128 * 1024)?;
            if nread == 0 {
                break;
            }

            let mut written = 0;
            while written < nread {
                let nxfer = rlibc::splice(pin, -1, wh.as_raw_fd(), offset, 128 * 1024)?;

                written += nxfer;
                offset += nxfer as i64;
            }
        }

        if let Err(e) = rlibc::close(pin) {
            rlibc::close(pout)?;
            return Err(RError::from(e));
        } else {
            rlibc::close(pout)?;
        }

        return Ok(());
    }

    fn copy(&self, to_cache: bool) -> error::Result<()> {
        let rh: &File;
        let wh: &File;
        if to_cache {
            rh = &self.src_file;
            wh = &self.cache_file;
        } else {
            rh = &self.cache_file;
            wh = &self.src_file;
        }

        let size = rh.filesize()?;
        if size < wh.filesize()? {
            wh.truncate(size)?;
        }

        if let Err(e) = Handle::copy_splice(rh, wh) {
            if e.raw_os_error().unwrap() == libc::EINVAL {
                Handle::copy_user(rh, wh)?;
            }
        }

        self.set_pristine(true)?;
        return Ok(());
    }
}

impl Drop for Handle {
    fn drop(&mut self) {
        if let Err(e) = self.cache_file.close() {
            error!("!close(cache) = {}", RError::from(e));
        }
        if self.src_file.valid() {
            if let Err(e) = self.src_file.close() {
                error!("!close(src) = {}", RError::from(e));
            }
        }
    }
}
<commit_msg>print more debug info when checksum doesn't match<commit_after>extern crate generic_array;
extern crate libc;
extern crate sha2;
extern crate xattr;

use std::ffi::{OsStr, OsString};
use std::io;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::RawFd;
use std::path::{Component, Path, PathBuf};

use self::generic_array::GenericArray;
use self::generic_array::typenum::U64;
use self::sha2::{Sha512, Digest};
use self::xattr::FileExt;

use catfs::error;
use catfs::error::RError;
use catfs::rlibc;
use catfs::rlibc::File;

pub struct Handle {
    src_file: File,
    cache_file: File,
    dirty: bool,
}

// no-op to workaround the fact that we send the entire CatFS at start
// time, but we never send anything. Could have used Unique but that
// bounds us to rust nightly
unsafe impl Send for Handle {}

fn make_rdwr(f: &mut u32) {
    *f = (*f & !rlibc::O_ACCMODE) | rlibc::O_RDWR;
}

fn maybe_unlinkat(dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
    if let Err(e) = rlibc::unlinkat(dir, path, 0) {
        if !error::is_enoent(&e) {
            return Err(e);
        }
    }
    return Ok(());
}

pub fn mkdirat_all(dir: RawFd, path: &AsRef<Path>, mode: u32) -> io::Result<()> {
    let mut p = PathBuf::new();

    for c in path.as_ref().components() {
        if let Component::Normal(n) = c {
            p.push(n);

            if let Err(e) = rlibc::mkdirat(dir, &p, mode) {
                if e.raw_os_error().unwrap() != libc::EEXIST {
                    return Err(e);
                }
            }
        }
    }

    return Ok(());
}

impl Handle {
    pub fn create(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        mode: u32,
    ) -> error::Result<Handle> {
        // need to read the cache file for writeback
        let mut cache_flags = flags.clone();
        if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut cache_flags);
        }
        //debug!("create {:b} {:b} {:#o}", flags, cache_flags, mode);

        if let Some(parent) = path.as_ref().parent() {
            mkdirat_all(cache_dir, &parent, 0o777)?;
        }

        let src_file = File::openat(src_dir, path, flags, mode)?;
        // we are able to create the src file, then the cache file
        // shouldn't be here, but it could be because of bug/crash,
        // so unlink it first
        maybe_unlinkat(cache_dir, path)?;

        return Ok(Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, mode)?,
            dirty: true,
        });
    }

    pub fn open(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        flags: u32,
        cache_valid_if_present: bool,
    ) -> error::Result<Handle> {
        // even if file is open for write only, I still need to be
        // able to read the src for read-modify-write
        let mut flags = flags;
        if (flags & rlibc::O_ACCMODE) == rlibc::O_WRONLY {
            make_rdwr(&mut flags);
        }

        let valid = Handle::validate_cache(src_dir, cache_dir, &path, cache_valid_if_present)?;
        debug!(
            "{:?} {} a valid cache file",
            path.as_ref(),
            if valid { "is" } else { "is not" },
        );
        let mut cache_flags = flags.clone();

        if !valid {
            // mkdir the parents
            if let Some(parent) = path.as_ref().parent() {
                mkdirat_all(cache_dir, &parent, 0o777)?;
            }
            // need to cache this file so need to open it for write
            cache_flags |= rlibc::O_CREAT;
            if (cache_flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
                make_rdwr(&mut cache_flags);
            }
        }

        let src_file: File;
        if valid && (flags & rlibc::O_ACCMODE) == rlibc::O_RDONLY {
            src_file = Default::default();
        } else {
            src_file = File::openat(src_dir, path, flags, 0o666)?;
        }

        let handle = Handle {
            src_file: src_file,
            cache_file: File::openat(cache_dir, path, cache_flags, 0o666)?,
            dirty: false,
        };

        if !valid && (flags & rlibc::O_TRUNC) == 0 {
            debug!("read ahead {:?}", path.as_ref());
            handle.copy(true)?;
        }

        return Ok(handle);
    }

    // the equivalent of:
    // getfattr -e hex --match=.* -d $f 2>/dev/null | grep =;
    // /usr/bin/stat -t --printf "%Y\n%s\n" $f
    // note that mtime is printed first and then size
    pub fn src_str_to_checksum(f: &File) -> error::Result<OsString> {
        let mut s = OsString::new();
        match f.list_xattr() {
            Ok(attrs) => {
                for x in attrs {
                    if let Some(v) = f.get_xattr(&x)? {
                        s.push(x);
                        s.push(OsStr::new("="));
                        s.push("0x");
                        for b in v {
                            s.push(format!("{:x}", b));
                        }
                        s.push("\n");
                    }
                }
            }
            Err(e) => {
                if e.raw_os_error().unwrap() != libc::ENOTSUP {
                    return Err(RError::from(e));
                }
            }
        }

        let st = f.stat()?;
        s.push(format!("{}\n", st.st_mtime));
        s.push(format!("{}\n", st.st_size));
        return Ok(s);
    }

    fn src_chksum(f: &File) -> error::Result<GenericArray<u8, U64>> {
        let s = Handle::src_str_to_checksum(f)?;
        debug!("checksum is {:?}", s);
        let mut h = Sha512::default();
        h.input(s.as_bytes());
        return Ok(h.result());
    }

    fn set_pristine(&self, pristine: bool) -> error::Result<()> {
        if pristine {
            self.cache_file.set_xattr(
                "user.catfs.src_chksum",
                Handle::src_chksum(&self.src_file)?
                    .as_slice(),
            )?;
        } else {
            self.cache_file.remove_xattr("user.catfs.src_chksum")?;
        }
        return Ok(());
    }

    fn is_pristine(src_file: &File, cache_file: &File) -> error::Result<bool> {
        if let Some(v) = cache_file.get_xattr("user.catfs.src_chksum")? {
            let expected = Handle::src_chksum(src_file)?;
            if v == expected.as_slice() {
                return Ok(true);
            } else {
                debug!("{:?} != {:?}, {} {}", v, expected, v.len(), expected.len());
                return Ok(false);
            }
        }
        debug!("user.catfs.src_chksum missing for cache_file");

        return Ok(false);
    }

    pub fn unlink(src_dir: RawFd, cache_dir: RawFd, path: &AsRef<Path>) -> io::Result<()> {
        maybe_unlinkat(cache_dir, path)?;
        return rlibc::unlinkat(src_dir, path, 0);
    }

    fn validate_cache(
        src_dir: RawFd,
        cache_dir: RawFd,
        path: &AsRef<Path>,
        cache_valid_if_present: bool,
    ) -> error::Result<bool> {
        match File::openat(src_dir, path, rlibc::O_RDONLY, 0) {
            Ok(mut src_file) => {
                match File::openat(cache_dir, path, rlibc::O_RDONLY, 0) {
                    Ok(mut cache_file) => {
                        let valid: bool;
                        if cache_valid_if_present || Handle::is_pristine(&src_file, &cache_file)? {
                            valid = true;
                        } else {
                            valid = false;
                            rlibc::unlinkat(cache_dir, path, 0)?;
                        }
                        src_file.close()?;
                        cache_file.close()?;
                        return Ok(valid);
                    }
                    Err(e) => {
                        src_file.close()?;
                        if error::try_enoent(e)? {
                            return Ok(false);
                        }
                    }
                }
            }
            Err(e) => {
                if error::try_enoent(e)? {
                    // the source file doesn't exist, the cache file shouldn't either
                    maybe_unlinkat(cache_dir, path)?;
                }
            }
        }

        return Ok(false);
    }

    pub fn read(&self, offset: u64, buf: &mut [u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_read: usize = 0;

        while bytes_read < nwant {
            match self.cache_file.read_at(
                &mut buf[bytes_read..],
                offset + (bytes_read as u64),
            ) {
                Ok(nread) => {
                    if nread == 0 {
                        return Ok(bytes_read);
                    }
                    bytes_read += nread;
                }
                Err(e) => {
                    if bytes_read > 0 {
                        return Ok(bytes_read);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        return Ok(bytes_read);
    }

    pub fn write(&mut self, offset: u64, buf: &[u8]) -> error::Result<usize> {
        let nwant = buf.len();
        let mut bytes_written: usize = 0;

        if !self.dirty {
            // assumes that the metadata will hit the disk before the
            // incoming data will, and not flushing
            self.set_pristine(false)?;
        }

        while bytes_written < nwant {
            match self.cache_file.write_at(
                &buf[bytes_written..],
                offset + (bytes_written as u64),
            ) {
                Ok(nwritten) => {
                    if nwritten == 0 {
                        return Ok(bytes_written);
                    }
                    bytes_written += nwritten;
                }
                Err(e) => {
                    if bytes_written > 0 {
                        return Ok(bytes_written);
                    } else {
                        return Err(RError::from(e));
                    }
                }
            }
        }

        if bytes_written != 0 {
            self.dirty = true;
        }

        return Ok(bytes_written);
    }

    pub fn flush(&mut self) -> error::Result<(bool)> {
        let mut flushed_to_src = false;
        if self.dirty {
            self.copy(false)?;
            self.dirty = false;
            self.cache_file.flush()?;
            // if file was opened for read only and cache is valid we
            // might not have opened src_file
            if self.src_file.valid() {
                self.src_file.flush()?;
                flushed_to_src = true;
            }
        }
        return Ok(flushed_to_src);
    }

    fn copy_user(rh: &File, wh: &File) -> error::Result<()> {
        let mut buf = [0u8; 32 * 1024];
        let mut offset = 0;
        loop {
            let nread = rh.read_at(&mut buf, offset)?;
            if nread == 0 {
                break;
            }
            wh.write_at(&buf[..nread], offset)?;
            offset += nread as u64;
        }
        return Ok(());
    }

    fn copy_splice(rh: &File, wh: &File) -> error::Result<()> {
        let (pin, pout) = rlibc::pipe()?;

        let mut offset = 0;
        loop {
            let nread = rlibc::splice(rh.as_raw_fd(), offset, pout, -1, 128 * 1024)?;
            if nread == 0 {
                break;
            }

            let mut written = 0;
            while written < nread {
                let nxfer = rlibc::splice(pin, -1, wh.as_raw_fd(), offset, 128 * 1024)?;

                written += nxfer;
                offset += nxfer as i64;
            }
        }

        if let Err(e) = rlibc::close(pin) {
            rlibc::close(pout)?;
            return Err(RError::from(e));
        } else {
            rlibc::close(pout)?;
        }

        return Ok(());
    }

    fn copy(&self, to_cache: bool) -> error::Result<()> {
        let rh: &File;
        let wh: &File;
        if to_cache {
            rh = &self.src_file;
            wh = &self.cache_file;
        } else {
            rh = &self.cache_file;
            wh = &self.src_file;
        }

        let size = rh.filesize()?;
        if size < wh.filesize()? {
            wh.truncate(size)?;
        }

        if let Err(e) = Handle::copy_splice(rh, wh) {
            if e.raw_os_error().unwrap() == libc::EINVAL {
                Handle::copy_user(rh, wh)?;
            }
        }

        self.set_pristine(true)?;
        return Ok(());
    }
}

impl Drop for Handle {
    fn drop(&mut self) {
        if let Err(e) = self.cache_file.close() {
            error!("!close(cache) = {}", RError::from(e));
        }
        if self.src_file.valid() {
            if let Err(e) = self.src_file.close() {
                error!("!close(src) = {}", RError::from(e));
            }
        }
    }
}
<|endoftext|>


<commit_before>use hyper;
use serde_json;

pub use self::param::*;
use self::http::TwitchHttpClient;
use error::Result;
use model;

mod param;
mod http;

pub struct TwitchClient {
    http_client: TwitchHttpClient,
}

impl TwitchClient {
    pub fn new() -> TwitchClient {
        TwitchClientBuilder::new().build()
    }
}

pub struct TwitchClientBuilder {
    client_id: Option<String>,
    hyper_client: Option<hyper::Client>,
}

impl TwitchClientBuilder {
    pub fn new() -> TwitchClientBuilder {
        TwitchClientBuilder {
            client_id: None,
            hyper_client: None,
        }
    }

    pub fn client_id(mut self, client_id: String) -> TwitchClientBuilder {
        self.client_id = Some(client_id);
        self
    }

    pub fn hyper_client(mut self, hyper_client: hyper::Client) -> TwitchClientBuilder {
        self.hyper_client = Some(hyper_client);
        self
    }

    pub fn build(self) -> TwitchClient {
        let client = match self.hyper_client {
            Some(client) => client,
            None => hyper::Client::new(),
        };

        TwitchClient {
            http_client: TwitchHttpClient::new(self.client_id, client),
        }
    }
}

impl TwitchClient {
    pub fn get_top_games(&self, params: &TopGamesParams) -> Result<model::game::TopGames> {
        let response = try!(self.http_client.get_content_with_params("/games/top", params));
        let top_games: model::game::TopGames = try!(serde_json::from_str(&response));
        Ok(top_games)
    }

    pub fn get_ingests(&self) -> Result<model::ingest::Ingests> {
        let response = try!(self.http_client.get_content("/ingests"));
        let ingests: model::ingest::Ingests = try!(serde_json::from_str(&response));
        Ok(ingests)
    }

    pub fn get_basic_info(&self) -> Result<model::root::BasicInfo> {
        let response = try!(self.http_client.get_content("/"));
        let basic_info: model::root::BasicInfo = try!(serde_json::from_str(&response));
        Ok(basic_info)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;
    use std::io::Read;
    use serde_json;

    #[test]
    fn test_get_top_games_default_params() {
        let client = create_test_twitch_client();
        let top_games = client.get_top_games(&TopGamesParams::default()).unwrap();
        assert_eq!(top_games.link_self(), "https://api.twitch.tv/kraken/games/top?limit=10&offset=0");
        assert_eq!(top_games.link_next(), "https://api.twitch.tv/kraken/games/top?limit=10&offset=10");
        assert!(top_games.total() > 0, "top_games.total() = {} > 0", top_games.total());
        assert_eq!(top_games.top().len(), 10);
    }

    #[test]
    fn test_get_top_games_custom_params() {
        let client = create_test_twitch_client();
        let params = TopGamesParamsBuilder::default()
                .offset(0)
                .limit(2)
                .build();
        let top_games = client.get_top_games(&params).unwrap();
        assert_eq!(top_games.link_self(), "https://api.twitch.tv/kraken/games/top?limit=2&offset=0");
        assert_eq!(top_games.link_next(), "https://api.twitch.tv/kraken/games/top?limit=2&offset=2");
        assert!(top_games.total() > 0, "top_games.total() = {} > 0", top_games.total());
        assert_eq!(top_games.top().len(), 2);
    }

    #[test]
    fn test_get_ingests() {
        let client = create_test_twitch_client();
        let ingests = client.get_ingests().unwrap();
        assert_eq!(ingests.link_self(), "https://api.twitch.tv/kraken/ingests");
        assert!(ingests.ingests().len() > 0, "ingests.ingests().len() = {} > 0", ingests.ingests().len());
    }

    #[test]
    fn test_get_basic_info() {
        let client = create_test_twitch_client();
        let basic_info = client.get_basic_info().unwrap();
        assert_eq!(basic_info.link_user(), "https://api.twitch.tv/kraken/user");
        assert_eq!(basic_info.link_channel(), "https://api.twitch.tv/kraken/channel");
        assert_eq!(basic_info.link_search(), "https://api.twitch.tv/kraken/search");
        assert_eq!(basic_info.link_streams(), "https://api.twitch.tv/kraken/streams");
        assert_eq!(basic_info.link_ingests(), "https://api.twitch.tv/kraken/ingests");
        assert_eq!(basic_info.link_teams(), "https://api.twitch.tv/kraken/teams");
        assert!(basic_info.link_users().is_none(), "expecting no link for unauthenticated access");
        assert!(basic_info.link_channels().is_none(), "expecting no link for unauthenticated access");
        assert!(basic_info.link_chat().is_none(), "expecting no link for unauthenticated access");
        let token = basic_info.token();
        assert!(!token.valid(), "expecting invalid token for unauthenticated access");
        assert!(token.user_name().is_none(), "expecting no user name for unauthenticated access");
        assert!(token.authorization().is_none(), "expecting no auth info for unauthenticated access");
    }

    fn create_test_twitch_client() -> TwitchClient {
        let auth = read_auth();
        let twitch_client = TwitchClientBuilder::new()
            .client_id(auth.client_id)
            .build();
        twitch_client
    }

    fn read_auth() -> Auth {
        let mut auth_file = match File::open("twitch_auth.json") {
            Ok(file) => file,
            Err(err) => panic!("
            Tests create a new TwitchClient with info specified in 'twitch_auth.json'.
            As this info should be kept private it is not committed to the vcs.
            You need to create the file in order to run these tests.
            An template named 'twitch_auth_template.json' is provided at the project root.
            Original error: {}
            ", err),
        };
        let mut auth_string = String::new();
        auth_file.read_to_string(&mut auth_string).unwrap();
        let auth: Auth = serde_json::from_str(&auth_string).unwrap();
        auth
    }

    #[derive(Deserialize, Debug)]
    struct Auth {
        name: String,
        client_id: String,
        redirect_uri: Option<String>,
        client_secret: Option<String>,
    }
}
<commit_msg>Removed "get_" prefix from client function names<commit_after>use hyper;
use serde_json;

pub use self::param::*;
use self::http::TwitchHttpClient;
use error::Result;
use model;

mod param;
mod http;

pub struct TwitchClient {
    http_client: TwitchHttpClient,
}

impl TwitchClient {
    pub fn new() -> TwitchClient {
        TwitchClientBuilder::new().build()
    }
}

pub struct TwitchClientBuilder {
    client_id: Option<String>,
    hyper_client: Option<hyper::Client>,
}

impl TwitchClientBuilder {
    pub fn new() -> TwitchClientBuilder {
        TwitchClientBuilder {
            client_id: None,
            hyper_client: None,
        }
    }

    pub fn client_id(mut self, client_id: String) -> TwitchClientBuilder {
        self.client_id = Some(client_id);
        self
    }

    pub fn hyper_client(mut self, hyper_client: hyper::Client) -> TwitchClientBuilder {
        self.hyper_client = Some(hyper_client);
        self
    }

    pub fn build(self) -> TwitchClient {
        let client = match self.hyper_client {
            Some(client) => client,
            None => hyper::Client::new(),
        };

        TwitchClient {
            http_client: TwitchHttpClient::new(self.client_id, client),
        }
    }
}

impl TwitchClient {
    pub fn top_games(&self, params: &TopGamesParams) -> Result<model::game::TopGames> {
        let response = try!(self.http_client.get_content_with_params("/games/top", params));
        let top_games: model::game::TopGames = try!(serde_json::from_str(&response));
        Ok(top_games)
    }

    pub fn ingests(&self) -> Result<model::ingest::Ingests> {
        let response = try!(self.http_client.get_content("/ingests"));
        let ingests: model::ingest::Ingests = try!(serde_json::from_str(&response));
        Ok(ingests)
    }

    pub fn basic_info(&self) -> Result<model::root::BasicInfo> {
        let response = try!(self.http_client.get_content("/"));
        let basic_info: model::root::BasicInfo = try!(serde_json::from_str(&response));
        Ok(basic_info)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs::File;
    use std::io::Read;
    use serde_json;

    #[test]
    fn test_top_games_with_default_params() {
        let client = create_test_twitch_client();
        let top_games = client.top_games(&TopGamesParams::default()).unwrap();
        assert_eq!(top_games.link_self(), "https://api.twitch.tv/kraken/games/top?limit=10&offset=0");
        assert_eq!(top_games.link_next(), "https://api.twitch.tv/kraken/games/top?limit=10&offset=10");
        assert!(top_games.total() > 0, "top_games.total() = {} > 0", top_games.total());
        assert_eq!(top_games.top().len(), 10);
    }

    #[test]
    fn test_top_games_with_custom_params() {
        let client = create_test_twitch_client();
        let params = TopGamesParamsBuilder::default()
                .offset(0)
                .limit(2)
                .build();
        let top_games = client.top_games(&params).unwrap();
        assert_eq!(top_games.link_self(), "https://api.twitch.tv/kraken/games/top?limit=2&offset=0");
        assert_eq!(top_games.link_next(), "https://api.twitch.tv/kraken/games/top?limit=2&offset=2");
        assert!(top_games.total() > 0, "top_games.total() = {} > 0", top_games.total());
        assert_eq!(top_games.top().len(), 2);
    }

    #[test]
    fn test_ingests() {
        let client = create_test_twitch_client();
        let ingests = client.ingests().unwrap();
        assert_eq!(ingests.link_self(), "https://api.twitch.tv/kraken/ingests");
        assert!(ingests.ingests().len() > 0, "ingests.ingests().len() = {} > 0", ingests.ingests().len());
    }

    #[test]
    fn test_basic_info() {
        let client = create_test_twitch_client();
        let basic_info = client.basic_info().unwrap();
        assert_eq!(basic_info.link_user(), "https://api.twitch.tv/kraken/user");
        assert_eq!(basic_info.link_channel(), "https://api.twitch.tv/kraken/channel");
        assert_eq!(basic_info.link_search(), "https://api.twitch.tv/kraken/search");
        assert_eq!(basic_info.link_streams(), "https://api.twitch.tv/kraken/streams");
        assert_eq!(basic_info.link_ingests(), "https://api.twitch.tv/kraken/ingests");
        assert_eq!(basic_info.link_teams(), "https://api.twitch.tv/kraken/teams");
        assert!(basic_info.link_users().is_none(), "expecting no link for unauthenticated access");
        assert!(basic_info.link_channels().is_none(), "expecting no link for unauthenticated access");
        assert!(basic_info.link_chat().is_none(), "expecting no link for unauthenticated access");
        let token = basic_info.token();
        assert!(!token.valid(), "expecting invalid token for unauthenticated access");
        assert!(token.user_name().is_none(), "expecting no user name for unauthenticated access");
        assert!(token.authorization().is_none(), "expecting no auth info for unauthenticated access");
    }

    fn create_test_twitch_client() -> TwitchClient {
        let auth = read_auth();
        let twitch_client = TwitchClientBuilder::new()
            .client_id(auth.client_id)
            .build();
        twitch_client
    }

    fn read_auth() -> Auth {
        let mut auth_file = match File::open("twitch_auth.json") {
            Ok(file) => file,
            Err(err) => panic!("
            Tests create a new TwitchClient with info specified in 'twitch_auth.json'.
            As this info should be kept private it is not committed to the vcs.
            You need to create the file in order to run these tests.
            An template named 'twitch_auth_template.json' is provided at the project root.
            Original error: {}
            ", err),
        };
        let mut auth_string = String::new();
        auth_file.read_to_string(&mut auth_string).unwrap();
        let auth: Auth = serde_json::from_str(&auth_string).unwrap();
        auth
    }

    #[derive(Deserialize, Debug)]
    struct Auth {
        name: String,
        client_id: String,
        redirect_uri: Option<String>,
        client_secret: Option<String>,
    }
}
<|endoftext|>