<commit_before>use std::cmp::max;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};

use grep::matcher::{Captures, Matcher};
use grep::regex::{RegexMatcher, RegexMatcherBuilder};
use grep::searcher::{
    BinaryDetection, Searcher, SearcherBuilder, Sink, SinkContext, SinkContextKind, SinkMatch,
};
use ignore::Walk;
use structopt::StructOpt;
use text_io::read;

#[derive(Debug, StructOpt)]
#[structopt(name = "fnr")]
/// Look for things, optionally replace them.
struct Opts {
    /// Run search in case insensitive mode
    #[structopt(short, long)]
    insensitive: bool,

    /// Print out replacements without actually performing them
    #[structopt(short, long)]
    dry_run: bool,

    #[structopt(short = "A", long, default_value = "0")]
    after: usize,
    #[structopt(short = "B", long, default_value = "0")]
    before: usize,
    #[structopt(short = "C", long, default_value = "0")]
    context: usize,

    #[structopt(short, long)]
    prompt: bool,

    /// What to search for.
    #[structopt(name = "FIND")]
    find: String,

    /// What to replace it with. Required unless `prompt` is set.
    #[structopt(name = "REPLACE")]
    replace: Option<String>,

    /// Locations to search. Current directory if not specified.
    #[structopt(name = "PATH", parse(from_os_str))]
    paths: Vec<PathBuf>,
}

#[derive(Debug, Clone)]
struct Line(u64, String);

#[derive(Debug)]
struct SearchMatch {
    line: Line,
    context_pre: Vec<Line>,
    context_post: Vec<Line>,
}

impl SearchMatch {
    fn as_change(&self, line: String) -> Change {
        return Change {
            line_number: self.line.0,
            new_line: line,
        };
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
enum MatchState {
    Init,
    Before,
    Match,
    After,
}

#[derive(Debug)]
struct SearchMatchCollector {
    state: MatchState,

    cur_context_pre: Vec<Line>,
    cur_context_post: Vec<Line>,
    cur_match_line: Option<Line>,

    matches: Vec<SearchMatch>,
}

impl SearchMatchCollector {
    fn new() -> SearchMatchCollector {
        return SearchMatchCollector {
            state: MatchState::Init,
            cur_match_line: None,
            cur_context_pre: Vec::new(),
            cur_context_post: Vec::new(),
            matches: Vec::new(),
        };
    }

    fn maybe_emit(&mut self) {
        if let Some(line) = &self.cur_match_line {
            // TODO: clones are wasteful here.
            let m = SearchMatch {
                line: line.clone(),
                context_pre: self.cur_context_pre.clone(),
                context_post: self.cur_context_post.clone(),
            };

            self.matches.push(m);
            self.reset();
        }
    }

    fn reset(&mut self) {
        self.state = MatchState::Init;

        self.cur_match_line = None;
        self.cur_context_pre.clear();
        self.cur_context_post.clear();
    }

    #[inline]
    fn transition(&mut self, next: MatchState) {
        match (self.state, next) {
            // Beginning a new match or ending a previous one
            (MatchState::Match, MatchState::Before)       // Have before context, no after context
            | (MatchState::Match, MatchState::Match)      // No before context, no after context
            | (MatchState::After, MatchState::Before)     // Have before context, have after context
            | (MatchState::After, MatchState::Match) => { // Have after context, no before context
                self.maybe_emit();
            }

            (_prev, next) => {
                self.state = next;
            }
        }
    }

    fn collect(&mut self) -> &Vec<SearchMatch> {
        self.maybe_emit();
        return &self.matches;
    }
}

impl Drop for SearchMatchCollector {
    fn drop(&mut self) {
        self.maybe_emit();
    }
}

impl Sink for SearchMatchCollector {
    type Error = std::io::Error;

    fn matched(
        &mut self,
        _searcher: &Searcher,
        mat: &SinkMatch<'_>,
    ) -> Result<bool, std::io::Error> {
        self.transition(MatchState::Match);

        // TODO: handle errors
        let line = Line(
            mat.line_number().expect("grab line number"),
            String::from_utf8_lossy(mat.bytes()).to_string(),
        );

        self.cur_match_line = Some(line);

        Ok(true)
    }

    fn context(
        &mut self,
        _searcher: &Searcher,
        ctx: &SinkContext<'_>,
    ) -> Result<bool, std::io::Error> {
        // TODO: handle errors.
        // TODO: can we avoid the additional .to_string() by storing a COW instead?
        let line = Line(
            ctx.line_number().expect("grab line number"),
            String::from_utf8_lossy(ctx.bytes()).to_string(),
        );

        match ctx.kind() {
            &SinkContextKind::Before => {
                self.transition(MatchState::Before);
                self.cur_context_pre.push(line);
            }
            &SinkContextKind::After => {
                self.transition(MatchState::After);
                self.cur_context_post.push(line);
            }
            &SinkContextKind::Other => {}
        }

        Ok(true)
    }
}

trait Replacer {
    fn replace(&self, input: &str) -> Option<String>;
}

struct RegexReplacer<'a> {
    matcher: &'a RegexMatcher,
    template: &'a str,
}

impl<'a> Replacer for RegexReplacer<'a> {
    fn replace(&self, input: &str) -> Option<String> {
        let mut caps = self.matcher.new_captures().unwrap();
        let mut dst = vec![];

        self.matcher
            .replace_with_captures(input.as_bytes(), &mut caps, &mut dst, |caps, dst| {
                caps.interpolate(
                    |name| self.matcher.capture_index(name),
                    input.as_bytes(),
                    self.template.as_bytes(),
                    dst,
                );
                false
            })
            .unwrap();

        Some(String::from_utf8_lossy(&dst).to_string())
    }
}

type MatchDecider = dyn Fn(&SearchMatch) -> ReplacementDecision;

#[derive(Debug)]
struct Change {
    line_number: u64,
    // TODO: use slice here
    new_line: String,
}

struct SearchMatchProcessor<'a> {
    replacer: &'a dyn Replacer,
    acceptor: &'a MatchDecider,

    decide_all: Option<bool>,
}

impl<'a> SearchMatchProcessor<'a> {
    fn new(replacer: &'a dyn Replacer, acceptor: &'a MatchDecider) -> SearchMatchProcessor<'a> {
        return SearchMatchProcessor {
            replacer,
            acceptor,
            decide_all: None,
        };
    }

    fn handle_path(&mut self, path: &Path, matches: &Vec<SearchMatch>) {
        if matches.is_empty() || Some(false) == self.decide_all {
            return;
        }
        println!(
            "--- {}: {} match{}",
            path.display(),
            matches.len(),
            if matches.len() == 1 { "" } else { "es" }
        );
        let mut accept_remaining = false;

        let mut change_list = vec![];
        for m in matches {
            let replacement = if let Some(r) = self.replacer.replace(&m.line.1) {
                r
            } else {
                println!("TODO: replacer failed on line");
                continue;
            };
            self.display(m, &replacement);

            let change = if accept_remaining || Some(true) == self.decide_all {
                m.as_change(replacement)
            } else {
                match (self.acceptor)(m) {
                    ReplacementDecision::IgnoreThis => continue,
                    ReplacementDecision::IgnoreFile => break,
                    ReplacementDecision::IgnoreRest => {
                        self.decide_all = Some(false);
                        break;
                    }
                    ReplacementDecision::AcceptThis => m.as_change(replacement),
                    ReplacementDecision::AcceptFile => {
                        accept_remaining = true;
                        m.as_change(replacement)
                    }
                    ReplacementDecision::EditThis => {
                        print!("[replace with] ");
                        std::io::stdout().flush().unwrap();
                        let mut line: String = read!("{}\n");
                        line.push('\n');
                        self.display(m, &line);
                        println!("--");
                        m.as_change(line)
                    }
                }
            };
            change_list.push(change);
        }

        if !change_list.is_empty() {
            self.apply_changes(path, &mut change_list);
        }

        println!("");
    }

    fn display(&self, m: &SearchMatch, replacement: &str) {
        for line in &m.context_pre {
            print!("    {}: {}", line.0, line.1);
        }

        // TODO: Multiple matches on same line
        print!("-   {}: {}", m.line.0, m.line.1);
        print!("+   {}: {}", m.line.0, replacement);

        // TODO: print gap between non-consecutive lines
        for line in &m.context_post {
            print!("    {}: {}", line.0, line.1);
        }
    }

    fn apply_changes(&self, path: &Path, mut changes: &[Change]) {
        let src = File::open(path).expect("couldn't open file");
        let reader = BufReader::new(src);
        // TODO: fix this
        let dst = File::create("/tmp/replaceme").expect("couldn't create file");
        let mut writer = BufWriter::new(dst);

        let mut line_num = 0;
        for line in reader.lines() {
            let line = line.unwrap();
            line_num += 1;

            if !changes.is_empty() && changes[0].line_number == line_num {
                writer.write(changes[0].new_line.as_bytes()).unwrap();
                changes = &changes[1..];
            } else {
                // TODO: don't strip off newlines to begin with
                writer.write(line.as_bytes()).unwrap();
                writer.write(b"\n").unwrap();
            };
        }
    }
}

#[derive(Debug)]
enum ReplacementDecision {
    IgnoreThis,
    IgnoreFile,
    IgnoreRest,
    AcceptThis,
    AcceptFile,
    EditThis,
}

fn prompt_for_decision() -> ReplacementDecision {
    loop {
        print!("Stage this replacement [Y,n,q,a,e,d,?] ");
        std::io::stdout().flush().unwrap();
        let line: String = read!("{}\n");
        // TODO: ^D should not result in acceptance

        return match line.as_str() {
            "y" | "Y" | "" => ReplacementDecision::AcceptThis,
            "n" => ReplacementDecision::IgnoreThis,
            "q" => ReplacementDecision::IgnoreRest,
            "a" => ReplacementDecision::AcceptFile,
            "d" => ReplacementDecision::IgnoreFile,
            "e" => ReplacementDecision::EditThis,

            "?" | _ => {
                println!(
                    "
Y - replace this line
n - do not replace this line
q - quit; do not replace this line or any remaining ones
a - replace this line and all remaining ones in this file
d - do not replace this line nor any remaining ones in this file
e - edit this replacement
? - show help
"
                );
                continue;
            }
        };
    }
}

