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

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

use chrono::{DateTime, Date, Utc, Local, Duration};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;
use colored::*;

#[derive(Eq, PartialEq, 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 projects"))
        .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")))
        .subcommand(SubCommand::with_name("delete")
            .about("Delete all intervals for project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .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"));
    }

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

    match matches.subcommand_name() {
        Some("status") => status(),
        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() {
                let message = format!("project {} is being tracked", period.project);
                eprintln!("Error: {}",  message.red());
                return eprintln!("Try stopping your current project with {} first.", "stop".blue());
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project {} at {}", current_period.project.blue(), humanize_time(current_period.start_time));
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn status() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            let diff = Utc::now().signed_duration_since(period.start_time);
            return println!("Project {} started {} ({})", period.project.magenta(), humanize_duration(diff), humanize_datetime(period.start_time).blue());
        }
    }
    eprintln!("No running project");
}

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;
                let diff = Utc::now().signed_duration_since(period.start_time);
                println!("Stopped project {}, started {}", period.project.blue(), humanize_duration(diff));
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("Error: {}", "No project started.".red());
    }
}

fn cancel() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            save_periods(periods);
            let diff = Utc::now().signed_duration_since(period.start_time);
            println!("Canceled project {}, started {}", period.project.blue(), humanize_duration(diff));
            return
        }
    }
    eprintln!("Error: {}", "No project started".red());
}

fn restart() {
    let periods = get_periods();
    let mut new_periods = periods.to_vec();
    if let Some(period) = periods.last() {
        if !period.end_time.is_none() {
            let new_period = create_period(&period.project);
            new_periods.push(new_period);
            save_periods(new_periods);
            return println!("Tracking last running project: {}", period.project.blue());
        } else {
            let message = format!("No project to restart. Project {} is being tracked", period.project);
            eprintln!("Error: {}",  message.red());
            return eprintln!("Try stopping your current project with {} first.", "stop".blue());
        }
    }
    eprintln!("Error: {}", "No previous project to restart".red());
}

fn log() {
    let periods = get_periods();
    let mut days: HashMap<Date<chrono::Local>, Vec<Period>> = HashMap::new();

    for period in periods.iter() {
        let time = period.start_time.with_timezone(&Local).date();
        days.entry(time).or_insert(Vec::new()).push(period.clone());
    }
    for (date, day) in days.iter() {
        let d = day.into_iter().fold(Duration::zero(), |acc, ref x| acc + (x.end_time.unwrap_or(Utc::now()).signed_duration_since(x.start_time)));
        println!("{date} ({duration})", date=date.with_timezone(&Local).format("%A %-d %B %Y").to_string().green(), duration=format_duration(d).bold());
        for period in day.iter() {
            match period.end_time {
                Some(end_time) => {
                    let diff = end_time.signed_duration_since(period.start_time);
                    println!("    {start} to {end} {project} {duration}",
                        project=period.project.blue(),
                        start=humanize_time(period.start_time),
                        end=humanize_time(end_time),
                        duration=format_duration(diff));
                },
                None => {
                    let diff = Utc::now().signed_duration_since(period.start_time);
                    println!("    {start} to --:-- {project} {duration}",
                        project=period.project.blue(),
                        start=humanize_time(period.start_time),
                        duration=format_duration(diff));
                },
            }
        }
    }
}

fn report() {
    let periods = get_periods();
    let mut days: HashMap<String, Vec<Period>> = HashMap::new();
    let mut start_date = Utc::now().with_timezone(&Local).date();
    let mut results = Vec::new();

    for period in periods.iter() {
        days.entry(period.project.clone()).or_insert(Vec::new()).push(period.clone());
    }
    for (project, intervals) in days.iter() {
        let d = intervals.into_iter().fold(Duration::zero(), |acc, ref x| acc + (x.end_time.unwrap_or(Utc::now()).signed_duration_since(x.start_time)));
        for x in intervals.iter() {
            if x.start_time.with_timezone(&Local).date() < start_date {
                start_date = x.start_time.with_timezone(&Local).date();
            }
        }
        results.push(format!("{project} {duration}", project=project.green(), duration=format_duration(d).bold()));
    }
    println!("{start} -> {end}",
        start=start_date.format("%A %-d %B %Y").to_string().blue(),
        end=Utc::now().format("%A %-d %B %Y").to_string().blue());
    for x in results.iter() {
        println!("{}", x);
    }
}

fn amend(project_name: &str) {
    let mut periods = get_periods();
    if let Some(mut period) = periods.pop() {
        if period.end_time.is_none() {
            let old_name = period.project.clone();
            period.project = String::from(project_name);
            println!("Renamed tracking project {old} -> {new}", old=old_name.red(), new=period.project.green());
            periods.push(period);
            save_periods(periods);
            return
        }
    }
    eprintln!("Error: {}", "No project started".red());
}

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

fn delete(project_name: &str) {
    let periods = get_periods();
    let filtered_periods = periods.clone().into_iter().filter(|x| x.project != project_name).collect();
    if filtered_periods == periods {
        eprintln!("Error: {}", "Project not found.".red());
    } else {
        save_periods(filtered_periods);
        println!("Deleted project {project}", project=project_name.blue());
    }
}

fn humanize_datetime(time: DateTime<Utc>) -> String {
    time.with_timezone(&Local).format("%F %H:%M").to_string()
}

fn humanize_time(time: DateTime<Utc>) -> String {
    time.with_timezone(&Local).format("%H:%M").to_string()
}

fn format_duration(duration: Duration) -> String{
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();
    let seconds = duration.num_seconds();
    if minutes == 0 {
        return format!("{}s", seconds)
    } else if hours == 0 {
        return format!("{minutes}m", minutes=minutes)
    } else if days == 0 {
        return format!("{hours}h, {minutes}m", hours=hours, minutes=minutes/(hours*60))
    } else {
        return format!("{days}d, {hours}h, {minutes}m", hours=hours/(days*24), minutes=minutes/(hours*60), days=days)
    }
}

fn humanize_duration(time: Duration) -> String {
    let hours = time.num_hours();
    let minutes = time.num_minutes();
    let seconds = time.num_seconds();
    if minutes == 0 {
        if seconds < 5 {
            return String::from("just now")
        }
        return String::from("seconds ago")
    } else if hours == 0 {
        if minutes == 1 {
            return format!("{minutes} minute ago", minutes=minutes)
        } else {
            return format!("{minutes} minutes ago", minutes=minutes)
        }
    } else {
        return format!("{hours}:{minutes}", hours=hours, minutes=minutes)
    }
}

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, Metadata) {
    let config_file = get_config_file_path();
    (OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open datafile: {:?}", config_file)),
    fs::metadata(&config_file).expect("Couldn't access datafile metadata")
    )
}

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, metadata) = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(_) if metadata.len() > 0 => 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 edit subcommand<commit_after>#[macro_use]
extern crate clap;
extern crate chrono;
#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;
extern crate colored;

use std::env;
use std::fs;
use std::fs::{OpenOptions, File, Metadata};
use std::io::{Write, ErrorKind};
use std::path::PathBuf;
use std::collections::{HashMap};
use std::process::Command;

use chrono::{DateTime, Date, Utc, Local, Duration};
use clap::{App, Arg, AppSettings, SubCommand};
use serde_json::Error;
use colored::*;

#[derive(Eq, PartialEq, 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 projects"))
        .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"))
        .subcommand(SubCommand::with_name("delete")
            .about("Delete all intervals for project")
            .arg(Arg::with_name("project")
                .help("new project name")
                .required(true)))
        .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("delete") {
        delete(matches.value_of("project").expect("missing project name"));
    }

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

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() {
                let message = format!("project {} is being tracked", period.project);
                eprintln!("Error: {}",  message.red());
                return eprintln!("Try stopping your current project with {} first.", "stop".blue());
            }
        }
    }
    let current_period = create_period(project_name);
    // store current period in file
    print!("Started tracking project {} at {}", current_period.project.blue(), humanize_time(current_period.start_time));
    periods.push(current_period);
    save_periods(periods.to_vec());
}

