<commit_before>//! A Rust snippet runner.
//!
//! Please see [readme](https://github.com/stevedonovan/runner/blob/master/readme.md)
extern crate easy_shortcuts as es;
extern crate lapp;
use es::traits::*;
use std::process;
use std::env;
use std::fs;
use std::path::PathBuf;
use std::collections::HashMap;
use std::io::Write;

mod crate_utils;
mod platform;

use platform::{open,edit,EXE};

fn rustup_lib() -> String {
    es::shell("rustc --print sysroot") + "/lib"
}

// this will be initially written to ~/.cargo/.runner/prelude and
// can then be edited.
const PRELUDE: &'static str = "
#![allow(unused_imports)]
#![allow(dead_code)]
use std::fs;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::env;
use std::path::{PathBuf,Path};
#[allow(unused_macros)]
macro_rules! debug {
    ($x:expr) => {
        println!(\"{} = {:?}\",stringify!($x),$x);
    }
}

";

fn runner_directory() -> PathBuf {
    let mut home = env::home_dir().or_die("no home!");
    home.push(".cargo");
    home.push(".runner");
    home
}

fn cargo(args: &[&str]) {
    let res = process::Command::new("cargo")
        .args(args)
        .status()
        .or_die("can't run cargo");
    if ! res.success() {
        es::quit("cargo failed");
    }
}

const STATIC_CACHE: &str = "static-cache";
const DYNAMIC_CACHE: &str = "dy-cache";

fn static_cache_dir() -> PathBuf {
    runner_directory().join(STATIC_CACHE)
}

fn static_cache_dir_check(args: &lapp::Args) -> PathBuf {
    let static_cache = static_cache_dir();
    if ! static_cache.exists() {
        args.quit("please build static cache with --create first");
    }
    static_cache
}

fn build_static_cache() {
    cargo(&["build"]);
    cargo(&["build","--release"]);
    cargo(&["doc"]);
}

fn create_static_cache(crates: &[String], create: bool) {
    use std::io::prelude::*;
    let mut home = runner_directory();
    env::set_current_dir(&home).or_die("cannot change to home directory");
    if create {
        cargo(&["new","--bin",STATIC_CACHE]);
    }
    home.push(STATIC_CACHE);
    env::set_current_dir(&home).or_die("could not create static cache");
    {
        let mut deps = fs::OpenOptions::new().append(true)
            .open("Cargo.toml").or_die("could not append to cargo.toml");
        for c in crates {
            write!(deps,"{}=\"*\"\n",c).or_die("could not modify cargo.toml");
        }
    }
    build_static_cache();
}

// this is always called first and has the important role to ensure that
// runner's directory structure is created properly.
fn get_prelude() -> String {
    let home = runner_directory();
    let pristine = ! home.is_dir();
    if pristine {
        fs::create_dir(&home).or_die("cannot create runner directory");
    }
    let prelude = home.join("prelude");
    let bin = home.join("bin");
    if pristine {
        es::write_all(&prelude,PRELUDE);
        fs::create_dir(&home.join(DYNAMIC_CACHE)).or_die("cannot create dynamic cache");
    }
    if pristine || ! bin.is_dir() {
        fs::create_dir(&bin).or_die("cannot create output directory");
    }
    es::read_to_string(&prelude)
}

fn get_cache(build_static: bool, optimize: bool) -> PathBuf {
    let mut home = runner_directory();
    let scache;
    let cache = if build_static {
        let dir = if optimize {"release"} else {"debug"};
        scache = format!("{}/target/{}/deps",STATIC_CACHE,dir);
        &scache
    } else {DYNAMIC_CACHE};
    home.push(cache);
    home
}

fn add_aliases(aliases: Vec<String>) {
    if aliases.len() == 0 { return; }
    let alias_file = runner_directory().join("alias");
    let mut f = if alias_file.is_file() {
        fs::OpenOptions::new().append(true).open(&alias_file)
    } else {
        fs::File::create(&alias_file)
    }.or_die("cannot open runner alias file");

    for crate_alias in aliases {
        write!(f,"{}\n",crate_alias).or_die("cannot write to runner alias file");
    }
}

fn get_aliases() -> HashMap<String,String> {
    let alias_file = runner_directory().join("alias");
    if ! alias_file.is_file() { return HashMap::new(); }
	es::lines(es::open(&alias_file))
      .filter_map(|s| s.split_at_delim('=').trim()) // split into (String,String)
	  .to_map()
}

fn massage_snippet(code: String, prelude: String, extern_crates: Vec<String>) -> String {
    fn indent_line(line: &str) -> String {
        format!("    {}\n",line)
    }
    let mut prefix = prelude;
    let mut body = String::new();
    {
        if extern_crates.len() > 0 {
            let aliases = get_aliases();
            for c in extern_crates {
                prefix += &if let Some(aliased) = aliases.get(&c) {
                    format!("extern crate {} as {};",aliased,c)
                } else {
                    format!("extern crate {};",c)
                };
            }
        }
        let mut lines = code.lines();
        let mut first = true;
        for line in lines.by_ref() {
            let line = line.trim_left();
            if first { // files may start with #! shebang...
                if line.starts_with("#!") {
                    continue;
                }
                first = false;
            }
            if line.starts_with("//") || line.starts_with("#[macro_use") ||
                line.starts_with("extern ") || line.starts_with("use ") {
                prefix += line;
                prefix.push('\n');
            } else
            if line.len() > 0 {
                body += &indent_line(line);
                break;
            }
        }
        // and indent the rest!
        body.extend(lines.map(indent_line));
    }

    format!("{}
use std::error::Error;
fn run() -> Result<(),Box<Error>> {{
{}    Ok(())
}}
fn main() {{
    run().unwrap();
}}
        ",prefix,body)

}

const USAGE: &str = "
Compile and run small Rust snippets
  -s, --static build statically (default is dynamic)
  -O, --optimize optimized static build
  -e, --expression evaluate an expression
  -i, --iterator evaluate an iterator
  -n, --lines evaluate expression over stdin; 'line' is defined
  -x, --extern... (string) add an extern crate to the snippet

  Cache Management:
  --create (string...) initialize the static cache with crates
  --add  (string...) add new crates to the cache (after --create)
  --edit  edit the static cache Cargo.toml
  --build rebuild the static cache
  --doc  display
  --edit-prelude edit the default prelude for snippets
  --alias (string...) crate aliases in form alias=crate_name (used with -x)

  Dynamic compilation:
  -P, --crate-path show path of crate source in Cargo cache
  -C, --compile  compile crate dynamically (limited)

  <program> (string) Rust program, snippet or expression
  <args> (string...) arguments to pass to program
";

fn main() {
    let args = lapp::parse_args(USAGE);
    let prelude = get_prelude();

    let aliases = args.get_strings("alias");
    if aliases.len() > 0 {
        add_aliases(aliases);
        return;
    }

    if args.get_bool("edit-prelude") {
        let rdir = runner_directory().join("prelude");
        edit(&rdir);
        return;
    }

    // Static Cache Management
    let crates = args.get_strings("create");
    if crates.len() > 0 {
        create_static_cache(&crates,true);
        return;
    }
    let crates = args.get_strings("add");
    if crates.len() > 0 {
        static_cache_dir_check(&args);
        create_static_cache(&crates,false);
        return;
    }

    if args.get_bool("edit") || args.get_bool("build") || args.get_bool("doc") {
        let static_cache = static_cache_dir_check(&args);
        if args.get_bool("build") {
            env::set_current_dir(&static_cache).or_die("static cache wasn't a directory?");
            build_static_cache();
        } else
        if args.get_bool("doc") {
            let docs = static_cache.join("target/doc/static_cache/index.html");
            open(&docs);
        } else {
            let toml = static_cache.join("Cargo.toml");
            edit(&toml);
        }
        return;
    }

    let first_arg = args.get_string("program");
    let file = PathBuf::from(&first_arg);

    // Dynamically linking crates (experimental!)
    if args.get_bool("crate-path") || args.get_bool("compile") {
        let (crate_path,crate_name) = if file.exists() {
            let filename = crate_utils::path_file_name(&file);
            if file.is_dir() { // assumed to be Cargo directory
                if ! file.join("Cargo.toml").exists() {
                    args.quit("not a Cargo project directory");
                }
                (file.join("src/lib.rs"), filename)
            } else { // should be just a Rust source file
                if file.extension().or_die("expecting extension") != "rs" {
                    args.quit("expecting Rust source file");
                }
                let name = crate_utils::path_file_name(&file.with_extension(""));
                (file, name)
            }
        } else {
            (crate_utils::cache_path(&first_arg).join("src/lib.rs"), first_arg)
        };
        if args.get_bool("crate-path") {
            println!("{}",crate_utils::cache_path(&crate_name).display());
        } else {
            let valid_crate_name = crate_name.replace('-',"_");
            let cache = get_cache(false, false);
            process::Command::new("rustc")
                .args(&["-C","prefer-dynamic"]).args(&["-C","debuginfo=0"])
                .arg("-L").arg(&cache)
                .args(&["--crate-type","dylib"])
                .arg("--out-dir").arg(&cache)
                .arg("--crate-name").arg(&valid_crate_name)
                .arg(crate_path)
           .status().or_die("can't run rustc");
        }
        return;
    }

    let build_static = args.get_bool("static");
    let optimize = args.get_bool("optimize");

    // we'll pass rest of arguments to program
    let program_args = args.get_strings("args");

    let cache = get_cache(build_static, optimize);

    let mut snippet = false;
    let mut code = if args.get_bool("expression") {
        // Evaluating an expression: just print it out.
        format!("println!(\"{{:?}}\",{});", first_arg)
    } else
    if args.get_bool("iterator") {
        // The expression is anything that implements IntoIterator
        format!("let iter = {};\n for val in iter {{ println!(\"{{:?}}\",val);}}", first_arg)
    } else
    if args.get_bool("lines") {
        // The variable 'line' is available to an expression, evaluated for each line in stdin
        format!("
            let stdin = io::stdin();
            for line in stdin.lock().lines() {{
                let line = line?;
                let val = {};
                println!(\"{{:?}}\",val);
            }}
            ", first_arg)
    } else { // otherwise, just a file
        snippet = true;
        es::read_to_string(&file)
    };

    // proper Rust programs are accepted (this is a bit rough)
    if code.find("fn main").is_none() {
        code = massage_snippet(code,prelude, args.get_strings("extern"));
    }

    // we are going to put the expanded source and resulting exe in the runner bin dir
    let mut out_file = runner_directory().join("bin");
    if snippet {
        out_file.push(&file);
        out_file.set_extension("rs");
    } else {
        out_file.push("tmp.rs");
    }
    let mut program = out_file.clone();
    program.set_extension(EXE);

    es::write_all(&out_file,&code);

    let mut builder = process::Command::new("rustc");
    if ! build_static {
        builder.args(&["-C","prefer-dynamic"]).args(&["-C","debuginfo=0"]);
    } else {
        builder.arg(if optimize {"-O"} else {"-g"});
    }
    builder.arg("-L").arg(&cache);
    let status = builder.arg("-o").arg(&program)
        .arg(&out_file)
        .status().or_die("can't run rustc");
    if ! status.success() {
        return;
    }

    let mut builder = process::Command::new(&program);
    if ! build_static {
        builder.env("LD_LIBRARY_PATH",format!("{}:{}",rustup_lib(),cache.display()));
    }
    builder.args(&program_args)
        .status()
        .or_die(&format!("can't run program {:?}",program));

}

<commit_msg>--wild (-X) like --extern with wildcard use; --cfg with -C<commit_after>//! A Rust snippet runner.
//!
//! Please see [readme](https://github.com/stevedonovan/runner/blob/master/readme.md)
extern crate easy_shortcuts as es;
extern crate lapp;
use es::traits::*;
use std::process;
use std::env;
use std::fs;
use std::path::PathBuf;
use std::collections::HashMap;
use std::io::Write;

mod crate_utils;
mod platform;

use platform::{open,edit,EXE};

fn rustup_lib() -> String {
    es::shell("rustc --print sysroot") + "/lib"
}

// this will be initially written to ~/.cargo/.runner/prelude and
// can then be edited.
const PRELUDE: &'static str = "
#![allow(unused_imports)]
#![allow(dead_code)]
use std::fs;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::env;
use std::path::{PathBuf,Path};
#[allow(unused_macros)]
macro_rules! debug {
    ($x:expr) => {
        println!(\"{} = {:?}\",stringify!($x),$x);
    }
}

";

fn runner_directory() -> PathBuf {
    let mut home = env::home_dir().or_die("no home!");
    home.push(".cargo");
    home.push(".runner");
    home
}

fn cargo(args: &[&str]) {
    let res = process::Command::new("cargo")
        .args(args)
        .status()
        .or_die("can't run cargo");
    if ! res.success() {
        es::quit("cargo failed");
    }
}

const STATIC_CACHE: &str = "static-cache";
const DYNAMIC_CACHE: &str = "dy-cache";

fn static_cache_dir() -> PathBuf {
    runner_directory().join(STATIC_CACHE)
}

fn static_cache_dir_check(args: &lapp::Args) -> PathBuf {
    let static_cache = static_cache_dir();
    if ! static_cache.exists() {
        args.quit("please build static cache with --create first");
    }
    static_cache
}

fn build_static_cache() {
    cargo(&["build"]);
    cargo(&["build","--release"]);
    cargo(&["doc"]);
}

fn create_static_cache(crates: &[String], create: bool) {
    use std::io::prelude::*;
    let mut home = runner_directory();
    env::set_current_dir(&home).or_die("cannot change to home directory");
    if create {
        cargo(&["new","--bin",STATIC_CACHE]);
    }
    home.push(STATIC_CACHE);
    env::set_current_dir(&home).or_die("could not create static cache");
    {
        let mut deps = fs::OpenOptions::new().append(true)
            .open("Cargo.toml").or_die("could not append to cargo.toml");
        for c in crates {
            write!(deps,"{}=\"*\"\n",c).or_die("could not modify cargo.toml");
        }
    }
    build_static_cache();
}

// this is always called first and has the important role to ensure that
// runner's directory structure is created properly.
fn get_prelude() -> String {
    let home = runner_directory();
    let pristine = ! home.is_dir();
    if pristine {
        fs::create_dir(&home).or_die("cannot create runner directory");
    }
    let prelude = home.join("prelude");
    let bin = home.join("bin");
    if pristine {
        es::write_all(&prelude,PRELUDE);
        fs::create_dir(&home.join(DYNAMIC_CACHE)).or_die("cannot create dynamic cache");
    }
    if pristine || ! bin.is_dir() {
        fs::create_dir(&bin).or_die("cannot create output directory");
    }
    es::read_to_string(&prelude)
}

fn get_cache(build_static: bool, optimize: bool) -> PathBuf {
    let mut home = runner_directory();
    let scache;
    let cache = if build_static {
        let dir = if optimize {"release"} else {"debug"};
        scache = format!("{}/target/{}/deps",STATIC_CACHE,dir);
        &scache
    } else {DYNAMIC_CACHE};
    home.push(cache);
    home
}

fn add_aliases(aliases: Vec<String>) {
    if aliases.len() == 0 { return; }
    let alias_file = runner_directory().join("alias");
    let mut f = if alias_file.is_file() {
        fs::OpenOptions::new().append(true).open(&alias_file)
    } else {
        fs::File::create(&alias_file)
    }.or_die("cannot open runner alias file");

    for crate_alias in aliases {
        write!(f,"{}\n",crate_alias).or_die("cannot write to runner alias file");
    }
}

fn get_aliases() -> HashMap<String,String> {
    let alias_file = runner_directory().join("alias");
    if ! alias_file.is_file() { return HashMap::new(); }
	es::lines(es::open(&alias_file))
      .filter_map(|s| s.split_at_delim('=').trim()) // split into (String,String)
	  .to_map()
}

fn massage_snippet(code: String, prelude: String, extern_crates: Vec<String>, wild_crates: Vec<String>) -> String {
    fn indent_line(line: &str) -> String {
        format!("    {}\n",line)
    }
    let mut prefix = prelude;
    let mut body = String::new();
    {
        if extern_crates.len() > 0 {
            let aliases = get_aliases();
            for c in extern_crates {
                prefix += &if let Some(aliased) = aliases.get(&c) {
                    format!("extern crate {} as {};",aliased,c)
                } else {
                    format!("extern crate {};",c)
                };
            }
            for c in wild_crates {
                prefix += &format!("use {}::*;",c);
            }
        }
        let mut lines = code.lines();
        let mut first = true;
        for line in lines.by_ref() {
            let line = line.trim_left();
            if first { // files may start with #! shebang...
                if line.starts_with("#!") {
                    continue;
                }
                first = false;
            }
            if line.starts_with("//") || line.starts_with("#[macro_use") ||
                line.starts_with("extern ") || line.starts_with("use ") {
                prefix += line;
                prefix.push('\n');
            } else
            if line.len() > 0 {
                body += &indent_line(line);
                break;
            }
        }
        // and indent the rest!
        body.extend(lines.map(indent_line));
    }

    format!("{}
use std::error::Error;
fn run() -> Result<(),Box<Error>> {{
{}    Ok(())
}}
fn main() {{
    run().unwrap();
}}
        ",prefix,body)

}

const USAGE: &str = "
Compile and run small Rust snippets
  -s, --static build statically (default is dynamic)
  -O, --optimize optimized static build
  -e, --expression evaluate an expression
  -i, --iterator evaluate an iterator
  -n, --lines evaluate expression over stdin; 'line' is defined
  -x, --extern... (string) add an extern crate to the snippet
  -X, --wild... (string) like -x but implies wildcard use

  Cache Management:
  --create (string...) initialize the static cache with crates
  --add  (string...) add new crates to the cache (after --create)
  --edit  edit the static cache Cargo.toml
  --build rebuild the static cache
  --doc  display documentation
  --edit-prelude edit the default prelude for snippets
  --alias (string...) crate aliases in form alias=crate_name (used with -x)

  Dynamic compilation:
  -P, --crate-path show path of crate source in Cargo cache
  -C, --compile  compile crate dynamically (limited)
  --cfg... (string) pass configuration variables to rustc

  <program> (string) Rust program, snippet or expression
  <args> (string...) arguments to pass to program
";

fn main() {
    let args = lapp::parse_args(USAGE);
    let prelude = get_prelude();

    let aliases = args.get_strings("alias");
    if aliases.len() > 0 {
        add_aliases(aliases);
        return;
    }

    if args.get_bool("edit-prelude") {
        let rdir = runner_directory().join("prelude");
        edit(&rdir);
        return;
    }

    // Static Cache Management
    let crates = args.get_strings("create");
    if crates.len() > 0 {
        create_static_cache(&crates,true);
        return;
    }
    let crates = args.get_strings("add");
    if crates.len() > 0 {
        static_cache_dir_check(&args);
        create_static_cache(&crates,false);
        return;
    }

    if args.get_bool("edit") || args.get_bool("build") || args.get_bool("doc") {
        let static_cache = static_cache_dir_check(&args);
        if args.get_bool("build") {
            env::set_current_dir(&static_cache).or_die("static cache wasn't a directory?");
            build_static_cache();
        } else
        if args.get_bool("doc") {
            let docs = static_cache.join("target/doc/static_cache/index.html");
            open(&docs);
        } else {
            let toml = static_cache.join("Cargo.toml");
            edit(&toml);
        }
        return;
    }

    let first_arg = args.get_string("program");
    let file = PathBuf::from(&first_arg);

    // Dynamically linking crates (experimental!)
    if args.get_bool("crate-path") || args.get_bool("compile") {
        let (crate_path,crate_name) = if file.exists() {
            let filename = crate_utils::path_file_name(&file);
            if file.is_dir() { // assumed to be Cargo directory
                if ! file.join("Cargo.toml").exists() {
                    args.quit("not a Cargo project directory");
                }
                (file.join("src/lib.rs"), filename)
            } else { // should be just a Rust source file
                if file.extension().or_die("expecting extension") != "rs" {
                    args.quit("expecting Rust source file");
                }
                let name = crate_utils::path_file_name(&file.with_extension(""));
                (file, name)
            }
        } else {
            (crate_utils::cache_path(&first_arg).join("src/lib.rs"), first_arg)
        };
        if args.get_bool("crate-path") {
            println!("{}",crate_utils::cache_path(&crate_name).display());
        } else {
            let valid_crate_name = crate_name.replace('-',"_");
            let cache = get_cache(false, false);
            let mut builder = process::Command::new("rustc");
                builder.args(&["-C","prefer-dynamic"]).args(&["-C","debuginfo=0"])
                .arg("-L").arg(&cache)
                .args(&["--crate-type","dylib"])
                .arg("--out-dir").arg(&cache)
                .arg("--crate-name").arg(&valid_crate_name)
                .arg(crate_path);
           for c in args.get_strings("cfg") {
                builder.arg("--cfg").arg(&c);
           }
           builder.status().or_die("can't run rustc");
        }
        return;
    }

    let build_static = args.get_bool("static");
    let optimize = args.get_bool("optimize");

    // we'll pass rest of arguments to program
    let program_args = args.get_strings("args");

    let cache = get_cache(build_static, optimize);

    let mut snippet = false;
    let mut code = if args.get_bool("expression") {
        // Evaluating an expression: just print it out.
        format!("println!(\"{{:?}}\",{});", first_arg)
    } else
    if args.get_bool("iterator") {
        // The expression is anything that implements IntoIterator
        format!("let iter = {};\n for val in iter {{ println!(\"{{:?}}\",val);}}", first_arg)
    } else
    if args.get_bool("lines") {
        // The variable 'line' is available to an expression, evaluated for each line in stdin
        format!("
            let stdin = io::stdin();
            for line in stdin.lock().lines() {{
                let line = line?;
                let val = {};
                println!(\"{{:?}}\",val);
            }}
            ", first_arg)
    } else { // otherwise, just a file
        snippet = true;
        es::read_to_string(&file)
    };

    // proper Rust programs are accepted (this is a bit rough)
    if code.find("fn main").is_none() {
        let mut extern_crates = args.get_strings("extern");
        let wild_crates = args.get_strings("wild");
        if wild_crates.len() > 0 {
            extern_crates.extend(wild_crates.iter().cloned());
        }
        code = massage_snippet(code,prelude, extern_crates, wild_crates);
    }

    // we are going to put the expanded source and resulting exe in the runner bin dir
    let mut out_file = runner_directory().join("bin");
    if snippet {
        out_file.push(&file);
        out_file.set_extension("rs");
    } else {
        out_file.push("tmp.rs");
    }
    let mut program = out_file.clone();
    program.set_extension(EXE);

    es::write_all(&out_file,&code);

    let mut builder = process::Command::new("rustc");
    if ! build_static {
        builder.args(&["-C","prefer-dynamic"]).args(&["-C","debuginfo=0"]);
    } else {
        builder.arg(if optimize {"-O"} else {"-g"});
    }
    builder.arg("-L").arg(&cache);
    let status = builder.arg("-o").arg(&program)
        .arg(&out_file)
        .status().or_die("can't run rustc");
    if ! status.success() {
        return;
    }

    let mut builder = process::Command::new(&program);
    if ! build_static {
        builder.env("LD_LIBRARY_PATH",format!("{}:{}",rustup_lib(),cache.display()));
    }
    builder.args(&program_args)
        .status()
        .or_die(&format!("can't run program {:?}",program));

}

<|endoftext|>


<commit_before>extern crate core;
extern crate getopts;
extern crate hyper;
extern crate id3;
extern crate image;
extern crate iron;
extern crate mount;
extern crate oven;
extern crate params;
extern crate regex;
extern crate rustc_serialize;
extern crate staticfile;
extern crate sqlite;
extern crate toml;
extern crate url;

#[cfg(windows)]
extern crate uuid;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
#[cfg(windows)]
extern crate shell32;
#[cfg(windows)]
extern crate user32;

use getopts::Options;
use iron::prelude::*;
use mount::Mount;
use staticfile::Static;
use std::path;
use std::sync::Arc;

mod api;
mod collection;
mod config;
mod ddns;
mod error;
mod index;
mod ui;
mod utils;
mod thumbnails;
mod vfs;

const DEFAULT_CONFIG_FILE_NAME: &'static str = "polaris.toml";
const INDEX_FILE_NAME: &'static str = "index.sqlite";

fn main() {

    // Parse CLI options
    let args: Vec<String> = std::env::args().collect();
    let mut options = Options::new();
    options.optopt("c", "config", "set the configuration file", "FILE");
    let matches = match options.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string()),
    };
    let config_file_name = matches.opt_str("c").unwrap_or(DEFAULT_CONFIG_FILE_NAME.to_owned());

    // Parse config
    println!("Reading configuration from {}", config_file_name);
    let config_file = path::Path::new(config_file_name.as_str());
    let config = config::Config::parse(&config_file).unwrap();

    // Init VFS
    let vfs = Arc::new(vfs::Vfs::new(config.vfs.clone()));

    // Init index
    println!("Starting up index");
    let index_path = path::Path::new(INDEX_FILE_NAME);
    let index = Arc::new(index::Index::new(&index_path, vfs.clone(), &config.album_art_pattern).unwrap());
    let index_ref = index.clone();
    std::thread::spawn(move || index_ref.run());

    // Start server
    println!("Starting up server");
    let mut api_chain;
    {
        let api_handler;
        {
            let mut collection = collection::Collection::new(vfs, index);
            collection.load_config(&config).unwrap();
            let collection = Arc::new(collection);
            api_handler = api::get_api_handler(collection);
        }
        api_chain = Chain::new(api_handler);

        let auth_secret = config.secret.to_owned();
        let cookie_middleware = oven::new(auth_secret.into_bytes());
        api_chain.link(cookie_middleware);
    }

    let mut mount = Mount::new();
    mount.mount("/api/", api_chain);
    mount.mount("/", Static::new(path::Path::new("../polaris-web")));
    let mut server = Iron::new(mount).http(("0.0.0.0", 5050)).unwrap();

    // Start DDNS updates
    match config.ddns {
        Some(ref ddns_config) => {
            let ddns_config = ddns_config.clone();
            std::thread::spawn(|| {
                ddns::run(ddns_config);
            });
        },
        None => (),
    };

    // Run UI
    ui::run();

    println!("Shutting down server");
    server.close().unwrap();
}
<commit_msg>Reverting accidental commit<commit_after>extern crate core;
extern crate getopts;
extern crate hyper;
extern crate id3;
extern crate image;
extern crate iron;
extern crate mount;
extern crate oven;
extern crate params;
extern crate regex;
extern crate rustc_serialize;
extern crate staticfile;
extern crate sqlite;
extern crate toml;
extern crate url;

#[cfg(windows)]
extern crate uuid;
#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
extern crate kernel32;
#[cfg(windows)]
extern crate shell32;
#[cfg(windows)]
extern crate user32;

use getopts::Options;
use iron::prelude::*;
use mount::Mount;
use staticfile::Static;
use std::path;
use std::sync::Arc;

mod api;
mod collection;
mod config;
mod ddns;
mod error;
mod index;
mod ui;
mod utils;
mod thumbnails;
mod vfs;

const DEFAULT_CONFIG_FILE_NAME: &'static str = "polaris.toml";
const INDEX_FILE_NAME: &'static str = "index.sqlite";

fn main() {

    // Parse CLI options
    let args: Vec<String> = std::env::args().collect();
    let mut options = Options::new();
    options.optopt("c", "config", "set the configuration file", "FILE");
    let matches = match options.parse(&args[1..]) {
        Ok(m) => m,
        Err(f) => panic!(f.to_string()),
    };
    let config_file_name = matches.opt_str("c").unwrap_or(DEFAULT_CONFIG_FILE_NAME.to_owned());

    // Parse config
    println!("Reading configuration from {}", config_file_name);
    let config_file = path::Path::new(config_file_name.as_str());
    let config = config::Config::parse(&config_file).unwrap();

    // Init VFS
    let vfs = Arc::new(vfs::Vfs::new(config.vfs.clone()));

    // Init index
    println!("Starting up index");
    let index_path = path::Path::new(INDEX_FILE_NAME);
    let index = Arc::new(index::Index::new(&index_path, vfs.clone(), &config.album_art_pattern).unwrap());
    let index_ref = index.clone();
    std::thread::spawn(move || index_ref.run());

    // Start server
    println!("Starting up server");
    let mut api_chain;
    {
        let api_handler;
        {
            let mut collection = collection::Collection::new(vfs, index);
            collection.load_config(&config).unwrap();
            let collection = Arc::new(collection);
            api_handler = api::get_api_handler(collection);
        }
        api_chain = Chain::new(api_handler);

        let auth_secret = config.secret.to_owned();
        let cookie_middleware = oven::new(auth_secret.into_bytes());
        api_chain.link(cookie_middleware);
    }

    let mut mount = Mount::new();
    mount.mount("/api/", api_chain);
    mount.mount("/", Static::new(path::Path::new("web")));
    let mut server = Iron::new(mount).http(("0.0.0.0", 5050)).unwrap();

    // Start DDNS updates
    match config.ddns {
        Some(ref ddns_config) => {
            let ddns_config = ddns_config.clone();
            std::thread::spawn(|| {
                ddns::run(ddns_config);
            });
        },
        None => (),
    };

    // Run UI
    ui::run();

    println!("Shutting down server");
    server.close().unwrap();
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate peresil;
extern crate rustc_serialize;
extern crate docopt;

mod parser;
mod compiler;
mod machine;

use std::fs::File;
use std::io::prelude::*;
use std::collections::BTreeMap;

use parser::Parser;
use compiler::Program;
use machine::{Input, Output, Registers, Tile, Machine};

use docopt::Docopt;

#[derive(Debug, Copy, Clone)]
pub enum Register {
    Direct(u8),
    Indirect(u8),
}

fn append_string(input: &mut Input, s: &str) {
    input.extend(s.chars().map(Tile::Letter));
}

fn append_zero_terminated_string(input: &mut Input, s: &str) {
    append_string(input, s);
    input.push(Tile::num(0));
}

type Level = (Input, Registers, Output);

// Copy inbox to outbox, losing duplicates
fn level_35() -> Level {
    let mut input = Vec::new();
    append_string(&mut input, "eabedebaeb");

    let mut registers = BTreeMap::new();
    registers.insert(14, Tile::num(0));

    let mut output = Vec::new();
    append_string(&mut output, "eabd");

    (input, registers, output)
}

// Given two zero-terminated words, output the word that is first in
// alphabetical order
fn level_36() -> Level {
    let mut input = Vec::new();
    append_zero_terminated_string(&mut input, "aab");
    append_zero_terminated_string(&mut input, "aaa");

    let mut registers = BTreeMap::new();
    registers.insert(23, Tile::num(0));
    registers.insert(24, Tile::num(10));

    let mut output = Vec::new();
    append_string(&mut output, "aaa");

    (input, registers, output)
}

fn level_37() -> Level {
    let input = [0, 23].iter().cloned().map(Tile::num).collect();

    let mut registers = BTreeMap::new();
    let z = [
        (0, 'e', 13),
        (3, 'c', 23),
        (10, 'p', 20),
        (13, 's', 3),
        (20, 'e', -1),
        (23, 'a', 10),
    ];
    for &(idx, c, v) in &z {
        registers.insert(idx, Tile::Letter(c));
        registers.insert(idx + 1, Tile::num(v));
    }

    let mut output = Vec::new();
    append_string(&mut output, "escapeape");

    (input, registers, output)
}

// Given numbers, output the digits of the numbers
fn level_38() -> Level {
    let input = [33, 505, 7, 979].iter().cloned().map(Tile::num).collect();

    let mut registers = BTreeMap::new();
    registers.insert(9, Tile::num(0));
    registers.insert(10, Tile::num(10));
    registers.insert(11, Tile::num(100));

    let output = [3, 3, 5, 0, 5, 7, 9, 7, 9].iter().cloned().map(Tile::num).collect();

    (input, registers, output)
}

fn report_parsing_error(s: &str, offset: usize, errors: &[parser::Error]) {
    let upto = &s[..offset];
    let leading_nl = upto.rfind("\n").map(|x| x + 1).unwrap_or(0);
    let after = &s[offset..];
    let trailing_nl = after.find("\n").unwrap_or(after.len()) + offset;

    let line = &s[leading_nl..trailing_nl];
    let inner_offset = offset - leading_nl;

    println!("Error occured while parsing:");
    println!("{}", line);
    for _ in 0..inner_offset { print!(" ") }
    println!("^");
    println!("{:?}", errors);
}

const USAGE: &'static str = "
Human Resource Machine simulator.

Usage:
  human-resource-machine <level> <file>
";

#[derive(Debug, Clone, RustcDecodable)]
struct Args {
    arg_level: usize,
    arg_file: String,
}

fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut f = File::open(args.arg_file).expect("Could not open source file");

    let mut s = String::new();
    f.read_to_string(&mut s).expect("Could not read source file");

    let t = Parser::new(&s);

    let p = match Program::compile(t) {
        Ok(p) => p,
        Err(compiler::Error::ParserError((offset, errors))) => {
            report_parsing_error(&s, offset, &errors);
            return;
        },
        Err(e) =>  {
            println!("Error occurred while compiling: {:?}", e);
            return;
        },
    };

    let (input, registers, output) = match args.arg_level {
        35 => level_35(),
        36 => level_36(),
        37 => level_37(),
        38 => level_38(),
        _ => panic!("Unknown level {}", args.arg_level),
    };
    let mut m = Machine::new(p, input, registers);

    match m.run() {
        Ok(..) => {
            let actual_output = m.into_output();
            println!("Program completed");
            if actual_output == output {
                println!("Output matched!");
            } else {
                println!("Output did not match");
                println!("Expected: {:?}", output);
                println!("Got:      {:?}", actual_output);
            }
        },
        Err(e) => {
            println!("Program failed");
            println!("{:?}", e);
        }
    }
}
<commit_msg>Document level 37<commit_after>#[macro_use]
extern crate peresil;
extern crate rustc_serialize;
extern crate docopt;

mod parser;
mod compiler;
mod machine;

use std::fs::File;
use std::io::prelude::*;
use std::collections::BTreeMap;

use parser::Parser;
use compiler::Program;
use machine::{Input, Output, Registers, Tile, Machine};

use docopt::Docopt;

#[derive(Debug, Copy, Clone)]
pub enum Register {
    Direct(u8),
    Indirect(u8),
}

fn append_string(input: &mut Input, s: &str) {
    input.extend(s.chars().map(Tile::Letter));
}

fn append_zero_terminated_string(input: &mut Input, s: &str) {
    append_string(input, s);
    input.push(Tile::num(0));
}

type Level = (Input, Registers, Output);

// Copy inbox to outbox, losing duplicates
fn level_35() -> Level {
    let mut input = Vec::new();
    append_string(&mut input, "eabedebaeb");

    let mut registers = BTreeMap::new();
    registers.insert(14, Tile::num(0));

    let mut output = Vec::new();
    append_string(&mut output, "eabd");

    (input, registers, output)
}

// Given two zero-terminated words, output the word that is first in
// alphabetical order
fn level_36() -> Level {
    let mut input = Vec::new();
    append_zero_terminated_string(&mut input, "aab");
    append_zero_terminated_string(&mut input, "aaa");

    let mut registers = BTreeMap::new();
    registers.insert(23, Tile::num(0));
    registers.insert(24, Tile::num(10));

    let mut output = Vec::new();
    append_string(&mut output, "aaa");

    (input, registers, output)
}

// There are pairs of letters and next pointers in the registers,
// starting at the input, follow the chain of registers until you get
// to -1. Output each letter on the way.
fn level_37() -> Level {
    let input = [0, 23].iter().cloned().map(Tile::num).collect();

    let mut registers = BTreeMap::new();
    let z = [
        (0, 'e', 13),
        (3, 'c', 23),
        (10, 'p', 20),
        (13, 's', 3),
        (20, 'e', -1),
        (23, 'a', 10),
    ];
    for &(idx, c, v) in &z {
        registers.insert(idx, Tile::Letter(c));
        registers.insert(idx + 1, Tile::num(v));
    }

    let mut output = Vec::new();
    append_string(&mut output, "escapeape");

    (input, registers, output)
}

// Given numbers, output the digits of the numbers
fn level_38() -> Level {
    let input = [33, 505, 7, 979].iter().cloned().map(Tile::num).collect();

    let mut registers = BTreeMap::new();
    registers.insert(9, Tile::num(0));
    registers.insert(10, Tile::num(10));
    registers.insert(11, Tile::num(100));

    let output = [3, 3, 5, 0, 5, 7, 9, 7, 9].iter().cloned().map(Tile::num).collect();

    (input, registers, output)
}

fn report_parsing_error(s: &str, offset: usize, errors: &[parser::Error]) {
    let upto = &s[..offset];
    let leading_nl = upto.rfind("\n").map(|x| x + 1).unwrap_or(0);
    let after = &s[offset..];
    let trailing_nl = after.find("\n").unwrap_or(after.len()) + offset;

    let line = &s[leading_nl..trailing_nl];
    let inner_offset = offset - leading_nl;

    println!("Error occured while parsing:");
    println!("{}", line);
    for _ in 0..inner_offset { print!(" ") }
    println!("^");
    println!("{:?}", errors);
}

const USAGE: &'static str = "
Human Resource Machine simulator.

Usage:
  human-resource-machine <level> <file>
";

#[derive(Debug, Clone, RustcDecodable)]
struct Args {
    arg_level: usize,
    arg_file: String,
}

fn main() {
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut f = File::open(args.arg_file).expect("Could not open source file");

    let mut s = String::new();
    f.read_to_string(&mut s).expect("Could not read source file");

    let t = Parser::new(&s);

    let p = match Program::compile(t) {
        Ok(p) => p,
        Err(compiler::Error::ParserError((offset, errors))) => {
            report_parsing_error(&s, offset, &errors);
            return;
        },
        Err(e) =>  {
            println!("Error occurred while compiling: {:?}", e);
            return;
        },
    };

    let (input, registers, output) = match args.arg_level {
        35 => level_35(),
        36 => level_36(),
        37 => level_37(),
        38 => level_38(),
        _ => panic!("Unknown level {}", args.arg_level),
    };
    let mut m = Machine::new(p, input, registers);

    match m.run() {
        Ok(..) => {
            let actual_output = m.into_output();
            println!("Program completed");
            if actual_output == output {
                println!("Output matched!");
            } else {
                println!("Output did not match");
                println!("Expected: {:?}", output);
                println!("Got:      {:?}", actual_output);
            }
        },
        Err(e) => {
            println!("Program failed");
            println!("{:?}", e);
        }
    }
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate clap;
extern crate chrono;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::env;
use std::fs;
use std::fs::{OpenOptions, File};
use std::io::{Write, ErrorKind};
use std::path::PathBuf;

use chrono::{DateTime, Utc};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Period {
    project: String,
    start_time: DateTime<Utc>,
    end_time: Option<DateTime<Utc>>,
}

fn main() {
    let matches = App::new("Doug")
        .version(crate_version!())
        .about("A time tracking command-line utility")
        .author(crate_authors!())
        .settings(&[
            AppSettings::DeriveDisplayOrder,
            AppSettings::GlobalVersion,
            AppSettings::SubcommandRequiredElseHelp,
            AppSettings::VersionlessSubcommands,
            AppSettings::DisableHelpSubcommand,
        ])
        .subcommand(SubCommand::with_name("start")
            .about("Track new or existing project")
            .arg(Arg::with_name("project")
                .help("project to track")
                .required(true)))
        .subcommand(SubCommand::with_name("status")
            .about("Display elapsed time, start time, and running project name"))
        .subcommand(SubCommand::with_name("stop")
            .about("Stop any running projects"))
        .subcommand(SubCommand::with_name("cancel")
            .about("Stop running project and remove most recent time interval"))
        .subcommand(SubCommand::with_name("restart")
            .about("Track last running project"))
        .subcommand(SubCommand::with_name("log")
            .about("Display time intervals across all projects"))
        .subcommand(SubCommand::with_name("report")
            .about("Display aggregate time from last week"))
        .subcommand(SubCommand::with_name("amend")
            .about("Change name of currently running project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .subcommand(SubCommand::with_name("edit")
            .about("Edit last frame or currently running frame")
            .arg(Arg::with_name("project")
                .help("project to track")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("start") {
        if matches.is_present("project") {
            start(matches.value_of("project").unwrap());
        }
    }

    if let Some(matches) = matches.subcommand_matches("amend") {
        if matches.is_present("project") {
            amend(matches.value_of("project").expect("missing project name"));
        }
    }

    if let Some(matches) = matches.subcommand_matches("edit") {
        edit(matches.value_of("project"));
    }

    match matches.subcommand_name() {
        Some("stop") => stop(),
        Some("cancel") => cancel(),
        Some("restart") => restart(),
        Some("log") => log(),
        Some("report") => report(),
        _ => {},
    }
}

fn start(project_name: &str) {

    let mut periods = get_periods();

    if !periods.is_empty() {
        let last_index = periods.len() - 1;
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                eprintln!("Sorry, you're currently tracking project `{}`, started `{}`", period.project, period.start_time);
                eprintln!("Try stopping your current project with `stop` first.`");
                return
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project '{}'", current_period.project);
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn stop() {
    let mut periods = get_periods();
    let mut updated_period = false;
    let last_index = periods.len() - 1;
    if periods.len() > 0 {
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                period.end_time = Some(Utc::now());
                updated_period = true;
                println!("Stopped project: `{}`", period.project);
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("No running project to stop.");
    }
}

fn cancel() {
    unimplemented!();
}

fn restart() {
    unimplemented!();
}

fn log() {
    unimplemented!();
}

fn report() {
    unimplemented!();
}

fn amend(project_name: &str) {
    println!("Amend project: {:?}", project_name);
    unimplemented!();
}

fn edit(project_name: Option<&str>) {
    if let Some(name) = project_name {
        println!("Edit project: {}", name);
    }
    unimplemented!();
}

fn create_period(project: &str) -> Period {
    Period {
        project: String::from(project),
        start_time: Utc::now(),
        end_time: None,
    }
}

fn get_config_folder() -> PathBuf {
    let home_dir = env::var("HOME").expect("Failed to find home directory from environment 'HOME'");
    let mut config_folder = PathBuf::from(home_dir);
    config_folder.push(".config/doug");
    config_folder
}

fn get_config_file_path() -> PathBuf {
    let config_folder = get_config_folder();
    let mut config_file = PathBuf::from(&config_folder);
    config_file.push("periods.json");
    config_file
}

fn get_config_back_file_path() -> PathBuf {
    let config_file = get_config_file_path();
    config_file.with_extension("json-backup")
}

fn get_config_file() -> File {
    let config_file = get_config_file_path();
    OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open configuration file: {:?}", config_file))
}

fn get_periods() -> Vec<Period> {
    let config_folder = get_config_folder();

    match fs::create_dir(&config_folder) {
        Err(ref error) if error.kind() == ErrorKind::AlreadyExists => {},
        Err(error) => panic!("There was a problem creating the config directory: {:?}", error),
        Ok(_) => {},
    }

    let file = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(ref error) if error.is_eof() => Vec::new(),
        Err(error) => panic!("There was a serialization issue: {:?}", error),
    };
    periods
}

fn save_periods(periods: Vec<Period>) {
    let serialized = serde_json::to_string(&periods).expect("Couldn't serialize data to string");
    let config_file = get_config_file_path();
    let config_file_backup = get_config_back_file_path();
    fs::copy(&config_file, &config_file_backup).expect("Couldn't create backup file");
    let mut file = OpenOptions::new()
                    .create(true)
                    .write(true)
                    .truncate(true)
                    .open(&config_file)
                    .expect("Couldn't open file for saving period.");

    file.write_all(serialized.as_bytes()).expect("Couldn't write serialized data to file");
}
<commit_msg>Add cancel subcommand (#3)<commit_after>#[macro_use]
extern crate clap;
extern crate chrono;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

use std::env;
use std::fs;
use std::fs::{OpenOptions, File};
use std::io::{Write, ErrorKind};
use std::path::PathBuf;

use chrono::{DateTime, Utc};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;

#[derive(Serialize, Deserialize, Debug, Clone)]
struct Period {
    project: String,
    start_time: DateTime<Utc>,
    end_time: Option<DateTime<Utc>>,
}

fn main() {
    let matches = App::new("Doug")
        .version(crate_version!())
        .about("A time tracking command-line utility")
        .author(crate_authors!())
        .settings(&[
            AppSettings::DeriveDisplayOrder,
            AppSettings::GlobalVersion,
            AppSettings::SubcommandRequiredElseHelp,
            AppSettings::VersionlessSubcommands,
            AppSettings::DisableHelpSubcommand,
        ])
        .subcommand(SubCommand::with_name("start")
            .about("Track new or existing project")
            .arg(Arg::with_name("project")
                .help("project to track")
                .required(true)))
        .subcommand(SubCommand::with_name("status")
            .about("Display elapsed time, start time, and running project name"))
        .subcommand(SubCommand::with_name("stop")
            .about("Stop any running projects"))
        .subcommand(SubCommand::with_name("cancel")
            .about("Stop running project and remove most recent time interval"))
        .subcommand(SubCommand::with_name("restart")
            .about("Track last running project"))
        .subcommand(SubCommand::with_name("log")
            .about("Display time intervals across all projects"))
        .subcommand(SubCommand::with_name("report")
            .about("Display aggregate time from last week"))
        .subcommand(SubCommand::with_name("amend")
            .about("Change name of currently running project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .subcommand(SubCommand::with_name("edit")
            .about("Edit last frame or currently running frame")
            .arg(Arg::with_name("project")
                .help("project to track")))
        .get_matches();

    if let Some(matches) = matches.subcommand_matches("start") {
        if matches.is_present("project") {
            start(matches.value_of("project").unwrap());
        }
    }

    if let Some(matches) = matches.subcommand_matches("amend") {
        if matches.is_present("project") {
            amend(matches.value_of("project").expect("missing project name"));
        }
    }

    if let Some(matches) = matches.subcommand_matches("edit") {
        edit(matches.value_of("project"));
    }

    match matches.subcommand_name() {
        Some("stop") => stop(),
        Some("cancel") => cancel(),
        Some("restart") => restart(),
        Some("log") => log(),
        Some("report") => report(),
        _ => {},
    }
}

fn start(project_name: &str) {

    let mut periods = get_periods();

    if !periods.is_empty() {
        let last_index = periods.len() - 1;
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                eprintln!("Sorry, you're currently tracking project `{}`, started `{}`", period.project, period.start_time);
                eprintln!("Try stopping your current project with `stop` first.`");
                return
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project '{}'", current_period.project);
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn stop() {
    let mut periods = get_periods();
    let mut updated_period = false;
    let last_index = periods.len() - 1;
    if periods.len() > 0 {
        if let Some(period) = periods.get_mut(last_index) {
            if period.end_time.is_none() {
                period.end_time = Some(Utc::now());
                updated_period = true;
                println!("Stopped project: `{}`", period.project);
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("No running project to stop.");
    }
}

fn cancel() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            save_periods(periods);
            println!("Canceled running project: {}", period.project);
            return
        }
    }
    eprintln!("No running project to cancel");
}

fn restart() {
    unimplemented!();
}

fn log() {
    unimplemented!();
}

fn report() {
    unimplemented!();
}

fn amend(project_name: &str) {
    println!("Amend project: {:?}", project_name);
    unimplemented!();
}

fn edit(project_name: Option<&str>) {
    if let Some(name) = project_name {
        println!("Edit project: {}", name);
    }
    unimplemented!();
}

fn create_period(project: &str) -> Period {
    Period {
        project: String::from(project),
        start_time: Utc::now(),
        end_time: None,
    }
}

fn get_config_folder() -> PathBuf {
    let home_dir = env::var("HOME").expect("Failed to find home directory from environment 'HOME'");
    let mut config_folder = PathBuf::from(home_dir);
    config_folder.push(".config/doug");
    config_folder
}

fn get_config_file_path() -> PathBuf {
    let config_folder = get_config_folder();
    let mut config_file = PathBuf::from(&config_folder);
    config_file.push("periods.json");
    config_file
}

fn get_config_back_file_path() -> PathBuf {
    let config_file = get_config_file_path();
    config_file.with_extension("json-backup")
}

fn get_config_file() -> File {
    let config_file = get_config_file_path();
    OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open configuration file: {:?}", config_file))
}

fn get_periods() -> Vec<Period> {
    let config_folder = get_config_folder();

    match fs::create_dir(&config_folder) {
        Err(ref error) if error.kind() == ErrorKind::AlreadyExists => {},
        Err(error) => panic!("There was a problem creating the config directory: {:?}", error),
        Ok(_) => {},
    }

    let file = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(ref error) if error.is_eof() => Vec::new(),
        Err(error) => panic!("There was a serialization issue: {:?}", error),
    };
    periods
}

fn save_periods(periods: Vec<Period>) {
    let serialized = serde_json::to_string(&periods).expect("Couldn't serialize data to string");
    let config_file = get_config_file_path();
    let config_file_backup = get_config_back_file_path();
    fs::copy(&config_file, &config_file_backup).expect("Couldn't create backup file");
    let mut file = OpenOptions::new()
                    .create(true)
                    .write(true)
                    .truncate(true)
                    .open(&config_file)
                    .expect("Couldn't open file for saving period.");

    file.write_all(serialized.as_bytes()).expect("Couldn't write serialized data to file");
}
<|endoftext|>


<commit_before>extern crate crypto;
extern crate num;
extern crate rustc_serialize;
extern crate time;
extern crate protobuf;

use address::Address;
use node::{Node, Deserialize, Serialize, UdpNode};
use server::Server;
use std::env;
use std::sync::mpsc;

mod address;
mod messages;
mod node;
mod node_bucket;
mod routing_table;
mod server;

fn main() {
    let args: Vec<String> = env::args().collect();
    let secret = args[1].clone();
    let address = Address::for_content(secret.as_str());
    let port = args[2].clone().parse::<u16>().unwrap();

    let (tx, messages) = mpsc::channel();
    server::UdpServer::new(port).start(tx);

    let mut my_origin = messages::Node::new();
    my_origin.set_id(address.to_str());
    my_origin.set_ip_address(vec![127, 0, 0, 1]);
    my_origin.set_port(port as u32);

    let mut routing_table = routing_table::RoutingTable::new(8, address);

    if let Some(bootstrap) = args.get(3) {
        println!("Bootstrapping");
        let bootstrap_address = ("127.0.0.1", bootstrap.clone().parse::<u16>().unwrap());
        let mut find_node_query = messages::FindNodeQuery::new();
        find_node_query.set_origin(my_origin.clone());
        find_node_query.set_target(address.to_str());
        let mut envelope = messages::Envelope::new();
        envelope.set_message_type(messages::Envelope_Type::FIND_NODE_QUERY);
        envelope.set_find_node_query(find_node_query);
        let node = UdpNode::new(Address::null(), bootstrap_address);
        node.send(envelope);
    }

    loop {
        let (size, buf) = messages.recv().unwrap();
        let message = protobuf::parse_from_bytes::<messages::Envelope>(&buf[0..size]).unwrap();
        println!("Message: {:?}", message);

        match message.get_message_type() {
            messages::Envelope_Type::FIND_NODE_QUERY => {
                let find_node_query = message.get_find_node_query();
                let origin = node::UdpNode::deserialize(find_node_query.get_origin());
                let target = Address::from_str(find_node_query.get_target());

                {
                    let nodes: Vec<&Box<node::Node>> = routing_table.nearest_to(&target);
                    println!("Nearest nodes: {:?}", nodes);
                    let nodes: Vec<messages::Node> = nodes.iter().map(|n| n.serialize()).collect();
                    let mut find_node_response = messages::FindNodeResponse::new();
                    find_node_response.set_origin(my_origin.clone());
                    find_node_response.set_nodes(
                        protobuf::RepeatedField::from_slice(nodes.as_slice())
                    );
                    let mut response = messages::Envelope::new();
                    response.set_message_type(messages::Envelope_Type::FIND_NODE_RESPONSE);
                    response.set_find_node_response(find_node_response);

                    origin.send(response);
                }

                routing_table.insert(origin);
            }
            messages::Envelope_Type::FIND_NODE_RESPONSE => {
                let find_node_response = message.get_find_node_response();
                let origin = node::UdpNode::deserialize(find_node_response.get_origin());
                routing_table.insert(origin);

                for node in find_node_response.get_nodes() {
                    let node = node::UdpNode::deserialize(node);
                    routing_table.insert(node);
                }
            }
        }
        println!("Routing Table: {:?}", routing_table);
    }
}
<commit_msg>Alphabetize extern crates<commit_after>extern crate crypto;
extern crate num;
extern crate protobuf;
extern crate rustc_serialize;
extern crate time;

use address::Address;
use node::{Node, Deserialize, Serialize, UdpNode};
use server::Server;
use std::env;
use std::sync::mpsc;

mod address;
mod messages;
mod node;
mod node_bucket;
mod routing_table;
mod server;

fn main() {
    let args: Vec<String> = env::args().collect();
    let secret = args[1].clone();
    let address = Address::for_content(secret.as_str());
    let port = args[2].clone().parse::<u16>().unwrap();

    let (tx, messages) = mpsc::channel();
    server::UdpServer::new(port).start(tx);

    let mut my_origin = messages::Node::new();
    my_origin.set_id(address.to_str());
    my_origin.set_ip_address(vec![127, 0, 0, 1]);
    my_origin.set_port(port as u32);

    let mut routing_table = routing_table::RoutingTable::new(8, address);

    if let Some(bootstrap) = args.get(3) {
        println!("Bootstrapping");
        let bootstrap_address = ("127.0.0.1", bootstrap.clone().parse::<u16>().unwrap());
        let mut find_node_query = messages::FindNodeQuery::new();
        find_node_query.set_origin(my_origin.clone());
        find_node_query.set_target(address.to_str());
        let mut envelope = messages::Envelope::new();
        envelope.set_message_type(messages::Envelope_Type::FIND_NODE_QUERY);
        envelope.set_find_node_query(find_node_query);
        let node = UdpNode::new(Address::null(), bootstrap_address);
        node.send(envelope);
    }

    loop {
        let (size, buf) = messages.recv().unwrap();
        let message = protobuf::parse_from_bytes::<messages::Envelope>(&buf[0..size]).unwrap();
        println!("Message: {:?}", message);

        match message.get_message_type() {
            messages::Envelope_Type::FIND_NODE_QUERY => {
                let find_node_query = message.get_find_node_query();
                let origin = node::UdpNode::deserialize(find_node_query.get_origin());
                let target = Address::from_str(find_node_query.get_target());

                {
                    let nodes: Vec<&Box<node::Node>> = routing_table.nearest_to(&target);
                    println!("Nearest nodes: {:?}", nodes);
                    let nodes: Vec<messages::Node> = nodes.iter().map(|n| n.serialize()).collect();
                    let mut find_node_response = messages::FindNodeResponse::new();
                    find_node_response.set_origin(my_origin.clone());
                    find_node_response.set_nodes(
                        protobuf::RepeatedField::from_slice(nodes.as_slice())
                    );
                    let mut response = messages::Envelope::new();
                    response.set_message_type(messages::Envelope_Type::FIND_NODE_RESPONSE);
                    response.set_find_node_response(find_node_response);

                    origin.send(response);
                }

                routing_table.insert(origin);
            }
            messages::Envelope_Type::FIND_NODE_RESPONSE => {
                let find_node_response = message.get_find_node_response();
                let origin = node::UdpNode::deserialize(find_node_response.get_origin());
                routing_table.insert(origin);

                for node in find_node_response.get_nodes() {
                    let node = node::UdpNode::deserialize(node);
                    routing_table.insert(node);
                }
            }
        }
        println!("Routing Table: {:?}", routing_table);
    }
}
<|endoftext|>


<commit_before>//! # mm_api_notify
#[cfg(test)]
extern crate mockito;

extern crate app_dirs;
#[macro_use]
extern crate bson;
extern crate chrono;
extern crate clap;
extern crate fern;
#[macro_use]
extern crate log;
extern crate mm_client;
extern crate mongo_driver;
extern crate rayon;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;

mod client;
mod config;
mod error;
mod hooks;
mod objects;
mod runtime;
mod storage;
mod types;

use app_dirs::{get_app_dir, AppDataType, AppInfo};
use chrono::offset::Utc;
use chrono::{Duration, NaiveDateTime};
use clap::{App, Arg};
use serde_json::error::Result as JsonResult;
use serde_json::Value as Json;

use std::str::FromStr;
use std::{thread, time};

use client::{APIClient, ClientResult, MMClient};
use config::{parse_config, APIConfig, DBConfig};
use error::{IngestError, IngestResult};
use hooks::Payload;
use objects::{Collection, Importable, Object};
use runtime::Runtime;
use storage::{MongoStore, Storage};
use types::{RunResult, StorageEngine, ThreadedAPI};

///
/// Starts processing
///
fn main() {
    let matches = App::new("Media Manager Notifier")
        .version(env!("CARGO_PKG_VERSION"))
        .arg(
            Arg::with_name("config")
                .short("c")
                .long("config")
                .takes_value(true)
                .help("Path to configuration file"),
        )
        .arg(
            Arg::with_name("build")
                .short("b")
                .long("build")
                .takes_value(false)
                .help("Performs a full build run prior to entering the update loop"),
        )
        .arg(
            Arg::with_name("show")
                .short("s")
                .long("show")
                .takes_value(true)
                .requires("build")
                .help("Limits the full build to a single show"),
        )
        .arg(
            Arg::with_name("skip-update")
                .short("k")
                .long("skip-update")
                .takes_value(false)
                .help("Prevents update loop from running"),
        )
        .arg(
            Arg::with_name("start-time")
                .long("start-time")
                .short("t")
                .takes_value(true)
                .help("Defines the timestamp to start building or updating from"),
        )
        .arg(
            Arg::with_name("log-level")
                .long("log-level")
                .short("l")
                .takes_value(true)
                .help("Defines the log level to run at. Defaults to WARN"),
        )
        .arg(
            Arg::with_name("query")
                .long("query")
                .short("q")
                .takes_value(true)
                .number_of_values(2)
                .value_names(&["type", "id"])
                .conflicts_with_all(&["build", "skip-update", "start-time"])
                .help(
                    "Queries the cache with a type and id pair and displays the payload that the \
                     runner will emit",
                ),
        )
        .get_matches();

    let config_path = if !matches.is_present("config") {
        let info = AppInfo {
            name: env!("CARGO_PKG_NAME"),
            author: env!("CARGO_PKG_AUTHORS"),
        };

        let path = get_app_dir(AppDataType::UserConfig, &info, "/")
            .and_then(|mut path| {
                path.push("config.toml");
                Ok(path)
            })
            .expect("Failed to run. Unable to determine path default config location.");

        path.to_str().map(|str| str.to_string())
    } else {
        matches.value_of("config").map(|str| str.to_string())
    }
    .expect("Failed to run. Unable to parse path to default config location.");

    let conf_res = parse_config(config_path.as_str())
        .ok_or(IngestError::InvalidConfig)
        .and_then(|config| {
            // Initialize logging
            if let &Some(ref log_location) = &config.log.location {
                let config_log_level_filter = match config.log.level {
                    Some(ref level) => log::LogLevelFilter::from_str(level.as_str()).ok(),
                    None => None,
                };

                let log_level = matches
                    .value_of("log-level")
                    .and_then(|level| log::LogLevelFilter::from_str(level).ok())
                    .or(config_log_level_filter)
                    .unwrap_or(log::LogLevelFilter::Warn);

                fern::Dispatch::new()
                    .format(|out, message, record| {
                        out.finish(format_args!(
                            "[{}][{}] {}",
                            Utc::now().format("%Y-%m-%d][%H:%M:%S"),
                            record.level(),
                            message
                        ))
                    })
                    .level(log_level)
                    .chain(std::io::stdout())
                    .chain(fern::log_file(log_location.as_str()).expect("Failed to open log file"))
                    .apply()
                    .expect("Failed to initialize logger");
            }

            // Initialize the thread pools
            rayon::initialize(rayon::Configuration::new().num_threads(config.thread_pool_size))
                .or_else(|err| {
                    panic!(
                        "Failed to initalize the configured thread pool size. Unable to \
                         start. {}",
                        err
                    );
                })
                .and_then(|_| {
                    let store = get_store(&config.db);
                    let api = get_api_client(&config.mm);

                    let runtime = Runtime {
                        api: api,
                        config: config,
                        store: store,
                    };

                    if let Some(query) = matches.values_of("query") {
                        let query_args = query.collect::<Vec<&str>>();
                        match runtime.store.get(query_args[1], query_args[0]) {
                            Some(Ok(obj)) => match Payload::from_object(&obj, &runtime.store) {
                                Some(payload) => {
                                    println!("{}", serde_json::to_string_pretty(&payload).unwrap())
                                }
                                None => error!("Failed to generate payload from object."),
                            },
                            _ => println!("Could not find the requested object in the cache."),
                        };
                    } else {
                        let time_arg = matches.value_of("start-time").map_or(0, |arg| {
                            arg.parse::<i64>().expect("Could not parse start time")
                        });

                        let build_res = if matches.is_present("build") {
                            if let Some(show) = matches.value_of("show") {
                                run_show(&runtime, time_arg, show).ok()
                            } else if let Some(franchise) = matches.value_of("franchise") {
                                run_franchise(&runtime, time_arg, franchise).ok()
                            } else {
                                run_build(&runtime, time_arg).ok()
                            }
                        } else {
                            None
                        };

                        if !matches.is_present("skip-update") {
                            let now = Utc::now().timestamp();

                            let update_start_time =
                                if time_arg < (now - runtime.config.mm.changelog_max_timespan) {
                                    compute_update_start_time(
                                        runtime.store.updated_at(),
                                        runtime.config.mm.changelog_max_timespan,
                                    )
                                } else {
                                    time_arg + build_res.map_or(0, |(dur, _)| dur.num_seconds())
                                };

                            run_update_loop(&runtime, update_start_time)
                        };
                    }

                    Ok(())
                })
        });

    conf_res.expect("Failed to parse config.")
}

fn get_store(config: &DBConfig) -> MongoStore {
    MongoStore::new(config).expect("Failed to connect to storage")
}

fn get_api_client(config: &APIConfig) -> MMClient {
    MMClient::new(Some(config)).expect("Failed to initalize network client")
}

fn run_build<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    info!(
        "Starting build run from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    let result = import_collection(runtime.api.all_shows(), runtime, run_start_time);
    print_runtime("Create", &result);

    result
}

fn run_show<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
    id: &str,
) -> IngestResult<RunResult> {
    info!(
        "Starting show build run from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    let result = import_object(runtime.api.show(id), runtime, run_start_time);
    print_runtime("Create", &result);

    result
}

fn run_franchise<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
    id: &str,
) -> IngestResult<RunResult> {
    info!(
        "Starting franchise build run from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    let result = import_object(runtime.api.franchise(id), runtime, run_start_time);
    print_runtime("Create", &result);

    result
}

fn compute_update_start_time(last_updated_at: Option<i64>, max_timespan: i64) -> i64 {
    let newest_db_timestamp = last_updated_at.unwrap_or(0);
    let now = Utc::now().timestamp();

    if newest_db_timestamp < (now - max_timespan) {
        warn!(
            "Newest database record exceeds the maximum threshold for updates. Performing \
             update run with the maximum threshold. Consider performing a build (-b) run to \
             fully update the database."
        );
        now - max_timespan
    } else {
        newest_db_timestamp
    }
}

fn run_update_loop<T: StorageEngine, S: ThreadedAPI>(runtime: &Runtime<T, S>, run_start_time: i64) {
    let mut import_start_time = run_start_time;
    let mut import_completion_time = Utc::now().timestamp();
    let mut next_run_time = run_start_time;
    let label = "Update";

    info!(
        "Starting update loop from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    loop {
        if Utc::now().timestamp() > next_run_time {
            next_run_time = Utc::now().timestamp() + runtime.config.min_runtime_delta;

            import_start_time = import_start_time - runtime.config.lookback_timeframe;

            info!(
                "Starting update run from {} : {}",
                run_start_time,
                NaiveDateTime::from_timestamp(import_start_time, 0)
            );

            let run_time = run_update(runtime, import_start_time);

            print_runtime(label, &run_time);

            import_start_time = import_completion_time;
            import_completion_time = Utc::now().timestamp();

            let diff = next_run_time - import_completion_time;

            if diff > 0 {
                thread::sleep(time::Duration::from_secs(diff as u64));
            }
        }
    }
}

fn run_update<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    let date_string = NaiveDateTime::from_timestamp(run_start_time, 0)
        .format("%Y-%m-%dT%H:%M:%S%.3fZ")
        .to_string();

    import_collection(
        runtime.api.changes(date_string.as_str()),
        runtime,
        run_start_time,
    )
}

fn import_collection<T: StorageEngine, S: ThreadedAPI>(
    response: ClientResult<String>,
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    let start_time = Utc::now();

    let collection = match response {
        Ok(response_string) => {
            let full_json: JsonResult<Json> = serde_json::from_str(response_string.as_str());
            match full_json {
                Ok(json) => Collection::from_json(&json),
                Err(err) => Err(IngestError::Parse(err)),
            }
        }
        Err(err) => Err(IngestError::Client(err)),
    };

    collection
        .and_then(|coll| {
            let res = coll.import(runtime, true, run_start_time);
            Ok((Utc::now().signed_duration_since(start_time), res))
        })
        .or(Ok((Duration::seconds(0), (0, 1))))
}

fn import_object<T: StorageEngine, S: ThreadedAPI>(
    response: ClientResult<String>,
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    let start_time = Utc::now();

    let object = match response {
        Ok(response_string) => {
            let full_json: JsonResult<Json> = serde_json::from_str(response_string.as_str());
            match full_json {
                Ok(json) => Object::from_json(&json),
                Err(err) => Err(IngestError::Parse(err)),
            }
        }
        Err(err) => Err(IngestError::Client(err)),
    };

    object
        .and_then(|obj| {
            let res = obj.import(runtime, true, run_start_time);
            Ok((Utc::now().signed_duration_since(start_time), res))
        })
        .or(Ok((Duration::seconds(0), (0, 1))))
}

fn print_runtime(label: &str, run_time: &IngestResult<RunResult>) {
    match *run_time {
        Ok(ref results) => print_sucess(label, results),
        Err(ref err) => print_failure(label, err),
    }
}

fn print_sucess(label: &str, &(dur, (pass, fail)): &RunResult) {
    info!(
        "{} run took {} seconds with {} successes and {} failures.",
        label,
        dur.num_seconds(),
        pass,
        fail
    )
}

fn print_failure(label: &str, error: &IngestError) {
    error!("{} run failed to run to completion. {}", label, error)
}
<commit_msg>Add franchise cli option<commit_after>//! # mm_api_notify
#[cfg(test)]
extern crate mockito;

extern crate app_dirs;
#[macro_use]
extern crate bson;
extern crate chrono;
extern crate clap;
extern crate fern;
#[macro_use]
extern crate log;
extern crate mm_client;
extern crate mongo_driver;
extern crate rayon;
extern crate serde;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json;

mod client;
mod config;
mod error;
mod hooks;
mod objects;
mod runtime;
mod storage;
mod types;

use app_dirs::{get_app_dir, AppDataType, AppInfo};
use chrono::offset::Utc;
use chrono::{Duration, NaiveDateTime};
use clap::{App, Arg};
use serde_json::error::Result as JsonResult;
use serde_json::Value as Json;

use std::str::FromStr;
use std::{thread, time};

use client::{APIClient, ClientResult, MMClient};
use config::{parse_config, APIConfig, DBConfig};
use error::{IngestError, IngestResult};
use hooks::Payload;
use objects::{Collection, Importable, Object};
use runtime::Runtime;
use storage::{MongoStore, Storage};
use types::{RunResult, StorageEngine, ThreadedAPI};

///
/// Starts processing
///
fn main() {
    let matches = App::new("Media Manager Notifier")
        .version(env!("CARGO_PKG_VERSION"))
        .arg(
            Arg::with_name("config")
                .short("c")
                .long("config")
                .takes_value(true)
                .help("Path to configuration file"),
        )
        .arg(
            Arg::with_name("build")
                .short("b")
                .long("build")
                .takes_value(false)
                .help("Performs a full build run prior to entering the update loop"),
        )
        .arg(
            Arg::with_name("show")
                .short("s")
                .long("show")
                .takes_value(true)
                .requires("build")
                .help("Limits the full build to a single show"),
        )
        .arg(
            Arg::with_name("franchise")
                .long("franchise")
                .takes_value(true)
                .requires("build")
                .help("Limits the full build to a single franchise"),
        )
        .arg(
            Arg::with_name("skip-update")
                .short("k")
                .long("skip-update")
                .takes_value(false)
                .help("Prevents update loop from running"),
        )
        .arg(
            Arg::with_name("start-time")
                .long("start-time")
                .short("t")
                .takes_value(true)
                .help("Defines the timestamp to start building or updating from"),
        )
        .arg(
            Arg::with_name("log-level")
                .long("log-level")
                .short("l")
                .takes_value(true)
                .help("Defines the log level to run at. Defaults to WARN"),
        )
        .arg(
            Arg::with_name("query")
                .long("query")
                .short("q")
                .takes_value(true)
                .number_of_values(2)
                .value_names(&["type", "id"])
                .conflicts_with_all(&["build", "skip-update", "start-time"])
                .help(
                    "Queries the cache with a type and id pair and displays the payload that the \
                     runner will emit",
                ),
        )
        .get_matches();

    let config_path = if !matches.is_present("config") {
        let info = AppInfo {
            name: env!("CARGO_PKG_NAME"),
            author: env!("CARGO_PKG_AUTHORS"),
        };

        let path = get_app_dir(AppDataType::UserConfig, &info, "/")
            .and_then(|mut path| {
                path.push("config.toml");
                Ok(path)
            })
            .expect("Failed to run. Unable to determine path default config location.");

        path.to_str().map(|str| str.to_string())
    } else {
        matches.value_of("config").map(|str| str.to_string())
    }
    .expect("Failed to run. Unable to parse path to default config location.");

    let conf_res = parse_config(config_path.as_str())
        .ok_or(IngestError::InvalidConfig)
        .and_then(|config| {
            // Initialize logging
            if let &Some(ref log_location) = &config.log.location {
                let config_log_level_filter = match config.log.level {
                    Some(ref level) => log::LogLevelFilter::from_str(level.as_str()).ok(),
                    None => None,
                };

                let log_level = matches
                    .value_of("log-level")
                    .and_then(|level| log::LogLevelFilter::from_str(level).ok())
                    .or(config_log_level_filter)
                    .unwrap_or(log::LogLevelFilter::Warn);

                fern::Dispatch::new()
                    .format(|out, message, record| {
                        out.finish(format_args!(
                            "[{}][{}] {}",
                            Utc::now().format("%Y-%m-%d][%H:%M:%S"),
                            record.level(),
                            message
                        ))
                    })
                    .level(log_level)
                    .chain(std::io::stdout())
                    .chain(fern::log_file(log_location.as_str()).expect("Failed to open log file"))
                    .apply()
                    .expect("Failed to initialize logger");
            }

            // Initialize the thread pools
            rayon::initialize(rayon::Configuration::new().num_threads(config.thread_pool_size))
                .or_else(|err| {
                    panic!(
                        "Failed to initalize the configured thread pool size. Unable to \
                         start. {}",
                        err
                    );
                })
                .and_then(|_| {
                    let store = get_store(&config.db);
                    let api = get_api_client(&config.mm);

                    let runtime = Runtime {
                        api: api,
                        config: config,
                        store: store,
                    };

                    if let Some(query) = matches.values_of("query") {
                        let query_args = query.collect::<Vec<&str>>();
                        match runtime.store.get(query_args[1], query_args[0]) {
                            Some(Ok(obj)) => match Payload::from_object(&obj, &runtime.store) {
                                Some(payload) => {
                                    println!("{}", serde_json::to_string_pretty(&payload).unwrap())
                                }
                                None => error!("Failed to generate payload from object."),
                            },
                            _ => println!("Could not find the requested object in the cache."),
                        };
                    } else {
                        let time_arg = matches.value_of("start-time").map_or(0, |arg| {
                            arg.parse::<i64>().expect("Could not parse start time")
                        });

                        let build_res = if matches.is_present("build") {
                            if let Some(show) = matches.value_of("show") {
                                run_show(&runtime, time_arg, show).ok()
                            } else if let Some(franchise) = matches.value_of("franchise") {
                                run_franchise(&runtime, time_arg, franchise).ok()
                            } else {
                                run_build(&runtime, time_arg).ok()
                            }
                        } else {
                            None
                        };

                        if !matches.is_present("skip-update") {
                            let now = Utc::now().timestamp();

                            let update_start_time =
                                if time_arg < (now - runtime.config.mm.changelog_max_timespan) {
                                    compute_update_start_time(
                                        runtime.store.updated_at(),
                                        runtime.config.mm.changelog_max_timespan,
                                    )
                                } else {
                                    time_arg + build_res.map_or(0, |(dur, _)| dur.num_seconds())
                                };

                            run_update_loop(&runtime, update_start_time)
                        };
                    }

                    Ok(())
                })
        });

    conf_res.expect("Failed to parse config.")
}

fn get_store(config: &DBConfig) -> MongoStore {
    MongoStore::new(config).expect("Failed to connect to storage")
}

fn get_api_client(config: &APIConfig) -> MMClient {
    MMClient::new(Some(config)).expect("Failed to initalize network client")
}

fn run_build<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    info!(
        "Starting build run from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    let result = import_collection(runtime.api.all_shows(), runtime, run_start_time);
    print_runtime("Create", &result);

    result
}

fn run_show<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
    id: &str,
) -> IngestResult<RunResult> {
    info!(
        "Starting show build run from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    let result = import_object(runtime.api.show(id), runtime, run_start_time);
    print_runtime("Create", &result);

    result
}

fn run_franchise<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
    id: &str,
) -> IngestResult<RunResult> {
    info!(
        "Starting franchise build run from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    let result = import_object(runtime.api.franchise(id), runtime, run_start_time);
    print_runtime("Create", &result);

    result
}

fn compute_update_start_time(last_updated_at: Option<i64>, max_timespan: i64) -> i64 {
    let newest_db_timestamp = last_updated_at.unwrap_or(0);
    let now = Utc::now().timestamp();

    if newest_db_timestamp < (now - max_timespan) {
        warn!(
            "Newest database record exceeds the maximum threshold for updates. Performing \
             update run with the maximum threshold. Consider performing a build (-b) run to \
             fully update the database."
        );
        now - max_timespan
    } else {
        newest_db_timestamp
    }
}

fn run_update_loop<T: StorageEngine, S: ThreadedAPI>(runtime: &Runtime<T, S>, run_start_time: i64) {
    let mut import_start_time = run_start_time;
    let mut import_completion_time = Utc::now().timestamp();
    let mut next_run_time = run_start_time;
    let label = "Update";

    info!(
        "Starting update loop from {} : {}",
        run_start_time,
        NaiveDateTime::from_timestamp(run_start_time, 0)
    );

    loop {
        if Utc::now().timestamp() > next_run_time {
            next_run_time = Utc::now().timestamp() + runtime.config.min_runtime_delta;

            import_start_time = import_start_time - runtime.config.lookback_timeframe;

            info!(
                "Starting update run from {} : {}",
                run_start_time,
                NaiveDateTime::from_timestamp(import_start_time, 0)
            );

            let run_time = run_update(runtime, import_start_time);

            print_runtime(label, &run_time);

            import_start_time = import_completion_time;
            import_completion_time = Utc::now().timestamp();

            let diff = next_run_time - import_completion_time;

            if diff > 0 {
                thread::sleep(time::Duration::from_secs(diff as u64));
            }
        }
    }
}

fn run_update<T: StorageEngine, S: ThreadedAPI>(
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    let date_string = NaiveDateTime::from_timestamp(run_start_time, 0)
        .format("%Y-%m-%dT%H:%M:%S%.3fZ")
        .to_string();

    import_collection(
        runtime.api.changes(date_string.as_str()),
        runtime,
        run_start_time,
    )
}

fn import_collection<T: StorageEngine, S: ThreadedAPI>(
    response: ClientResult<String>,
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    let start_time = Utc::now();

    let collection = match response {
        Ok(response_string) => {
            let full_json: JsonResult<Json> = serde_json::from_str(response_string.as_str());
            match full_json {
                Ok(json) => Collection::from_json(&json),
                Err(err) => Err(IngestError::Parse(err)),
            }
        }
        Err(err) => Err(IngestError::Client(err)),
    };

    collection
        .and_then(|coll| {
            let res = coll.import(runtime, true, run_start_time);
            Ok((Utc::now().signed_duration_since(start_time), res))
        })
        .or(Ok((Duration::seconds(0), (0, 1))))
}

fn import_object<T: StorageEngine, S: ThreadedAPI>(
    response: ClientResult<String>,
    runtime: &Runtime<T, S>,
    run_start_time: i64,
) -> IngestResult<RunResult> {
    let start_time = Utc::now();

    let object = match response {
        Ok(response_string) => {
            let full_json: JsonResult<Json> = serde_json::from_str(response_string.as_str());
            match full_json {
                Ok(json) => Object::from_json(&json),
                Err(err) => Err(IngestError::Parse(err)),
            }
        }
        Err(err) => Err(IngestError::Client(err)),
    };

    object
        .and_then(|obj| {
            let res = obj.import(runtime, true, run_start_time);
            Ok((Utc::now().signed_duration_since(start_time), res))
        })
        .or(Ok((Duration::seconds(0), (0, 1))))
}

fn print_runtime(label: &str, run_time: &IngestResult<RunResult>) {
    match *run_time {
        Ok(ref results) => print_sucess(label, results),
        Err(ref err) => print_failure(label, err),
    }
}

fn print_sucess(label: &str, &(dur, (pass, fail)): &RunResult) {
    info!(
        "{} run took {} seconds with {} successes and {} failures.",
        label,
        dur.num_seconds(),
        pass,
        fail
    )
}

fn print_failure(label: &str, error: &IngestError) {
    error!("{} run failed to run to completion. {}", label, error)
}
<|endoftext|>