fn main() {
    let opts: Opts = Opts::from_args();
    println!("Parsed opts: {:?}", opts);

    let matcher = RegexMatcherBuilder::new()
        .case_insensitive(opts.insensitive)
        .build(&opts.find)
        .expect("bad pattern");

    let mut searcher = SearcherBuilder::new()
        .binary_detection(BinaryDetection::quit(0x00))
        .line_number(true)
        .before_context(max(opts.context, opts.before))
        .after_context(max(opts.context, opts.after))
        .build();

    // TODO: Confirm that template does not reference more capture groups than exist.
    let template_str = &opts.replace.unwrap();
    let replacer = RegexReplacer {
        matcher: &matcher,
        template: template_str,
    };

    let acceptor = if opts.dry_run {
        |_: &SearchMatch| ReplacementDecision::IgnoreThis
    } else if opts.prompt {
        |_: &SearchMatch| prompt_for_decision()
    } else {
        |_: &SearchMatch| ReplacementDecision::AcceptThis
    };
    let mut proc = SearchMatchProcessor::new(&replacer, &acceptor);

    let paths = if opts.paths.is_empty() {
        vec![Path::new("./").to_owned()]
    } else {
        opts.paths
    };

    for path in paths {
        // TODO: Support files as well as directories here
        for dir_entry in Walk::new(path) {
            let dir_entry = dir_entry.unwrap();
            let path = dir_entry.path();
            if !path.is_file() {
                continue;
            }

            let mut sink = SearchMatchCollector::new();

            searcher
                .search_path(&matcher, dir_entry.path(), &mut sink)
                .expect("search failed");

            let matches = sink.collect();
            proc.handle_path(path, matches);
        }
    }

    println!("All done.");
}
<commit_msg>Add barebones color support<commit_after>use std::cmp::max;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
use std::path::{Path, PathBuf};

use grep::matcher::{Captures, Matcher};
use grep::regex::{RegexMatcher, RegexMatcherBuilder};
use grep::searcher::{
    BinaryDetection, Searcher, SearcherBuilder, Sink, SinkContext, SinkContextKind, SinkMatch,
};
use ignore::Walk;
use structopt::StructOpt;
use text_io::read;

#[derive(Debug, StructOpt)]
#[structopt(name = "fnr")]
/// Look for things, optionally replace them.
struct Opts {
    /// Run search in case insensitive mode
    #[structopt(short, long)]
    insensitive: bool,

    /// Print out replacements without actually performing them
    #[structopt(short, long)]
    dry_run: bool,

    #[structopt(short = "A", long, default_value = "0")]
    after: usize,
    #[structopt(short = "B", long, default_value = "0")]
    before: usize,
    #[structopt(short = "C", long, default_value = "0")]
    context: usize,

    #[structopt(short, long)]
    prompt: bool,

    /// What to search for.
    #[structopt(name = "FIND")]
    find: String,

    /// What to replace it with.
    #[structopt(name = "REPLACE")]
    replace: String,

    /// Locations to search. Current directory if not specified.
    #[structopt(name = "PATH", parse(from_os_str))]
    paths: Vec<PathBuf>,
}

#[derive(Debug, Clone)]
struct Line(u64, String);

#[derive(Debug)]
struct SearchMatch {
    line: Line,
    context_pre: Vec<Line>,
    context_post: Vec<Line>,
}

impl SearchMatch {
    fn as_change(&self, line: String) -> Change {
        return Change {
            line_number: self.line.0,
            new_line: line,
        };
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
enum MatchState {
    Init,
    Before,
    Match,
    After,
}

#[derive(Debug)]
struct SearchMatchCollector {
    state: MatchState,

    cur_context_pre: Vec<Line>,
    cur_context_post: Vec<Line>,
    cur_match_line: Option<Line>,

    matches: Vec<SearchMatch>,
}

impl SearchMatchCollector {
    fn new() -> SearchMatchCollector {
        return SearchMatchCollector {
            state: MatchState::Init,
            cur_match_line: None,
            cur_context_pre: Vec::new(),
            cur_context_post: Vec::new(),
            matches: Vec::new(),
        };
    }

    fn maybe_emit(&mut self) {
        if let Some(line) = &self.cur_match_line {
            // TODO: clones are wasteful here.
            let m = SearchMatch {
                line: line.clone(),
                context_pre: self.cur_context_pre.clone(),
                context_post: self.cur_context_post.clone(),
            };

            self.matches.push(m);
            self.reset();
        }
    }

    fn reset(&mut self) {
        self.state = MatchState::Init;

        self.cur_match_line = None;
        self.cur_context_pre.clear();
        self.cur_context_post.clear();
    }

    #[inline]
    fn transition(&mut self, next: MatchState) {
        match (self.state, next) {
            // Beginning a new match or ending a previous one
            (MatchState::Match, MatchState::Before)       // Have before context, no after context
            | (MatchState::Match, MatchState::Match)      // No before context, no after context
            | (MatchState::After, MatchState::Before)     // Have before context, have after context
            | (MatchState::After, MatchState::Match) => { // Have after context, no before context
                self.maybe_emit();
            }

            (_prev, next) => {
                self.state = next;
            }
        }
    }

    fn collect(&mut self) -> &Vec<SearchMatch> {
        self.maybe_emit();
        return &self.matches;
    }
}

impl Drop for SearchMatchCollector {
    fn drop(&mut self) {
        self.maybe_emit();
    }
}

impl Sink for SearchMatchCollector {
    type Error = std::io::Error;

    fn matched(
        &mut self,
        _searcher: &Searcher,
        mat: &SinkMatch<'_>,
    ) -> Result<bool, std::io::Error> {
        self.transition(MatchState::Match);

        // TODO: handle errors
        let line = Line(
            mat.line_number().expect("grab line number"),
            String::from_utf8_lossy(mat.bytes()).to_string(),
        );

        self.cur_match_line = Some(line);

        Ok(true)
    }

    fn context(
        &mut self,
        _searcher: &Searcher,
        ctx: &SinkContext<'_>,
    ) -> Result<bool, std::io::Error> {
        // TODO: handle errors.
        // TODO: can we avoid the additional .to_string() by storing a COW instead?
        let line = Line(
            ctx.line_number().expect("grab line number"),
            String::from_utf8_lossy(ctx.bytes()).to_string(),
        );

        match ctx.kind() {
            &SinkContextKind::Before => {
                self.transition(MatchState::Before);
                self.cur_context_pre.push(line);
            }
            &SinkContextKind::After => {
                self.transition(MatchState::After);
                self.cur_context_post.push(line);
            }
            &SinkContextKind::Other => {}
        }

        Ok(true)
    }
}

trait Replacer {
    fn replace(&self, input: &str) -> Option<String>;
}

struct RegexReplacer<'a> {
    matcher: &'a RegexMatcher,
    template: &'a str,
}

impl<'a> Replacer for RegexReplacer<'a> {
    fn replace(&self, input: &str) -> Option<String> {
        let mut caps = self.matcher.new_captures().unwrap();
        let mut dst = vec![];

        self.matcher
            .replace_with_captures(input.as_bytes(), &mut caps, &mut dst, |caps, dst| {
                caps.interpolate(
                    |name| self.matcher.capture_index(name),
                    input.as_bytes(),
                    self.template.as_bytes(),
                    dst,
                );
                false
            })
            .unwrap();

        Some(String::from_utf8_lossy(&dst).to_string())
    }
}

type MatchDecider = dyn Fn(&SearchMatch) -> ReplacementDecision;

#[derive(Debug)]
struct Change {
    line_number: u64,
    // TODO: use slice here
    new_line: String,
}

struct SearchMatchProcessor<'a> {
    replacer: &'a dyn Replacer,
    acceptor: &'a MatchDecider,

    decide_all: Option<bool>,
}

impl<'a> SearchMatchProcessor<'a> {
    fn new(replacer: &'a dyn Replacer, acceptor: &'a MatchDecider) -> SearchMatchProcessor<'a> {
        return SearchMatchProcessor {
            replacer,
            acceptor,
            decide_all: None,
        };
    }

    fn handle_path(&mut self, path: &Path, matches: &Vec<SearchMatch>) {
        if matches.is_empty() || Some(false) == self.decide_all {
            return;
        }
        println!(
            "--- {}: {} match{}",
            path.display(),
            matches.len(),
            if matches.len() == 1 { "" } else { "es" }
        );
        let mut accept_remaining = false;

        let mut change_list = vec![];
        for m in matches {
            let replacement = if let Some(r) = self.replacer.replace(&m.line.1) {
                r
            } else {
                println!("TODO: replacer failed on line");
                continue;
            };
            self.display(m, &replacement);

            let change = if accept_remaining || Some(true) == self.decide_all {
                m.as_change(replacement)
            } else {
                match (self.acceptor)(m) {
                    ReplacementDecision::IgnoreThis => continue,
                    ReplacementDecision::IgnoreFile => break,
                    ReplacementDecision::IgnoreRest => {
                        self.decide_all = Some(false);
                        break;
                    }
                    ReplacementDecision::AcceptThis => m.as_change(replacement),
                    ReplacementDecision::AcceptFile => {
                        accept_remaining = true;
                        m.as_change(replacement)
                    }
                    ReplacementDecision::EditThis => {
                        print!("[replace with] ");
                        std::io::stdout().flush().unwrap();
                        let mut line: String = read!("{}\n");
                        line.push('\n');
                        self.display(m, &line);
                        println!("--");
                        m.as_change(line)
                    }
                }
            };
            change_list.push(change);
        }

        if !change_list.is_empty() {
            self.apply_changes(path, &mut change_list);
        }

        println!("");
    }

    fn display(&self, m: &SearchMatch, replacement: &str) {
        // TODO: only show this gap if non-consecutive
        println!("    ...");
        for line in &m.context_pre {
            print!("    {}: {}", line.0, line.1);
        }

        // TODO: Multiple matches on same line
        // TODO: Disable colors when not atty
        print!("\x1B[31m-   {}: {}\x1B[0m", m.line.0, m.line.1);
        print!("\x1B[32m+   {}: {}\x1B[0m", m.line.0, replacement);

        for line in &m.context_post {
            print!("    {}: {}", line.0, line.1);
        }
    }

    fn apply_changes(&self, path: &Path, mut changes: &[Change]) {
        let src = File::open(path).expect("couldn't open file");
        let reader = BufReader::new(src);
        // TODO: fix this
        let dst = File::create("/tmp/replaceme").expect("couldn't create file");
        let mut writer = BufWriter::new(dst);

        let mut line_num = 0;
        for line in reader.lines() {
            let line = line.unwrap();
            line_num += 1;

            if !changes.is_empty() && changes[0].line_number == line_num {
                writer.write(changes[0].new_line.as_bytes()).unwrap();
                changes = &changes[1..];
            } else {
                // TODO: don't strip off newlines to begin with
                writer.write(line.as_bytes()).unwrap();
                writer.write(b"\n").unwrap();
            };
        }
    }
}