fn status() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            let diff = Utc::now().signed_duration_since(period.start_time);
            return println!("Project {} started {} ({})", period.project.magenta(), humanize_duration(diff), humanize_datetime(period.start_time).blue());
        }
    }
    eprintln!("No running project");
}

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;
                let diff = Utc::now().signed_duration_since(period.start_time);
                println!("Stopped project {}, started {}", period.project.blue(), humanize_duration(diff));
            }
        }
    }
    if updated_period {
        save_periods(periods);
    } else {
        eprintln!("Error: {}", "No project started.".red());
    }
}

fn cancel() {
    let mut periods = get_periods();
    if let Some(period) = periods.pop() {
        if period.end_time.is_none() {
            save_periods(periods);
            let diff = Utc::now().signed_duration_since(period.start_time);
            println!("Canceled project {}, started {}", period.project.blue(), humanize_duration(diff));
            return
        }
    }
    eprintln!("Error: {}", "No project started".red());
}

fn restart() {
    let periods = get_periods();
    let mut new_periods = periods.to_vec();
    if let Some(period) = periods.last() {
        if !period.end_time.is_none() {
            let new_period = create_period(&period.project);
            new_periods.push(new_period);
            save_periods(new_periods);
            return println!("Tracking last running project: {}", period.project.blue());
        } else {
            let message = format!("No project to restart. Project {} is being tracked", period.project);
            eprintln!("Error: {}",  message.red());
            return eprintln!("Try stopping your current project with {} first.", "stop".blue());
        }
    }
    eprintln!("Error: {}", "No previous project to restart".red());
}

fn log() {
    let periods = get_periods();
    let mut days: HashMap<Date<chrono::Local>, Vec<Period>> = HashMap::new();

    for period in periods.iter() {
        let time = period.start_time.with_timezone(&Local).date();
        days.entry(time).or_insert(Vec::new()).push(period.clone());
    }
    for (date, day) in days.iter() {
        let d = day.into_iter().fold(Duration::zero(), |acc, ref x| acc + (x.end_time.unwrap_or(Utc::now()).signed_duration_since(x.start_time)));
        println!("{date} ({duration})", date=date.with_timezone(&Local).format("%A %-d %B %Y").to_string().green(), duration=format_duration(d).bold());
        for period in day.iter() {
            match period.end_time {
                Some(end_time) => {
                    let diff = end_time.signed_duration_since(period.start_time);
                    println!("    {start} to {end} {project} {duration}",
                        project=period.project.blue(),
                        start=humanize_time(period.start_time),
                        end=humanize_time(end_time),
                        duration=format_duration(diff));
                },
                None => {
                    let diff = Utc::now().signed_duration_since(period.start_time);
                    println!("    {start} to --:-- {project} {duration}",
                        project=period.project.blue(),
                        start=humanize_time(period.start_time),
                        duration=format_duration(diff));
                },
            }
        }
    }
}

fn report() {
    let periods = get_periods();
    let mut days: HashMap<String, Vec<Period>> = HashMap::new();
    let mut start_date = Utc::now().with_timezone(&Local).date();
    let mut results = Vec::new();

    for period in periods.iter() {
        days.entry(period.project.clone()).or_insert(Vec::new()).push(period.clone());
    }
    for (project, intervals) in days.iter() {
        let d = intervals.into_iter().fold(Duration::zero(), |acc, ref x| acc + (x.end_time.unwrap_or(Utc::now()).signed_duration_since(x.start_time)));
        for x in intervals.iter() {
            if x.start_time.with_timezone(&Local).date() < start_date {
                start_date = x.start_time.with_timezone(&Local).date();
            }
        }
        results.push(format!("{project} {duration}", project=project.green(), duration=format_duration(d).bold()));
    }
    println!("{start} -> {end}",
        start=start_date.format("%A %-d %B %Y").to_string().blue(),
        end=Utc::now().format("%A %-d %B %Y").to_string().blue());
    for x in results.iter() {
        println!("{}", x);
    }
}

fn amend(project_name: &str) {
    let mut periods = get_periods();
    if let Some(mut period) = periods.pop() {
        if period.end_time.is_none() {
            let old_name = period.project.clone();
            period.project = String::from(project_name);
            println!("Renamed tracking project {old} -> {new}", old=old_name.red(), new=period.project.green());
            periods.push(period);
            save_periods(periods);
            return
        }
    }
    eprintln!("Error: {}", "No project started".red());
}

fn edit() {
    let path = get_config_file_path();
    println!("File: {}", path.to_str().unwrap().blue());
    if let Some(editor) = env::var_os("EDITOR") {
        let mut edit = Command::new(editor);
        edit.arg(path.clone());
        let status = edit.status();
        if !status.is_ok() {
            eprintln!("Error: {}", "Problem with editing.".red());
        }
    } else {
        eprintln!("Error: {}", "Couldn't open editor".red());
    }
}

fn delete(project_name: &str) {
    let periods = get_periods();
    let filtered_periods = periods.clone().into_iter().filter(|x| x.project != project_name).collect();
    if filtered_periods == periods {
        eprintln!("Error: {}", "Project not found.".red());
    } else {
        save_periods(filtered_periods);
        println!("Deleted project {project}", project=project_name.blue());
    }
}

fn humanize_datetime(time: DateTime<Utc>) -> String {
    time.with_timezone(&Local).format("%F %H:%M").to_string()
}

fn humanize_time(time: DateTime<Utc>) -> String {
    time.with_timezone(&Local).format("%H:%M").to_string()
}

fn format_duration(duration: Duration) -> String{
    let days = duration.num_days();
    let hours = duration.num_hours();
    let minutes = duration.num_minutes();
    let seconds = duration.num_seconds();
    if minutes == 0 {
        return format!("{}s", seconds)
    } else if hours == 0 {
        return format!("{minutes}m", minutes=minutes)
    } else if days == 0 {
        return format!("{hours}h, {minutes}m", hours=hours, minutes=minutes/(hours*60))
    } else {
        return format!("{days}d, {hours}h, {minutes}m", hours=hours/(days*24), minutes=minutes/(hours*60), days=days)
    }
}

