<commit_before>#![windows_subsystem(windows)]

#[cfg(windows)] extern crate winrt;
#[macro_use]    extern crate error_chain;
extern crate chrono;

#[macro_use] mod util;
mod config;
mod error;
mod feed;
mod math;
mod notify;

use config::Config;
use chrono::{Utc, Timelike};
use feed::Feed;
use feed::statistics::{AverageData, ListenerStats};
use std::time::Duration;
use std::path::{Path, PathBuf};

error_chain! {
    links {
        Config(config::Error, config::ErrorKind);
        Feed(feed::Error, feed::ErrorKind);
        Statistics(feed::statistics::Error, feed::statistics::ErrorKind);
        Notify(notify::Error, notify::ErrorKind);
    }

    foreign_links {
        Io(std::io::Error);
    }
}

fn main() {
    #[cfg(windows)]
    let rt = winrt::RuntimeContext::init();

    match start() {
        Ok(_) => (),
        Err(err) => eprintln!("fatal error: {:?}", err),
    }

    #[cfg(windows)]
    rt.uninit();
}

fn start() -> Result<()> {
    let exe_dir = get_exe_directory()?;
    let config_path = exe_dir.clone().join("config.yaml");

    let mut averages = AverageData::new(exe_dir.join("averages.csv"));

    if averages.path.exists() {
        averages.load()?;
    }

    loop {
        match perform_update(&mut averages, &config_path) {
            Ok(_) => (),
            Err(err) => error::display(&err),
        }

        std::thread::sleep(Duration::from_secs(5 * 60));
    }
}

fn perform_update(averages: &mut AverageData, config_path: &Path) -> Result<()> {
    let config = Config::from_file(config_path)?;
    let hour = Utc::now().hour();

    let mut display_feeds = Vec::new();

    for feed in Feed::download_and_scrape(&config)? {
        if feed.listeners < config.misc.minimum_listeners {
            continue
        }

        let stats = update_feed_stats(hour, &feed, &config, averages);

        if cfg!(feature = "print-feed-data") {
            print_info(&feed, &stats);
        }

        if stats.has_spiked || feed.alert.is_some() {
            display_feeds.push((feed, stats.clone()));
        }
    }

    show_feeds(display_feeds)?;

    averages.save()?;
    Ok(())
}

fn update_feed_stats<'a>(hour: u32, feed: &Feed, config: &Config, averages: &'a mut AverageData)
    -> &'a ListenerStats {

    let stats = averages.data
        .entry(feed.id)
        .or_insert(ListenerStats::new());

    stats.update(hour as usize, feed, &config);
    stats
}

fn show_feeds(mut feeds: Vec<(Feed, ListenerStats)>) -> Result<()> {
    feeds.sort_by_key(|&(ref f, _)| f.listeners);

    let total = feeds.len() as i32;

    for (i, (feed, stats)) in feeds.into_iter().enumerate() {
        notify::create_update(1 + i as i32, total, &feed, &stats)?;
    }

    Ok(())
}

fn print_info(feed: &Feed, stats: &ListenerStats) {
    println!("[{}] {}", feed.id, feed.name);
    println!("\tlisteners    | {}", feed.listeners);

    println!("\taverage lis. | cur: {} last: {} samples: {:?}",
        stats.average.current,
        stats.average.last,
        stats.average.data);

    println!("\tunskewed avg | {:?}", stats.unskewed_average);
    println!("\thas spiked   | {}", stats.has_spiked);
    println!("\ttimes spiked | {}", stats.spike_count);
}

fn get_exe_directory() -> Result<PathBuf> {
    let mut path = std::env::current_exe()?;
    path.pop();
    Ok(path)
}<commit_msg>Use config update time instead of a hard-coded value<commit_after>#![windows_subsystem(windows)]

#[cfg(windows)] extern crate winrt;
#[macro_use]    extern crate error_chain;
extern crate chrono;

#[macro_use] mod util;
mod config;
mod error;
mod feed;
mod math;
mod notify;

use config::Config;
use chrono::{Utc, Timelike};
use feed::Feed;
use feed::statistics::{AverageData, ListenerStats};
use std::time::Duration;
use std::path::PathBuf;

error_chain! {
    links {
        Config(config::Error, config::ErrorKind);
        Feed(feed::Error, feed::ErrorKind);
        Statistics(feed::statistics::Error, feed::statistics::ErrorKind);
        Notify(notify::Error, notify::ErrorKind);
    }

    foreign_links {
        Io(std::io::Error);
    }
}

fn main() {
    #[cfg(windows)]
    let rt = winrt::RuntimeContext::init();

    match start() {
        Ok(_) => (),
        Err(err) => eprintln!("fatal error: {:?}", err),
    }

    #[cfg(windows)]
    rt.uninit();
}

fn start() -> Result<()> {
    let exe_dir = get_exe_directory()?;
    let config_path = exe_dir.clone().join("config.yaml");

    let mut averages = AverageData::new(exe_dir.join("averages.csv"));

    if averages.path.exists() {
        averages.load()?;
    }

    loop {
        let config = Config::from_file(&config_path)?;

        match perform_update(&mut averages, &config) {
            Ok(_) => (),
            Err(err) => error::display(&err),
        }

        std::thread::sleep(Duration::from_secs((config.misc.update_time * 60.0) as u64));
    }
}

fn perform_update(averages: &mut AverageData, config: &Config) -> Result<()> {
    let hour = Utc::now().hour();
    let mut display_feeds = Vec::new();

    for feed in Feed::download_and_scrape(&config)? {
        if feed.listeners < config.misc.minimum_listeners {
            continue
        }

        let stats = update_feed_stats(hour, &feed, &config, averages);

        if cfg!(feature = "print-feed-data") {
            print_info(&feed, &stats);
        }

        if stats.has_spiked || feed.alert.is_some() {
            display_feeds.push((feed, stats.clone()));
        }
    }

    show_feeds(display_feeds)?;

    averages.save()?;
    Ok(())
}

fn update_feed_stats<'a>(hour: u32, feed: &Feed, config: &Config, averages: &'a mut AverageData)
    -> &'a ListenerStats {

    let stats = averages.data
        .entry(feed.id)
        .or_insert(ListenerStats::new());

    stats.update(hour as usize, feed, &config);
    stats
}

fn show_feeds(mut feeds: Vec<(Feed, ListenerStats)>) -> Result<()> {
    feeds.sort_by_key(|&(ref f, _)| f.listeners);

    let total = feeds.len() as i32;

    for (i, (feed, stats)) in feeds.into_iter().enumerate() {
        notify::create_update(1 + i as i32, total, &feed, &stats)?;
    }

    Ok(())
}

fn print_info(feed: &Feed, stats: &ListenerStats) {
    println!("[{}] {}", feed.id, feed.name);
    println!("\tlisteners    | {}", feed.listeners);

    println!("\taverage lis. | cur: {} last: {} samples: {:?}",
        stats.average.current,
        stats.average.last,
        stats.average.data);

    println!("\tunskewed avg | {:?}", stats.unskewed_average);
    println!("\thas spiked   | {}", stats.has_spiked);
    println!("\ttimes spiked | {}", stats.spike_count);
}

fn get_exe_directory() -> Result<PathBuf> {
    let mut path = std::env::current_exe()?;
    path.pop();
    Ok(path)
}<|endoftext|>


<commit_before>#![allow(dead_code)]

#[macro_use]
extern crate error_chain;
extern crate fs2;
extern crate libc;
extern crate tempdir;
extern crate walkdir;

use std::hash::{Hash, Hasher};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::{env, mem, process};

mod cargo;
mod errors;
mod flock;
mod fs;
mod io;
mod parse;
mod rustc;
mod sysroot;
mod toml;
mod xargo;

use parse::Args;
use errors::*;

fn main() {
    match run() {
        Err(e) => {
            writeln!(io::stderr(), "{:?}", e).ok();
            process::exit(1);
        }
        Ok(status) => {
            if !status.success() {
                process::exit(status.code().unwrap_or(1))
            }
        }
    }
}