#[derive(Debug)]
enum ReplacementDecision {
    IgnoreThis,
    IgnoreFile,
    IgnoreRest,
    AcceptThis,
    AcceptFile,
    EditThis,
}

fn prompt_for_decision() -> ReplacementDecision {
    loop {
        print!("Stage this replacement [Y,n,q,a,e,d,?] ");
        std::io::stdout().flush().unwrap();
        let line: String = read!("{}\n");
        // TODO: ^D should not result in acceptance

        return match line.as_str() {
            "y" | "Y" | "" => ReplacementDecision::AcceptThis,
            "n" => ReplacementDecision::IgnoreThis,
            "q" => ReplacementDecision::IgnoreRest,
            "a" => ReplacementDecision::AcceptFile,
            "d" => ReplacementDecision::IgnoreFile,
            "e" => ReplacementDecision::EditThis,

            "?" | _ => {
                println!(
                    "
Y - replace this line
n - do not replace this line
q - quit; do not replace this line or any remaining ones
a - replace this line and all remaining ones in this file
d - do not replace this line nor any remaining ones in this file
e - edit this replacement
? - show help
"
                );
                continue;
            }
        };
    }
}

fn main() {
    let opts: Opts = Opts::from_args();
    println!("Parsed opts: {:?}", opts);

    let matcher = RegexMatcherBuilder::new()
        .case_insensitive(opts.insensitive)
        .build(&opts.find)
        .expect("bad pattern");

    let mut searcher = SearcherBuilder::new()
        .binary_detection(BinaryDetection::quit(0x00))
        .line_number(true)
        .before_context(max(opts.context, opts.before))
        .after_context(max(opts.context, opts.after))
        .build();

    // TODO: Confirm that template does not reference more capture groups than exist.
    let replacer = RegexReplacer {
        matcher: &matcher,
        template: &opts.replace,
    };

    let acceptor = if opts.dry_run {
        |_: &SearchMatch| ReplacementDecision::IgnoreThis
    } else if opts.prompt {
        |_: &SearchMatch| prompt_for_decision()
    } else {
        |_: &SearchMatch| ReplacementDecision::AcceptThis
    };
    let mut proc = SearchMatchProcessor::new(&replacer, &acceptor);

    let paths = if opts.paths.is_empty() {
        vec![Path::new("./").to_owned()]
    } else {
        opts.paths
    };

    for path in paths {
        // TODO: Support files as well as directories here
        for dir_entry in Walk::new(path) {
            let dir_entry = dir_entry.unwrap();
            let path = dir_entry.path();
            if !path.is_file() {
                continue;
            }

            let mut sink = SearchMatchCollector::new();

            searcher
                .search_path(&matcher, dir_entry.path(), &mut sink)
                .expect("search failed");

            let matches = sink.collect();
            proc.handle_path(path, matches);
        }
    }

    println!("All done.");
}
<|endoftext|>


<commit_before>#[macro_use]
extern crate serde_derive;
extern crate docopt;
extern crate image;
extern crate gpx;
extern crate time;

use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::path;

use docopt::Docopt;
use gpx::read;
use gpx::{Gpx, Track};
use image::ImageBuffer;

const USAGE: &'static str = "
Generate video from GPX files.

Usage:
  derivers <top-lat> <left-lng> <bottom-lat> <right-lng> <width> <height> <directory>
";

#[derive(Debug, Deserialize)]
struct CommandArgs {
    arg_top_lat: f64,
    arg_left_lng: f64,
    arg_bottom_lat: f64,
    arg_right_lng: f64,
    arg_width: u32,
    arg_height: u32,
    arg_directory: String,
}

#[derive(Debug)]
struct Activity {
    name: String,
    date: time::Tm,
    track_points: Vec<(f64, f64)>,
}

fn parse_gpx(path: &path::Path) -> Option<Activity> {
    let file = File::open(path).unwrap();
    let reader = BufReader::new(file);

    let gpx: Gpx = read(reader).unwrap();

    // Nothing to do if there are no tracks
    if gpx.tracks.len() == 0 {
        return None;
    } else if gpx.tracks.len() > 1 {
        println!("Warning! more than 1 track, just taking first");
    }

    let track: &Track = &gpx.tracks[0];

    let mut activity = Activity {
        name: String::from("unnamed"),
        date: time::empty_tm(),
        track_points: vec![],
    };

    if let Some(ref name) = track.name {
        activity.name = name.clone();
    }

    if let Some(metadata) = gpx.metadata {

        if let Some(_time) = metadata.time {
            // FIXME: update this
            activity.date = time::now();
        }
    }

    Some(activity)
}

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

    println!("{:?}", args);

    let dir_entry = fs::read_dir(args.arg_directory).unwrap();
    let files: Vec<path::PathBuf> = dir_entry.map(|p| p.unwrap().path()).collect();

    for path in files {
        println!("Reading GPX file: {:?}", path);

        let activity = parse_gpx(path.as_path());
        println!("Activity: {:?}", activity);
    }
}
<commit_msg>Keep track of track points<commit_after>#[macro_use]
extern crate serde_derive;
extern crate docopt;
extern crate image;
extern crate gpx;
extern crate geo;
extern crate time;

use std::fs;
use std::fs::File;
use std::io::BufReader;
use std::path;

use docopt::Docopt;
use gpx::read;
use gpx::{Gpx, Track};
use geo::Point;
use image::ImageBuffer;

const USAGE: &'static str = "
Generate video from GPX files.

Usage:
  derivers <top-lat> <left-lng> <bottom-lat> <right-lng> <width> <height> <directory>
";

#[derive(Debug, Deserialize)]
struct CommandArgs {
    arg_top_lat: f64,
    arg_left_lng: f64,
    arg_bottom_lat: f64,
    arg_right_lng: f64,
    arg_width: u32,
    arg_height: u32,
    arg_directory: String,
}

#[derive(Debug)]
struct Activity {
    name: String,
    date: time::Tm,
    track_points: Vec<Point<f64>>,
}

fn parse_gpx(path: &path::Path) -> Option<Activity> {
    let file = File::open(path).unwrap();
    let reader = BufReader::new(file);

    let gpx: Gpx = read(reader).unwrap();

    // Nothing to do if there are no tracks
    if gpx.tracks.len() == 0 {
        return None;
    } else if gpx.tracks.len() > 1 {
        println!("Warning! more than 1 track, just taking first");
    }

    let track: &Track = &gpx.tracks[0];

    let mut activity = Activity {
        name: String::from("unnamed"),
        date: time::empty_tm(),
        track_points: vec![],
    };

    if let Some(ref name) = track.name {
        activity.name = name.clone();
    }

    if let Some(metadata) = gpx.metadata {
        if let Some(_time) = metadata.time {
            // FIXME: update this
            activity.date = time::now();
        }
    }

    for seg in track.segments.iter() {
        let points = seg.points.iter().map(|ref wpt| wpt.point());
        activity.track_points.extend(points);
    }

    Some(activity)
}

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

    println!("{:?}", args);

    let dir_entry = fs::read_dir(args.arg_directory).unwrap();
    let files: Vec<path::PathBuf> = dir_entry.map(|p| p.unwrap().path()).collect();

    for path in files {
        let activity = parse_gpx(path.as_path());
        println!("Activity: {:?}", activity.unwrap().name);
    }
}
<|endoftext|>


<commit_before>extern crate curl;
extern crate regex;

use curl::http;
use regex::Regex;
use std::io::prelude::*;
use std::fs::File;

static URL: &'static str = "http://alpha.wallhaven.cc/random";

static REX: &'static str = r#"<img [^>]*? src=".*?th-(\d+\..{3})"[^>]*?>"#;

fn get_body(url : &str) -> String {
    let resp = http::handle()
        .get(url)
        .exec()
        .unwrap_or_else(|e| {
            panic!("Failed to get {}; error is {}", url, e);
        });
    let body = std::str::from_utf8(resp.get_body()).unwrap_or_else(|e| {
        panic!("Failed to parse response from {}; error is {}", url, e);
    });
    let ret = body.to_string();
    return ret;
}

fn get_img_name(html : &str) -> String {
    let re = Regex::new(REX).unwrap();
    let cap = re.captures(html).unwrap();
    let mut ret_a = "http://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-".to_string();
    let ret_b = cap.at(1).unwrap_or("");
    let ret = ret_a+ret_b; // :0
    return ret;
}

//fn download_img(url : str) {
//    let mut f = try!(File::create("foo.jpg"));
//}

fn main() {
    let html = get_body(URL);
    let img = get_img_name(&html);
    println!("{}", img);
}
<commit_msg>ugly Rust code<commit_after>extern crate curl;
extern crate regex;

use curl::http;
use regex::Regex;
use std::io::prelude::*;
use std::io::Error;
use std::fs::File;

static URL: &'static str = "http://alpha.wallhaven.cc/random";
static REX: &'static str = r#"<img [^>]*? src=".*?th-(\d+\..{3})"[^>]*?>"#;

fn get_body(url : &str) -> String {
    let resp = http::handle()
        .get(url)
        .exec()
        .unwrap_or_else(|e| {
            panic!("Failed to get {}; error is {}", url, e);
        });
    let body = std::str::from_utf8(resp.get_body()).unwrap_or_else(|e| {
        panic!("Failed to parse response from {}; error is {}", url, e);
    });
    let ret = body.to_string();
    return ret;
}

fn get_img_name(html : &str) -> (String, String) {
    let re = Regex::new(REX).unwrap();
    let cap = re.captures(html).unwrap();
    let ret_a = "http://wallpapers.wallhaven.cc/wallpapers/full/wallhaven-".to_string();
    let ret_b = cap.at(1).unwrap_or("");
    let ret = ret_a+ret_b; // :0
    return (ret, ret_b.to_string());
}

fn download_img(url : &str, name : &str)  -> Result<(), Error>  {
    let resp = http::handle()
        .get(url)
        .exec()
        .unwrap();
    let mut f = try!(File::create(name));
    try!(f.write_all(resp.get_body()));
    Ok(())
}

fn main() {
    let html = get_body(URL);
    let (img_url,img_name) = get_img_name(&html);
    download_img(&img_url, &img_name);
    //println!("{}", img);
}
<|endoftext|>


<commit_before>// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
// Boston, MA 02110-1335, USA.

extern crate glib;
use glib::prelude::*;

extern crate gstreamer as gst;
use gst::prelude::*;

use std::sync::mpsc;