fn humanize_duration(time: Duration) -> String {
    let hours = time.num_hours();
    let minutes = time.num_minutes();
    let seconds = time.num_seconds();
    if minutes == 0 {
        if seconds < 5 {
            return String::from("just now")
        }
        return String::from("seconds ago")
    } else if hours == 0 {
        if minutes == 1 {
            return format!("{minutes} minute ago", minutes=minutes)
        } else {
            return format!("{minutes} minutes ago", minutes=minutes)
        }
    } else {
        return format!("{hours}:{minutes}", hours=hours, minutes=minutes)
    }
}

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, Metadata) {
    let config_file = get_config_file_path();
    (OpenOptions::new()
        .create(true)
        .read(true)
        .write(true)
        .open(&config_file)
        .expect(&format!("Couldn't open datafile: {:?}", config_file)),
    fs::metadata(&config_file).expect("Couldn't access datafile metadata")
    )
}

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, metadata) = get_config_file();

    let periods: Result<Vec<Period>, Error> = serde_json::from_reader(file);
    let periods = match periods {
        Ok(p) => p,
        Err(_) if metadata.len() > 0 => 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>use std::io;
use std::io::prelude::*;
use std::net::TcpStream;
use std::fs::File;
use std::env;

extern crate chrono;
use chrono::*;

#[macro_use]
extern crate clap;

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

extern crate stats;

extern crate prometheus;
extern crate hyper;

mod timing_analyzer;
use timing_analyzer::Timing;
mod error_analyzer;
mod args;
mod filter;
mod log_parser;
use log_parser::log_events::*;
mod render;
mod request_response_matcher;
use request_response_matcher::*;
mod http_handler;

fn main() {
    env_logger::init().expect("Failed to initialize logging.");

    let args = args::parse_args(env::args()).expect("Failed to parse arguments.");

    let result = run(&args);

    let mut stream;
    let mut renderer: Box<render::Renderer>;

    renderer = match args.graphite_server {
        Some(ref graphite_server) => {
            stream = TcpStream::connect((graphite_server.as_ref(), args.graphite_port.unwrap()))
                .expect("Could not connect to the Graphite server");

            Box::new(render::GraphiteRenderer::new(UTC::now(),
                                                   args.graphite_prefix.clone(),
                                                   &mut stream))
        }
        None => Box::new(render::TerminalRenderer::new()),
    };

    renderer.render(result);

    if args.prometheus_listen.is_some() {
        let binding_address = args.prometheus_listen.clone().unwrap();
        http_handler::listen_http(args, &binding_address);
    }

}

fn run(args: &args::RequestLogAnalyzerArgs) -> Option<timing_analyzer::RequestLogAnalyzerResult> {
    let input: Box<io::Read> = match args.filename.as_ref() {
        "-" => Box::new(io::stdin()),
        _ => Box::new(File::open(&args.filename).expect("Failed to open file.")),
    };

    let pairs = extract_pairs(input, &args.conditions);
    timing_analyzer::analyze(&pairs)
}

fn extract_pairs(input: Box<io::Read>,
                 conditions: &filter::FilterConditions)
                 -> Vec<Box<RequestResponsePair>> {
    let reader = io::BufReader::new(input);

    let mut requests: Vec<Request> = Vec::new();
    let mut responses: Vec<Response> = Vec::new();
    let mut pairs: Vec<Box<RequestResponsePair>> = Vec::new();

    for line in reader.lines() {
        let parsed_line = log_parser::parse_line(&line.expect("Failed to read line."));

        match parsed_line {
            Ok(event) => {
                match event {
                    LogEvent::Request(request) => requests.push(request),
                    LogEvent::Response(response) => responses.push(response),
                }

                let mut new_pairs: Vec<Box<RequestResponsePair>> =
                    extract_matching_request_response_pairs(&mut requests, &mut responses)
                        .into_iter()
                        .filter(|pair| filter::matches_filter(pair, conditions))
                        .map(|pair| Box::new(pair))
                        .collect();

                pairs.append(&mut new_pairs);
            }
            Err(err) => warn!("{}", err),
        }
    }
    pairs
}

#[test]
fn test_extract_pairs() {
    let conditions = filter::FilterConditions {
        include_terms: None,
        exclude_terms: None,
        latest_time: None,
    };

    let timings = extract_pairs(Box::new(File::open("src/test/simple-1.log").unwrap()),
                                &conditions);

    assert_eq!(timings[0].num_milliseconds(), 7);
    assert_eq!(timings[1].num_milliseconds(), 10);
    assert_eq!(timings.len(), 2);
}

#[test]
fn test_extract_timings_ignore_broken_lines() {
    let conditions = filter::FilterConditions {
        include_terms: None,
        exclude_terms: None,
        latest_time: None,
    };

    let timings = extract_pairs(Box::new(File::open("src/test/broken.log").unwrap()),
                                &conditions);

    assert_eq!(timings[0].num_milliseconds(), 7);
    assert_eq!(timings.len(), 1);
}

#[test]
fn test_run() {
    let args = args::RequestLogAnalyzerArgs {
        filename: String::from("src/test/simple-1.log"),
        conditions: filter::FilterConditions {
            include_terms: None,
            exclude_terms: None,
            latest_time: None,
        },
        graphite_server: None,
        graphite_port: Some(2003),
        graphite_prefix: None,
        prometheus_listen: None,
    };

    let result = run(&args).unwrap();
    assert_eq!(result.count, 2);
}
<commit_msg>Name pairs consistently, also in tests<commit_after>use std::io;
use std::io::prelude::*;
use std::net::TcpStream;
use std::fs::File;
use std::env;

extern crate chrono;
use chrono::*;

#[macro_use]
extern crate clap;

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

extern crate stats;

extern crate prometheus;
extern crate hyper;

mod timing_analyzer;
use timing_analyzer::Timing;
mod error_analyzer;
mod args;
mod filter;
mod log_parser;
use log_parser::log_events::*;
mod render;
mod request_response_matcher;
use request_response_matcher::*;
mod http_handler;

fn main() {
    env_logger::init().expect("Failed to initialize logging.");

    let args = args::parse_args(env::args()).expect("Failed to parse arguments.");

    let result = run(&args);

    let mut stream;
    let mut renderer: Box<render::Renderer>;

    renderer = match args.graphite_server {
        Some(ref graphite_server) => {
            stream = TcpStream::connect((graphite_server.as_ref(), args.graphite_port.unwrap()))
                .expect("Could not connect to the Graphite server");

            Box::new(render::GraphiteRenderer::new(UTC::now(),
                                                   args.graphite_prefix.clone(),
                                                   &mut stream))
        }
        None => Box::new(render::TerminalRenderer::new()),
    };

    renderer.render(result);

    if args.prometheus_listen.is_some() {
        let binding_address = args.prometheus_listen.clone().unwrap();
        http_handler::listen_http(args, &binding_address);
    }

}

fn run(args: &args::RequestLogAnalyzerArgs) -> Option<timing_analyzer::RequestLogAnalyzerResult> {
    let input: Box<io::Read> = match args.filename.as_ref() {
        "-" => Box::new(io::stdin()),
        _ => Box::new(File::open(&args.filename).expect("Failed to open file.")),
    };

    let pairs = extract_pairs(input, &args.conditions);
    timing_analyzer::analyze(&pairs)
}

fn extract_pairs(input: Box<io::Read>,
                 conditions: &filter::FilterConditions)
                 -> Vec<Box<RequestResponsePair>> {
    let reader = io::BufReader::new(input);

    let mut requests: Vec<Request> = Vec::new();
    let mut responses: Vec<Response> = Vec::new();
    let mut pairs: Vec<Box<RequestResponsePair>> = Vec::new();

    for line in reader.lines() {
        let parsed_line = log_parser::parse_line(&line.expect("Failed to read line."));

        match parsed_line {
            Ok(event) => {
                match event {
                    LogEvent::Request(request) => requests.push(request),
                    LogEvent::Response(response) => responses.push(response),
                }

                let mut new_pairs: Vec<Box<RequestResponsePair>> =
                    extract_matching_request_response_pairs(&mut requests, &mut responses)
                        .into_iter()
                        .filter(|pair| filter::matches_filter(pair, conditions))
                        .map(|pair| Box::new(pair))
                        .collect();

                pairs.append(&mut new_pairs);
            }
            Err(err) => warn!("{}", err),
        }
    }
    pairs
}

#[test]
fn test_extract_pairs() {
    let conditions = filter::FilterConditions {
        include_terms: None,
        exclude_terms: None,
        latest_time: None,
    };

    let pairs = extract_pairs(Box::new(File::open("src/test/simple-1.log").unwrap()),
                              &conditions);

    assert_eq!(pairs[0].num_milliseconds(), 7);
    assert_eq!(pairs[1].num_milliseconds(), 10);
    assert_eq!(pairs.len(), 2);
}

#[test]
fn test_extract_pairs_ignore_broken_lines() {
    let conditions = filter::FilterConditions {
        include_terms: None,
        exclude_terms: None,
        latest_time: None,
    };

    let pairs = extract_pairs(Box::new(File::open("src/test/broken.log").unwrap()),
                              &conditions);

    assert_eq!(pairs[0].num_milliseconds(), 7);
    assert_eq!(pairs.len(), 1);
}

#[test]
fn test_run() {
    let args = args::RequestLogAnalyzerArgs {
        filename: String::from("src/test/simple-1.log"),
        conditions: filter::FilterConditions {
            include_terms: None,
            exclude_terms: None,
            latest_time: None,
        },
        graphite_server: None,
        graphite_port: Some(2003),
        graphite_prefix: None,
        prometheus_listen: None,
    };

    let result = run(&args).unwrap();
    assert_eq!(result.count, 2);
}
<|endoftext|>


<commit_before>fn main() {

    let mut a = "abc";

    println!("main before abc run a is: {}", a);

    let b = abc(&mut a);

    println!("main after abc run a is {}", a);
    println!("main after abc run b is {}", b);
}

fn abc<'a>(a: &'a mut &str) -> &'static str {

    let temp: &str = get();

    *a = "abc changed";

    print_temp(temp);

    temp
}

fn get() -> &'static str {

    "xyz"
}

fn print_temp(a: &str) {

    println!("temp is: {}", a);
}<commit_msg>test: 提交修改<commit_after>fn main() {

    let mut a = "abc";

    println!("main before abc run a is: {}", a);

    let b = abc(&mut a);

    println!("main after abc run b is {}", b);

    println!("main after abc run a is {}", a);
}

fn abc(a: &mut &str) -> &'static str {

    let temp: &str = get();

    *a = "abc changed";

    print_temp(temp);

    temp
}

fn get() -> &'static str {

    "xyz"
}

fn print_temp(a: &str) {

    println!("temp is: {}", a);
}<|endoftext|>