fn run() -> Result<ExitStatus> {
    let Args { mut cmd, target, subcommand, verbose } = try!(parse::args());

    let target = if let Some(target) = target.as_ref() {
        Some(try!(Target::from(target)))
    } else {
        if let Some(target) = try!(parse::target_in_cargo_config()) {
            Some(try!(Target::from(&target)))
        } else {
            None
        }
    };

    let needs_sysroot = match subcommand.as_ref().map(|s| &s[..]) {
        // we need rebuild the sysroot for these subcommands
        None | Some("clean") | Some("init") | Some("new") |
        Some("update") | Some("search") => None,
        _ => {
            if let Some(target) = target.as_ref() {
                if target.needs_sysroot() {
                    try!(sysroot::update(target, verbose));
                    Some(target)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };

    let locks = if let Some(target) = needs_sysroot {
        // TODO(Filesystem.display()) this could be better ...
        let sysroot = format!("{}", try!(xargo::home()).display());

        if subcommand.as_ref().map(|s| &s[..]) == Some("doc") {
            let mut flags = try!(rustc::flags(target, "rustdocflags"));
            flags.push("--sysroot".to_owned());

            flags.push(sysroot.clone());

            cmd.env("RUSTDOCFLAGS", flags.join(" "));
        }

        let mut flags = try!(rustc::flags(target, "rustflags"));
        flags.push("--sysroot".to_owned());

        // TODO(Filesystem.display()) this could be better ...
        flags.push(sysroot);

        cmd.env("RUSTFLAGS", flags.join(" "));

        // Make sure the sysroot is not blown up while the Cargo command is running
        Some((xargo::lock_ro(&try!(rustc::meta()).host),
              xargo::lock_ro(target.triple())))
    } else {
        None
    };

    let status = try!(cmd.status()
        .chain_err(|| "failed to execute `cargo`. Is it not installed?"));

    mem::drop(locks);

    Ok(status)
}

#[derive(Debug)]
pub enum Target {
    BuiltIn { triple: String },
    Custom { triple: String, json: PathBuf },
    Path { triple: String, json: PathBuf },
}

impl Target {
    fn from(target: &str) -> Result<Target> {
        let target_list = try!(rustc::target_list());

        let target = target.to_owned();
        if target_list.iter().any(|t| t == &target) {
            Ok(Target::BuiltIn { triple: target })
        } else {
            if target.ends_with(".json") {
                if let Some(triple) = Path::new(&target)
                    .file_stem()
                    .and_then(|f| f.to_str()) {
                    Ok(Target::Path {
                        json: PathBuf::from(&target),
                        triple: triple.to_owned(),
                    })
                } else {
                    try!(Err(format!("error extracting triple from {}",
                                     target)))
                }
            } else {
                let json = Path::new(&target).with_extension("json");

                if json.exists() {
                    Ok(Target::Custom {
                        json: json,
                        triple: target,
                    })
                } else {
                    if let Some(target_dir) = env::var_os("RUST_TARGET_PATH") {
                        let json = PathBuf::from(target_dir)
                            .join(&target)
                            .with_extension("json");

                        if json.exists() {
                            return Ok(Target::Custom {
                                json: json,
                                triple: target,
                            });
                        }
                    }

                    try!(Err(format!("no target specification file found \
                                      for {}",
                                     target)))
                }
            }
        }
    }
}

impl Target {
    fn has_atomics(&self) -> Result<bool> {
        let mut cmd = rustc::rustc();
        cmd.arg("--target");

        match *self {
            Target::BuiltIn { ref triple } => {
                cmd.arg(triple);
            }
            Target::Custom { ref triple, .. } => {
                cmd.arg(triple);
            }
            Target::Path { ref json, .. } => {
                cmd.arg(json);
            }
        }

        cmd.args(&["--print", "cfg"]);

        Ok(try!(cmd.run_and_get_stdout()).contains("target_has_atomic"))
    }

    fn hash<H>(&self, hasher: &mut H) -> Result<()>
        where H: Hasher
    {
        match *self {
            Target::BuiltIn { .. } => {}
            Target::Custom { ref json, .. } |
            Target::Path { ref json, .. } => try!(io::read(json)).hash(hasher),
        }

        Ok(())
    }

    fn needs_sysroot(&self) -> bool {
        match *self {
            Target::BuiltIn { ref triple } => {
                match &triple[..] {
                    "thumbv6m-none-eabi" |
                    "thumbv7m-none-eabi" |
                    "thumbv7em-none-eabi" |
                    "thumbv7em-none-eabihf" => true,
                    _ => false,
                }
            }
            _ => true,
        }
    }

    fn triple(&self) -> &str {
        match *self {
            Target::BuiltIn { ref triple } |
            Target::Custom { ref triple, .. } |
            Target::Path { ref triple, .. } => triple,
        }
    }
}

trait CommandExt {
    fn run_and_get_stdout(&mut self) -> Result<String>;
    fn run_or_error(&mut self) -> Result<()>;
}

impl CommandExt for Command {
    fn run_and_get_stdout(&mut self) -> Result<String> {
        let cmd = &format!("`{:?}`", self);

        let output = try!(self.output()
            .chain_err(|| format!("failed to execute {}", cmd)));

        if !output.status.success() {
            try!(Err(format!("{} failed with exit status: {:?}",
                             cmd,
                             output.status.code())))
        }

        Ok(try!(String::from_utf8(output.stdout)
            .chain_err(|| format!("{} output was not UTF-8 encoded", cmd))))
    }

    fn run_or_error(&mut self) -> Result<()> {
        let cmd = &format!("`{:?}`", self);

        let exit = try!(self.status()
            .chain_err(|| format!("failed to execute {}", cmd)));

        if !exit.success() {
            try!(Err(format!("{} failed with exit status {:?}",
                             cmd,
                             exit.code())))
        }

        Ok(())
    }
}
<commit_msg>nicer errors messages<commit_after>#![allow(dead_code)]

#[macro_use]
extern crate error_chain;
extern crate fs2;
extern crate libc;
extern crate tempdir;
extern crate walkdir;

use std::hash::{Hash, Hasher};
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
use std::{env, mem, process};

mod cargo;
mod errors;
mod flock;
mod fs;
mod io;
mod parse;
mod rustc;
mod sysroot;
mod toml;
mod xargo;

use parse::Args;
use errors::*;

fn main() {
    fn show_backtrace() -> bool {
        env::var("RUST_BACKTRACE").as_ref().map(|s| &s[..]) == Ok("1")
    }

    match run() {
        Err(e) => {
            let stderr = io::stderr();
            let mut stderr = stderr.lock();

            writeln!(stderr, "{}", e).ok();

            for e in e.iter().skip(1) {
                writeln!(stderr, "caused by: {}", e).ok();
            }

            if show_backtrace() {
                if let Some(backtrace) = e.backtrace() {
                    writeln!(stderr, "{:?}", backtrace).ok();
                }
            }

            process::exit(1)
        },
        Ok(status) => {
            if !status.success() {
                process::exit(status.code().unwrap_or(1))
            }
        }
    }
}

fn run() -> Result<ExitStatus> {
    let Args { mut cmd, target, subcommand, verbose } = try!(parse::args());

    let target = if let Some(target) = target.as_ref() {
        Some(try!(Target::from(target)))
    } else {
        if let Some(target) = try!(parse::target_in_cargo_config()) {
            Some(try!(Target::from(&target)))
        } else {
            None
        }
    };

    let needs_sysroot = match subcommand.as_ref().map(|s| &s[..]) {
        // we don't need to rebuild the sysroot for these subcommands
        None | Some("clean") | Some("init") | Some("new") |
        Some("update") | Some("search") => None,
        _ => {
            if let Some(target) = target.as_ref() {
                if target.needs_sysroot() {
                    try!(sysroot::update(target, verbose));
                    Some(target)
                } else {
                    None
                }
            } else {
                None
            }
        }
    };

    let locks = if let Some(target) = needs_sysroot {
        // TODO(Filesystem.display()) this could be better ...
        let sysroot = format!("{}", try!(xargo::home()).display());

        if subcommand.as_ref().map(|s| &s[..]) == Some("doc") {
            let mut flags = try!(rustc::flags(target, "rustdocflags"));
            flags.push("--sysroot".to_owned());

            flags.push(sysroot.clone());

            cmd.env("RUSTDOCFLAGS", flags.join(" "));
        }

        let mut flags = try!(rustc::flags(target, "rustflags"));
        flags.push("--sysroot".to_owned());

        // TODO(Filesystem.display()) this could be better ...
        flags.push(sysroot);

        cmd.env("RUSTFLAGS", flags.join(" "));

        // Make sure the sysroot is not blown up while the Cargo command is running
        Some((xargo::lock_ro(&try!(rustc::meta()).host),
              xargo::lock_ro(target.triple())))
    } else {
        None
    };

    let status = try!(cmd.status()
        .chain_err(|| "failed to execute `cargo`. Is it not installed?"));

    mem::drop(locks);

    Ok(status)
}

#[derive(Debug)]
pub enum Target {
    BuiltIn { triple: String },
    Custom { triple: String, json: PathBuf },
    Path { triple: String, json: PathBuf },
}

impl Target {
    fn from(target: &str) -> Result<Target> {
        let target_list = try!(rustc::target_list());

        let target = target.to_owned();
        if target_list.iter().any(|t| t == &target) {
            Ok(Target::BuiltIn { triple: target })
        } else {
            if target.ends_with(".json") {
                if let Some(triple) = Path::new(&target)
                    .file_stem()
                    .and_then(|f| f.to_str()) {
                    Ok(Target::Path {
                        json: PathBuf::from(&target),
                        triple: triple.to_owned(),
                    })
                } else {
                    try!(Err(format!("error extracting triple from {}",
                                     target)))
                }
            } else {
                let json = Path::new(&target).with_extension("json");

                if json.exists() {
                    Ok(Target::Custom {
                        json: json,
                        triple: target,
                    })
                } else {
                    if let Some(target_dir) = env::var_os("RUST_TARGET_PATH") {
                        let json = PathBuf::from(target_dir)
                            .join(&target)
                            .with_extension("json");

                        if json.exists() {
                            return Ok(Target::Custom {
                                json: json,
                                triple: target,
                            });
                        }
                    }

                    try!(Err(format!("no target specification file found \
                                      for {}",
                                     target)))
                }
            }
        }
    }
}

impl Target {
    fn has_atomics(&self) -> Result<bool> {
        let mut cmd = rustc::rustc();
        cmd.arg("--target");

        match *self {
            Target::BuiltIn { ref triple } => {
                cmd.arg(triple);
            }
            Target::Custom { ref triple, .. } => {
                cmd.arg(triple);
            }
            Target::Path { ref json, .. } => {
                cmd.arg(json);
            }
        }

        cmd.args(&["--print", "cfg"]);

        Ok(try!(cmd.run_and_get_stdout()).contains("target_has_atomic"))
    }

    fn hash<H>(&self, hasher: &mut H) -> Result<()>
        where H: Hasher
    {
        match *self {
            Target::BuiltIn { .. } => {}
            Target::Custom { ref json, .. } |
            Target::Path { ref json, .. } => try!(io::read(json)).hash(hasher),
        }

        Ok(())
    }

    fn needs_sysroot(&self) -> bool {
        match *self {
            Target::BuiltIn { ref triple } => {
                match &triple[..] {
                    "thumbv6m-none-eabi" |
                    "thumbv7m-none-eabi" |
                    "thumbv7em-none-eabi" |
                    "thumbv7em-none-eabihf" => true,
                    _ => false,
                }
            }
            _ => true,
        }
    }

    fn triple(&self) -> &str {
        match *self {
            Target::BuiltIn { ref triple } |
            Target::Custom { ref triple, .. } |
            Target::Path { ref triple, .. } => triple,
        }
    }
}

trait CommandExt {
    fn run_and_get_stdout(&mut self) -> Result<String>;
    fn run_or_error(&mut self) -> Result<()>;
}

impl CommandExt for Command {
    fn run_and_get_stdout(&mut self) -> Result<String> {
        let cmd = &format!("`{:?}`", self);

        let output = try!(self.output()
            .chain_err(|| format!("failed to execute {}", cmd)));

        if !output.status.success() {
            try!(Err(format!("{} failed with exit status: {:?}",
                             cmd,
                             output.status.code())))
        }

        Ok(try!(String::from_utf8(output.stdout)
            .chain_err(|| format!("{} output was not UTF-8 encoded", cmd))))
    }

    fn run_or_error(&mut self) -> Result<()> {
        let cmd = &format!("`{:?}`", self);

        let exit = try!(self.status()
            .chain_err(|| format!("failed to execute {}", cmd)));

        if !exit.success() {
            try!(Err(format!("{} failed with exit status {:?}",
                             cmd,
                             exit.code())))
        }

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


<commit_before>use std::ptr::null_mut;
struct Node<T> {
    val: T,
    next: *mut Box<Node<T>>
}

struct LinkedList<T> {
    head: *mut Box<Node<T>>,
    tail: *mut Box<Node<T>>,
}

impl<T> LinkedList<T> {
    fn new() -> LinkedList<T> {
        return LinkedList {
            head: null_mut(),
            tail: null_mut(),
        };

    }
//    fn append(&self, val: T) {
//        let
//    }
}

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}
impl Person {
    fn new() -> Person {
        return Person {
            name: "pzwu".to_string(),
            age: 25,
        };
    }
}

impl Drop for Person {
    fn drop(&mut self) {
        println!("drop self.name = {:#?}", self.name);
    }

}
fn main() {
    {
        let mut p: Box<Person> = Box::new(Person {
            name: "1".to_string(),
            age: 199,
        });

        let mut raw_p = &mut p as *mut Box<Person>;
        //    let ref_p = &p;
        //    let raw_p_2_ref = &mut unsafe {*raw_p};
        unsafe {
            *raw_p = Box::new(Person {
                name: "2".to_string(),
                age: 200,
            });

            println!("raw_p = {:#?}", *raw_p);

            let mut raw_p_2_ref = &mut *raw_p;
            let mut raw_p_2_ref1 = &mut *raw_p;

            let mut_borrow_p = &mut p;

            println!("mut_borrow_p = {:#?}", mut_borrow_p);
            println!("raw_p_2_ref = {:#?}", raw_p_2_ref);
            println!("raw_p_2_ref1 = {:#?}", raw_p_2_ref1);

            *raw_p_2_ref = Box::new(Person {
                name: "3".to_string(),
                age: 300,
            });
        }

        let mut raw_move_p = null_mut();

        {
            let mut move_p = p;
            raw_move_p = &mut move_p as *mut Box<Person>
        }
        //    *raw_p = Box::new(20);

        for i in 0..2 {
            let temp = Box::new(Person {
                name: "temp".to_string(),
                age: i,
            });
        }
//        let mut_borrow_p = &mut p;
        unsafe {
            println!("raw_move_p = {:#?}", *raw_move_p);
        }

//        println!("mut_borrow_p = {:#?}", mut_borrow_p);
        println!("inner end");
    }

    println!("end!");

}<commit_msg>execise<commit_after>use std::ptr::null_mut;

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}
impl Person {
    fn new() -> Person {
        return Person {
            name: "pzwu".to_string(),
            age: 25,
        };
    }
}

impl Drop for Person {
    fn drop(&mut self) {
        println!("drop self.name = {:#?}", self.name);
    }

}
fn main() {
    {
        let mut p: Box<Person> = Box::new(Person {
            name: "1".to_string(),
            age: 199,
        });

        let mut raw_p = &mut p as *mut Box<Person>;
        //    let ref_p = &p;
        //    let raw_p_2_ref = &mut unsafe {*raw_p};
        unsafe {
            *raw_p = Box::new(Person {
                name: "2".to_string(),
                age: 200,
            });

            println!("raw_p = {:#?}", *raw_p);

            let mut raw_p_2_ref = &mut *raw_p;
            let mut raw_p_2_ref1 = &mut *raw_p;

            let mut_borrow_p = &mut p;

            println!("mut_borrow_p = {:#?}", mut_borrow_p);
            println!("raw_p_2_ref = {:#?}", raw_p_2_ref);
            println!("raw_p_2_ref1 = {:#?}", raw_p_2_ref1);

            *raw_p_2_ref = Box::new(Person {
                name: "3".to_string(),
                age: 300,
            });
        }

        let mut raw_move_p = null_mut();

        {
            let mut move_p = p;
            raw_move_p = &mut move_p as *mut Box<Person>
        }
        //    *raw_p = Box::new(20);

        for i in 0..2 {
            let temp = Box::new(Person {
                name: "temp".to_string(),
                age: i,
            });
        }
//        let mut_borrow_p = &mut p;
        unsafe {
            println!("raw_move_p = {:#?}", *raw_move_p);
        }

//        println!("mut_borrow_p = {:#?}", mut_borrow_p);
        println!("inner end");
    }

    println!("end!");

}<|endoftext|>


<commit_before>#[macro_use]
mod macros;

mod git;

extern crate getopts;
extern crate hyper;
extern crate oauth2;
extern crate queryst;
extern crate rustc_serialize;
extern crate tempdir;
extern crate unicase;
extern crate url;
extern crate uuid;

use getopts::Options;
use hyper::header;
use hyper::net::Openssl;
use hyper::server::{Server, Request, Response};
use hyper::status::StatusCode;
use hyper::uri::RequestUri::AbsolutePath;
use oauth2::Config;
use rustc_serialize::Encodable;
use rustc_serialize::json::{self, Json};
use queryst::parse;
use std::collections::{HashMap, HashSet};
use std::env;
use std::error::Error;
use std::io::Read;
use std::string::String;
use std::sync::{Arc, Mutex};
use unicase::UniCase;
use url::{parse_path, Url};
use self::uuid::Uuid;

fn main() {
    let (listen_address, ssl, oauth_config, oauth_redirect_path, whitelisted_domains) = parse_flags();

    let handler = Handler{
        oauth_config: oauth_config,
        oauth_redirect_path: oauth_redirect_path,
        whitelisted_domains: whitelisted_domains,
        redirect_uris: Arc::new(Mutex::new(HashMap::new())),
    };

    println!("Listening on {}", listen_address);
    if ssl.is_some() {
        Server::https(&listen_address[..], ssl.unwrap()).unwrap().handle(handler).unwrap();
    } else {
        Server::http(&listen_address[..]).unwrap().handle(handler).unwrap();
    }
}

fn parse_flags() -> (String, Option<Openssl>, oauth2::Config, String, HashSet<String>) {
    let mut opts = Options::new();

    opts.optopt("p", "port", "Port on which to listen", "3000");
    opts.optopt("i", "interface", "Interface on which to listen", "0.0.0.0");

    opts.optflag("s", "ssl", "Whether to listen for HTTPS or just HTTP");
    opts.optopt("c", "certfile", "SSL certificate file", "/path/to/foo.crt");
    opts.optopt("k", "keyfile", "SSL key file", "/path/to/foo.key");

    opts.optopt("h", "host_and_port", "Hostname and port", "127.0.0.1:443");
    opts.optopt("", "oauth_client_id", "Github oauth client ID", "clientid");
    opts.optopt("", "oauth_client_secret", "Github oauth client secret", "clientsecret");
    opts.optopt("", "oauth_redirect_path", "Github oauth redirect URL", "/oauth/redirect");

    opts.optopt("d", "whitelisted_domains", "Hostname and port of domains to which to allow redirection (comma-separated)", "");

    let args: Vec<String> = env::args().collect();
    let matches = opts.parse(&args[1..]).unwrap();
    let interface = matches.opt_str("i").unwrap_or("0.0.0.0".to_owned());
    let port = matches.opt_str("p").unwrap_or("3000".to_owned());

    let ssl = match matches.opt_present("s") {
        true => Some(Openssl::with_cert_and_key(matches.opt_str("c").unwrap(), matches.opt_str("k").unwrap()).unwrap()),
        false => None,
    };

    let mut oauth_config = oauth2::Config::new(
        &matches.opt_str("oauth_client_id").unwrap(),
        &matches.opt_str("oauth_client_secret").unwrap(),
        "https://github.com/login/oauth/authorize",
        "https://github.com/login/oauth/access_token"
    );
    let oauth_redirect_path = matches.opt_str("oauth_redirect_path").unwrap();
    oauth_config.redirect_url = format!("https://{}{}", matches.opt_str("host_and_port").unwrap(), oauth_redirect_path);

    let whitelisted_domains = matches.opt_str("d").unwrap()
        .split(",")
        .map(|x| x.to_string())
        .collect();

    let host_and_port = format!("{}:{}", interface, port);
    return (host_and_port, ssl, oauth_config, oauth_redirect_path, whitelisted_domains);
}

struct Handler {
    oauth_config: oauth2::Config,
    oauth_redirect_path: String,
    whitelisted_domains: HashSet<String>,
    redirect_uris: Arc<Mutex<HashMap<String, String>>>,
}

impl hyper::server::Handler for Handler {
    fn handle(&self, req: Request, mut res: Response) {
        let (path, qs) = match req.uri {
            AbsolutePath(ref p) => match parse_path(p) {
                Ok((path, qs, _)) => (format!("/{}", path.join("/")), qs),
                Err(err) => { self.error(res, err.description()); return },
            },
            _ => { self.not_found(res); return },
        };

        res.headers_mut().set(header::AccessControlAllowOrigin::Any);
        res.headers_mut().set(header::AccessControlAllowHeaders(vec![UniCase("content-type".to_owned())]));

        if req.method == hyper::method::Method::Options {
            return
        }

        match &path[..] {
            "/squashmerge" => match req.method {
                hyper::Post => { self.handle_request(req, res, Handler::handle_squash_request) },
                _ => self.not_allowed(res)
            },
            "/rewritehistory" => match req.method {
                hyper::Post => { self.handle_request(req, res, Handler::handle_rewrite_history_request) },
                _ => self.not_allowed(res),
            },
            "/oauth/authorize" => match req.method {
                hyper::Get => self.handle_oauth_authorize_request(qs, res),
                _ => self.not_allowed(res),
            },
            _ => {
                if path == &self.oauth_redirect_path[..] {
                    match req.method {
                        hyper::Get => match qs {
                            Some(q) => self.handle_oauth_exchange_request(&q, res),
                            None => self.bad_request(res),
                        },
                        _ => self.not_allowed(res),
                    }
                } else {
                    self.not_found(res);
                }
            },
        };
    }
}

impl Handler {
    fn handle_request<F>(&self, mut req: Request, res: Response, handler: F)
        where F : Fn(&str) -> Result<String, String> {

        let mut body_string = String::new();
        let err = req.read_to_string(&mut body_string);
        if err.is_err() {
            self.error(res, &err.err().unwrap().description());
            return;
        }

        match handler(&body_string) {
            Ok(sha) => self.send_json(res, &ShaResponse { sha: &sha, merged: true }),
            Err(err) => self.error(res, &err),
        };
    }

    fn handle_squash_request(body_string: &str) -> Result<String, String> {
        let body = try_or_string!(Json::from_str(body_string));

        return git::squash_merge(
            get_json_string!(body, "base_repo"),
            get_json_string!(body, "base_branch"),
            get_json_string!(body, "head_repo"),
            get_json_string!(body, "head_branch"),
            get_json_string!(body, "commit_message"),
            get_json_string!(body, "username"),
            get_json_string!(body, "password"),
            get_json_string!(body, "committer_name"),
            get_json_string!(body, "committer_email"),
        );
    }

    fn handle_rewrite_history_request(body_string: &str) -> Result<String, String> {
        let body = try_or_string!(Json::from_str(body_string));

        return git::rewrite_history(
            get_json_string!(body, "repo"),
            get_json_string!(body, "branch"),
            get_json_string!(body, "baseline_repo"),
            get_json_string!(body, "baseline_branch"),
            get_json_string!(body, "commit_message"),
            get_json_string!(body, "username"),
            get_json_string!(body, "password"),
            get_json_string!(body, "committer_name"),
            get_json_string!(body, "committer_email"),
        );
    }

    fn handle_oauth_authorize_request(&self, qs: Option<String>, mut res: Response) {
        let uuid = Uuid::new_v4().to_hyphenated_string();

        let redirect_uri = self.get_redirect_uri(&qs);

        if redirect_uri.is_none() {
            self.bad_request(res);
            return;
        }

        let mut uris = self.redirect_uris.lock().unwrap();
        uris.insert(uuid.clone(), redirect_uri.unwrap().to_owned());

        // TODO: Implement clone for oauth2::Config
        let mut oauth_config = oauth2::Config::new(
            &self.oauth_config.client_id,
            &self.oauth_config.client_secret,
            &self.oauth_config.auth_url.to_string(),
            &self.oauth_config.token_url.to_string(),
        );
        oauth_config.redirect_url = self.oauth_config.redirect_url.clone();
        oauth_config.scopes.extend(self.get_scopes(&qs));

        let _ = *res.status_mut() = StatusCode::Found;
        res.headers_mut().set(header::Location(oauth_config.authorize_url(uuid).to_string()));
    }

    fn handle_oauth_exchange_request(&self, qs: &str, res: Response) {
        let obj = match parse(qs) {
            Ok(q) => q,
            Err(err) => { self.error(res, &format!("Error parsing querystring: {}", err.message)); return },
        };
        let code = match obj.find("code") {
            Some(c) => match c.as_string() {
                Some(c) => c,
                None => { self.error(res, "code not a string"); return },
            },
            None => { self.error(res, "No code present"); return },
        };
        let state = match obj.find("state") {
            Some(c) => match c.as_string() {
                Some(c) => c,
                None => { self.error(res, "state not a string"); return },
            },
            None => { self.error(res, "No state present"); return },
        };
        let token = self.oauth_config.exchange(code.to_owned());
        match token {
            Ok(tok) => self.respond_with_token(res, state, &tok.access_token),
            Err(tok) => self.error(res, &format!("Error exchanging token: {}", tok)),
        };
    }

    fn respond_with_token(&self, mut res: Response, uuid: &str, token: &str) {
        let mut uris = self.redirect_uris.lock().unwrap();
        match uris.remove(uuid) {
            Some(uri) => {
                let _ = *res.status_mut() = StatusCode::Found;
                // TODO: Form URI better
                res.headers_mut().set(header::Location(format!("{}?token={}", uri, token)));
            },
            None => { self.bad_request(res); },
        };
    }

    fn send_json<T: Encodable>(&self, mut res: Response, obj: &T) {
        match json::encode(obj) {
            Ok(o) => { let _ = res.send(o.as_bytes()); },
            Err(_) => {
                let _ = *res.status_mut() = StatusCode::InternalServerError;
                let _ = res.send("{\"message\": \"error serializing response\"}".as_bytes());
            },
        };
    }

    fn get_redirect_uri(&self, qs: &Option<String>) -> Option<String> {
        let string = self.get_string_qs(qs, "redirect_uri");
        if string.is_none() {
            return string
        }

        let url = Url::parse(&string.clone().unwrap());
        match url {
            Ok(u) => {
                if &u.scheme != "https" {
                    return None;
                }
                let domain = u.domain();
                if domain.is_none() {
                    return None;
                }
                if !self.is_whitelisted_domain(domain.unwrap()) {
                    return None;
                }
            },
            Err(_) => {return None; },
        };
        return string;
    }

    fn is_whitelisted_domain(&self, domain: &str) -> bool {
        return self.whitelisted_domains.contains(&domain.to_owned());
    }

    fn get_scopes(&self, qs: &Option<String>) -> Vec<String> {
        let val = self.get_string_qs(qs, "scopes")
            .clone()
            .unwrap_or("".to_owned());
        return val
            .split(",")
            .map(|x| x.to_string())
            .collect();
    }

    fn get_string_qs(&self, qs: &Option<String>, key: &str) -> Option<String> {
        let kv = match qs.clone() {
            Some(q) => match parse(&q) {
                Ok(kv) => kv,
                Err(_) => return None,
            },
            None => return None,
        };
        return kv.find(key)
            .and_then(|v| v.as_string())
            .and_then(|v| Some(v.to_owned().clone()));
    }

    fn error(&self, mut res: Response, description: &str) {
        let _ = *res.status_mut() = StatusCode::InternalServerError;
        self.send_json(res, &ErrorResponse { message: description });
    }

    fn bad_request(&self, mut res: Response) {
        let _ = *res.status_mut() = StatusCode::BadRequest;
        self.send_json(res, &ErrorResponse { message: "Bad request" });
    }

    fn not_allowed(&self, mut res: Response) {
        let _ = *res.status_mut() = StatusCode::MethodNotAllowed;
    }

    fn not_found(&self, mut res: Response) {
        let _ = *res.status_mut() = StatusCode::NotFound;
    }
}

#[derive(RustcEncodable)]
struct ShaResponse<'a> {
    sha: &'a str,
    merged: bool,
}

#[derive(RustcEncodable)]
struct ErrorResponse<'a> {
    message: &'a str,
}
<commit_msg>Say why request was bad<commit_after>#[macro_use]
mod macros;

mod git;

extern crate getopts;
extern crate hyper;
extern crate oauth2;
extern crate queryst;
extern crate rustc_serialize;
extern crate tempdir;
extern crate unicase;
extern crate url;
extern crate uuid;

use getopts::Options;
use hyper::header;
use hyper::net::Openssl;
use hyper::server::{Server, Request, Response};
use hyper::status::StatusCode;
use hyper::uri::RequestUri::AbsolutePath;
use oauth2::Config;
use rustc_serialize::Encodable;
use rustc_serialize::json::{self, Json};
use queryst::parse;
use std::collections::{HashMap, HashSet};
use std::env;
use std::error::Error;
use std::io::Read;
use std::string::String;
use std::sync::{Arc, Mutex};
use unicase::UniCase;
use url::{parse_path, Url};
use self::uuid::Uuid;

fn main() {
    let (listen_address, ssl, oauth_config, oauth_redirect_path, whitelisted_domains) = parse_flags();

    let handler = Handler{
        oauth_config: oauth_config,
        oauth_redirect_path: oauth_redirect_path,
        whitelisted_domains: whitelisted_domains,
        redirect_uris: Arc::new(Mutex::new(HashMap::new())),
    };

    println!("Listening on {}", listen_address);
    if ssl.is_some() {
        Server::https(&listen_address[..], ssl.unwrap()).unwrap().handle(handler).unwrap();
    } else {
        Server::http(&listen_address[..]).unwrap().handle(handler).unwrap();
    }
}

fn parse_flags() -> (String, Option<Openssl>, oauth2::Config, String, HashSet<String>) {
    let mut opts = Options::new();

    opts.optopt("p", "port", "Port on which to listen", "3000");
    opts.optopt("i", "interface", "Interface on which to listen", "0.0.0.0");

    opts.optflag("s", "ssl", "Whether to listen for HTTPS or just HTTP");
    opts.optopt("c", "certfile", "SSL certificate file", "/path/to/foo.crt");
    opts.optopt("k", "keyfile", "SSL key file", "/path/to/foo.key");

    opts.optopt("h", "host_and_port", "Hostname and port", "127.0.0.1:443");
    opts.optopt("", "oauth_client_id", "Github oauth client ID", "clientid");
    opts.optopt("", "oauth_client_secret", "Github oauth client secret", "clientsecret");
    opts.optopt("", "oauth_redirect_path", "Github oauth redirect URL", "/oauth/redirect");

    opts.optopt("d", "whitelisted_domains", "Hostname and port of domains to which to allow redirection (comma-separated)", "");

    let args: Vec<String> = env::args().collect();
    let matches = opts.parse(&args[1..]).unwrap();
    let interface = matches.opt_str("i").unwrap_or("0.0.0.0".to_owned());
    let port = matches.opt_str("p").unwrap_or("3000".to_owned());

    let ssl = match matches.opt_present("s") {
        true => Some(Openssl::with_cert_and_key(matches.opt_str("c").unwrap(), matches.opt_str("k").unwrap()).unwrap()),
        false => None,
    };

    let mut oauth_config = oauth2::Config::new(
        &matches.opt_str("oauth_client_id").unwrap(),
        &matches.opt_str("oauth_client_secret").unwrap(),
        "https://github.com/login/oauth/authorize",
        "https://github.com/login/oauth/access_token"
    );
    let oauth_redirect_path = matches.opt_str("oauth_redirect_path").unwrap();
    oauth_config.redirect_url = format!("https://{}{}", matches.opt_str("host_and_port").unwrap(), oauth_redirect_path);

    let whitelisted_domains = matches.opt_str("d").unwrap()
        .split(",")
        .map(|x| x.to_string())
        .collect();

    let host_and_port = format!("{}:{}", interface, port);
    return (host_and_port, ssl, oauth_config, oauth_redirect_path, whitelisted_domains);
}

struct Handler {
    oauth_config: oauth2::Config,
    oauth_redirect_path: String,
    whitelisted_domains: HashSet<String>,
    redirect_uris: Arc<Mutex<HashMap<String, String>>>,
}

impl hyper::server::Handler for Handler {
    fn handle(&self, req: Request, mut res: Response) {
        let (path, qs) = match req.uri {
            AbsolutePath(ref p) => match parse_path(p) {
                Ok((path, qs, _)) => (format!("/{}", path.join("/")), qs),
                Err(err) => { self.error(res, err.description()); return },
            },
            _ => { self.not_found(res); return },
        };

        res.headers_mut().set(header::AccessControlAllowOrigin::Any);
        res.headers_mut().set(header::AccessControlAllowHeaders(vec![UniCase("content-type".to_owned())]));

        if req.method == hyper::method::Method::Options {
            return
        }

        match &path[..] {
            "/squashmerge" => match req.method {
                hyper::Post => { self.handle_request(req, res, Handler::handle_squash_request) },
                _ => self.not_allowed(res)
            },
            "/rewritehistory" => match req.method {
                hyper::Post => { self.handle_request(req, res, Handler::handle_rewrite_history_request) },
                _ => self.not_allowed(res),
            },
            "/oauth/authorize" => match req.method {
                hyper::Get => self.handle_oauth_authorize_request(qs, res),
                _ => self.not_allowed(res),
            },
            _ => {
                if path == &self.oauth_redirect_path[..] {
                    match req.method {
                        hyper::Get => match qs {
                            Some(q) => self.handle_oauth_exchange_request(&q, res),
                            None => self.bad_request(res, "Missing querstring"),
                        },
                        _ => self.not_allowed(res),
                    }
                } else {
                    self.not_found(res);
                }
            },
        };
    }
}

impl Handler {
    fn handle_request<F>(&self, mut req: Request, res: Response, handler: F)
        where F : Fn(&str) -> Result<String, String> {

        let mut body_string = String::new();
        let err = req.read_to_string(&mut body_string);
        if err.is_err() {
            self.error(res, &err.err().unwrap().description());
            return;
        }

        match handler(&body_string) {
            Ok(sha) => self.send_json(res, &ShaResponse { sha: &sha, merged: true }),
            Err(err) => self.error(res, &err),
        };
    }

    fn handle_squash_request(body_string: &str) -> Result<String, String> {
        let body = try_or_string!(Json::from_str(body_string));

        return git::squash_merge(
            get_json_string!(body, "base_repo"),
            get_json_string!(body, "base_branch"),
            get_json_string!(body, "head_repo"),
            get_json_string!(body, "head_branch"),
            get_json_string!(body, "commit_message"),
            get_json_string!(body, "username"),
            get_json_string!(body, "password"),
            get_json_string!(body, "committer_name"),
            get_json_string!(body, "committer_email"),
        );
    }

    fn handle_rewrite_history_request(body_string: &str) -> Result<String, String> {
        let body = try_or_string!(Json::from_str(body_string));

        return git::rewrite_history(
            get_json_string!(body, "repo"),
            get_json_string!(body, "branch"),
            get_json_string!(body, "baseline_repo"),
            get_json_string!(body, "baseline_branch"),
            get_json_string!(body, "commit_message"),
            get_json_string!(body, "username"),
            get_json_string!(body, "password"),
            get_json_string!(body, "committer_name"),
            get_json_string!(body, "committer_email"),
        );
    }

    fn handle_oauth_authorize_request(&self, qs: Option<String>, mut res: Response) {
        let uuid = Uuid::new_v4().to_hyphenated_string();

        let redirect_uri = self.get_redirect_uri(&qs);

        if redirect_uri.is_none() {
            self.bad_request(res, "Missing redirect_uri");
            return;
        }

        let mut uris = self.redirect_uris.lock().unwrap();
        uris.insert(uuid.clone(), redirect_uri.unwrap().to_owned());

        // TODO: Implement clone for oauth2::Config
        let mut oauth_config = oauth2::Config::new(
            &self.oauth_config.client_id,
            &self.oauth_config.client_secret,
            &self.oauth_config.auth_url.to_string(),
            &self.oauth_config.token_url.to_string(),
        );
        oauth_config.redirect_url = self.oauth_config.redirect_url.clone();
        oauth_config.scopes.extend(self.get_scopes(&qs));

        let _ = *res.status_mut() = StatusCode::Found;
        res.headers_mut().set(header::Location(oauth_config.authorize_url(uuid).to_string()));
    }

    fn handle_oauth_exchange_request(&self, qs: &str, res: Response) {
        let obj = match parse(qs) {
            Ok(q) => q,
            Err(err) => { self.error(res, &format!("Error parsing querystring: {}", err.message)); return },
        };
        let code = match obj.find("code") {
            Some(c) => match c.as_string() {
                Some(c) => c,
                None => { self.error(res, "code not a string"); return },
            },
            None => { self.error(res, "No code present"); return },
        };
        let state = match obj.find("state") {
            Some(c) => match c.as_string() {
                Some(c) => c,
                None => { self.error(res, "state not a string"); return },
            },
            None => { self.error(res, "No state present"); return },
        };
        let token = self.oauth_config.exchange(code.to_owned());
        match token {
            Ok(tok) => self.respond_with_token(res, state, &tok.access_token),
            Err(tok) => self.error(res, &format!("Error exchanging token: {}", tok)),
        };
    }

    fn respond_with_token(&self, mut res: Response, uuid: &str, token: &str) {
        let mut uris = self.redirect_uris.lock().unwrap();
        match uris.remove(uuid) {
            Some(uri) => {
                let _ = *res.status_mut() = StatusCode::Found;
                // TODO: Form URI better
                res.headers_mut().set(header::Location(format!("{}?token={}", uri, token)));
            },
            None => { self.bad_request(res, "Unknown caller"); },
        };
    }

    fn send_json<T: Encodable>(&self, mut res: Response, obj: &T) {
        match json::encode(obj) {
            Ok(o) => { let _ = res.send(o.as_bytes()); },
            Err(_) => {
                let _ = *res.status_mut() = StatusCode::InternalServerError;
                let _ = res.send("{\"message\": \"error serializing response\"}".as_bytes());
            },
        };
    }

    fn get_redirect_uri(&self, qs: &Option<String>) -> Option<String> {
        let string = self.get_string_qs(qs, "redirect_uri");
        if string.is_none() {
            return string
        }

        let url = Url::parse(&string.clone().unwrap());
        match url {
            Ok(u) => {
                if &u.scheme != "https" {
                    return None;
                }
                let domain = u.domain();
                if domain.is_none() {
                    return None;
                }
                if !self.is_whitelisted_domain(domain.unwrap()) {
                    return None;
                }
            },
            Err(_) => { return None; },
        };
        return string;
    }

    fn is_whitelisted_domain(&self, domain: &str) -> bool {
        return self.whitelisted_domains.contains(&domain.to_owned());
    }

    fn get_scopes(&self, qs: &Option<String>) -> Vec<String> {
        let val = self.get_string_qs(qs, "scopes")
            .clone()
            .unwrap_or("".to_owned());
        return val
            .split(",")
            .map(|x| x.to_string())
            .collect();
    }

    fn get_string_qs(&self, qs: &Option<String>, key: &str) -> Option<String> {
        let kv = match qs.clone() {
            Some(q) => match parse(&q) {
                Ok(kv) => kv,
                Err(_) => return None,
            },
            None => return None,
        };
        return kv.find(key)
            .and_then(|v| v.as_string())
            .and_then(|v| Some(v.to_owned().clone()));
    }

    fn error(&self, mut res: Response, description: &str) {
        let _ = *res.status_mut() = StatusCode::InternalServerError;
        self.send_json(res, &ErrorResponse { message: description });
    }

    fn bad_request(&self, mut res: Response, reason: &str) {
        let _ = *res.status_mut() = StatusCode::BadRequest;
        self.send_json(res, &ErrorResponse { message: &format!("Bad request: {}", reason) });
    }

    fn not_allowed(&self, mut res: Response) {
        let _ = *res.status_mut() = StatusCode::MethodNotAllowed;
    }

    fn not_found(&self, mut res: Response) {
        let _ = *res.status_mut() = StatusCode::NotFound;
    }
}

#[derive(RustcEncodable)]
struct ShaResponse<'a> {
    sha: &'a str,
    merged: bool,
}