fn init() {
    use std::sync::{Once, ONCE_INIT};
    static INIT: Once = ONCE_INIT;

    INIT.call_once(|| {
        gst::init().unwrap();

        #[cfg(debug_assertions)]
        {
            use std::path::Path;

            let mut path = Path::new("target/debug");
            if !path.exists() {
                path = Path::new("../target/debug");
            }

            gst::Registry::get().scan_path(path);
        }
        #[cfg(not(debug_assertions))]
        {
            use std::path::Path;

            let mut path = Path::new("target/release");
            if !path.exists() {
                path = Path::new("../target/release");
            }

            gst::Registry::get().scan_path(path);
        }
    });
}

#[test]
fn test_push() {
    init();

    let pipeline = gst::Pipeline::new(None);
    let appsrc = gst::ElementFactory::make("ts-appsrc", None).unwrap();
    let appsink = gst::ElementFactory::make("appsink", None).unwrap();
    pipeline.add_many(&[&appsrc, &appsink]).unwrap();
    appsrc.link(&appsink).unwrap();

    let caps = gst::Caps::new_simple("foo/bar", &[]);
    appsrc.set_property("caps", &caps).unwrap();
    appsrc.set_property("do-timestamp", &true).unwrap();

    appsink.set_property("emit-signals", &true).unwrap();

    let (sender, receiver) = mpsc::sync_channel(20);

    appsink
        .connect("new-sample", true, move |args| {
            let appsink = args[0].get::<gst::Element>().unwrap();

            let sample = appsink
                .emit("pull-sample", &[])
                .unwrap()
                .unwrap()
                .get::<gst::Sample>()
                .unwrap();
            sender.send(sample).unwrap();

            Some(gst::FlowReturn::Ok.to_value())
        })
        .unwrap();

    pipeline
        .set_state(gst::State::Playing)
        .into_result()
        .unwrap();

    assert!(
        appsrc
            .emit("push-buffer", &[&gst::Buffer::new()])
            .unwrap()
            .unwrap()
            .get::<bool>()
            .unwrap()
    );
    assert!(
        appsrc
            .emit("push-buffer", &[&gst::Buffer::new()])
            .unwrap()
            .unwrap()
            .get::<bool>()
            .unwrap()
    );
    assert!(
        appsrc
            .emit("push-buffer", &[&gst::Buffer::new()])
            .unwrap()
            .unwrap()
            .get::<bool>()
            .unwrap()
    );
    assert!(
        appsrc
            .emit("end-of-stream", &[])
            .unwrap()
            .unwrap()
            .get::<bool>()
            .unwrap()
    );

    let sample = receiver.recv().unwrap();
    assert!(sample.get_buffer().is_some());
    assert_eq!(Some(&caps), sample.get_caps().as_ref());

    let sample = receiver.recv().unwrap();
    assert!(sample.get_buffer().is_some());
    assert_eq!(Some(&caps), sample.get_caps().as_ref());

    let sample = receiver.recv().unwrap();
    assert!(sample.get_buffer().is_some());
    assert_eq!(Some(&caps), sample.get_caps().as_ref());

    pipeline.set_state(gst::State::Null).into_result().unwrap();
}
<commit_msg>threadshare: Add test for single and multi-threaded appsrc<commit_after>// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
// Boston, MA 02110-1335, USA.

extern crate glib;
use glib::prelude::*;

extern crate gstreamer as gst;
use gst::prelude::*;

use std::sync::{Arc, Mutex};

fn init() {
    use std::sync::{Once, ONCE_INIT};
    static INIT: Once = ONCE_INIT;

    INIT.call_once(|| {
        gst::init().unwrap();

        #[cfg(debug_assertions)]
        {
            use std::path::Path;

            let mut path = Path::new("target/debug");
            if !path.exists() {
                path = Path::new("../target/debug");
            }

            gst::Registry::get().scan_path(path);
        }
        #[cfg(not(debug_assertions))]
        {
            use std::path::Path;

            let mut path = Path::new("target/release");
            if !path.exists() {
                path = Path::new("../target/release");
            }

            gst::Registry::get().scan_path(path);
        }
    });
}

fn test_push(n_threads: i32) {
    init();

    let pipeline = gst::Pipeline::new(None);
    let appsrc = gst::ElementFactory::make("ts-appsrc", None).unwrap();
    let appsink = gst::ElementFactory::make("appsink", None).unwrap();
    pipeline.add_many(&[&appsrc, &appsink]).unwrap();
    appsrc.link(&appsink).unwrap();

    let caps = gst::Caps::new_simple("foo/bar", &[]);
    appsrc.set_property("caps", &caps).unwrap();
    appsrc.set_property("context-threads", &n_threads).unwrap();
    appsrc.set_property("do-timestamp", &true).unwrap();

    appsink.set_property("emit-signals", &true).unwrap();

    let samples = Arc::new(Mutex::new(Vec::new()));

    let samples_clone = samples.clone();
    appsink
        .connect("new-sample", true, move |args| {
            let appsink = args[0].get::<gst::Element>().unwrap();

            let sample = appsink
                .emit("pull-sample", &[])
                .unwrap()
                .unwrap()
                .get::<gst::Sample>()
                .unwrap();

            samples_clone.lock().unwrap().push(sample);

            Some(gst::FlowReturn::Ok.to_value())
        })
        .unwrap();

    pipeline
        .set_state(gst::State::Playing)
        .into_result()
        .unwrap();

    for _ in 0..3 {
        assert!(
            appsrc
                .emit("push-buffer", &[&gst::Buffer::new()])
                .unwrap()
                .unwrap()
                .get::<bool>()
                .unwrap()
        );
    }

    assert!(
        appsrc
            .emit("end-of-stream", &[])
            .unwrap()
            .unwrap()
            .get::<bool>()
            .unwrap()
    );

    let mut eos = false;
    let bus = pipeline.get_bus().unwrap();
    while let Some(msg) = bus.timed_pop(5 * gst::SECOND) {
        use gst::MessageView;
        match msg.view() {
            MessageView::Eos(..) => {
                eos = true;
                break;
            }
            MessageView::Error(..) => unreachable!(),
            _ => (),
        }
    }

    assert!(eos);
    let samples = samples.lock().unwrap();
    assert_eq!(samples.len(), 3);

    for sample in samples.iter() {
        assert!(sample.get_buffer().is_some());
        assert_eq!(Some(&caps), sample.get_caps().as_ref());
    }

    pipeline.set_state(gst::State::Null).into_result().unwrap();
}

#[test]
fn test_push_single_threaded() {
    test_push(-1);
}

#[test]
fn test_push_multi_threaded() {
    test_push(2);
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
//! This is a webui about to replace the TUI, to be nice, better accessable, and
//! new technology using websockets (since actix changed a lot actix_web, many
//! implementation should probably be reworked)
use super::super::config;
use super::PeerRepresentation;
use actix::prelude::StreamHandler;
use actix::{Actor, ActorContext, AsyncContext};
use actix_files as fs;
use actix_web::{
    http::StatusCode,
    web::{self, HttpResponse, Json},
    App, Error, HttpRequest, HttpServer,
};
use actix_web_actors::ws;
use get_if_addrs;
use hostname;
use std::{
    ffi::OsString,
    fmt, io,
    net::IpAddr,
    string::String,
    sync::{Arc, Mutex},
    time::{Duration, Instant},
    vec::Vec,
};

/// How often heartbeat pings are sent
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);

pub struct WebServerState {
    id: PeerRepresentation,
    nr_connections: Arc<Mutex<usize>>,
}

/// needs to be serializable for json
#[derive(Serialize)]
struct JSONResponse {
    cmd: WebCommand,
}

#[derive(Serialize)]
enum WebCommand {
    Started,
    #[allow(dead_code)]
    NewMdnsClient(String),
}

impl fmt::Display for JSONResponse {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.cmd)
    }
}
impl fmt::Display for WebCommand {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            WebCommand::Started => write!(f, "Started"),
            WebCommand::NewMdnsClient(param) => write!(f, "MDNS_IP: {}", param),
        }
    }
}

/// dynamic development files
//#[get("/app.js")]
//fn app_js() -> Result<fs::NamedFile> {
//Ok(fs::NamedFile::open("src/ctrl/webui/js/app.js")?)
//}

pub struct WebUI {
    #[allow(dead_code)]
    id: PeerRepresentation,
    #[allow(dead_code)]
    serve_others: bool,
}