<commit_before>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Alacritty - The GPU Enhanced Terminal
#![cfg_attr(feature = "cargo-clippy", deny(clippy, if_not_else, enum_glob_use, wrong_pub_self_convention))]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
#![cfg_attr(all(test, feature = "bench"), feature(test))]

// With the default subsystem, 'console', windows creates an additional console
// window for the program.
// This is silently ignored on non-windows systems.
// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details.
#![windows_subsystem = "windows"]

#[macro_use]
extern crate alacritty;

#[macro_use]
extern crate log;
#[cfg(target_os = "macos")]
extern crate dirs;

use std::error::Error;
use std::sync::Arc;

#[cfg(target_os = "macos")]
use std::env;

#[cfg(not(windows))]
use std::os::unix::io::AsRawFd;

#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};

use alacritty::cli;
use alacritty::config::{self, Config};
use alacritty::display::Display;
use alacritty::event;
use alacritty::event_loop::{self, EventLoop, Msg};
#[cfg(target_os = "macos")]
use alacritty::locale;
use alacritty::logging;
use alacritty::sync::FairMutex;
use alacritty::term::Term;
use alacritty::tty::{self, process_should_exit};
use alacritty::util::fmt::Red;

fn main() {
    // When linked with the windows subsystem windows won't automatically attach
    // to the console of the parent process, so we do it explicitly. This fails
    // silently if the parent has no console.
    #[cfg(windows)]
    unsafe {AttachConsole(ATTACH_PARENT_PROCESS);}

    // Load command line options and config
    let options = cli::Options::load();
    let config = load_config(&options).update_dynamic_title(&options);

    // Switch to home directory
    #[cfg(target_os = "macos")]
    env::set_current_dir(dirs::home_dir().unwrap()).unwrap();
    // Set locale
    #[cfg(target_os = "macos")]
    locale::set_locale_environment();

    // Run alacritty
    if let Err(err) = run(config, &options) {
        die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err));
    }

    info!("Goodbye.");
}

/// Load configuration
///
/// If a configuration file is given as a command line argument we don't
/// generate a default file. If an empty configuration file is given, i.e.
/// /dev/null, we load the compiled-in defaults.)
#[cfg(not(windows))]
fn load_config(options: &cli::Options) -> Config {
    let config_path = options.config_path()
        .or_else(Config::installed_config)
        .unwrap_or_else(|| {
            Config::write_defaults()
                .unwrap_or_else(|err| die!("Write defaults config failure: {}", err))
        });

    Config::load_from(&*config_path).unwrap_or_else(|err| {
        eprintln!("Error: {}; Loading default config", err);
        Config::default()
    })
}
#[cfg(windows)]
fn load_config(options: &cli::Options) -> Config {
    let config_path = options
        .config_path()
        .or_else(|| Config::installed_config())
        .unwrap_or_else(|| {
            Config::write_defaults()
                .unwrap_or_else(|err| die!("Write defaults config failure: {}", err))
        });

    Config::load_from(&*config_path).unwrap_or_else(|err| match err {
        config::Error::NotFound => {
            die!("Config file not found after writing: {}", config_path.display());
        }
        config::Error::Empty => {
            eprintln!("Empty config; Loading defaults");
            Config::default()
        }
        _ => die!("{}", err),
    })
}

/// Run Alacritty
///
/// Creates a window, the terminal state, pty, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> {
    // Initialize the logger first as to capture output from other subsystems
    logging::initialize(options)?;

    info!("Welcome to Alacritty.");
    if let Some(config_path) = config.path() {
        info!("Configuration loaded from {}", config_path.display());
    };

    // Create a display.
    //
    // The display manages a window and can draw the terminal
    let mut display = Display::new(&config, options)?;

    info!(
        "PTY Dimensions: {:?} x {:?}",
        display.size().lines(),
        display.size().cols()
    );

    // Create the terminal
    //
    // This object contains all of the state about what's being displayed. It's
    // wrapped in a clonable mutex since both the I/O loop and display need to
    // access it.
    let terminal = Term::new(&config, display.size().to_owned());
    let terminal = Arc::new(FairMutex::new(terminal));

    // Find the window ID for setting $WINDOWID
    let window_id = display.get_window_id();

    // Create the pty
    //
    // The pty forks a process to run the shell on the slave side of the
    // pseudoterminal. A file descriptor for the master side is retained for
    // reading/writing to the shell.
    let pty = tty::new(&config, options, &display.size(), window_id);

    // Get a reference to something that we can resize
    //
    // This exists because rust doesn't know the interface is thread-safe
    // and we need to be able to resize the PTY from the main thread while the IO
    // thread owns the EventedRW object.
    #[cfg(windows)]
    let resize_handle = unsafe { &mut *pty.winpty.get() };
    #[cfg(not(windows))]
    let mut resize_handle = pty.fd.as_raw_fd();

    // Create the pseudoterminal I/O loop
    //
    // pty I/O is ran on another thread as to not occupy cycles used by the
    // renderer and input processing. Note that access to the terminal state is
    // synchronized since the I/O loop updates the state, and the display
    // consumes it periodically.
    let event_loop = EventLoop::new(
        Arc::clone(&terminal),
        display.notifier(),
        pty,
        options.ref_test,
    );

    // The event loop channel allows write requests from the event processor
    // to be sent to the loop and ultimately written to the pty.
    let loop_tx = event_loop.channel();

    // Event processor
    //
    // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback
    let mut processor = event::Processor::new(
        event_loop::Notifier(event_loop.channel()),
        display.resize_channel(),
        options,
        &config,
        options.ref_test,
        display.size().to_owned(),
    );

    // Create a config monitor when config was loaded from path
    //
    // The monitor watches the config file for changes and reloads it. Pending
    // config changes are processed in the main loop.
    let config_monitor = match (options.live_config_reload, config.live_config_reload()) {
        // Start monitor if CLI flag says yes
        (Some(true), _) |
        // Or if no CLI flag was passed and the config says yes
        (None, true) => config.path()
                .map(|path| config::Monitor::new(path, display.notifier())),
        // Otherwise, don't start the monitor
        _ => None,
    };

    // Kick off the I/O thread
    let _io_thread = event_loop.spawn(None);

    info!("Initialisation complete");

    // Main display loop
    loop {
        // Process input and window events
        let mut terminal_lock = processor.process_events(&terminal, display.window());

        // Handle config reloads
        if let Some(new_config) = config_monitor
            .as_ref()
            .and_then(|monitor| monitor.pending_config())
        {
            config = new_config.update_dynamic_title(options);
            display.update_config(&config);
            processor.update_config(&config);
            terminal_lock.update_config(&config);
            terminal_lock.dirty = true;
        }

        // Maybe draw the terminal
        if terminal_lock.needs_draw() {
            // Try to update the position of the input method editor
            display.update_ime_position(&terminal_lock);
            // Handle pending resize events
            //
            // The second argument is a list of types that want to be notified
            // of display size changes.
            #[cfg(windows)]
            display.handle_resize(&mut terminal_lock, &config, &mut [resize_handle, &mut processor]);
            #[cfg(not(windows))]
            display.handle_resize(&mut terminal_lock, &config, &mut [&mut resize_handle, &mut processor]);

            drop(terminal_lock);

            // Draw the current state of the terminal
            display.draw(&terminal, &config);
        }

        // Begin shutdown if the flag was raised.
        if process_should_exit() {
            break;
        }
    }

    loop_tx
        .send(Msg::Shutdown)
        .expect("Error sending shutdown to event loop");

    // FIXME patch notify library to have a shutdown method
    // config_reloader.join().ok();

    Ok(())
}
<commit_msg>Refactor platform-specific code<commit_after>// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! Alacritty - The GPU Enhanced Terminal
#![cfg_attr(feature = "cargo-clippy", deny(clippy, if_not_else, enum_glob_use, wrong_pub_self_convention))]
#![cfg_attr(feature = "nightly", feature(core_intrinsics))]
#![cfg_attr(all(test, feature = "bench"), feature(test))]

// With the default subsystem, 'console', windows creates an additional console
// window for the program.
// This is silently ignored on non-windows systems.
// See https://msdn.microsoft.com/en-us/library/4cc7ya5b.aspx for more details.
#![windows_subsystem = "windows"]

#[macro_use]
extern crate alacritty;