#[derive(RustcEncodable)]
struct ErrorResponse<'a> {
    message: &'a str,
}
<|endoftext|>


<commit_before>#![forbid(unsafe_code)]

pub mod api;
pub mod db;
pub mod indexer;
pub mod model;
pub mod opening;
pub mod util;

use std::{mem, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};

use axum::{
    extract::{Extension, Path, Query},
    handler::get,
    http::StatusCode,
    AddExtensionLayer, Router,
};
use clap::Clap;
use futures_util::stream::Stream;
use shakmaty::{fen::Fen, variant::VariantPosition, zobrist::Zobrist, CastlingMode};
use tokio::sync::watch;

use crate::{
    api::{
        Error, GameRow, GameRowWithUci, PersonalMoveRow, PersonalQuery, PersonalQueryFilter,
        PersonalResponse,
    },
    db::Database,
    indexer::{IndexerOpt, IndexerStub},
    model::{PersonalKeyBuilder, PersonalKeyPrefix, UserId},
    opening::{Opening, Openings},
    util::NdJson,
};

#[derive(Clap)]
struct Opt {
    #[clap(long = "bind", default_value = "127.0.0.1:9000")]
    bind: SocketAddr,
    #[clap(long = "db", default_value = "_db")]
    db: PathBuf,
    #[clap(flatten)]
    indexer: IndexerOpt,
}