impl WebUI {
    pub async fn run(id: PeerRepresentation, _serve: bool) -> io::Result<()> {
        let sys = actix::System::new("http-server");
        let connection_count = Arc::new(Mutex::new(0));

        let local_addresses = get_if_addrs::get_if_addrs().unwrap();

        let initial_state = Arc::new(Mutex::new(WebServerState {
            id: id.clone(),
            nr_connections: connection_count.clone(),
        }));

        // take all local addresses and start if necessary
        // one server with multiple binds
        let web_server = local_addresses
            .into_iter()
            .filter(|ipaddr| {
                let name = ipaddr.name.clone();
                // only use loopback addresses no 2nd ethernet cards
                if ipaddr.addr.is_loopback() {
                    info!(
                        "{} {:?} is a loopback device, good!",
                        ipaddr.addr.ip().to_string(),
                        name
                    );
                    true
                } else {
                    warn!("{} is not a loopback device!", ipaddr.addr.ip().to_string());
                    true
                }
            })
            .fold(
                Ok(HttpServer::new(move || {
                    App::new()
                        // each server has an initial state (e.g. 0 connections)
                        .data(initial_state.clone())
                        .service(web::resource("/app.js").to(WebUI::js_app))
                        .default_service(web::resource("").to(WebUI::single_page))
                        //.default_service(web::resource("").to(WebUI::dyn_devel_html)) // Todo: only for devel
                        //.service(web::resource("/app.js").to(WebUI::dyn_devel_js)) // todo: only for devel
                        .service(web::resource("/jquery.min.js").to(|| {
                            HttpResponse::Ok()
                                .content_type("text/html; charset=utf-8")
                                .body(*config::webui::jquery::JS_JQUERY)
                        }))
                        .service(web::resource("favicon.png").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/png")
                                .body(*config::webui::FAVICON)
                        }))
                        .service(web::resource("sheep.svg").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/svg+xml")
                                .body(*config::webui::PIC_SHEEP)
                        }))
                        .service(
                            web::resource("/css/{name}").route(web::get().to(WebUI::bootstrap_css)),
                        )
                        .service(
                            web::resource("/js/{name}").route(web::get().to(WebUI::bootstrap_js)),
                        )
                        .service(web::resource("/ws").route(web::get().to(WebUI::ws_index)))
                })),
                |web_server_binding_chain: Result<HttpServer<_, _, _, _>, io::Error>, ipaddr| {
                    web_server_binding_chain.and_then(|webserver| {
                        let bind_format = match ipaddr.addr.ip() {
                            IpAddr::V4(ipv4) => {
                                format!("{}:{:?}", ipv4.to_string(), config::net::PORT_WEBSOCKET)
                            }
                            IpAddr::V6(ipv6) => {
                                format!("{}:{:?}", ipv6.to_string(), config::net::PORT_WEBSOCKET)
                            }
                        };
                        let try_bind = webserver.bind(bind_format.clone()).map_err(|error| {
                            error!("On IP ({:?}): {:?}", bind_format, error);
                            error
                        })?;
                        Ok(try_bind)
                    })
                },
            );
        web_server?.run();
        sys.run()
    }

    #[allow(dead_code)]
    async fn dyn_devel_html(
        _state: web::Data<Arc<Mutex<WebServerState>>>,
        _req: HttpRequest,
        _path: web::Path<(String,)>,
    ) -> Result<fs::NamedFile, Error> {
        Ok(fs::NamedFile::open("src/ctrl/webui/html/single_page.html")?)
    }

    #[allow(dead_code)]
    async fn dyn_devel_js() -> Result<fs::NamedFile, Error> {
        Ok(fs::NamedFile::open("src/ctrl/webui/js/app.js")?)
    }

    async fn single_page(
        state: web::Data<Arc<Mutex<WebServerState>>>,
    ) -> Result<HttpResponse, Error> {
        // change state
        let mut data = state.lock().unwrap();
        let id = data.id;
        *(data.nr_connections.lock().unwrap()) += 1;

        let id_page = Self::replace_static_content(*config::webui::HTML_PAGE, &id);

        Ok(HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(id_page))
    }

    async fn bootstrap_css(path: web::Path<(String,)>) -> Result<HttpResponse, Error> {
        let css = &*path.0;
        let output = match css {
            "bootstrap.css" => Some(*config::webui::bootstrap::CSS),
            "bootstrap.css.map" => Some(*config::webui::bootstrap::CSS_MAP),
            "bootstrap.min.css" => Some(*config::webui::bootstrap::CSS_MIN),
            "bootstrap.min.css.map" => Some(*config::webui::bootstrap::CSS_MIN_MAP),
            "bootstrap-grid.css" => Some(*config::webui::bootstrap::CSS_GRID),
            "bootstrap-grid.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MAP),
            "bootstrap-grid.min.css" => Some(*config::webui::bootstrap::CSS_GRID_MIN),
            "bootstrap-grid.min.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MIN_MAP),
            "bootstrap-reboot.css" => Some(*config::webui::bootstrap::CSS_REBOOT),
            "bootstrap-reboot.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MAP),
            "bootstrap-reboot.min.css" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN),
            "bootstrap-reboot.min.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN_MAP),
            _ => {
                error!("CSS: not found {}", css);
                None
            }
        };
        if let Some(content) = output {
            Ok(HttpResponse::build(StatusCode::OK)
                .content_type("text/css; charset=utf-8")
                .body(content))
        } else {
            Ok(HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("text/css; charset=utf-8")
                .body(""))
        }
    }

    async fn bootstrap_js(path: web::Path<(String,)>) -> Result<HttpResponse, Error> {
        let js = &*path.0;
        let output = match js {
            "bootstrap.js" => Some(*config::webui::bootstrap::JS),
            "bootstrap.js.map" => Some(*config::webui::bootstrap::JS_MAP),
            "bootstrap.min.js" => Some(*config::webui::bootstrap::JS_MIN),
            "bootstrap.min.js.map" => Some(*config::webui::bootstrap::JS_MIN_MAP),
            "bootstrap.bundle.js" => Some(*config::webui::bootstrap::JS_BUNDLE),
            "bootstrap.bundle.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MAP),
            "bootstrap.bundle.min.js" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN),
            "bootstrap.bundle.min.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN_MAP),
            _ => {
                error!("JS: not found {}", js);
                None
            }
        };
        if let Some(content) = output {
            Ok(HttpResponse::build(StatusCode::OK)
                .content_type("application/javascript; charset=utf-8")
                .body(content))
        } else {
            Ok(HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("application/javascript; charset=utf-8")
                .body(""))
        }
    }

    async fn js_app(state: web::Data<Arc<Mutex<WebServerState>>>) -> Result<HttpResponse, Error> {
        let data = state.lock().unwrap();
        let id = data.id;

        let output = Self::replace_static_content(*config::webui::JS_APP, &id);
        Ok(HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(output))
    }

    async fn ws_index(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, Error> {
        ws::start(MyWebSocket::new(), &req, stream)
    }

    fn replace_static_content(html_in: &str, id: &PeerRepresentation) -> String {
        // short inline struct
        struct ReplaceStatic<'a> {
            r: &'a str,
            c: String,
        }

        let id_string = std::format!("{:x?}", id);
        let hostname = hostname::get()
            .unwrap_or(OsString::from("undefined"))
            .into_string()
            .unwrap_or(String::from("undefined"));

        let changers: [ReplaceStatic; 4] = [
            ReplaceStatic {
                r: config::net::HTML_REPLACE_STATIC_URL_SOURCE,
                c: config::net::HTML_URL_SOURCE.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_WEBSOCKET,
                c: config::net::WEBSOCKET_ADDR.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_UUID,
                c: id_string,
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_HOSTNAME,
                c: hostname,
            },
        ];
        let mut replace_this = html_in.to_string();

        for replacer in &changers {
            replace_this = str::replace(&replace_this, &replacer.r, &replacer.c);
        }
        replace_this
    }
}

/// websocket connection is long running connection, it easier
/// to handle with an actor
struct MyWebSocket {
    /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
    /// otherwise we drop connection.
    hb: Instant,
}

impl Actor for MyWebSocket {
    type Context = ws::WebsocketContext<Self>;

    /// Method is called on actor start. We start the heartbeat process here.
    fn started(&mut self, ctx: &mut Self::Context) {
        self.hb(ctx);
    }
    fn stopped(&mut self, _ctx: &mut Self::Context) {
        // On system stop this may or may not run
        warn!("shutting down whole actix-system");
        actix::System::current().stop();
    }
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
    /// Handler for `ws::Message`
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        // process websocket messages
        match msg {
            Ok(good_message) => {
                trace!(":: {:?}", good_message);
                match good_message {
                    ws::Message::Ping(msg) => {
                        self.hb = Instant::now();
                        ctx.pong(&msg);
                    }
                    ws::Message::Pong(_) => {
                        self.hb = Instant::now();
                    }
                    ws::Message::Text(text) => {
                        let m = text.trim();
                        // /command
                        if m.starts_with('/') {
                            let v: Vec<&str> = m.splitn(2, ' ').collect();
                            match v[0] {
                                "/start" => {
                                    ctx.text(
                                        Json(JSONResponse {
                                            cmd: WebCommand::Started,
                                        })
                                        .to_string(),
                                    );
                                }
                                _ => ctx.text(format!("!!! unknown command: {:?}", m)),
                            }
                        }
                    }
                    ws::Message::Binary(bin) => ctx.binary(bin),
                    ws::Message::Close(_) => {
                        ctx.stop();
                    }
                    ws::Message::Nop => (),
                    ws::Message::Continuation(_) => (), // todo: what's this?
                }
            }
            Err(e) => warn!("message was not good {}", e),
        }
    }
}

impl MyWebSocket {
    fn new() -> Self {
        Self { hb: Instant::now() }
    }

    /// helper method that sends ping to client every second.
    ///
    /// also this method checks heartbeats from client
    fn hb(&self, ctx: &mut <Self as Actor>::Context) {
        ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
            // check client heartbeats
            if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
                // heartbeat timed out
                error!("Websocket Client heartbeat failed, disconnecting!");

                // stop actor
                ctx.stop();

                // don't try to send a ping
                return;
            }

            ctx.ping(b"");
        });
    }
}
<commit_msg>[cleanup] simplify answer to responder in webui<commit_after>#![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))]
//! This is a webui about to replace the TUI, to be nice, better accessable, and
//! new technology using websockets (since actix changed a lot actix_web, many
//! implementation should probably be reworked)
use super::super::config;
use super::PeerRepresentation;
use actix::prelude::StreamHandler;
use actix::{Actor, ActorContext, AsyncContext};
use actix_files as fs;
use actix_web::{
    http::StatusCode,
    web::{self, HttpResponse, Json},
    App, Error, HttpRequest, HttpServer, Responder,
};
use actix_web_actors::ws;
use get_if_addrs;
use hostname;
use std::{
    ffi::OsString,
    fmt, io,
    net::IpAddr,
    string::String,
    sync::{Arc, Mutex},
    time::{Duration, Instant},
    vec::Vec,
};

/// How often heartbeat pings are sent
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// How long before lack of client response causes a timeout
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);

pub struct WebServerState {
    id: PeerRepresentation,
    nr_connections: Arc<Mutex<usize>>,
}

/// needs to be serializable for json
#[derive(Serialize)]
struct JSONResponse {
    cmd: WebCommand,
}

#[derive(Serialize)]
enum WebCommand {
    Started,
    #[allow(dead_code)]
    NewMdnsClient(String),
}

impl fmt::Display for JSONResponse {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.cmd)
    }
}
impl fmt::Display for WebCommand {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            WebCommand::Started => write!(f, "Started"),
            WebCommand::NewMdnsClient(param) => write!(f, "MDNS_IP: {}", param),
        }
    }
}

/// dynamic development files
//#[get("/app.js")]
//fn app_js() -> Result<fs::NamedFile> {
//Ok(fs::NamedFile::open("src/ctrl/webui/js/app.js")?)
//}

pub struct WebUI {
    #[allow(dead_code)]
    id: PeerRepresentation,
    #[allow(dead_code)]
    serve_others: bool,
}