#[macro_use]
extern crate log;
#[cfg(target_os = "macos")]
extern crate dirs;

use std::error::Error;
use std::sync::Arc;

#[cfg(target_os = "macos")]
use std::env;

#[cfg(not(windows))]
use std::os::unix::io::AsRawFd;

#[cfg(windows)]
extern crate winapi;
#[cfg(windows)]
use winapi::um::wincon::{AttachConsole, ATTACH_PARENT_PROCESS};

use alacritty::cli;
use alacritty::config::{self, Config};
use alacritty::display::Display;
use alacritty::event;
use alacritty::event_loop::{self, EventLoop, Msg};
#[cfg(target_os = "macos")]
use alacritty::locale;
use alacritty::logging;
use alacritty::sync::FairMutex;
use alacritty::term::Term;
use alacritty::tty::{self, process_should_exit};
use alacritty::util::fmt::Red;

fn main() {
    // When linked with the windows subsystem windows won't automatically attach
    // to the console of the parent process, so we do it explicitly. This fails
    // silently if the parent has no console.
    #[cfg(windows)]
    unsafe {AttachConsole(ATTACH_PARENT_PROCESS);}

    // Load command line options and config
    let options = cli::Options::load();
    let config = load_config(&options).update_dynamic_title(&options);

    // Switch to home directory
    #[cfg(target_os = "macos")]
    env::set_current_dir(dirs::home_dir().unwrap()).unwrap();
    // Set locale
    #[cfg(target_os = "macos")]
    locale::set_locale_environment();

    // Run alacritty
    if let Err(err) = run(config, &options) {
        die!("Alacritty encountered an unrecoverable error:\n\n\t{}\n", Red(err));
    }

    info!("Goodbye.");
}

/// Load configuration
///
/// If a configuration file is given as a command line argument we don't
/// generate a default file. If an empty configuration file is given, i.e.
/// /dev/null, we load the compiled-in defaults.)
#[cfg(not(windows))]
fn load_config(options: &cli::Options) -> Config {
    let config_path = options.config_path()
        .or_else(Config::installed_config)
        .unwrap_or_else(|| {
            Config::write_defaults()
                .unwrap_or_else(|err| die!("Write defaults config failure: {}", err))
        });

    Config::load_from(&*config_path).unwrap_or_else(|err| {
        eprintln!("Error: {}; Loading default config", err);
        Config::default()
    })
}
#[cfg(windows)]
fn load_config(options: &cli::Options) -> Config {
    let config_path = options
        .config_path()
        .or_else(|| Config::installed_config())
        .unwrap_or_else(|| {
            Config::write_defaults()
                .unwrap_or_else(|err| die!("Write defaults config failure: {}", err))
        });

    Config::load_from(&*config_path).unwrap_or_else(|err| match err {
        config::Error::NotFound => {
            die!("Config file not found after writing: {}", config_path.display());
        }
        config::Error::Empty => {
            eprintln!("Empty config; Loading defaults");
            Config::default()
        }
        _ => die!("{}", err),
    })
}

/// Run Alacritty
///
/// Creates a window, the terminal state, pty, I/O event loop, input processor,
/// config change monitor, and runs the main display loop.
fn run(mut config: Config, options: &cli::Options) -> Result<(), Box<Error>> {
    // Initialize the logger first as to capture output from other subsystems
    logging::initialize(options)?;

    info!("Welcome to Alacritty.");
    if let Some(config_path) = config.path() {
        info!("Configuration loaded from {}", config_path.display());
    };

    // Create a display.
    //
    // The display manages a window and can draw the terminal
    let mut display = Display::new(&config, options)?;

    info!(
        "PTY Dimensions: {:?} x {:?}",
        display.size().lines(),
        display.size().cols()
    );

    // Create the terminal
    //
    // This object contains all of the state about what's being displayed. It's
    // wrapped in a clonable mutex since both the I/O loop and display need to
    // access it.
    let terminal = Term::new(&config, display.size().to_owned());
    let terminal = Arc::new(FairMutex::new(terminal));

    // Find the window ID for setting $WINDOWID
    let window_id = display.get_window_id();

    // Create the pty
    //
    // The pty forks a process to run the shell on the slave side of the
    // pseudoterminal. A file descriptor for the master side is retained for
    // reading/writing to the shell.
    let pty = tty::new(&config, options, &display.size(), window_id);

    // Get a reference to something that we can resize
    //
    // This exists because rust doesn't know the interface is thread-safe
    // and we need to be able to resize the PTY from the main thread while the IO
    // thread owns the EventedRW object.
    #[cfg(windows)]
    let resize_handle = unsafe { &mut *pty.winpty.get() };
    #[cfg(not(windows))]
    let resize_handle = &mut pty.fd.as_raw_fd();

    // Create the pseudoterminal I/O loop
    //
    // pty I/O is ran on another thread as to not occupy cycles used by the
    // renderer and input processing. Note that access to the terminal state is
    // synchronized since the I/O loop updates the state, and the display
    // consumes it periodically.
    let event_loop = EventLoop::new(
        Arc::clone(&terminal),
        display.notifier(),
        pty,
        options.ref_test,
    );

    // The event loop channel allows write requests from the event processor
    // to be sent to the loop and ultimately written to the pty.
    let loop_tx = event_loop.channel();

    // Event processor
    //
    // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback
    let mut processor = event::Processor::new(
        event_loop::Notifier(event_loop.channel()),
        display.resize_channel(),
        options,
        &config,
        options.ref_test,
        display.size().to_owned(),
    );

    // Create a config monitor when config was loaded from path
    //
    // The monitor watches the config file for changes and reloads it. Pending
    // config changes are processed in the main loop.
    let config_monitor = match (options.live_config_reload, config.live_config_reload()) {
        // Start monitor if CLI flag says yes
        (Some(true), _) |
        // Or if no CLI flag was passed and the config says yes
        (None, true) => config.path()
                .map(|path| config::Monitor::new(path, display.notifier())),
        // Otherwise, don't start the monitor
        _ => None,
    };

    // Kick off the I/O thread
    let _io_thread = event_loop.spawn(None);

    info!("Initialisation complete");

    // Main display loop
    loop {
        // Process input and window events
        let mut terminal_lock = processor.process_events(&terminal, display.window());

        // Handle config reloads
        if let Some(new_config) = config_monitor
            .as_ref()
            .and_then(|monitor| monitor.pending_config())
        {
            config = new_config.update_dynamic_title(options);
            display.update_config(&config);
            processor.update_config(&config);
            terminal_lock.update_config(&config);
            terminal_lock.dirty = true;
        }

        // Maybe draw the terminal
        if terminal_lock.needs_draw() {
            // Try to update the position of the input method editor
            display.update_ime_position(&terminal_lock);

            // Handle pending resize events
            //
            // The second argument is a list of types that want to be notified
            // of display size changes.
            display.handle_resize(&mut terminal_lock, &config, &mut [resize_handle, &mut processor]);

            drop(terminal_lock);

            // Draw the current state of the terminal
            display.draw(&terminal, &config);
        }

        // Begin shutdown if the flag was raised.
        if process_should_exit() {
            break;
        }
    }

    loop_tx
        .send(Msg::Shutdown)
        .expect("Error sending shutdown to event loop");

    // FIXME patch notify library to have a shutdown method
    // config_reloader.join().ok();

    Ok(())
}
<|endoftext|>


<commit_before>//!                             The Unlicense
//! This is free and unencumbered software released into the public domain.
//!
//! Anyone is free to copy, modify, publish, use, compile, sell, or
//! distribute this software, either in source code form or as a compiled
//! binary, for any purpose, commercial or non-commercial, and by any
//! means.
//!
//! In jurisdictions that recognize copyright laws, the author or authors
//! of this software dedicate any and all copyright interest in the
//! software to the public domain. We make this dedication for the benefit
//! of the public at large and to the detriment of our heirs and
//! successors. We intend this dedication to be an overt act of
//! relinquishment in perpetuity of all present and future rights to this
//! software under copyright law.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//! EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//! MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//! IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
//! OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//! ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//! OTHER DEALINGS IN THE SOFTWARE.
//!
//! For more information, please refer to <http://unlicense.org>
//!

extern crate iron;
extern crate router;
extern crate hyper;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper_rustls;
extern crate urlencoded;
extern crate persistent;
extern crate lru_cache;
extern crate time;
extern crate crypto_hash;