#[tokio::main]
async fn main() {
    env_logger::init_from_env(
        env_logger::Env::new()
            .filter("EXPLORER_LOG")
            .write_style("EXPLORER_LOG_STYLE"),
    );

    let opt = Opt::parse();

    let openings: &'static Openings = Box::leak(Box::new(Openings::build_table()));
    let db = Arc::new(Database::open(opt.db).expect("db"));
    let (indexer, join_handles) = IndexerStub::spawn(db.clone(), opt.indexer);

    let app = Router::new()
        .route("/admin/:prop", get(db_property))
        .route("/admin/game/:prop", get(game_property))
        .route("/admin/personal/:prop", get(personal_property))
        .route("/personal", get(personal))
        .layer(AddExtensionLayer::new(openings))
        .layer(AddExtensionLayer::new(db))
        .layer(AddExtensionLayer::new(indexer));

    axum::Server::bind(&opt.bind)
        .serve(app.into_make_service())
        .with_graceful_shutdown(async {
            tokio::signal::ctrl_c().await.expect("wait for ctrl-c");
        })
        .await
        .expect("bind");

    for join_handle in join_handles {
        join_handle.await.expect("indexer");
    }
}

async fn db_property(
    Extension(db): Extension<Arc<Database>>,
    Path(prop): Path<String>,
) -> Result<String, StatusCode> {
    db.queryable()
        .db_property(&prop)
        .expect("get property")
        .ok_or(StatusCode::NOT_FOUND)
}