impl WebUI {
    pub async fn run(id: PeerRepresentation, _serve: bool) -> io::Result<()> {
        let sys = actix::System::new("http-server");
        let connection_count = Arc::new(Mutex::new(0));

        let local_addresses = get_if_addrs::get_if_addrs().unwrap();

        let initial_state = Arc::new(Mutex::new(WebServerState {
            id: id.clone(),
            nr_connections: connection_count.clone(),
        }));

        // take all local addresses and start if necessary
        // one server with multiple binds
        let web_server = local_addresses
            .into_iter()
            .filter(|ipaddr| {
                let name = ipaddr.name.clone();
                // only use loopback addresses no 2nd ethernet cards
                if ipaddr.addr.is_loopback() {
                    info!(
                        "{} {:?} is a loopback device, good!",
                        ipaddr.addr.ip().to_string(),
                        name
                    );
                    true
                } else {
                    warn!("{} is not a loopback device!", ipaddr.addr.ip().to_string());
                    true
                }
            })
            .fold(
                Ok(HttpServer::new(move || {
                    App::new()
                        // each server has an initial state (e.g. 0 connections)
                        .data(initial_state.clone())
                        .service(web::resource("/app.js").to(WebUI::js_app))
                        .default_service(web::resource("").to(WebUI::single_page))
                        //.default_service(web::resource("").to(WebUI::dyn_devel_html)) // Todo: only for devel
                        //.service(web::resource("/app.js").to(WebUI::dyn_devel_js)) // todo: only for devel
                        .service(web::resource("/jquery.min.js").to(|| {
                            HttpResponse::Ok()
                                .content_type("text/html; charset=utf-8")
                                .body(*config::webui::jquery::JS_JQUERY)
                        }))
                        .service(web::resource("favicon.png").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/png")
                                .body(*config::webui::FAVICON)
                        }))
                        .service(web::resource("sheep.svg").to(|| {
                            HttpResponse::Ok()
                                .content_type("image/svg+xml")
                                .body(*config::webui::PIC_SHEEP)
                        }))
                        .service(
                            web::resource("/css/{name}").route(web::get().to(WebUI::bootstrap_css)),
                        )
                        .service(
                            web::resource("/js/{name}").route(web::get().to(WebUI::bootstrap_js)),
                        )
                        .service(web::resource("/ws").route(web::get().to(WebUI::websocket_answer)))
                })),
                |web_server_binding_chain: Result<HttpServer<_, _, _, _>, io::Error>, ipaddr| {
                    web_server_binding_chain.and_then(|webserver| {
                        let bind_format = match ipaddr.addr.ip() {
                            IpAddr::V4(ipv4) => {
                                format!("{}:{:?}", ipv4.to_string(), config::net::PORT_WEBSOCKET)
                            }
                            IpAddr::V6(ipv6) => {
                                format!("{}:{:?}", ipv6.to_string(), config::net::PORT_WEBSOCKET)
                            }
                        };
                        let try_bind = webserver.bind(bind_format.clone()).map_err(|error| {
                            error!("On IP ({:?}): {:?}", bind_format, error);
                            error
                        })?;
                        Ok(try_bind)
                    })
                },
            );
        web_server?.run();
        sys.run()
    }

    #[allow(dead_code)]
    async fn dyn_devel_html(
        _state: web::Data<Arc<Mutex<WebServerState>>>,
        _req: HttpRequest,
        _path: web::Path<(String,)>,
    ) -> Result<fs::NamedFile, Error> {
        Ok(fs::NamedFile::open("src/ctrl/webui/html/single_page.html")?)
    }

    #[allow(dead_code)]
    async fn dyn_devel_js() -> impl Responder {
        fs::NamedFile::open("src/ctrl/webui/js/app.js")
    }

    async fn single_page(state: web::Data<Arc<Mutex<WebServerState>>>) -> impl Responder {
        // change state
        let mut data = state.lock().unwrap();
        let id = data.id;
        *(data.nr_connections.lock().unwrap()) += 1;

        let id_page = Self::replace_static_content(*config::webui::HTML_PAGE, &id);

        HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(id_page)
    }

    async fn bootstrap_css(path: web::Path<(String,)>) -> impl Responder {
        let css = &*path.0;
        let output = match css {
            "bootstrap.css" => Some(*config::webui::bootstrap::CSS),
            "bootstrap.css.map" => Some(*config::webui::bootstrap::CSS_MAP),
            "bootstrap.min.css" => Some(*config::webui::bootstrap::CSS_MIN),
            "bootstrap.min.css.map" => Some(*config::webui::bootstrap::CSS_MIN_MAP),
            "bootstrap-grid.css" => Some(*config::webui::bootstrap::CSS_GRID),
            "bootstrap-grid.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MAP),
            "bootstrap-grid.min.css" => Some(*config::webui::bootstrap::CSS_GRID_MIN),
            "bootstrap-grid.min.css.map" => Some(*config::webui::bootstrap::CSS_GRID_MIN_MAP),
            "bootstrap-reboot.css" => Some(*config::webui::bootstrap::CSS_REBOOT),
            "bootstrap-reboot.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MAP),
            "bootstrap-reboot.min.css" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN),
            "bootstrap-reboot.min.css.map" => Some(*config::webui::bootstrap::CSS_REBOOT_MIN_MAP),
            _ => {
                error!("CSS: not found {}", css);
                None
            }
        };
        if let Some(content) = output {
            HttpResponse::build(StatusCode::OK)
                .content_type("text/css; charset=utf-8")
                .body(content)
        } else {
            HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("text/css; charset=utf-8")
                .body("")
        }
    }

    async fn bootstrap_js(path: web::Path<(String,)>) -> impl Responder {
        let js = &*path.0;
        let output = match js {
            "bootstrap.js" => Some(*config::webui::bootstrap::JS),
            "bootstrap.js.map" => Some(*config::webui::bootstrap::JS_MAP),
            "bootstrap.min.js" => Some(*config::webui::bootstrap::JS_MIN),
            "bootstrap.min.js.map" => Some(*config::webui::bootstrap::JS_MIN_MAP),
            "bootstrap.bundle.js" => Some(*config::webui::bootstrap::JS_BUNDLE),
            "bootstrap.bundle.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MAP),
            "bootstrap.bundle.min.js" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN),
            "bootstrap.bundle.min.js.map" => Some(*config::webui::bootstrap::JS_BUNDLE_MIN_MAP),
            _ => {
                error!("JS: not found {}", js);
                None
            }
        };
        if let Some(content) = output {
            HttpResponse::build(StatusCode::OK)
                .content_type("application/javascript; charset=utf-8")
                .body(content)
        } else {
            HttpResponse::build(StatusCode::NOT_FOUND)
                .content_type("application/javascript; charset=utf-8")
                .body("")
        }
    }

    async fn js_app(state: web::Data<Arc<Mutex<WebServerState>>>) -> impl Responder {
        let data = state.lock().unwrap();
        let id = data.id;

        let output = Self::replace_static_content(*config::webui::JS_APP, &id);
        HttpResponse::build(StatusCode::OK)
            .content_type("text/html; charset=utf-8")
            .body(output)
    }

    async fn websocket_answer(
        req: HttpRequest,
        stream: web::Payload,
    ) -> Result<HttpResponse, Error> {
        ws::start(MyWebSocket::new(), &req, stream)
    }

    fn replace_static_content(html_in: &str, id: &PeerRepresentation) -> String {
        // short inline struct
        struct ReplaceStatic<'a> {
            r: &'a str,
            c: String,
        }

        let id_string = std::format!("{:x?}", id);
        let hostname = hostname::get()
            .unwrap_or(OsString::from("undefined"))
            .into_string()
            .unwrap_or(String::from("undefined"));

        let changers: [ReplaceStatic; 4] = [
            ReplaceStatic {
                r: config::net::HTML_REPLACE_STATIC_URL_SOURCE,
                c: config::net::HTML_URL_SOURCE.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_WEBSOCKET,
                c: config::net::WEBSOCKET_ADDR.to_string(),
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_UUID,
                c: id_string,
            },
            ReplaceStatic {
                r: config::webui::HTML_REPLACE_HOSTNAME,
                c: hostname,
            },
        ];
        let mut replace_this = html_in.to_string();

        for replacer in &changers {
            replace_this = str::replace(&replace_this, &replacer.r, &replacer.c);
        }
        replace_this
    }
}

/// websocket connection is long running connection, it easier
/// to handle with an actor
struct MyWebSocket {
    /// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
    /// otherwise we drop connection.
    hb: Instant,
}

impl Actor for MyWebSocket {
    type Context = ws::WebsocketContext<Self>;

    /// Method is called on actor start. We start the heartbeat process here.
    fn started(&mut self, ctx: &mut Self::Context) {
        self.hb(ctx);
    }
    fn stopped(&mut self, _ctx: &mut Self::Context) {
        // On system stop this may or may not run
        warn!("shutting down whole actix-system");
        actix::System::current().stop();
    }
}

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
    /// Handler for `ws::Message`
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
        // process websocket messages
        match msg {
            Ok(good_message) => {
                trace!(":: {:?}", good_message);
                match good_message {
                    ws::Message::Ping(msg) => {
                        self.hb = Instant::now();
                        ctx.pong(&msg);
                    }
                    ws::Message::Pong(_) => {
                        self.hb = Instant::now();
                    }
                    ws::Message::Text(text) => {
                        let m = text.trim();
                        // /command
                        if m.starts_with('/') {
                            let v: Vec<&str> = m.splitn(2, ' ').collect();
                            match v[0] {
                                "/start" => {
                                    ctx.text(
                                        Json(JSONResponse {
                                            cmd: WebCommand::Started,
                                        })
                                        .to_string(),
                                    );
                                }
                                _ => ctx.text(format!("!!! unknown command: {:?}", m)),
                            }
                        }
                    }
                    ws::Message::Binary(bin) => ctx.binary(bin),
                    ws::Message::Close(_) => {
                        ctx.stop();
                    }
                    ws::Message::Nop => (),
                    ws::Message::Continuation(_) => (), // todo: what's this?
                }
            }
            Err(e) => warn!("message was not good {}", e),
        }
    }
}

impl MyWebSocket {
    fn new() -> Self {
        Self { hb: Instant::now() }
    }

    /// helper method that sends ping to client every second.
    ///
    /// also this method checks heartbeats from client
    fn hb(&self, ctx: &mut <Self as Actor>::Context) {
        ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
            // check client heartbeats
            if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
                // heartbeat timed out
                error!("Websocket Client heartbeat failed, disconnecting!");

                // stop actor
                ctx.stop();

                // don't try to send a ping
                return;
            }

            ctx.ping(b"");
        });
    }
}
<|endoftext|>


<commit_before><commit_msg>Avoid Vec indexing in tests<commit_after><|endoftext|>


<commit_before>#[cfg(test)]
mod tests {
    use super::super::common::*;

    use hyper::client::Response;
    use hyper::status::StatusCode;
    use rustc_serialize::json;
    use std::collections::HashMap;

    #[derive(RustcDecodable)]
    struct DeletedResult {
        deleted_translations: usize,
    }

    #[derive(RustcEncodable)]
    struct NewTranslation {
        key: Option<&'static str>,
        locale: Option<&'static str>,
        content: Option<&'static str>,
    }

    #[derive(Clone, RustcDecodable)]
    struct TranslationForLocale {
        id: i32,
        locale: String,
        content: Option<String>,
        created_at: String,
        user_id: Option<i32>,
        validator_id: Option<i32>,
        validated_at: Option<String>,
    }