use std::vec::Vec;
use hyper::Client as HyperClient;
use hyper::client::Response as HyperResponse;
use hyper::net::HttpsConnector;
use router::{Router, NoRoute};
use iron::prelude::*;
use iron::status;
use iron::{AfterMiddleware, Handler};
use regex::Regex;
use std::io::Read;
use hyper_rustls::TlsClient as RustlsClient;
use urlencoded::{UrlEncodedQuery, UrlDecodingError};
use std::collections::HashMap;
use std::option::Option;
use iron::headers::{ContentType, ContentDisposition, CacheControl, LastModified, ETag, Expires,
                    IfModifiedSince, IfNoneMatch, EntityTag, HttpDate, CacheDirective};
use lru_cache::LruCache;
use persistent::Write;
use iron::typemap::Key;
use std::result::Result;
use crypto_hash::{Algorithm as HashAlgorithm, hex_digest};
use std::ops::Deref;
use std::sync::{Arc, Mutex};

fn verify_md5(md5: &str) -> bool {
    lazy_static! {
        static ref MD5_RE: Regex = Regex::new("^[0-9a-f]{32}$").unwrap();
    }

    MD5_RE.is_match(md5)
}

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct CacheControlKey {
    email_md5: String,
    size: Option<i64>,
    default: Option<String>,
}

impl CacheControlKey {
    fn from_request(req: &mut Request) -> Result<CacheControlKey, Response> {
        let email_md5 = req.extensions
            .get::<Router>()
            .unwrap()
            .find("email_md5")
            .unwrap_or("")
            .to_lowercase();

        if !verify_md5(email_md5.as_str()) {
            return Err(Response::with((status::NotFound)));
        }

        let maybe_query = req.get::<UrlEncodedQuery>()
            .map(|val| Some(val))
            .unwrap_or_else(|err: UrlDecodingError| {
                match err {
                    UrlDecodingError::EmptyQuery => Some(HashMap::new()),
                    _ => None,
                }
            });

        let query = match maybe_query {
            Some(m) => m,
            None => return Err(Response::with((status::BadRequest))),
        };

        let size_str = query.get("s")
            .map(|s_vec| s_vec.clone())
            .map(|s_vec| { if s_vec.len() > 0 { Some(s_vec) } else { None } })
            .unwrap_or(None)
            .map(|s_vec| s_vec[0].clone());

        let default = query.get("d")
            .map(|d_vec| d_vec.clone())
            .map(|d_vec| { if d_vec.len() > 0 { Some(d_vec) } else { None } })
            .unwrap_or(None)
            .map(|d_vec| d_vec[0].clone());

        let size: Option<i64> = match size_str {
            Some(m) => {
                match m.parse::<i64>() {
                    Ok(m) => Some(m),
                    Err(_) => return Err(Response::with((status::BadRequest))),
                }
            }
            None => None,
        };

        Ok(CacheControlKey {
            email_md5: email_md5,
            size: size,
            default: default,
        })
    }
}

#[derive(Clone)]
struct CacheControlData {
    pub etag: EntityTag,
    pub last_modified: HttpDate,
    pub content_type: ContentType,
    pub content_disposition: Option<ContentDisposition>,
    pub cache_control: CacheControl,
    pub expires: HttpDate,
}

impl CacheControlData {
    fn from_response(res: &HyperResponse, res_body: &[u8]) -> AvatarResult<CacheControlData> {
        let etag = EntityTag::strong(hex_digest(HashAlgorithm::SHA256, res_body.to_vec()));
        let last_modified = res.headers
            .get::<LastModified>()
            .map(|m| m.deref().clone())
            .unwrap_or_else(|| HttpDate(time::now()));

        let content_type = try!(res.headers
            .get::<ContentType>()
            .map(|m| m.clone())
            .ok_or_else(|| Response::with((status::BadGateway))));

        let content_disposition = res.headers.get::<ContentDisposition>().map(|m| m.clone());
        let cache_control = CacheControl(vec![CacheDirective::MaxAge(600 as u32)]);
        let expires = HttpDate(time::now() + time::Duration::minutes(10));

        Ok(CacheControlData {
            etag: etag,
            last_modified: last_modified,
            content_type: content_type,
            content_disposition: content_disposition,
            cache_control: cache_control,
            expires: expires,
        })
    }

    fn set_cache_headers(&self, mut res: &mut Response) {
        res.headers.set(ETag(self.etag.clone()));
        res.headers.set(LastModified(self.last_modified.clone()));
        if let Some(m) = self.content_disposition.clone() {
            res.headers.set(m);
        }
        res.headers.set(self.cache_control.clone());
        res.headers.set(Expires(self.expires.clone()));

    }
}

#[derive(Clone)]
struct Cache;
impl Key for Cache {
    type Value = LruCache<CacheControlKey, CacheControlData>;
}

struct Avatar {
    pub buf: Vec<u8>,
    pub cc_key: CacheControlKey,
    pub cc_data: CacheControlData,
}

type AvatarResult<T> = Result<T, Response>;

impl Avatar {
    fn make_origin_url(cc_key: &CacheControlKey) -> String {
        let mut origin_url = format!("https://secure.gravatar.com/avatar/{}", cc_key.email_md5);

        let mut params: Vec<String> = Vec::new();

        if let Some(ref m) = cc_key.size {
            params.push(format!("s={}", m));
        }

        if let Some(ref m) = cc_key.default {
            params.push(format!("d={}", m));
        }

        if params.len() > 0 {
            origin_url.push_str("?");
            origin_url.push_str(params.join("&").as_str());
        }

        origin_url
    }

    pub fn from_response(res: &mut HyperResponse, cc_key: CacheControlKey) -> AvatarResult<Avatar> {
        if res.status != hyper::Ok {
            return Err(Response::with((status::BadGateway)));
        }

        let mut avatar_body: Vec<u8> = Vec::new();

        try!(res.read_to_end(&mut avatar_body)
            .map_err(|_| Response::with((status::BadGateway))));

        let cc_data = try!(CacheControlData::from_response(res, avatar_body.as_slice()));

        Ok(Avatar {
            buf: avatar_body,
            cc_key: cc_key,
            cc_data: cc_data,
        })

    }

    pub fn fetch(cc_key: CacheControlKey) -> AvatarResult<Avatar> {
        lazy_static! {
            static ref HYPER_CLIENT: HyperClient = HyperClient::with_connector(HttpsConnector::new(RustlsClient::new()));
        }

        let origin_url = Avatar::make_origin_url(&cc_key);

        let mut origin_res = try!(HYPER_CLIENT.get(origin_url.as_str())
            .send()
            .map_err(|_| Response::with((status::BadGateway))));

        Avatar::from_response(&mut origin_res, cc_key)
    }
}

struct MainHandler;

impl MainHandler {
    fn read_from_cache(&self,
                       cc_key: &CacheControlKey,
                       cache_mutex: &Arc<Mutex<LruCache<CacheControlKey, CacheControlData>>>)
                       -> Option<CacheControlData> {
        let mut cache = cache_mutex.lock().unwrap();

        let cc_data = match cache.get_mut(cc_key).map(|m| m.clone()) {
            Some(m) => m,
            None => return None,
        };

        if cc_data.expires > HttpDate(time::now()) {
            Some(cc_data)
        } else {
            cache.remove(cc_key);
            None
        }
    }

    fn response_with_200(&self, avatar: &Avatar) -> IronResult<Response> {
        let mut res = Response::with((avatar.cc_data.content_type.deref().clone(),
                                      status::Ok,
                                      avatar.buf.clone()));

        avatar.cc_data.set_cache_headers(&mut res);
        Ok(res)

    }

    fn response_with_304(&self, cc_data: &CacheControlData) -> IronResult<Response> {
        let mut res = Response::with((cc_data.content_type.deref().clone(), status::NotModified));

        cc_data.set_cache_headers(&mut res);

        Ok(res)
    }
}