async fn game_property(
    Extension(db): Extension<Arc<Database>>,
    Path(prop): Path<String>,
) -> Result<String, StatusCode> {
    db.queryable()
        .game_property(&prop)
        .expect("get property")
        .ok_or(StatusCode::NOT_FOUND)
}

async fn personal_property(
    Extension(db): Extension<Arc<Database>>,
    Path(prop): Path<String>,
) -> Result<String, StatusCode> {
    db.queryable()
        .personal_property(&prop)
        .expect("get property")
        .ok_or(StatusCode::NOT_FOUND)
}

struct PersonalStreamState {
    indexing: Option<watch::Receiver<()>>,
    key: PersonalKeyPrefix,
    db: Arc<Database>,
    filter: PersonalQueryFilter,
    pos: VariantPosition,
    opening: Option<&'static Opening>,
    first: bool,
    done: bool,
}

async fn personal(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(indexer): Extension<IndexerStub>,
    Query(query): Query<PersonalQuery>,
) -> Result<NdJson<impl Stream<Item = PersonalResponse>>, Error> {
    let player = UserId::from(query.player);
    let indexing = indexer.index_player(&player, query.update).await;

    let variant = query.variant.into();

    let mut pos = Zobrist::new(match query.fen {
        Some(fen) => VariantPosition::from_setup(variant, &Fen::from(fen), CastlingMode::Chess960)?,
        None => VariantPosition::new(variant),
    });

    let opening = openings.classify_and_play(&mut pos, query.play)?;

    let key = PersonalKeyBuilder::with_user_pov(&player, query.color)
        .with_zobrist(variant, pos.zobrist_hash());

    let state = PersonalStreamState {
        filter: query.filter,
        db,
        indexing,
        opening,
        key,
        pos: pos.into_inner(),
        first: true,
        done: false,
    };

    Ok(NdJson::new(futures_util::stream::unfold(
        state,
        |mut state| async move {
            if state.done {
                return None;
            }

            let first = mem::replace(&mut state.first, false);
            state.done = match state.indexing {
                Some(ref mut indexing) => {
                    tokio::select! {
                        _ = indexing.changed() => true,
                        _ = tokio::time::sleep(Duration::from_millis(if first { 200 } else { 1000 })) => false,
                    }
                }
                None => true,
            };
            let queryable = state.db.queryable();
            let filtered = queryable
                .get_personal(&state.key, state.filter.since, state.filter.until)
                .expect("get personal")
                .prepare(&state.pos, &state.filter);

            Some((
                PersonalResponse {
                    total: filtered.total,
                    moves: filtered
                        .moves
                        .into_iter()
                        .map(|row| PersonalMoveRow {
                            uci: row.uci,
                            san: row.san,
                            stats: row.stats,
                            game: row.game.and_then(|id| {
                                queryable
                                    .get_game_info(id)
                                    .expect("get game")
                                    .map(|info| GameRow { id, info })
                            }),
                        })
                        .collect(),
                    recent_games: filtered
                        .recent_games
                        .into_iter()
                        .flat_map(|(uci, id)| {
                            queryable.get_game_info(id).expect("get game").map(|info| {
                                GameRowWithUci {
                                    uci,
                                    row: GameRow { id, info },
                                }
                            })
                        })
                        .collect(),
                    opening: state.opening,
                },
                state,
            ))
        },
    )))
}
<commit_msg>shorter artificial delay for first poll after update<commit_after>#![forbid(unsafe_code)]

pub mod api;
pub mod db;
pub mod indexer;
pub mod model;
pub mod opening;
pub mod util;

use std::{mem, net::SocketAddr, path::PathBuf, sync::Arc, time::Duration};

use axum::{
    extract::{Extension, Path, Query},
    handler::get,
    http::StatusCode,
    AddExtensionLayer, Router,
};
use clap::Clap;
use futures_util::stream::Stream;
use shakmaty::{fen::Fen, variant::VariantPosition, zobrist::Zobrist, CastlingMode};
use tokio::sync::watch;

use crate::{
    api::{
        Error, GameRow, GameRowWithUci, PersonalMoveRow, PersonalQuery, PersonalQueryFilter,
        PersonalResponse,
    },
    db::Database,
    indexer::{IndexerOpt, IndexerStub},
    model::{PersonalKeyBuilder, PersonalKeyPrefix, UserId},
    opening::{Opening, Openings},
    util::NdJson,
};

#[derive(Clap)]
struct Opt {
    #[clap(long = "bind", default_value = "127.0.0.1:9000")]
    bind: SocketAddr,
    #[clap(long = "db", default_value = "_db")]
    db: PathBuf,
    #[clap(flatten)]
    indexer: IndexerOpt,
}

#[tokio::main]
async fn main() {
    env_logger::init_from_env(
        env_logger::Env::new()
            .filter("EXPLORER_LOG")
            .write_style("EXPLORER_LOG_STYLE"),
    );

    let opt = Opt::parse();

    let openings: &'static Openings = Box::leak(Box::new(Openings::build_table()));
    let db = Arc::new(Database::open(opt.db).expect("db"));
    let (indexer, join_handles) = IndexerStub::spawn(db.clone(), opt.indexer);

    let app = Router::new()
        .route("/admin/:prop", get(db_property))
        .route("/admin/game/:prop", get(game_property))
        .route("/admin/personal/:prop", get(personal_property))
        .route("/personal", get(personal))
        .layer(AddExtensionLayer::new(openings))
        .layer(AddExtensionLayer::new(db))
        .layer(AddExtensionLayer::new(indexer));

    axum::Server::bind(&opt.bind)
        .serve(app.into_make_service())
        .with_graceful_shutdown(async {
            tokio::signal::ctrl_c().await.expect("wait for ctrl-c");
        })
        .await
        .expect("bind");

    for join_handle in join_handles {
        join_handle.await.expect("indexer");
    }
}

async fn db_property(
    Extension(db): Extension<Arc<Database>>,
    Path(prop): Path<String>,
) -> Result<String, StatusCode> {
    db.queryable()
        .db_property(&prop)
        .expect("get property")
        .ok_or(StatusCode::NOT_FOUND)
}

async fn game_property(
    Extension(db): Extension<Arc<Database>>,
    Path(prop): Path<String>,
) -> Result<String, StatusCode> {
    db.queryable()
        .game_property(&prop)
        .expect("get property")
        .ok_or(StatusCode::NOT_FOUND)
}

async fn personal_property(
    Extension(db): Extension<Arc<Database>>,
    Path(prop): Path<String>,
) -> Result<String, StatusCode> {
    db.queryable()
        .personal_property(&prop)
        .expect("get property")
        .ok_or(StatusCode::NOT_FOUND)
}

struct PersonalStreamState {
    indexing: Option<watch::Receiver<()>>,
    key: PersonalKeyPrefix,
    db: Arc<Database>,
    filter: PersonalQueryFilter,
    pos: VariantPosition,
    opening: Option<&'static Opening>,
    first: bool,
    done: bool,
}