    #[derive(RustcDecodable)]
    pub struct Translation {
        pub id: i32,
        pub key: String,
        pub locale: String,
        pub content: Option<String>,
        pub created_at: String,
        pub deleted_at: Option<String>,
        pub user_id: Option<i32>,
        pub validator_id: Option<i32>,
        pub validated_at: Option<String>,
    }

    #[test]
    fn test_create_without_token() {
        let new_translation = NewTranslation {
            key: Some("test.i_love_train"),
            locale: Some("en"),
            content: Some("I love train"),
        };

        let (response, content) = post_translation(new_translation, None);

        assert_eq!(StatusCode::Unauthorized, response.status);
        assert_eq!("", content)
    }

    #[test]
    fn test_create_without_key() {
        let new_translation = NewTranslation {
            key: None,
            locale: Some("en"),
            content: Some("I love train"),
        };

        let (response, _) = post_translation(new_translation, valid_token());

        assert_eq!(StatusCode::BadRequest, response.status);
    }

    #[test]
    fn test_create_without_locale() {
        let new_translation = NewTranslation {
            key: Some("test.i_love_train"),
            locale: None,
            content: Some("I love train"),
        };

        let (response, _) = post_translation(new_translation, valid_token());

        assert_eq!(StatusCode::BadRequest, response.status);
    }

    #[test]
    fn test_create_without_content() {
        let new_translation = NewTranslation {
            key: Some("test.i_love_train"),
            locale: Some("en"),
            content: None,
        };

        let (response, _) = post_translation(new_translation, valid_token());

        assert_eq!(StatusCode::BadRequest, response.status);
    }

    #[test]
    fn test_insert_and_delete() {
        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_1 = parse_translations_by_locales(&content);

        assert_eq!(1, translations_1.len());
        assert_eq!(6, translations_1.get(&"ui.add".to_string()).unwrap().len());

        // We create new translations on key `test.hello`
        let (response, content) = post_translation(
            NewTranslation {
                key: Some("test.hello"),
                locale: Some("fr"),
                content: Some("Bonjour"),
            },
            valid_token()
        );

        assert_eq!(StatusCode::Created, response.status);

        let translation = parse_translation(&content);

        assert_eq!("test.hello", translation.key);
        assert_eq!("fr", translation.locale);
        assert_eq!(Some("Bonjour".to_string()), translation.content);
        assert!(has_happened_now(&translation.created_at));
        assert_eq!(None, translation.deleted_at);
        assert_eq!(Some(1), translation.user_id);

        let (response, content) = post_translation(
            NewTranslation {
                key: Some("test.hello"),
                locale: Some("en"),
                content: Some("Hello"),
            },
            valid_token()
        );

        assert_eq!(StatusCode::Created, response.status);

        let translation = parse_translation(&content);

        assert_eq!("test.hello", translation.key);
        assert_eq!("en", translation.locale);
        assert_eq!(Some("Hello".to_string()), translation.content);
        assert!(has_happened_now(&translation.created_at));
        assert_eq!(None, translation.deleted_at);
        assert_eq!(Some(1), translation.user_id);

        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_2 = parse_translations_by_locales(&content);

        assert_eq!(2, translations_2.len());
        assert_eq!(6, translations_2.get(&"ui.add".to_string()).unwrap().len());
        assert_eq!(2, translations_2.get(&"test.hello".to_string()).unwrap().len());

        // We delete all translations with key equals to `test.hello`
        let (response, content) = delete("/api/v1/translations/test.hello", valid_token());
        let result: DeletedResult = json::decode(&content).unwrap();

        assert_eq!(StatusCode::Ok, response.status);
        assert_eq!(2, result.deleted_translations);

        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_3 = parse_translations_by_locales(&content);

        assert_eq!(1, translations_3.len());
        assert_eq!(6, translations_3.get(&"ui.add".to_string()).unwrap().len());

        // We fetch all translations with key `test.hello`
        let (response, content) = get("/api/v1/translations/test.hello", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_4 = parse_translations(&content);

        assert_eq!(2, translations_4.len());
        assert_eq!("test.hello", translations_4[0].key);
        assert_eq!("test.hello", translations_4[1].key);
        assert!(translations_4[0].deleted_at.is_some());
        assert!(translations_4[1].deleted_at.is_some());
    }

    #[test]
    fn test_validate_without_token() {
        let (response, content) = post("/api/v1/translations/1/validate", None, None);

        assert_eq!(StatusCode::Unauthorized, response.status);
        assert_eq!("", content);
    }

    #[test]
    fn test_validate_when_not_found() {
        let (response, content) = post("/api/v1/translations/999999/validate", None, valid_token());

        assert_eq!(StatusCode::NotFound, response.status);
        assert_eq!("", content);
    }

    #[test]
    fn test_validate_with_success() {
        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_1 = parse_translations_by_locales(&content);

        for translation in translations_1.get(&"ui.add".to_string()).unwrap() {
            // None are validated
            assert_eq!(None, translation.validator_id);
        }

        // We validate the first translation
        let (response, content) = post("/api/v1/translations/1/validate", None, valid_token());

        assert_eq!(StatusCode::NoContent, response.status);
        assert_eq!("", content);

        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_2 = parse_translations_by_locales(&content);
        let ui_add_translations = translations_2.get(&"ui.add".to_string()).unwrap();

        // The first translation is validated
        let validated_translation = ui_add_translations[0].clone();
        assert_eq!(1, validated_translation.id);
        assert_eq!(Some(1), validated_translation.validator_id);
        assert!(has_happened_now(validated_translation.validated_at.unwrap().as_str()));

        // The other translations are not validated
        assert_eq!(None, ui_add_translations[1].validator_id);
        assert_eq!(None, ui_add_translations[1].validated_at);
        assert_eq!(None, ui_add_translations[2].validator_id);
        assert_eq!(None, ui_add_translations[2].validated_at);
        assert_eq!(None, ui_add_translations[3].validator_id);
        assert_eq!(None, ui_add_translations[3].validated_at);
        assert_eq!(None, ui_add_translations[4].validator_id);
        assert_eq!(None, ui_add_translations[4].validated_at);
        assert_eq!(None, ui_add_translations[5].validator_id);
    }

    #[test]
    fn test_delete_without_token() {
        let (response, content) = delete("/api/v1/translations/hey.you", None);

        assert_eq!(StatusCode::Unauthorized, response.status);
        assert_eq!("", content);
    }

    #[test]
    fn test_delete_with_a_key_without_translations() {
        let (response, content) = delete("/api/v1/translations/not.found.key", valid_token());
        let result: DeletedResult = json::decode(&content).unwrap();

        assert_eq!(StatusCode::NotFound, response.status);
        assert_eq!(0, result.deleted_translations);
    }

    fn parse_translations_by_locales(content: &str) -> HashMap<String, Vec<TranslationForLocale>> {
        json::decode(content).unwrap()
    }

    fn parse_translation(content: &str) -> Translation {
        json::decode(content).unwrap()
    }

    fn parse_translations(content: &str) -> Vec<Translation> {
        json::decode(content).unwrap()
    }

    fn post_translation(translation: NewTranslation, token: Option<String>) -> (Response, String) {
        let body = json::encode(&translation).unwrap();

        post("/api/v1/translations", Some(body), token)
    }
}
<commit_msg>Use `[]` to get a HashMap value<commit_after>#[cfg(test)]
mod tests {
    use super::super::common::*;

    use hyper::client::Response;
    use hyper::status::StatusCode;
    use rustc_serialize::json;
    use std::collections::HashMap;

    #[derive(RustcDecodable)]
    struct DeletedResult {
        deleted_translations: usize,
    }

    #[derive(RustcEncodable)]
    struct NewTranslation {
        key: Option<&'static str>,
        locale: Option<&'static str>,
        content: Option<&'static str>,
    }

    #[derive(Clone, RustcDecodable)]
    struct TranslationForLocale {
        id: i32,
        locale: String,
        content: Option<String>,
        created_at: String,
        user_id: Option<i32>,
        validator_id: Option<i32>,
        validated_at: Option<String>,
    }

    #[derive(RustcDecodable)]
    pub struct Translation {
        pub id: i32,
        pub key: String,
        pub locale: String,
        pub content: Option<String>,
        pub created_at: String,
        pub deleted_at: Option<String>,
        pub user_id: Option<i32>,
        pub validator_id: Option<i32>,
        pub validated_at: Option<String>,
    }

    #[test]
    fn test_create_without_token() {
        let new_translation = NewTranslation {
            key: Some("test.i_love_train"),
            locale: Some("en"),
            content: Some("I love train"),
        };

        let (response, content) = post_translation(new_translation, None);

        assert_eq!(StatusCode::Unauthorized, response.status);
        assert_eq!("", content)
    }

    #[test]
    fn test_create_without_key() {
        let new_translation = NewTranslation {
            key: None,
            locale: Some("en"),
            content: Some("I love train"),
        };

        let (response, _) = post_translation(new_translation, valid_token());

        assert_eq!(StatusCode::BadRequest, response.status);
    }

    #[test]
    fn test_create_without_locale() {
        let new_translation = NewTranslation {
            key: Some("test.i_love_train"),
            locale: None,
            content: Some("I love train"),
        };

        let (response, _) = post_translation(new_translation, valid_token());

        assert_eq!(StatusCode::BadRequest, response.status);
    }

    #[test]
    fn test_create_without_content() {
        let new_translation = NewTranslation {
            key: Some("test.i_love_train"),
            locale: Some("en"),
            content: None,
        };

        let (response, _) = post_translation(new_translation, valid_token());

        assert_eq!(StatusCode::BadRequest, response.status);
    }