impl Handler for MainHandler {
    fn handle(&self, mut req: &mut Request) -> IronResult<Response> {
        let cache_mutex = req.get::<Write<Cache>>().unwrap();

        let cc_key = match CacheControlKey::from_request(&mut req) {
            Ok(v) => v,
            Err(res) => return Ok(res),
        };

        let maybe_if_modified_since = req.headers.get::<IfModifiedSince>();
        let maybe_if_none_match = req.headers.get::<IfNoneMatch>();

        if let Some(if_none_match) = maybe_if_none_match {
            // Prefer ETag.
            if let Some(cc_data) = self.read_from_cache(&cc_key, &cache_mutex) {
                if let &IfNoneMatch::Items(ref items) = if_none_match {
                    if items.as_slice().contains(&cc_data.etag) {
                        return self.response_with_304(&cc_data);
                    }
                }
            }
        } else if let Some(if_modified_since) = maybe_if_modified_since {
            if let Some(cc_data) = self.read_from_cache(&cc_key, &cache_mutex) {
                if if_modified_since.deref() == &cc_data.last_modified {
                    return self.response_with_304(&cc_data);
                }
            }
        }

        let avatar = match Avatar::fetch(cc_key) {
            Ok(v) => v,
            Err(res) => return Ok(res),
        };

        let res_result = self.response_with_200(&avatar);
        {
            let mut cache = cache_mutex.lock().unwrap();

            cache.insert(avatar.cc_key, avatar.cc_data);
        }
        res_result
    }
}

struct CustomErrorMsg;

impl CustomErrorMsg {
    fn alter_response(&self, res: Response) -> Response {
        if res.status == Some(status::NotFound) {
            Response::with((status::NotFound, "HTTP 404: Not Found."))

        } else if res.status == Some(status::BadRequest) {
            Response::with((status::BadRequest, "HTTP 400: Bad Request."))

        } else if res.status == Some(status::BadGateway) {
            Response::with((status::BadGateway, "HTTP 502: Bad Gateway."))

        } else {
            res

        }
    }
}

impl AfterMiddleware for CustomErrorMsg {
    fn after(&self, _: &mut Request, res: Response) -> IronResult<Response> {
        Ok(self.alter_response(res))
    }

    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        let res = err.response;
        let res = self.alter_response(res);

        if let Some(_) = err.error.downcast::<NoRoute>() {
            return Ok(res);
        }

        Err(IronError {
            error: err.error,
            response: res,
        })
    }
}

struct XPoweredBy;

impl XPoweredBy {
    fn add_header(&self, res: &mut Response) {
        res.headers.set_raw("X-Powered-By", vec![b"Rust".to_vec()]);
    }
}

impl AfterMiddleware for XPoweredBy {
    fn after(&self, _: &mut Request, mut res: Response) -> IronResult<Response> {
        self.add_header(&mut res);

        Ok(res)
    }

    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        let mut res = err.response;
        let inner_err = err.error;
        self.add_header(&mut res);

        Err(IronError {
            error: inner_err,
            response: res,
        })
    }
}

fn main() {
    let mut router = Router::new();

    router.get("/avatar/:email_md5", MainHandler, "email_md5");

    let mut chain = Chain::new(router);
    chain.link_after(CustomErrorMsg);
    chain.link_after(XPoweredBy);

    chain.link(Write::<Cache>::both(LruCache::new(102400)));

    Iron::new(chain).http("localhost:3000").unwrap();
}
<commit_msg>Improve Importations.<commit_after>//!                             The Unlicense
//! This is free and unencumbered software released into the public domain.
//!
//! Anyone is free to copy, modify, publish, use, compile, sell, or
//! distribute this software, either in source code form or as a compiled
//! binary, for any purpose, commercial or non-commercial, and by any
//! means.
//!
//! In jurisdictions that recognize copyright laws, the author or authors
//! of this software dedicate any and all copyright interest in the
//! software to the public domain. We make this dedication for the benefit
//! of the public at large and to the detriment of our heirs and
//! successors. We intend this dedication to be an overt act of
//! relinquishment in perpetuity of all present and future rights to this
//! software under copyright law.
//!
//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//! EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//! MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//! IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
//! OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//! ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//! OTHER DEALINGS IN THE SOFTWARE.
//!
//! For more information, please refer to <http://unlicense.org>
//!

extern crate iron;
extern crate router;
#[macro_use]
extern crate hyper;
#[macro_use]
extern crate lazy_static;
extern crate regex;
extern crate hyper_rustls;
extern crate urlencoded;
extern crate persistent;
extern crate lru_cache;
extern crate time;
extern crate crypto_hash;

use std::vec::Vec;
use std::ops::Deref;
use std::sync::{Arc, Mutex};
use std::io::Read;
use std::collections::HashMap;
use std::option::Option;
use std::result::Result;

use hyper::client::{Client as HyperClient, Response as HyperResponse};
use hyper::net::HttpsConnector as HyperHttpsConnector;
use hyper::status::StatusCode as HyperStatusCode;
use hyper::header as hyper_header;
use hyper_rustls::TlsClient as HyperRustlsClient;

use iron::{IronResult, IronError, Iron};
use iron::{Request as IronRequest, Response as IronResponse, Chain as IronChain,
           Plugin as IronPlugin};
use iron::{AfterMiddleware, Handler};
use iron::typemap::Key;

use router::{Router, NoRoute};
use urlencoded::{UrlEncodedQuery, UrlDecodingError};
use persistent::Write;

use regex::Regex;
use lru_cache::LruCache;
use crypto_hash::{Algorithm as HashAlgorithm, hex_digest};

header! { (XPoweredBy, "X-Powered-By") => [String] }

fn verify_md5(md5: &str) -> bool {
    lazy_static! {
        static ref MD5_RE: Regex = Regex::new("^[0-9a-f]{32}$").unwrap();
    }

    MD5_RE.is_match(md5)
}

#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct CacheControlKey {
    email_md5: String,
    size: Option<i64>,
    default: Option<String>,
}

impl CacheControlKey {
    fn from_request(req: &mut IronRequest) -> Result<CacheControlKey, IronResponse> {
        let email_md5 = req.extensions
            .get::<Router>()
            .unwrap()
            .find("email_md5")
            .unwrap_or("")
            .to_lowercase();

        if !verify_md5(email_md5.as_str()) {
            return Err(IronResponse::with((HyperStatusCode::NotFound)));
        }

        let maybe_query = req.get::<UrlEncodedQuery>()
            .map(|val| Some(val))
            .unwrap_or_else(|err: UrlDecodingError| {
                match err {
                    UrlDecodingError::EmptyQuery => Some(HashMap::new()),
                    _ => None,
                }
            });

        let query = match maybe_query {
            Some(m) => m,
            None => return Err(IronResponse::with((HyperStatusCode::BadRequest))),
        };

        let size_str = query.get("s")
            .map(|s_vec| s_vec.clone())
            .map(|s_vec| { if s_vec.len() > 0 { Some(s_vec) } else { None } })
            .unwrap_or(None)
            .map(|s_vec| s_vec[0].clone());

        let default = query.get("d")
            .map(|d_vec| d_vec.clone())
            .map(|d_vec| { if d_vec.len() > 0 { Some(d_vec) } else { None } })
            .unwrap_or(None)
            .map(|d_vec| d_vec[0].clone());

        let size: Option<i64> = match size_str {
            Some(m) => {
                match m.parse::<i64>() {
                    Ok(m) => Some(m),
                    Err(_) => return Err(IronResponse::with((HyperStatusCode::BadRequest))),
                }
            }
            None => None,
        };

        Ok(CacheControlKey {
            email_md5: email_md5,
            size: size,
            default: default,
        })
    }
}

#[derive(Clone)]
struct CacheControlData {
    pub etag: hyper_header::EntityTag,
    pub last_modified: hyper_header::HttpDate,
    pub content_type: hyper_header::ContentType,
    pub content_disposition: Option<hyper_header::ContentDisposition>,
    pub cache_control: hyper_header::CacheControl,
    pub expires: hyper_header::HttpDate,
}

impl CacheControlData {
    fn from_response(res: &HyperResponse, res_body: &[u8]) -> AvatarResult<CacheControlData> {
        let etag = hyper_header::EntityTag::strong(hex_digest(HashAlgorithm::SHA256,
                                                              res_body.to_vec()));
        let last_modified = res.headers
            .get::<hyper_header::LastModified>()
            .map(|m| m.deref().clone())
            .unwrap_or_else(|| hyper_header::HttpDate(time::now()));

        let content_type = try!(res.headers
            .get::<hyper_header::ContentType>()
            .map(|m| m.clone())
            .ok_or_else(|| IronResponse::with((HyperStatusCode::BadGateway))));

        let content_disposition =
            res.headers.get::<hyper_header::ContentDisposition>().map(|m| m.clone());
        let cache_control =
            hyper_header::CacheControl(vec![hyper_header::CacheDirective::MaxAge(600 as u32)]);
        let expires = hyper_header::HttpDate(time::now() + time::Duration::minutes(10));

        Ok(CacheControlData {
            etag: etag,
            last_modified: last_modified,
            content_type: content_type,
            content_disposition: content_disposition,
            cache_control: cache_control,
            expires: expires,
        })
    }