async fn personal(
    Extension(openings): Extension<&'static Openings>,
    Extension(db): Extension<Arc<Database>>,
    Extension(indexer): Extension<IndexerStub>,
    Query(query): Query<PersonalQuery>,
) -> Result<NdJson<impl Stream<Item = PersonalResponse>>, Error> {
    let player = UserId::from(query.player);
    let indexing = indexer.index_player(&player, query.update).await;

    let variant = query.variant.into();

    let mut pos = Zobrist::new(match query.fen {
        Some(fen) => VariantPosition::from_setup(variant, &Fen::from(fen), CastlingMode::Chess960)?,
        None => VariantPosition::new(variant),
    });

    let opening = openings.classify_and_play(&mut pos, query.play)?;

    let key = PersonalKeyBuilder::with_user_pov(&player, query.color)
        .with_zobrist(variant, pos.zobrist_hash());

    let state = PersonalStreamState {
        filter: query.filter,
        db,
        indexing,
        opening,
        key,
        pos: pos.into_inner(),
        first: true,
        done: false,
    };

    Ok(NdJson::new(futures_util::stream::unfold(
        state,
        |mut state| async move {
            if state.done {
                return None;
            }

            let first = mem::replace(&mut state.first, false);
            state.done = match state.indexing {
                Some(ref mut indexing) => {
                    tokio::select! {
                        _ = indexing.changed() => true,
                        _ = tokio::time::sleep(Duration::from_millis(if first { 60 } else { 1000 })) => false,
                    }
                }
                None => true,
            };

            let queryable = state.db.queryable();
            let filtered = queryable
                .get_personal(&state.key, state.filter.since, state.filter.until)
                .expect("get personal")
                .prepare(&state.pos, &state.filter);

            Some((
                PersonalResponse {
                    total: filtered.total,
                    moves: filtered
                        .moves
                        .into_iter()
                        .map(|row| PersonalMoveRow {
                            uci: row.uci,
                            san: row.san,
                            stats: row.stats,
                            game: row.game.and_then(|id| {
                                queryable
                                    .get_game_info(id)
                                    .expect("get game")
                                    .map(|info| GameRow { id, info })
                            }),
                        })
                        .collect(),
                    recent_games: filtered
                        .recent_games
                        .into_iter()
                        .flat_map(|(uci, id)| {
                            queryable.get_game_info(id).expect("get game").map(|info| {
                                GameRowWithUci {
                                    uci,
                                    row: GameRow { id, info },
                                }
                            })
                        })
                        .collect(),
                    opening: state.opening,
                },
                state,
            ))
        },
    )))
}
<|endoftext|>


<commit_before>extern crate gtk;
extern crate libnotify;
extern crate nix;

mod battery;
mod error;

use std::env;

use gtk::prelude::*;
use gtk::{ButtonsType, DialogFlags, MessageType, MessageDialog, Window};

use nix::unistd::{fork, ForkResult};

use crate::battery::Battery;
use crate::battery::BatteryStatus;
use crate::error::BatteryError;

const LEFT_CLICK: i32 = 1;
const RIGHT_CLICK: i32 = 3;
const CRITICAL_EXIT_CODE: i32 = 33;
const CRITICAL_BAT_LEVEL: u32 = 5;

fn console_output(bat: &Battery) {
    // Icons
    let plug          = '\u{f1e6}';
    let check         = '\u{f00c}';
    let full          = '\u{f240}';
    let three_quarter = '\u{f241}';
    let half          = '\u{f242}';
    let quarter       = '\u{f243}';
    let empty         = '\u{f244}';

    // Colors
    let green  = "#859900";
    let cyan   = "#2AA198";
    let yellow = "#B58900";
    let orange = "#CB4B16";
    let red    = "#DC322F";
    let bg     = "#073642";

    let mut percent = bat.percent_remaining();

    let (mut icon, mut color) = match percent {
        0  ... 5   => (empty, bg),
        6  ... 10  => (empty, red),
        11 ... 35  => (quarter, orange),
        36 ... 60  => (half, yellow),
        61 ... 85  => (three_quarter, cyan),
        86 ... 100 => (full, green),
        _          => std::process::exit(1),
    };

    match bat.charge_status {
        BatteryStatus::FULL => {
            icon = check;
            color = green;
            percent = 100;
        },
        BatteryStatus::CHARGING => {
            icon = plug;
        },
        _ => (),
    };

    let out = format!(" {} {}%\n",
                      color, icon, percent);
    print!("{0:}{0:}", out);

}

/*
 * Displays the GTK MessageDialog with information about the current status of
 * the battery. This contains all the fields that this program collects
 * information on.
 *
 * This function will return 0 on success and 1 on error.
 */
fn show_gtk_dialog(bat: &Battery) -> Result<(), &'static str> {
    if gtk::init().is_err() {
        return Err("Failed to initialize GTK");
    }

    let body_string = format!("Battery name:\t\t{}\n\
                              Battery charge:\t\t{}\n\
                              Charge when full:\t{}\n\
                              Design full:\t\t\t{}\n\
                              Cycle count:\t\t{}\n\
                              Charging status:\t{}\n\
                              Current now:\t\t{}\n\
                              Current avg:\t\t{}\n\
                              % Charged:\t\t\t{}%\n\
                              Time remaining:\t\t{}\n\
                              Battery health:\t\t{}%\n\
                              % Charged (abs):\t{}%\n",
                              bat.name, bat.charge_now, bat.charge_full,
                              bat.charge_full_design, bat.cycle_count,
                              bat.charge_status, bat.current_now,
                              bat.current_avg, bat.percent_remaining(),
                              bat.time_remaining(), bat.health(),
                              bat.abs_percent_remaining());

    MessageDialog::new(None::<&Window>,
                       DialogFlags::empty(),
                       MessageType::Info,
                       ButtonsType::Ok,
                       &body_string).run();
    Ok(())
}

fn get_env_var(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(ref val) if val.is_empty() => None,
        Ok(val) => Some(val),
        Err(_) => None
    }
}

fn show_notification(bat: &Battery) {
    let time_left = bat.time_remaining();
    libnotify::init("Battery").unwrap();
    let notification = libnotify::Notification::new("Time Remaining",
                                                    Some(&*time_left),
                                                    None);
    notification.show().unwrap();
    libnotify::uninit();
}

/*
 * For the purposes of i3blocks, 3 corresponds to a right click. When
 * that happens, fork() and then show a dialog. If we don't fork(), then
 * the dialog prevents this process from exiting and the block doesn't
 * update. The printing to the console is done in the parent thread.
 */
fn handle_button_presses(bat: &Battery) {
    let button: i32 = match get_env_var("BLOCK_BUTTON") {
        Some(val) => val.trim().parse().unwrap(),
        None => 0
    };

    match button {
        RIGHT_CLICK => {
            match fork() {
                Ok(ForkResult::Parent{ .. }) => (),
                Ok(ForkResult::Child) => {
                    if show_gtk_dialog(&bat).is_ok() {
                        std::process::exit(0);
                    } else {
                        std::process::exit(1);
                    }
                }
                Err(_) => eprintln!("Fork failed"),
            }
        },
        LEFT_CLICK => show_notification(&bat),
        _ => (),
    };
    console_output(&bat);
}

fn main() -> Result<(), BatteryError>{
    let battery_name = match get_env_var("BLOCK_INSTANCE") {
        Some(val) => val,
        None => "BAT0".to_owned(),
    };

    let bat = Battery::initialize(&battery_name)?;

    handle_button_presses(&bat);

    /*
     * i3blocks shows a block as critical if the process exits with code 33.
     * If the battery is less than 33% charged, the status is critical
     */
    if bat.percent_remaining() <= CRITICAL_BAT_LEVEL {
        std::process::exit(CRITICAL_EXIT_CODE);
    }

    Ok(())
}
<commit_msg>Cleanup GTK stats dialog<commit_after>extern crate gtk;
extern crate libnotify;
extern crate nix;

mod battery;
mod error;

use std::env;

use gtk::prelude::*;
use gtk::{Align, ButtonsType, DialogFlags, Grid, Label, MessageType, MessageDialog, Window};

use nix::unistd::{fork, ForkResult};

use crate::battery::Battery;
use crate::battery::BatteryStatus;
use crate::error::BatteryError;

const LEFT_CLICK: i32 = 1;
const RIGHT_CLICK: i32 = 3;
const CRITICAL_EXIT_CODE: i32 = 33;
const CRITICAL_BAT_LEVEL: u32 = 5;

fn console_output(bat: &Battery) {
    // Icons
    let plug          = '\u{f1e6}';
    let check         = '\u{f00c}';
    let full          = '\u{f240}';
    let three_quarter = '\u{f241}';
    let half          = '\u{f242}';
    let quarter       = '\u{f243}';
    let empty         = '\u{f244}';

    // Colors
    let green  = "#859900";
    let cyan   = "#2AA198";
    let yellow = "#B58900";
    let orange = "#CB4B16";
    let red    = "#DC322F";
    let bg     = "#073642";

    let mut percent = bat.percent_remaining();

    let (mut icon, mut color) = match percent {
        0  ... 5   => (empty, bg),
        6  ... 10  => (empty, red),
        11 ... 35  => (quarter, orange),
        36 ... 60  => (half, yellow),
        61 ... 85  => (three_quarter, cyan),
        86 ... 100 => (full, green),
        _          => std::process::exit(1),
    };

    match bat.charge_status {
        BatteryStatus::FULL => {
            icon = check;
            color = green;
            percent = 100;
        },
        BatteryStatus::CHARGING => {
            icon = plug;
        },
        _ => (),
    };

    let out = format!(" {} {}%\n",
                      color, icon, percent);
    print!("{0:}{0:}", out);

}

/// Creates a left-aligned GTK Label
fn create_label(content: &str) -> Label {
    let label = Label::new(Some(content));
    label.set_halign(Align::Start);
    return label;
}

/// Displays the GTK MessageDialog with information about the current status of
/// the battery. This contains all the fields that this program collects
/// information on.
/// This function will return 0 on success and 1 on error.
fn show_gtk_dialog(bat: &Battery) -> Result<(), &'static str> {
    if gtk::init().is_err() {
        return Err("Failed to initialize GTK");
    }

    let grid = Grid::new();

    let bat_name = format!("{}", bat.name);
    let bat_charge = format!("{} Ah", bat.charge_now);
    let charge_full = format!("{} Ah", bat.charge_full);
    let design_full = format!("{} Ah", bat.charge_full_design);
    let cycle_count = format!("{} cycles", bat.cycle_count);
    let charge_status = format!("{}", bat.charge_status);
    let current_now = format!("{} Ah", bat.current_now);
    let current_avg = format!("{} Ah", bat.current_avg);
    let percent = format!("{}%", bat.percent_remaining());
    let time = format!("{} hrs", bat.time_remaining());
    let health = format!("{}%", bat.health());
    let abs_percent = format!("{}%", bat.abs_percent_remaining());

    grid.attach(&create_label("Battery name:"), 0, 0, 1, 1);
    grid.attach(&create_label(bat_name.as_str()), 1, 0, 1, 1);
    grid.attach(&create_label("Battery charge:"), 0, 1, 1, 1);
    grid.attach(&create_label(bat_charge.as_str()), 1, 1, 1, 1);
    grid.attach(&create_label("Charge when full:"), 0, 2, 1, 1);
    grid.attach(&create_label(charge_full.as_str()), 1, 2, 1, 1);
    grid.attach(&create_label("Design full:"), 0, 3, 1, 1);
    grid.attach(&create_label(design_full.as_str()), 1, 3, 1, 1);
    grid.attach(&create_label("Cycle count:"), 0, 4, 1, 1);
    grid.attach(&create_label(cycle_count.as_str()), 1, 4, 1, 1);
    grid.attach(&create_label("Status:"), 0, 5, 1, 1);
    grid.attach(&create_label(charge_status.as_str()), 1, 5, 1, 1);
    grid.attach(&create_label("Current now:"), 0, 6, 1, 1);
    grid.attach(&create_label(current_now.as_str()), 1, 6, 1, 1);
    grid.attach(&create_label("Avg current:"), 0, 7, 1, 1);
    grid.attach(&create_label(current_avg.as_str()), 1, 7, 1, 1);
    grid.attach(&create_label("% Remaining:"), 0, 8, 1, 1);
    grid.attach(&create_label(percent.as_str()), 1, 8, 1, 1);
    grid.attach(&create_label("Time remaining:"), 0, 9, 1, 1);
    grid.attach(&create_label(time.as_str()), 1, 9, 1, 1);
    grid.attach(&create_label("Battery health:"), 0, 10, 1, 1);
    grid.attach(&create_label(health.as_str()), 1, 10, 1, 1);
    grid.attach(&create_label("Abs % remaining:"), 0, 11, 1, 1);
    grid.attach(&create_label(abs_percent.as_str()), 1, 11, 1, 1);

    grid.set_row_spacing(6);
    grid.set_column_spacing(6);

    let dialog = MessageDialog::new(None::<&Window>,
                                    DialogFlags::MODAL,
                                    MessageType::Info,
                                    ButtonsType::Ok,
                                    "Battery Stats");

    let content = dialog.get_content_area();
    content.pack_start(&grid, true, true, 6);
    content.set_border_width(18);
    dialog.show_all();
    dialog.run();

    Ok(())
}