    #[test]
    fn test_insert_and_delete() {
        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_1 = parse_translations_by_locales(&content);

        assert_eq!(1, translations_1.len());
        assert_eq!(6, translations_1.get(&"ui.add".to_string()).unwrap().len());

        // We create new translations on key `test.hello`
        let (response, content) = post_translation(
            NewTranslation {
                key: Some("test.hello"),
                locale: Some("fr"),
                content: Some("Bonjour"),
            },
            valid_token()
        );

        assert_eq!(StatusCode::Created, response.status);

        let translation = parse_translation(&content);

        assert_eq!("test.hello", translation.key);
        assert_eq!("fr", translation.locale);
        assert_eq!(Some("Bonjour".to_string()), translation.content);
        assert!(has_happened_now(&translation.created_at));
        assert_eq!(None, translation.deleted_at);
        assert_eq!(Some(1), translation.user_id);

        let (response, content) = post_translation(
            NewTranslation {
                key: Some("test.hello"),
                locale: Some("en"),
                content: Some("Hello"),
            },
            valid_token()
        );

        assert_eq!(StatusCode::Created, response.status);

        let translation = parse_translation(&content);

        assert_eq!("test.hello", translation.key);
        assert_eq!("en", translation.locale);
        assert_eq!(Some("Hello".to_string()), translation.content);
        assert!(has_happened_now(&translation.created_at));
        assert_eq!(None, translation.deleted_at);
        assert_eq!(Some(1), translation.user_id);

        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_2 = parse_translations_by_locales(&content);

        assert_eq!(2, translations_2.len());
        assert_eq!(6, translations_2.get(&"ui.add".to_string()).unwrap().len());
        assert_eq!(2, translations_2.get(&"test.hello".to_string()).unwrap().len());

        // We delete all translations with key equals to `test.hello`
        let (response, content) = delete("/api/v1/translations/test.hello", valid_token());
        let result: DeletedResult = json::decode(&content).unwrap();

        assert_eq!(StatusCode::Ok, response.status);
        assert_eq!(2, result.deleted_translations);

        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_3 = parse_translations_by_locales(&content);

        assert_eq!(1, translations_3.len());
        assert_eq!(6, translations_3.get(&"ui.add".to_string()).unwrap().len());

        // We fetch all translations with key `test.hello`
        let (response, content) = get("/api/v1/translations/test.hello", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_4 = parse_translations(&content);

        assert_eq!(2, translations_4.len());
        assert_eq!("test.hello", translations_4[0].key);
        assert_eq!("test.hello", translations_4[1].key);
        assert!(translations_4[0].deleted_at.is_some());
        assert!(translations_4[1].deleted_at.is_some());
    }

    #[test]
    fn test_validate_without_token() {
        let (response, content) = post("/api/v1/translations/1/validate", None, None);

        assert_eq!(StatusCode::Unauthorized, response.status);
        assert_eq!("", content);
    }

    #[test]
    fn test_validate_when_not_found() {
        let (response, content) = post("/api/v1/translations/999999/validate", None, valid_token());

        assert_eq!(StatusCode::NotFound, response.status);
        assert_eq!("", content);
    }

    #[test]
    fn test_validate_with_success() {
        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_1 = parse_translations_by_locales(&content);

        for translation in &translations_1[&"ui.add".to_string()] {
            // None are validated
            assert_eq!(None, translation.validator_id);
        }

        // We validate the first translation
        let (response, content) = post("/api/v1/translations/1/validate", None, valid_token());

        assert_eq!(StatusCode::NoContent, response.status);
        assert_eq!("", content);

        // We fetch all translations
        let (response, content) = get("/api/v1/translations", valid_token());

        assert_eq!(StatusCode::Ok, response.status);

        let translations_2 = parse_translations_by_locales(&content);
        let ui_add_translations = &translations_2[&"ui.add".to_string()];

        // The first translation is validated
        let validated_translation = ui_add_translations[0].clone();
        assert_eq!(1, validated_translation.id);
        assert_eq!(Some(1), validated_translation.validator_id);
        assert!(has_happened_now(validated_translation.validated_at.unwrap().as_str()));

        // The other translations are not validated
        assert_eq!(None, ui_add_translations[1].validator_id);
        assert_eq!(None, ui_add_translations[1].validated_at);
        assert_eq!(None, ui_add_translations[2].validator_id);
        assert_eq!(None, ui_add_translations[2].validated_at);
        assert_eq!(None, ui_add_translations[3].validator_id);
        assert_eq!(None, ui_add_translations[3].validated_at);
        assert_eq!(None, ui_add_translations[4].validator_id);
        assert_eq!(None, ui_add_translations[4].validated_at);
        assert_eq!(None, ui_add_translations[5].validator_id);
    }

    #[test]
    fn test_delete_without_token() {
        let (response, content) = delete("/api/v1/translations/hey.you", None);

        assert_eq!(StatusCode::Unauthorized, response.status);
        assert_eq!("", content);
    }

    #[test]
    fn test_delete_with_a_key_without_translations() {
        let (response, content) = delete("/api/v1/translations/not.found.key", valid_token());
        let result: DeletedResult = json::decode(&content).unwrap();

        assert_eq!(StatusCode::NotFound, response.status);
        assert_eq!(0, result.deleted_translations);
    }

    fn parse_translations_by_locales(content: &str) -> HashMap<String, Vec<TranslationForLocale>> {
        json::decode(content).unwrap()
    }

    fn parse_translation(content: &str) -> Translation {
        json::decode(content).unwrap()
    }

    fn parse_translations(content: &str) -> Vec<Translation> {
        json::decode(content).unwrap()
    }

    fn post_translation(translation: NewTranslation, token: Option<String>) -> (Response, String) {
        let body = json::encode(&translation).unwrap();

        post("/api/v1/translations", Some(body), token)
    }
}
<|endoftext|>


<commit_before>// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
// Boston, MA 02110-1335, USA.

//! A collection of GStreamer plugins which leverage the `threadshare` [`runtime`].
//!
//! [`runtime`]: runtime/index.html

// Needed for `select!` in `Socket::next`
// see https://docs.rs/futures/0.3.1/futures/macro.select.html
#![recursion_limit = "1024"]
#![crate_type = "cdylib"]

pub use tokio;

#[macro_use]
pub mod runtime;

pub mod socket;
mod tcpclientsrc;
mod udpsink;
mod udpsrc;

mod appsrc;
pub mod dataqueue;
mod jitterbuffer;
mod proxy;
mod queue;

use glib::translate::*;
use glib_sys as glib_ffi;

use gst;
use gst::gst_plugin_define;
use gst::prelude::*;
use gstreamer_sys as gst_ffi;

fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
    udpsrc::register(plugin)?;
    udpsink::register(plugin)?;
    tcpclientsrc::register(plugin)?;
    queue::register(plugin)?;
    proxy::register(plugin)?;
    appsrc::register(plugin)?;
    jitterbuffer::jitterbuffer::register(plugin)?;

    Ok(())
}

gst_plugin_define!(
    threadshare,
    env!("CARGO_PKG_DESCRIPTION"),
    plugin_init,
    concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
    "LGPL",
    env!("CARGO_PKG_NAME"),
    env!("CARGO_PKG_NAME"),
    env!("CARGO_PKG_REPOSITORY"),
    env!("BUILD_REL_DATE")
);

pub fn set_element_flags<T: glib::IsA<gst::Object> + glib::IsA<gst::Element>>(
    element: &T,
    flags: gst::ElementFlags,
) {
    unsafe {
        let ptr: *mut gst_ffi::GstObject = element.as_ptr() as *mut _;
        let _guard = MutexGuard::lock(&(*ptr).lock);
        (*ptr).flags |= flags.to_glib();
    }
}

struct MutexGuard<'a>(&'a glib_ffi::GMutex);

impl<'a> MutexGuard<'a> {
    pub fn lock(mutex: &'a glib_ffi::GMutex) -> Self {
        unsafe {
            glib_ffi::g_mutex_lock(mut_override(mutex));
        }
        MutexGuard(mutex)
    }
}

impl<'a> Drop for MutexGuard<'a> {
    fn drop(&mut self) {
        unsafe {
            glib_ffi::g_mutex_unlock(mut_override(self.0));
        }
    }
}

pub fn get_current_running_time(element: &gst::Element) -> gst::ClockTime {
    if let Some(clock) = element.get_clock() {
        if clock.get_time() > element.get_base_time() {
            clock.get_time() - element.get_base_time()
        } else {
            0.into()
        }
    } else {
        gst::CLOCK_TIME_NONE
    }
}
<commit_msg>threadshare: Disable all elements for now<commit_after>// Copyright (C) 2018 Sebastian Dröge <sebastian@centricular.com>
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the
// Free Software Foundation, Inc., 51 Franklin Street, Suite 500,
// Boston, MA 02110-1335, USA.

//! A collection of GStreamer plugins which leverage the `threadshare` [`runtime`].
//!
//! [`runtime`]: runtime/index.html

// Needed for `select!` in `Socket::next`
// see https://docs.rs/futures/0.3.1/futures/macro.select.html
#![recursion_limit = "1024"]
#![crate_type = "cdylib"]

pub use tokio;

#[macro_use]
pub mod runtime;

//pub mod socket;
//mod tcpclientsrc;
//mod udpsink;
//mod udpsrc;
//
//mod appsrc;
//pub mod dataqueue;
//mod jitterbuffer;
//mod proxy;
//mod queue;

use glib::translate::*;
use glib_sys as glib_ffi;

use gst;
use gst::gst_plugin_define;
use gst::prelude::*;
use gstreamer_sys as gst_ffi;

fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
    //udpsrc::register(plugin)?;
    //udpsink::register(plugin)?;
    //tcpclientsrc::register(plugin)?;
    //queue::register(plugin)?;
    //proxy::register(plugin)?;
    //appsrc::register(plugin)?;
    //jitterbuffer::jitterbuffer::register(plugin)?;

    Ok(())
}

gst_plugin_define!(
    threadshare,
    env!("CARGO_PKG_DESCRIPTION"),
    plugin_init,
    concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
    "LGPL",
    env!("CARGO_PKG_NAME"),
    env!("CARGO_PKG_NAME"),
    env!("CARGO_PKG_REPOSITORY"),
    env!("BUILD_REL_DATE")
);

pub fn set_element_flags<T: glib::IsA<gst::Object> + glib::IsA<gst::Element>>(
    element: &T,
    flags: gst::ElementFlags,
) {
    unsafe {
        let ptr: *mut gst_ffi::GstObject = element.as_ptr() as *mut _;
        let _guard = MutexGuard::lock(&(*ptr).lock);
        (*ptr).flags |= flags.to_glib();
    }
}

struct MutexGuard<'a>(&'a glib_ffi::GMutex);

impl<'a> MutexGuard<'a> {
    pub fn lock(mutex: &'a glib_ffi::GMutex) -> Self {
        unsafe {
            glib_ffi::g_mutex_lock(mut_override(mutex));
        }
        MutexGuard(mutex)
    }
}

impl<'a> Drop for MutexGuard<'a> {
    fn drop(&mut self) {
        unsafe {
            glib_ffi::g_mutex_unlock(mut_override(self.0));
        }
    }
}

pub fn get_current_running_time(element: &gst::Element) -> gst::ClockTime {
    if let Some(clock) = element.get_clock() {
        if clock.get_time() > element.get_base_time() {
            clock.get_time() - element.get_base_time()
        } else {
            0.into()
        }
    } else {
        gst::CLOCK_TIME_NONE
    }
}
<|endoftext|>