    fn set_cache_headers(&self, mut res: &mut IronResponse) {
        res.headers.set(hyper_header::ETag(self.etag.clone()));
        res.headers.set(hyper_header::LastModified(self.last_modified.clone()));
        if let Some(m) = self.content_disposition.clone() {
            res.headers.set(m);
        }
        res.headers.set(self.cache_control.clone());
        res.headers.set(hyper_header::Expires(self.expires.clone()));

    }
}

#[derive(Clone)]
struct Cache;
impl Key for Cache {
    type Value = LruCache<CacheControlKey, CacheControlData>;
}

struct Avatar {
    pub buf: Vec<u8>,
    pub cc_key: CacheControlKey,
    pub cc_data: CacheControlData,
}

type AvatarResult<T> = Result<T, IronResponse>;

impl Avatar {
    fn make_origin_url(cc_key: &CacheControlKey) -> String {
        let mut origin_url = format!("https://secure.gravatar.com/avatar/{}", cc_key.email_md5);

        let mut params: Vec<String> = Vec::new();

        if let Some(ref m) = cc_key.size {
            params.push(format!("s={}", m));
        }

        if let Some(ref m) = cc_key.default {
            params.push(format!("d={}", m));
        }

        if params.len() > 0 {
            origin_url.push_str("?");
            origin_url.push_str(params.join("&").as_str());
        }

        origin_url
    }

    pub fn from_response(res: &mut HyperResponse, cc_key: CacheControlKey) -> AvatarResult<Avatar> {
        if res.status != hyper::Ok {
            return Err(IronResponse::with((HyperStatusCode::BadGateway)));
        }

        let mut avatar_body: Vec<u8> = Vec::new();

        try!(res.read_to_end(&mut avatar_body)
            .map_err(|_| IronResponse::with((HyperStatusCode::BadGateway))));

        let cc_data = try!(CacheControlData::from_response(res, avatar_body.as_slice()));

        Ok(Avatar {
            buf: avatar_body,
            cc_key: cc_key,
            cc_data: cc_data,
        })

    }

    pub fn fetch(cc_key: CacheControlKey) -> AvatarResult<Avatar> {
        lazy_static! {
            static ref HYPER_CLIENT: HyperClient = HyperClient::with_connector(HyperHttpsConnector::new(HyperRustlsClient::new()));
        }

        let origin_url = Avatar::make_origin_url(&cc_key);

        let mut origin_res = try!(HYPER_CLIENT.get(origin_url.as_str())
            .send()
            .map_err(|_| IronResponse::with((HyperStatusCode::BadGateway))));

        Avatar::from_response(&mut origin_res, cc_key)
    }
}

struct MainHandler;

impl MainHandler {
    fn read_from_cache(&self,
                       cc_key: &CacheControlKey,
                       cache_mutex: &Arc<Mutex<LruCache<CacheControlKey, CacheControlData>>>)
                       -> Option<CacheControlData> {
        let mut cache = cache_mutex.lock().unwrap();

        let cc_data = match cache.get_mut(cc_key).map(|m| m.clone()) {
            Some(m) => m,
            None => return None,
        };

        if cc_data.expires > hyper_header::HttpDate(time::now()) {
            Some(cc_data)
        } else {
            cache.remove(cc_key);
            None
        }
    }

    fn response_with_200(&self, avatar: &Avatar) -> IronResult<IronResponse> {
        let mut res = IronResponse::with((avatar.cc_data.content_type.deref().clone(),
                                          HyperStatusCode::Ok,
                                          avatar.buf.clone()));

        avatar.cc_data.set_cache_headers(&mut res);
        Ok(res)

    }

    fn response_with_304(&self, cc_data: &CacheControlData) -> IronResult<IronResponse> {
        let mut res = IronResponse::with((cc_data.content_type.deref().clone(),
                                          HyperStatusCode::NotModified));

        cc_data.set_cache_headers(&mut res);

        Ok(res)
    }
}

impl Handler for MainHandler {
    fn handle(&self, mut req: &mut IronRequest) -> IronResult<IronResponse> {
        let cache_mutex = req.get::<Write<Cache>>().unwrap();

        let cc_key = match CacheControlKey::from_request(&mut req) {
            Ok(v) => v,
            Err(res) => return Ok(res),
        };

        let maybe_if_modified_since = req.headers.get::<hyper_header::IfModifiedSince>();
        let maybe_if_none_match = req.headers.get::<hyper_header::IfNoneMatch>();

        if let Some(if_none_match) = maybe_if_none_match {
            // Prefer ETag.
            if let Some(cc_data) = self.read_from_cache(&cc_key, &cache_mutex) {
                if let &hyper_header::IfNoneMatch::Items(ref items) = if_none_match {
                    if items.as_slice().contains(&cc_data.etag) {
                        return self.response_with_304(&cc_data);
                    }
                }
            }
        } else if let Some(if_modified_since) = maybe_if_modified_since {
            if let Some(cc_data) = self.read_from_cache(&cc_key, &cache_mutex) {
                if if_modified_since.deref() == &cc_data.last_modified {
                    return self.response_with_304(&cc_data);
                }
            }
        }

        let avatar = match Avatar::fetch(cc_key) {
            Ok(v) => v,
            Err(res) => return Ok(res),
        };

        let res_result = self.response_with_200(&avatar);
        {
            let mut cache = cache_mutex.lock().unwrap();

            cache.insert(avatar.cc_key, avatar.cc_data);
        }
        res_result
    }
}

struct CustomErrorMsg;

impl CustomErrorMsg {
    fn alter_response(&self, res: IronResponse) -> IronResponse {
        if res.status == Some(HyperStatusCode::NotFound) {
            IronResponse::with((HyperStatusCode::NotFound, "HTTP 404: Not Found."))

        } else if res.status == Some(HyperStatusCode::BadRequest) {
            IronResponse::with((HyperStatusCode::BadRequest, "HTTP 400: Bad Request."))

        } else if res.status == Some(HyperStatusCode::BadGateway) {
            IronResponse::with((HyperStatusCode::BadGateway, "HTTP 502: Bad Gateway."))

        } else {
            res

        }
    }
}

impl AfterMiddleware for CustomErrorMsg {
    fn after(&self, _: &mut IronRequest, res: IronResponse) -> IronResult<IronResponse> {
        Ok(self.alter_response(res))
    }

    fn catch(&self, _: &mut IronRequest, err: IronError) -> IronResult<IronResponse> {
        let res = err.response;
        let res = self.alter_response(res);

        if let Some(_) = err.error.downcast::<NoRoute>() {
            return Ok(res);
        }

        Err(IronError {
            error: err.error,
            response: res,
        })
    }
}

struct AddXPoweredBy;

impl AddXPoweredBy {
    fn add_header(&self, res: &mut IronResponse) {
        res.headers.set(XPoweredBy("Rust".to_owned()));
    }
}

impl AfterMiddleware for AddXPoweredBy {
    fn after(&self, _: &mut IronRequest, mut res: IronResponse) -> IronResult<IronResponse> {
        self.add_header(&mut res);

        Ok(res)
    }

    fn catch(&self, _: &mut IronRequest, err: IronError) -> IronResult<IronResponse> {
        let mut res = err.response;
        let inner_err = err.error;
        self.add_header(&mut res);

        Err(IronError {
            error: inner_err,
            response: res,
        })
    }
}

fn main() {
    let mut router = Router::new();

    router.get("/avatar/:email_md5", MainHandler, "email_md5");

    let mut chain = IronChain::new(router);
    chain.link_after(CustomErrorMsg);
    chain.link_after(AddXPoweredBy);

    chain.link(Write::<Cache>::both(LruCache::new(102400)));

    Iron::new(chain).http("localhost:3000").unwrap();
}
<|endoftext|>