fn get_env_var(key: &str) -> Option<String> {
    match env::var(key) {
        Ok(ref val) if val.is_empty() => None,
        Ok(val) => Some(val),
        Err(_) => None
    }
}

fn show_notification(bat: &Battery) {
    let time_left = bat.time_remaining();
    libnotify::init("Battery").unwrap();
    let notification = libnotify::Notification::new("Time Remaining",
                                                    Some(&*time_left),
                                                    None);
    notification.show().unwrap();
    libnotify::uninit();
}

/// For the purposes of i3blocks, 3 corresponds to a right click. When
/// that happens, fork() and then show a dialog. If we don't fork(), then
/// the dialog prevents this process from exiting and the block doesn't
/// update. The printing to the console is done in the parent thread.
fn handle_button_presses(bat: &Battery) {
    let button: i32 = match get_env_var("BLOCK_BUTTON") {
        Some(val) => val.trim().parse().unwrap(),
        None => 0
    };

    match button {
        RIGHT_CLICK => {
            match fork() {
                Ok(ForkResult::Parent{ .. }) => (),
                Ok(ForkResult::Child) => {
                    if show_gtk_dialog(&bat).is_ok() {
                        std::process::exit(0);
                    } else {
                        std::process::exit(1);
                    }
                }
                Err(_) => eprintln!("Fork failed"),
            }
        },
        LEFT_CLICK => show_notification(&bat),
        _ => (),
    };
    console_output(&bat);
}

fn main() -> Result<(), BatteryError>{
    let battery_name = match get_env_var("BLOCK_INSTANCE") {
        Some(val) => val,
        None => "BAT0".to_owned(),
    };

    let bat = Battery::initialize(&battery_name)?;

    handle_button_presses(&bat);

    /*
     * i3blocks shows a block as critical if the process exits with code 33.
     * If the battery is less than 33% charged, the status is critical
     */
    if bat.percent_remaining() <= CRITICAL_BAT_LEVEL {
        std::process::exit(CRITICAL_EXIT_CODE);
    }

    Ok(())
}
<|endoftext|>


<commit_before>#![allow(clippy::needless_range_loop)]

#[macro_use]
extern crate log;

extern crate sdl2;

use mem::Mem;
use std::path::Path;

mod cartridge;
mod cpu;
mod debug;
mod disasm;
mod interrupt;
mod joypad;
mod mem;
mod ram;
mod serial;
mod sound;
mod timer;
mod video;

//
// Memory Map
//

struct Dummy;
impl Mem for Dummy {
    fn loadb(&mut self, _: u16) -> u8 {
        //debug!("load in unmapped memory at 0x{:04X}", addr);
        0xff
    }

    fn storeb(&mut self, _: u16, _: u8) {
        //debug!("store in unmapped memory at 0x{:04X}", addr);
    }
}

struct MemMap<'a> {
    cart: Box<cartridge::Cartridge>,
    wram: ram::WorkRam,
    timer: timer::Timer,
    intr: interrupt::InterruptCtrl,
    sound: sound::Sound,
    video: video::Video,
    serial: serial::SerialIO<'a>,
    joypad: joypad::Joypad,
    dummy: Dummy,
}

impl<'a> MemMap<'a> {
    fn mem_from_addr(&mut self, addr: u16) -> &mut dyn Mem {
        match addr {
      0x0000..=0x7fff | // ROM banks
      0xa000..=0xbfff   // External RAM
                       => &mut *self.cart as &mut dyn Mem,
      0x8000..=0x9fff | // VRAM
      0xfe00..=0xfe9f | // OAM
      0xff40..=0xff4b   // Video I/O
                       => &mut self.video as &mut dyn Mem,
      0xc000..=0xfdff | // WRAM (including echo area 0xe000-0xfdff)
      0xff80..=0xfffe   // HRAM
                       => &mut self.wram as &mut dyn Mem,
      0xff00           => &mut self.joypad as &mut dyn Mem,
      0xff01..=0xff02  => &mut self.serial as &mut dyn Mem,
      0xff04..=0xff07  => &mut self.timer as &mut dyn Mem,
      0xff0f | 0xffff  => &mut self.intr as &mut dyn Mem,
      0xff10..=0xff3f  => &mut self.sound as &mut dyn Mem,
      _ => &mut self.dummy as &mut dyn Mem,
    }
    }
}

impl<'a> Mem for MemMap<'a> {
    fn loadb(&mut self, addr: u16) -> u8 {
        self.mem_from_addr(addr).loadb(addr)
    }

    fn storeb(&mut self, addr: u16, val: u8) {
        self.mem_from_addr(addr).storeb(addr, val)
    }
}

//
// Video Output
//

struct VideoOut {
    renderer: Box<sdl2::render::WindowCanvas>,
    texture: sdl2::render::Texture<'static>,
    _texture_creator: sdl2::render::TextureCreator<sdl2::video::WindowContext>,
}

impl VideoOut {
    fn new(sdl_video: &sdl2::VideoSubsystem, scale: u32) -> VideoOut {
        let window_width = video::SCREEN_WIDTH as u32 * scale;
        let window_height = video::SCREEN_HEIGHT as u32 * scale;

        let window = sdl_video
            .window("rustboy", window_width, window_height)
            .position_centered()
            .build()
            .unwrap();

        let renderer = window.into_canvas().build().unwrap();

        let texture_creator = renderer.texture_creator();
        let texture_creator_pointer =
            &texture_creator as *const sdl2::render::TextureCreator<sdl2::video::WindowContext>;

        let texture = unsafe { &*texture_creator_pointer }
            .create_texture_streaming(
                sdl2::pixels::PixelFormatEnum::ARGB8888,
                video::SCREEN_WIDTH as u32,
                video::SCREEN_HEIGHT as u32,
            )
            .unwrap();

        VideoOut {
            renderer: Box::new(renderer),
            texture,
            _texture_creator: texture_creator,
        }
    }

    fn blit_and_present(&mut self, pixels: &[u8]) {
        let _ = self.texture.update(None, pixels, video::SCREEN_WIDTH * 4);
        let _ = self.renderer.copy(&self.texture, None, None);
        self.renderer.present();
    }

    fn set_title(&mut self, title: &str) {
        self.renderer.window_mut().set_title(title).unwrap();
    }
}

fn keymap(code: sdl2::keyboard::Keycode) -> Option<joypad::Button> {
    match code {
        sdl2::keyboard::Keycode::Up => Some(joypad::Button::Up),
        sdl2::keyboard::Keycode::Down => Some(joypad::Button::Down),
        sdl2::keyboard::Keycode::Left => Some(joypad::Button::Left),
        sdl2::keyboard::Keycode::Right => Some(joypad::Button::Right),
        sdl2::keyboard::Keycode::Return => Some(joypad::Button::Start),
        sdl2::keyboard::Keycode::RShift => Some(joypad::Button::Select),
        sdl2::keyboard::Keycode::C => Some(joypad::Button::A),
        sdl2::keyboard::Keycode::X => Some(joypad::Button::B),
        _ => None,
    }
}

#[derive(PartialEq)]
enum State {
    Paused,
    Running,
    Step,
    Done,
}

fn main() {
    let args: Vec<_> = std::env::args().collect();
    if args.len() != 2 && !(args.len() == 3 && args[1] == "-d") {
        println!("Usage: {} [-d] rom.gb", args[0]);
        return;
    }

    let mut disassemble = false;
    let path = if args.len() == 2 {
        &args[1]
    } else {
        disassemble = true;
        &args[2]
    };

    let mut cart = match cartridge::Cartridge::from_path(Path::new(path)) {
        Ok(cart) => Box::new(cart),
        Err(e) => panic!("I/O error: {}", e),
    };

    if disassemble {
        // Disassemble only
        let mut d = disasm::Disasm {
            mem: &mut *cart,
            pc: 0,
        };
        while d.pc <= 0x7fff {
            let pc = d.pc;
            println!("${:04X}\t{}", pc, cpu::decode(&mut d));
        }
        return;
    }

    println!("Name: {}", cart.title);
    println!("Type: {}", cart.cartridge_type);

    let memmap = MemMap {
        cart,
        wram: ram::WorkRam::new(),
        timer: timer::Timer::new(),
        intr: interrupt::InterruptCtrl::new(),
        sound: sound::Sound,
        video: video::Video::new(),
        serial: serial::SerialIO::new(None),
        joypad: joypad::Joypad::new(),
        dummy: Dummy,
    };
    let mut cpu = cpu::Cpu::new(memmap);
    cpu.regs.pc = 0x100;

    let sdl_context = sdl2::init().unwrap();
    let sdl_video = sdl_context.video().unwrap();
    let mut sdl_events = sdl_context.event_pump().unwrap();
    let mut sdl_timer = sdl_context.timer().unwrap();

    let mut video_out = VideoOut::new(&sdl_video, 4);
    video_out.set_title("Rustboy");

    let mut state = State::Paused;
    let mut debugger = debug::Debugger::new();

    let counts_per_sec = sdl_timer.performance_frequency();
    let counts_per_frame =
        counts_per_sec * video::SCREEN_REFRESH_CYCLES as u64 / cpu::CYCLES_PER_SEC as u64;
    let mut last_frame_start_count = sdl_timer.performance_counter();

    let mut last_fps_update = last_frame_start_count;
    let mut frames = 0;

    println!("c/s: {}; c/f: {}", counts_per_sec, counts_per_frame);

    while state != State::Done {
        if state == State::Paused || state == State::Step {
            match debugger.prompt(&mut cpu) {
                debug::DebuggerCommand::Quit => break,
                debug::DebuggerCommand::Run => state = State::Running,
                debug::DebuggerCommand::Step => state = State::Step,
            }
        }

        // Emulation loop
        loop {
            let cycles = cpu.step();

            if let Some(timer::Signal::TIMAOverflow) = cpu.mem.timer.tick(cycles) {
                cpu.mem.intr.irq(interrupt::IRQ_TIMER);
            }

            let mut new_frame = false;
            let video_signals = cpu.mem.video.tick(cycles);
            for signal in &video_signals {
                match *signal {
                    video::Signal::Dma(base) => {
                        // Do DMA transfer instantaneously
                        let base_addr = u16::from(base) << 8;
                        for offset in 0x00u16..0xa0u16 {
                            let val = cpu.mem.loadb(base_addr + offset);
                            cpu.mem.storeb(0xfe00 + offset, val);
                        }
                    }
                    video::Signal::VBlank => {
                        video_out.blit_and_present(&cpu.mem.video.screen);
                        cpu.mem.intr.irq(interrupt::IRQ_VBLANK);
                        new_frame = true;
                    }
                    video::Signal::Lcd => cpu.mem.intr.irq(interrupt::IRQ_LCD),
                }
            }

            // Synchronize speed based on frame time
            if new_frame {
                let now = sdl_timer.performance_counter();
                let frame_time = now - last_frame_start_count;
                if frame_time < counts_per_frame {
                    let delay_msec = 1_000 * (counts_per_frame - frame_time) / counts_per_sec;
                    sdl_timer.delay(delay_msec as u32);
                }
                // TODO: What should we do when we take longer than counts_per_frame?
                last_frame_start_count = sdl_timer.performance_counter();

                frames += 1;
                if last_frame_start_count - last_fps_update > counts_per_sec {
                    let fps = frames * (last_frame_start_count - last_fps_update) / counts_per_sec;
                    video_out.set_title(format!("Rustboy - {} fps", fps).as_ref());
                    last_fps_update = now;
                    frames = 0;
                }

                // Exit emulation loop to handle events
                break;
            }

            if debugger.should_break(&cpu) {
                state = State::Paused;
                break;
            }

            if state == State::Step {
                // Stop emulation loop after one instruction
                break;
            }
        }

        // Event handling loop
        for event in sdl_events.poll_iter() {
            use sdl2::event::Event;

            match event {
                Event::Quit { .. } => {
                    state = State::Done;
                    break;
                }
                Event::KeyDown {
                    keycode: Some(key), ..
                } => match keymap(key) {
                    Some(button) => cpu.mem.joypad.set_button(button, true),
                    None => {
                        if key == sdl2::keyboard::Keycode::Escape {
                            state = State::Paused;
                        }
                    }
                },
                Event::KeyUp {
                    keycode: Some(key), ..
                } => {
                    if let Some(button) = keymap(key) {
                        cpu.mem.joypad.set_button(button, false)
                    }
                }
                _ => (),
            }
        }
    }
}
<commit_msg>Use std::time for timing<commit_after>#![allow(clippy::needless_range_loop)]

#[macro_use]
extern crate log;

extern crate sdl2;

use mem::Mem;
use std::path::Path;
use std::time::{Duration, Instant};

mod cartridge;
mod cpu;
mod debug;
mod disasm;
mod interrupt;
mod joypad;
mod mem;
mod ram;
mod serial;
mod sound;
mod timer;
mod video;

//
// Memory Map
//

struct Dummy;
impl Mem for Dummy {
    fn loadb(&mut self, _: u16) -> u8 {
        //debug!("load in unmapped memory at 0x{:04X}", addr);
        0xff
    }

    fn storeb(&mut self, _: u16, _: u8) {
        //debug!("store in unmapped memory at 0x{:04X}", addr);
    }
}

struct MemMap<'a> {
    cart: Box<cartridge::Cartridge>,
    wram: ram::WorkRam,
    timer: timer::Timer,
    intr: interrupt::InterruptCtrl,
    sound: sound::Sound,
    video: video::Video,
    serial: serial::SerialIO<'a>,
    joypad: joypad::Joypad,
    dummy: Dummy,
}

impl<'a> MemMap<'a> {
    fn mem_from_addr(&mut self, addr: u16) -> &mut dyn Mem {
        match addr {
      0x0000..=0x7fff | // ROM banks
      0xa000..=0xbfff   // External RAM
                       => &mut *self.cart as &mut dyn Mem,
      0x8000..=0x9fff | // VRAM
      0xfe00..=0xfe9f | // OAM
      0xff40..=0xff4b   // Video I/O
                       => &mut self.video as &mut dyn Mem,
      0xc000..=0xfdff | // WRAM (including echo area 0xe000-0xfdff)
      0xff80..=0xfffe   // HRAM
                       => &mut self.wram as &mut dyn Mem,
      0xff00           => &mut self.joypad as &mut dyn Mem,
      0xff01..=0xff02  => &mut self.serial as &mut dyn Mem,
      0xff04..=0xff07  => &mut self.timer as &mut dyn Mem,
      0xff0f | 0xffff  => &mut self.intr as &mut dyn Mem,
      0xff10..=0xff3f  => &mut self.sound as &mut dyn Mem,
      _ => &mut self.dummy as &mut dyn Mem,
    }
    }
}

impl<'a> Mem for MemMap<'a> {
    fn loadb(&mut self, addr: u16) -> u8 {
        self.mem_from_addr(addr).loadb(addr)
    }

    fn storeb(&mut self, addr: u16, val: u8) {
        self.mem_from_addr(addr).storeb(addr, val)
    }
}

//
// Video Output
//

struct VideoOut {
    renderer: Box<sdl2::render::WindowCanvas>,
    texture: sdl2::render::Texture<'static>,
    _texture_creator: sdl2::render::TextureCreator<sdl2::video::WindowContext>,
}

impl VideoOut {
    fn new(sdl_video: &sdl2::VideoSubsystem, scale: u32) -> VideoOut {
        let window_width = video::SCREEN_WIDTH as u32 * scale;
        let window_height = video::SCREEN_HEIGHT as u32 * scale;

        let window = sdl_video
            .window("rustboy", window_width, window_height)
            .position_centered()
            .build()
            .unwrap();

        let renderer = window.into_canvas().build().unwrap();

        let texture_creator = renderer.texture_creator();
        let texture_creator_pointer =
            &texture_creator as *const sdl2::render::TextureCreator<sdl2::video::WindowContext>;

        let texture = unsafe { &*texture_creator_pointer }
            .create_texture_streaming(
                sdl2::pixels::PixelFormatEnum::ARGB8888,
                video::SCREEN_WIDTH as u32,
                video::SCREEN_HEIGHT as u32,
            )
            .unwrap();

        VideoOut {
            renderer: Box::new(renderer),
            texture,
            _texture_creator: texture_creator,
        }
    }

    fn blit_and_present(&mut self, pixels: &[u8]) {
        let _ = self.texture.update(None, pixels, video::SCREEN_WIDTH * 4);
        let _ = self.renderer.copy(&self.texture, None, None);
        self.renderer.present();
    }

    fn set_title(&mut self, title: &str) {
        self.renderer.window_mut().set_title(title).unwrap();
    }
}

fn keymap(code: sdl2::keyboard::Keycode) -> Option<joypad::Button> {
    match code {
        sdl2::keyboard::Keycode::Up => Some(joypad::Button::Up),
        sdl2::keyboard::Keycode::Down => Some(joypad::Button::Down),
        sdl2::keyboard::Keycode::Left => Some(joypad::Button::Left),
        sdl2::keyboard::Keycode::Right => Some(joypad::Button::Right),
        sdl2::keyboard::Keycode::Return => Some(joypad::Button::Start),
        sdl2::keyboard::Keycode::RShift => Some(joypad::Button::Select),
        sdl2::keyboard::Keycode::C => Some(joypad::Button::A),
        sdl2::keyboard::Keycode::X => Some(joypad::Button::B),
        _ => None,
    }
}

#[derive(PartialEq)]
enum State {
    Paused,
    Running,
    Step,
    Done,
}

fn main() {
    let args: Vec<_> = std::env::args().collect();
    if args.len() != 2 && !(args.len() == 3 && args[1] == "-d") {
        println!("Usage: {} [-d] rom.gb", args[0]);
        return;
    }

    let mut disassemble = false;
    let path = if args.len() == 2 {
        &args[1]
    } else {
        disassemble = true;
        &args[2]
    };

    let mut cart = match cartridge::Cartridge::from_path(Path::new(path)) {
        Ok(cart) => Box::new(cart),
        Err(e) => panic!("I/O error: {}", e),
    };

    if disassemble {
        // Disassemble only
        let mut d = disasm::Disasm {
            mem: &mut *cart,
            pc: 0,
        };
        while d.pc <= 0x7fff {
            let pc = d.pc;
            println!("${:04X}\t{}", pc, cpu::decode(&mut d));
        }
        return;
    }

    println!("Name: {}", cart.title);
    println!("Type: {}", cart.cartridge_type);

    let memmap = MemMap {
        cart,
        wram: ram::WorkRam::new(),
        timer: timer::Timer::new(),
        intr: interrupt::InterruptCtrl::new(),
        sound: sound::Sound,
        video: video::Video::new(),
        serial: serial::SerialIO::new(None),
        joypad: joypad::Joypad::new(),
        dummy: Dummy,
    };
    let mut cpu = cpu::Cpu::new(memmap);
    cpu.regs.pc = 0x100;

    let sdl_context = sdl2::init().unwrap();
    let sdl_video = sdl_context.video().unwrap();
    let mut sdl_events = sdl_context.event_pump().unwrap();
    let mut sdl_timer = sdl_context.timer().unwrap();

    let mut video_out = VideoOut::new(&sdl_video, 4);
    video_out.set_title("Rustboy");

    let mut state = State::Paused;
    let mut debugger = debug::Debugger::new();

    let frame_duration =
        Duration::from_secs_f64(video::SCREEN_REFRESH_CYCLES as f64 / cpu::CYCLES_PER_SEC as f64);
    let mut last_frame_start = Instant::now();

    let mut last_fps_update = last_frame_start;
    let mut frames = 0;

    println!("f: {:?}", frame_duration);

    while state != State::Done {
        if state == State::Paused || state == State::Step {
            match debugger.prompt(&mut cpu) {
                debug::DebuggerCommand::Quit => break,
                debug::DebuggerCommand::Run => state = State::Running,
                debug::DebuggerCommand::Step => state = State::Step,
            }
        }

        // Emulation loop
        loop {
            let cycles = cpu.step();

            if let Some(timer::Signal::TIMAOverflow) = cpu.mem.timer.tick(cycles) {
                cpu.mem.intr.irq(interrupt::IRQ_TIMER);
            }

            let mut new_frame = false;
            let video_signals = cpu.mem.video.tick(cycles);
            for signal in &video_signals {
                match *signal {
                    video::Signal::Dma(base) => {
                        // Do DMA transfer instantaneously
                        let base_addr = u16::from(base) << 8;
                        for offset in 0x00u16..0xa0u16 {
                            let val = cpu.mem.loadb(base_addr + offset);
                            cpu.mem.storeb(0xfe00 + offset, val);
                        }
                    }
                    video::Signal::VBlank => {
                        video_out.blit_and_present(&cpu.mem.video.screen);
                        cpu.mem.intr.irq(interrupt::IRQ_VBLANK);
                        new_frame = true;
                    }
                    video::Signal::Lcd => cpu.mem.intr.irq(interrupt::IRQ_LCD),
                }
            }

            // Synchronize speed based on frame time
            if new_frame {
                let now = Instant::now();
                let frame_time = now - last_frame_start;
                if frame_time < frame_duration {
                    let delay_msec = (frame_duration - frame_time).as_millis();
                    sdl_timer.delay(delay_msec as u32);
                }
                // TODO: What should we do when we take longer than frame_duration?
                last_frame_start = Instant::now();

                frames += 1;
                let time_since_last_fps_update = last_frame_start - last_fps_update;
                if time_since_last_fps_update > Duration::from_secs(1) {
                    let fps = frames as f64 / (time_since_last_fps_update).as_secs_f64();
                    video_out.set_title(format!("Rustboy - {:.02} fps", fps).as_ref());
                    last_fps_update = now;
                    frames = 0;
                }

                // Exit emulation loop to handle events
                break;
            }

            if debugger.should_break(&cpu) {
                state = State::Paused;
                break;
            }

            if state == State::Step {
                // Stop emulation loop after one instruction
                break;
            }
        }

        // Event handling loop
        for event in sdl_events.poll_iter() {
            use sdl2::event::Event;

            match event {
                Event::Quit { .. } => {
                    state = State::Done;
                    break;
                }
                Event::KeyDown {
                    keycode: Some(key), ..
                } => match keymap(key) {
                    Some(button) => cpu.mem.joypad.set_button(button, true),
                    None => {
                        if key == sdl2::keyboard::Keycode::Escape {
                            state = State::Paused;
                        }
                    }
                },
                Event::KeyUp {
                    keycode: Some(key), ..
                } => {
                    if let Some(button) = keymap(key) {
                        cpu.mem.joypad.set_button(button, false)
                    }
                }
                _ => (),
            }
        }
    }
}
<|endoftext|>