<commit_before>use configuration::Configuration;
use std::slice::SliceExt;
use std::cmp::min;
use score::Score;
use sorted_result_set::SortedResultSet;
use std::ascii::AsciiExt;

#[derive(Debug)]
pub struct Search<'s> {
    pub config: &'s Configuration,
    pub current: usize,
    pub query: String,
    pub result: Vec<String>,
    choice_stack: Vec<Vec<&'s String>>,
    done: bool,
}

impl<'s> Search<'s> {
    pub fn blank(config: &'s Configuration) -> Search<'s> {
        let query = config.initial_search.clone();

        let mut choice_stack: Vec<Vec<&'s String>> = Vec::new();
        let mut result = Vec::new();
        let mut first_stack_frame = Vec::new();

        for choice in config.choices.iter().take(config.visible_limit) {
            result.push(choice.clone());
        }

        for choice in config.choices.iter() {
            first_stack_frame.push(choice);
        }

        choice_stack.push(first_stack_frame);

        Search::new(config, query, choice_stack, result, 0, false)
    }

    pub fn is_done(&self) -> bool {
        self.done
    }

    pub fn done(self) -> Search<'s> {
        Search::new(self.config, self.query, self.choice_stack, self.result, self.current, true)
    }

    pub fn selection(&self) -> Option<String> {
        if self.result.len() > 0 {
            Some(self.result[self.current].to_string())
        } else {
            None
        }
    }

    fn new(config: &'s Configuration, query: String, choice_stack: Vec<Vec<&'s String>>, result: Vec<String>, index: usize, done: bool) -> Search<'s> {
        Search { config: config,
                 current: index,
                 query: query,
                 result: result,
                 choice_stack: choice_stack,
                 done: done}
    }

    fn new_for_index(self, index: usize) -> Search<'s> {
        Search::new(self.config, self.query, self.choice_stack, self.result, index, self.done)
    }

    pub fn iter_matches<F: FnMut(&'s String, f32)>(query: &str, choices: &Vec<&'s String>, mut f: F) {
        let lower_query = query.to_ascii_lowercase();

        for choice in choices.iter() {
            let lower_choice = choice.to_ascii_lowercase();

            match Score::score(&lower_choice, &lower_query) {
                0.0     => continue,
                quality => f(choice, quality),
            };
        }
    }

    pub fn down(self) -> Search<'s> {
        let next_index = self.next_index();
        self.new_for_index(next_index)
    }

    pub fn up(self) -> Search<'s> {
        let next_index = self.prev_index();
        self.new_for_index(next_index)
    }

    pub fn append_to_search(mut self, input: &str) -> Search<'s> {
        let mut new_query = self.query.clone();
        new_query.push_str(input.as_slice());

        let mut results = SortedResultSet::new(self.config.visible_limit);
        let mut filtered_choices: Vec<&String> = Vec::new();
        Search::iter_matches(new_query.as_slice(), &self.choice_stack.last().unwrap(),
                        |match_str, quality| {
                                               results.push(match_str, quality);
                                               filtered_choices.push(match_str)
                                             });

        self.choice_stack.push(filtered_choices);
        Search::new(self.config, new_query, self.choice_stack, results.as_sorted_vec(), 0, self.done)
    }

    pub fn backspace(mut self) -> Search<'s> {
        let mut new_query = self.query.clone();
        new_query.pop();
        &self.choice_stack.pop();

        let mut results = SortedResultSet::new(self.config.visible_limit);
        Search::iter_matches(new_query.as_slice(), &self.choice_stack.last().unwrap(),
                        |match_str, quality| {
                                               results.push(match_str, quality);
                                             });
        Search::new(self.config, new_query, self.choice_stack, results.as_sorted_vec(), 0, self.done)
    }

    fn next_index(&self) -> usize {
        let next_index = self.current + 1;

        if next_index >= self.actual_limit() {
            0
        } else {
            next_index
        }
    }

    fn prev_index(&self) -> usize {
        if self.current == 0 {
            self.actual_limit() - 1
        } else {
            self.current - 1
        }
    }

    fn actual_limit(&self) -> usize {
        min(self.config.visible_limit, self.num_matches())
    }

    pub fn num_matches(&self) -> usize {
        self.choice_stack.last().unwrap().len()
    }
}

#[cfg(test)]
mod tests {
    use configuration::Configuration;
    use super::*;
    use sorted_result_set::SortedResultSet;

    fn one_two_three() -> Vec<String> {
        vec!["one".to_string(),
             "two".to_string(),
             "three".to_string()]
    }

    fn input_times(n: usize) ->Vec<String> {
        let mut result: Vec<String> = Vec::new();
        for thing in one_two_three().iter().cycle().take(n) {
            result.push(thing.clone());
        }
        result
    }

    #[test]
    fn it_selects_the_first_choice_by_default() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn it_selets_the_second_when_down_is_called() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.down().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_loop_around_when_reaching_end_of_list() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.down().down().down().down().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_loop_around_when_reaching_top_of_list() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.up().up().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_loop_around_when_reaching_visible_limit() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, Some(2));
        let search = Search::blank(&config);

        assert_eq!(search.down().down().down().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_moves_down_the_filtered_search_results() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.append_to_search("t").down().selection(), Some("three".to_string()));
    }

    #[test]
    fn it_moves_down_the_filtered_search_results_twice() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.append_to_search("t").append_to_search("w").selection(), Some("two".to_string()));
    }

    #[test]
    fn it_handles_not_matching_anything() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.append_to_search("x").selection(), None);
    }

    #[test]
    fn up_match_nothing_after_filtering_all_out() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("x");

        assert_eq!(search.up().selection(), None);
    }

    #[test]
    fn down_match_nothing_after_filtering_all_out() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("x");

        assert_eq!(search.down().selection(), None);
    }

    #[test]
    fn backspaces_over_characters() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("e");

        assert_eq!(search.query, "e");
        assert_eq!(search.backspace().query, "");
    }

    #[test]
    fn resets_the_index_when_removing_char_from_search() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("e");

        assert_eq!(search.down().backspace().current, 0);
    }

    #[test]
    fn resets_the_index_when_adding_char_to_seach() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).down();

        assert_eq!(search.append_to_search("o").current, 0);
    }

    #[test]
    fn previous_results_appear_after_backspace() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("t");

        assert_eq!(search.backspace().num_matches(), 3);
    }

    #[test]
    fn initial_search_is_not_done() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert!(!search.is_done());
    }

    #[test]
    fn done_search_is_done() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).done();

        assert!(search.is_done());
    }

    #[test]
    fn done_search_has_selection() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).done();

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn loop_around_when_reaching_bottom_of_choices() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("n").down();

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn loop_around_when_reaching_top_of_choices() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("n").up();

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn search_is_case_insensitive() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("T");

        assert_eq!(search.num_matches(), 2);
    }

    #[test]
    fn uses_configs_visible_limit_as_result_size() {
        let input = input_times(30);

        let mut bar = Vec::new();
        for f in input.iter() {
            bar.push((*f).clone());
        }

        let config = Configuration::from_inputs(bar, None, Some(20));
        let search = Search::blank(&config).append_to_search("T");

        assert_eq!(search.num_matches(), 20);
    }

}
<commit_msg>Fixes glitch when backspacing over initial search<commit_after>use configuration::Configuration;
use std::slice::SliceExt;
use std::cmp::min;
use score::Score;
use sorted_result_set::SortedResultSet;
use std::ascii::AsciiExt;

#[derive(Debug)]
pub struct Search<'s> {
    pub config: &'s Configuration,
    pub current: usize,
    pub query: String,
    pub result: Vec<String>,
    choice_stack: Vec<Vec<&'s String>>,
    done: bool,
}

impl<'s> Search<'s> {
    pub fn blank(config: &'s Configuration) -> Search<'s> {
        let query = config.initial_search.clone();

        let mut choice_stack: Vec<Vec<&'s String>> = Vec::new();
        let mut result = Vec::new();
        let mut first_stack_frame = Vec::new();

        for choice in config.choices.iter().take(config.visible_limit) {
            result.push(choice.clone());
        }

        for choice in config.choices.iter() {
            first_stack_frame.push(choice);
        }

        choice_stack.push(first_stack_frame);

        Search::new(config, query, choice_stack, result, 0, false)
    }

    pub fn is_done(&self) -> bool {
        self.done
    }

    pub fn done(self) -> Search<'s> {
        Search::new(self.config, self.query, self.choice_stack, self.result, self.current, true)
    }

    pub fn selection(&self) -> Option<String> {
        if self.result.len() > 0 {
            Some(self.result[self.current].to_string())
        } else {
            None
        }
    }

    fn new(config: &'s Configuration, query: String, choice_stack: Vec<Vec<&'s String>>, result: Vec<String>, index: usize, done: bool) -> Search<'s> {
        Search { config: config,
                 current: index,
                 query: query,
                 result: result,
                 choice_stack: choice_stack,
                 done: done}
    }

    fn new_for_index(self, index: usize) -> Search<'s> {
        Search::new(self.config, self.query, self.choice_stack, self.result, index, self.done)
    }

    pub fn iter_matches<F: FnMut(&'s String, f32)>(query: &str, choices: &Vec<&'s String>, mut f: F) {
        let lower_query = query.to_ascii_lowercase();

        for choice in choices.iter() {
            let lower_choice = choice.to_ascii_lowercase();

            match Score::score(&lower_choice, &lower_query) {
                0.0     => continue,
                quality => f(choice, quality),
            };
        }
    }

    pub fn down(self) -> Search<'s> {
        let next_index = self.next_index();
        self.new_for_index(next_index)
    }

    pub fn up(self) -> Search<'s> {
        let next_index = self.prev_index();
        self.new_for_index(next_index)
    }

    pub fn append_to_search(mut self, input: &str) -> Search<'s> {
        let mut new_query = self.query.clone();
        new_query.push_str(input.as_slice());

        let mut results = SortedResultSet::new(self.config.visible_limit);
        let mut filtered_choices: Vec<&String> = Vec::new();
        Search::iter_matches(new_query.as_slice(), &self.choice_stack.last().unwrap(),
                        |match_str, quality| {
                                               results.push(match_str, quality);
                                               filtered_choices.push(match_str)
                                             });

        self.choice_stack.push(filtered_choices);
        Search::new(self.config, new_query, self.choice_stack, results.as_sorted_vec(), 0, self.done)
    }

    pub fn backspace(mut self) -> Search<'s> {
        let mut new_query = self.query.clone();
        new_query.pop();

        if self.choice_stack.len() > 1 {
            self.choice_stack.pop();
        }

        let mut results = SortedResultSet::new(self.config.visible_limit);
        Search::iter_matches(new_query.as_slice(), &self.choice_stack.last().unwrap(),
                        |match_str, quality| {
                                               results.push(match_str, quality);
                                             });
        Search::new(self.config, new_query, self.choice_stack, results.as_sorted_vec(), 0, self.done)
    }

    fn next_index(&self) -> usize {
        let next_index = self.current + 1;

        if next_index >= self.actual_limit() {
            0
        } else {
            next_index
        }
    }

    fn prev_index(&self) -> usize {
        if self.current == 0 {
            self.actual_limit() - 1
        } else {
            self.current - 1
        }
    }

    fn actual_limit(&self) -> usize {
        min(self.config.visible_limit, self.num_matches())
    }

    pub fn num_matches(&self) -> usize {
        self.choice_stack.last().unwrap().len()
    }
}

#[cfg(test)]
mod tests {
    use configuration::Configuration;
    use super::*;
    use sorted_result_set::SortedResultSet;

    fn one_two_three() -> Vec<String> {
        vec!["one".to_string(),
             "two".to_string(),
             "three".to_string()]
    }

    fn input_times(n: usize) ->Vec<String> {
        let mut result: Vec<String> = Vec::new();
        for thing in one_two_three().iter().cycle().take(n) {
            result.push(thing.clone());
        }
        result
    }

    #[test]
    fn it_selects_the_first_choice_by_default() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn it_selets_the_second_when_down_is_called() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.down().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_loop_around_when_reaching_end_of_list() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.down().down().down().down().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_loop_around_when_reaching_top_of_list() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.up().up().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_loop_around_when_reaching_visible_limit() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, Some(2));
        let search = Search::blank(&config);

        assert_eq!(search.down().down().down().selection(), Some("two".to_string()));
    }

    #[test]
    fn it_moves_down_the_filtered_search_results() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.append_to_search("t").down().selection(), Some("three".to_string()));
    }

    #[test]
    fn it_moves_down_the_filtered_search_results_twice() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.append_to_search("t").append_to_search("w").selection(), Some("two".to_string()));
    }

    #[test]
    fn it_handles_not_matching_anything() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert_eq!(search.append_to_search("x").selection(), None);
    }

    #[test]
    fn up_match_nothing_after_filtering_all_out() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("x");

        assert_eq!(search.up().selection(), None);
    }

    #[test]
    fn down_match_nothing_after_filtering_all_out() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("x");

        assert_eq!(search.down().selection(), None);
    }

    #[test]
    fn backspaces_over_characters() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("e");

        assert_eq!(search.query, "e");
        assert_eq!(search.backspace().query, "");
    }

    #[test]
    fn resets_the_index_when_removing_char_from_search() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("e");

        assert_eq!(search.down().backspace().current, 0);
    }

    #[test]
    fn resets_the_index_when_adding_char_to_seach() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).down();

        assert_eq!(search.append_to_search("o").current, 0);
    }

    #[test]
    fn previous_results_appear_after_backspace() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("t");

        assert_eq!(search.backspace().num_matches(), 3);
    }

    #[test]
    fn initial_search_is_not_done() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        assert!(!search.is_done());
    }

    #[test]
    fn backspace_over_initial_doesnt_crash() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config);

        search.backspace();
    }

    #[test]
    fn done_search_is_done() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).done();

        assert!(search.is_done());
    }

    #[test]
    fn done_search_has_selection() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).done();

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn loop_around_when_reaching_bottom_of_choices() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("n").down();

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn loop_around_when_reaching_top_of_choices() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("n").up();

        assert_eq!(search.selection(), Some("one".to_string()));
    }

    #[test]
    fn search_is_case_insensitive() {
        let input = one_two_three();
        let config = Configuration::from_inputs(input, None, None);
        let search = Search::blank(&config).append_to_search("T");

        assert_eq!(search.num_matches(), 2);
    }

    #[test]
    fn uses_configs_visible_limit_as_result_size() {
        let input = input_times(30);

        let mut bar = Vec::new();
        for f in input.iter() {
            bar.push((*f).clone());
        }

        let config = Configuration::from_inputs(bar, None, Some(20));
        let search = Search::blank(&config).append_to_search("T");

        assert_eq!(search.num_matches(), 20);
    }

}
<|endoftext|>


<commit_before>#![feature(globs, slicing_syntax, if_let, unboxed_closures)]

extern crate getopts;
extern crate serialize;
extern crate glob;
extern crate iron;
extern crate persistent;
extern crate error;

use std::{ str, os };
use std::path::{ Path, GenericPath };
use std::io::{ fs, Reader };
use std::io::fs::{ File, PathExtensions };
use std::default::Default;
use std::sync::Mutex;

use getopts::{ optopt, optflag, getopts, usage, OptGroup };

use serialize::json;
use serialize::json::Json;

use iron::prelude::*;
use iron::response::modifiers::*;
use iron::status;
use iron::mime::*;
use iron::mime::SubLevel::Ext as SubExt;
use iron::middleware::ChainBuilder;
use iron::typemap::Assoc;

use persistent::Read;

pub mod constants;

#[deriving(Send, Clone, Default, Encodable, Decodable)]
struct Options {
    host: Option<String>,
    port: Option<u16>,
    root: Option<Path>,
    custom_css: Option<String>,
    filter: Option<String>,
    max_size: Option<u64>,
    not_found: Option<String>
}

struct OptCarrier;
impl Assoc<Mutex<Options>> for OptCarrier {}

static UNITS: &'static [&'static str] = &["B", "kB", "MB", "GB", "TB"];
const BRIEF: &'static str = "A minimal directory server, written in Rust with Iron.";

fn size_with_unit(mut size: u64) -> String {
    let mut frac = 0;
    let mut index = 0;

    while size > 1000 && index + 1 < UNITS.len() {
        frac = size % 1000;
        size /= 1000;
        index += 1;
    }

    format!("{}.{} {}", size, frac, UNITS[index])
}

fn render(root: Path, dir: Path, files: Vec<Path>) -> String {
    fn render_item(url: Path, size: u64, name: &str) -> String {
        format!("
{name}	{size}
\n",
        url = url.display(), size = size_with_unit(size), name = name)
    }

    let mut content = String::new();
    let mut up = dir.clone();
    up.pop();
    if root.is_ancestor_of(&up) {
        content.push_str(render_item(up.path_relative_from(&root).unwrap(), 0, "..")[]);
    }

    for file in files.iter() {
        let relative = file.path_relative_from(&root).unwrap();
        let stat = file.stat().unwrap();
        let filename = file.filename_display().as_maybe_owned().into_string();
        content.push_str(render_item(relative, stat.size, filename.clone()[])[]);
    }

    format!("<!DOCTYPE html>

    <title>{title}</title>

{title}

                        Name
                        Size

                {content}

",
            title = dir.display().as_maybe_owned(),
            css = constants::CSS,
            content = content)
}

fn plain(content: B) -> IronResult<Response> {
    Ok(Response::new()
       .set(Status(status::Ok))
       .set(Body(content)))
}

fn html(content: B) -> IronResult<Response> {
    plain(content).map(|r| r.set(ContentType(Mime(Text, Html, Vec::new()))))
}

fn binary(content: B) -> IronResult<Response> {
    plain(content).map(
        |r| r.set(ContentType(Mime(Application, SubExt("octet-stream".into_string()), Vec::new()))))
}

fn guess_text(data: &[u8]) -> bool {
    let mut total = 0u;
    let mut text = 0u;
    for (c, _) in data.iter().zip(range(0u, 1000)) {
        let c = *c as char;
        if c.is_alphanumeric() || c.is_whitespace() {
            text += 1;
        }
        total += 1;
    }
    text as f64 / total as f64 > 0.75
}

fn serve(req: &mut Request) -> IronResult<Response> {
    let root = {
        let o = req.get::<Read<OptCarrier, Mutex<Options>>>().unwrap();
        let mutex = o.lock();
        mutex.root.clone().unwrap_or_else(|| os::getcwd().ok().unwrap())
    };

    let mut path = root.clone();
    for part in req.url.path.iter() { path.push(part[]) }
    if !path.exists() { return html("Well, no... We don't have that today."); }

    if path.is_file() && root.is_ancestor_of(&path) {
        let content = match File::open(&path).read_to_end() {
            Ok(s) => s,
            Err(e) => return html(e.desc)
        };
        if guess_text(content[]) { plain(content[]) } else { binary(content[]) }
    } else {
        let mut content = match fs::readdir(&path) {
            Ok(s) => s,
            Err(e) => return html(e.desc)
        };
        content.sort_by(|a, b| a.filename_str().unwrap().cmp(b.filename_str().unwrap()));
        html(render(root, path, content)[])
    }
}

fn print_usage(program: &str, opts: &[OptGroup]) {
    println!("Usage: {} [options]\n", program);
    println!("{}", usage(BRIEF, opts));
}

fn main() {
    let args: Vec<String> = os::args();
    let program = args[0].clone();
    let opts = &[
        optopt("c", "config", "set config file name", "NAME"),
        optopt("a", "address", "the address to bind to", "HOST"),
        optopt("p", "port", "the port to serve", "PORT"),
        optopt("r", "root", "the uppermost directory to serve", "ROOT"),
        optflag("h", "help", "print this help menu")
    ];
    let matches = match getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => {
            println!("{}", f.to_string());
            return;
        }
    };

    if matches.opt_present("h") {
        print_usage(program[], opts);
        return;
    }

    let options: Mutex<Options> = Mutex::new(Default::default());

    matches.opt_str("c").map(|conf_file| {
        let conf_file = File::open(&Path::new(conf_file));
        conf_file.as_ref().map_err::<()>(|e| panic!("{}", e.desc)).unwrap();
        let json = match json::from_reader(&mut conf_file.ok().unwrap()) {
            Ok(Json::Object(o)) => o,
            _ => panic!("Invalid configuration file. Doesn't contain top-level object.")
        };
        let mut o = options.lock();
        o.host = match json.get("address") {
            Some(&Json::String(ref s)) => Some((*s).clone()),
            None => None,
            _ => panic!("Invalid configuration file. `address` field must be a string.")
        };
        o.port = match json.get("port") {
            Some(&Json::U64(u)) => Some(u as u16),
            None => None,
            _ => panic!("Invalid configuration file. `port` field must be an unsigned integer.")
        };
        o.root = match json.get("root") {
            Some(&Json::String(ref s)) => Some(Path::new((*s).clone())),
            None => None,
            _ => panic!("Invalid configuration file. `root` field must be a string.")
        };
    });

    let (host, port) = {
        let mut o = options.lock();
        o.host = o.host.clone().or(matches.opt_str("a"));
        o.port = o.port.or(matches.opt_str("p").and_then(|p| str::from_str(p[])));
        o.root = o.root.clone().or(matches.opt_str("r").and_then(|p| Path::new_opt(p)));
        (o.host.clone().unwrap_or("0.0.0.0".into_string()),
         o.port.clone().unwrap_or(8080))
    };

    let mut chain = ChainBuilder::new(serve);
    chain.link(Read::<OptCarrier, Mutex<Options>>::both(options));
    match Iron::new(chain).listen((host[], port)) {
        Ok(_) => (),
        Err(e) => println!("I'm sorry, I failed you. {}", e)
    }
}
<commit_msg>Filtering by filename and size<commit_after>#![feature(globs, slicing_syntax, if_let, unboxed_closures)]

extern crate getopts;
extern crate serialize;
extern crate iron;
extern crate persistent;
extern crate error;
extern crate regex;

use std::{ str, os };
use std::path::{ Path, GenericPath };
use std::io::{ fs, Reader };
use std::io::fs::{ File, PathExtensions };
use std::default::Default;
use std::sync::Mutex;

use regex::Regex;

use getopts::{ optopt, optflag, getopts, usage, OptGroup };

use serialize::json;
use serialize::json::Json;

use iron::prelude::*;
use iron::response::modifiers::*;
use iron::status;
use iron::mime::*;
use iron::mime::SubLevel::Ext as SubExt;
use iron::middleware::ChainBuilder;
use iron::typemap::Assoc;

use persistent::Read;

pub mod constants;

#[deriving(Send, Clone, Default, Encodable, Decodable)]
struct Options {
    host: Option<String>,
    port: Option<u16>,
    root: Option<Path>,
    custom_css: Option<String>,
    filter: Option<String>,
    max_size: Option<u64>,
}

struct OptCarrier;
impl Assoc<Mutex<Options>> for OptCarrier {}

static UNITS: &'static [&'static str] = &["B", "kB", "MB", "GB", "TB"];
const BRIEF: &'static str = "A minimal directory server, written in Rust with Iron.";

fn size_with_unit(mut size: u64) -> String {
    let mut frac = 0;
    let mut index = 0;

    while size > 1000 && index + 1 < UNITS.len() {
        frac = size % 1000;
        size /= 1000;
        index += 1;
    }

    format!("{}.{} {}", size, frac, UNITS[index])
}

fn render(root: Path, dir: Path, files: Vec<Path>, filter: Option<Regex>) -> String {
    fn render_item(url: Path, size: u64, name: &str) -> String {
        format!("
{name}	{size}
\n",
        url = url.display(), size = size_with_unit(size), name = name)
    }

    let mut content = String::new();
    let mut up = dir.clone();
    up.pop();
    if root.is_ancestor_of(&up) {
        content.push_str(render_item(up.path_relative_from(&root).unwrap(), 0, "..")[]);
    }

    for file in files.iter() {
        let relative = file.path_relative_from(&root).unwrap();
        let stat = file.stat().unwrap();
        let filename = file.filename_display().as_maybe_owned().into_string();
        if filter.as_ref().map_or(true, |f| f.is_match(filename[])) {
            content.push_str(render_item(relative, stat.size, filename.clone()[])[]);
        }
    }

    format!("<!DOCTYPE html>

    <title>{title}</title>

{title}

                        Name
                        Size

                {content}

",
            title = dir.display().as_maybe_owned(),
            css = constants::CSS,
            content = content)
}

fn plain(content: B) -> IronResult<Response> {
    Ok(Response::new()
       .set(Status(status::Ok))
       .set(Body(content)))
}

fn html(content: B) -> IronResult<Response> {
    plain(content).map(|r| r.set(ContentType(Mime(Text, Html, Vec::new()))))
}

fn binary(content: B) -> IronResult<Response> {
    plain(content).map(
        |r| r.set(ContentType(Mime(Application, SubExt("octet-stream".into_string()), Vec::new()))))
}

fn guess_text(data: &[u8]) -> bool {
    let mut total = 0u;
    let mut text = 0u;
    for (c, _) in data.iter().zip(range(0u, 1000)) {
        let c = *c as char;
        if c.is_alphanumeric() || c.is_whitespace() {
            text += 1;
        }
        total += 1;
    }
    text as f64 / total as f64 > 0.75
}

fn serve(req: &mut Request) -> IronResult<Response> {
    let (root, filter_str, max_size) = {
        let o = req.get::<Read<OptCarrier, Mutex<Options>>>().unwrap();
        let mutex = o.lock();
        (mutex.root.clone().unwrap_or_else(|| os::getcwd().ok().unwrap()),
         mutex.filter.clone(),
         mutex.max_size)
    };

    let mut path = root.clone();
    for part in req.url.path.iter() { path.push(part[]) }
    if !path.exists() { return html("Well, no... We don't have that today."); }

    let filter = filter_str.and_then(|s| Regex::new(s[]).ok());

    if path.is_file() && root.is_ancestor_of(&path) {
        let stat = path.stat();
        if stat.as_ref().ok().is_some() && max_size.is_some() && stat.ok().unwrap().size > max_size.unwrap() {
            return html("I'm afraid, I'm too lazy to serve the requested file. It's pretty big...")
        }
        let content = match File::open(&path).read_to_end() {
            Ok(s) => s,
            Err(e) => return html(e.desc)
        };

        if filter.as_ref().map_or(false, |f| !f.is_match(path.filename_str().unwrap())) {
            return html("I don't think you're allowed to do this.");
        }
        if guess_text(content[]) { plain(content[]) } else { binary(content[]) }
    } else {
        let mut content = match fs::readdir(&path) {
            Ok(s) => s,
            Err(e) => return html(e.desc)
        };
        content.sort_by(|a, b| a.filename_str().unwrap().cmp(b.filename_str().unwrap()));
        html(render(root, path, content, filter)[])
    }
}

fn print_usage(program: &str, opts: &[OptGroup]) {
    println!("Usage: {} [options]\n", program);
    println!("{}", usage(BRIEF, opts));
}

fn main() {
    let args: Vec<String> = os::args();
    let program = args[0].clone();
    let opts = &[
        optopt("c", "config", "set config file name", "NAME"),
        optopt("a", "address", "the address to bind to", "HOST"),
        optopt("p", "port", "the port to serve", "PORT"),
        optopt("r", "root", "the uppermost directory to serve", "ROOT"),
        optopt("f", "filter", "a regular expression to filter the filenames", "REGEX"),
        optopt("s", "size", "the maximum size of a file that will be served", "BYTES"),
        optflag("h", "help", "print this help menu")
    ];
    let matches = match getopts(args.tail(), opts) {
        Ok(m) => { m }
        Err(f) => {
            println!("{}", f.to_string());
            return;
        }
    };

    if matches.opt_present("h") {
        print_usage(program[], opts);
        return;
    }

    let options: Mutex<Options> = Mutex::new(Default::default());

    matches.opt_str("c").map(|conf_file| {
        let conf_file = File::open(&Path::new(conf_file));
        conf_file.as_ref().map_err::<()>(|e| panic!("{}", e.desc)).unwrap();
        let json = match json::from_reader(&mut conf_file.ok().unwrap()) {
            Ok(Json::Object(o)) => o,
            _ => panic!("Invalid configuration file. Doesn't contain top-level object.")
        };
        let mut o = options.lock();
        o.host = match json.get("address") {
            Some(&Json::String(ref s)) => Some((*s).clone()),
            None => None,
            _ => panic!("Invalid configuration file. `address` field must be a string.")
        };
        o.port = match json.get("port") {
            Some(&Json::U64(u)) => Some(u as u16),
            None => None,
            _ => panic!("Invalid configuration file. `port` field must be an unsigned integer.")
        };
        o.root = match json.get("root") {
            Some(&Json::String(ref s)) => Some(Path::new((*s).clone())),
            None => None,
            _ => panic!("Invalid configuration file. `root` field must be a string.")
        };
        o.filter = match json.get("filter") {
            Some(&Json::String(ref s)) => Some((*s).clone()),
            None => None,
            _ => panic!("Invalid configuration file. `filter` field must be a string.")
        };
        o.max_size = match json.get("size") {
            Some(&Json::U64(u)) => Some(u),
            None => None,
            _ => panic!("Invalid configuration file. `size` field must be an unsigned integer.")
        }
    });

    let (host, port) = {
        let mut o = options.lock();
        o.host = o.host.clone().or(matches.opt_str("a"));
        o.port = o.port.or(matches.opt_str("p").and_then(|p| str::from_str(p[])));
        o.root = o.root.clone().or(matches.opt_str("r").and_then(|p| Path::new_opt(p)));
        o.filter = o.filter.clone().or(matches.opt_str("f"));
        o.max_size = o.max_size.or(matches.opt_str("s").and_then(|s| str::from_str(s[])));
        (o.host.clone().unwrap_or("0.0.0.0".into_string()),
         o.port.clone().unwrap_or(8080))
    };

    let mut chain = ChainBuilder::new(serve);
    chain.link(Read::<OptCarrier, Mutex<Options>>::both(options));
    match Iron::new(chain).listen((host[], port)) {
        Ok(_) => (),
        Err(e) => println!("I'm sorry, I failed you. {}", e)
    }
}
<|endoftext|>


<commit_before>use std::io::{Write, Read};
use std::net::{TcpListener, TcpStream, SocketAddrV4};
use std::thread::{spawn, JoinHandle};
use std::time::Duration;
use std::str;
use std::sync::{Arc, Mutex};

pub struct Server {
    address: SocketAddrV4,
    expected_connections: Option<u32>,
    expected_echo_replies: Option<u32>,
}

impl Server {
    pub fn new(address: SocketAddrV4,
               expected_connections: Option<u32>,
               expected_replies: Option<u32>)
               -> Self {
        Server {
            address: address,
            expected_connections: expected_connections,
            expected_echo_replies: expected_replies,
        }
    }

    pub fn listen(&self) {
        let listener = TcpListener::bind(&self.address).unwrap();
        println!("listening started on {}, ready to accept", self.address);
        let replies_count = Arc::new(Mutex::new(0));
        let mut handles = Vec::<JoinHandle<()>>::new();
        for (connection_count, stream) in listener.incoming().enumerate() {
            let expected_echo_replies = self.expected_echo_replies;
            let count = replies_count.clone();
            handles.push(spawn(move || {
                handle_connection(stream.unwrap(), count, expected_echo_replies);
            }));
            if let Some(expected_connections) = self.expected_connections {
                if connection_count as u32 == expected_connections {
                    // we should probably kill the active threads first
                    break;
                }
            }
        }
        for handle in handles {
            handle.join().unwrap();
        }
    }
}

fn handle_connection(mut stream: TcpStream,
                     replies_count: Arc<Mutex<u32>>,
                     expected_replies: Option<u32>) {
    let server = stream.local_addr().unwrap();
    let peer = stream.peer_addr().unwrap();
    println!("{}: connection established from {}", server, peer);
    // We'll wait for 100 milliseconds to receive a message.
    // An io:Error (ErrorKind::WouldBlock) is returned after 100ms if there is nothing to read.
    stream.set_read_timeout(Some(Duration::from_millis(100))).unwrap();

    // We'll wait for 100 milliseconds to send a message
    stream.set_write_timeout(Some(Duration::from_millis(100))).unwrap();

    let mut read_buf: [u8; 128] = [0; 128];
    loop {
        match stream.read(&mut read_buf) {
            Ok(n) => {
                if n > 0 {
                    handle_message(&read_buf, n, &mut stream, &replies_count);
                    if let Some(expected_replies) = expected_replies {
                        if *replies_count.lock().unwrap() >= expected_replies {
                            return;
                        }
                    }
                } else {
                    // according to rust docs[1], n == 0 can indicate one of two scenarios:
                    //
                    // 1. This reader has reached its "end of file" and will likely no longer be
                    //    able to produce bytes. Note that this does not mean that the reader will
                    //    always no longer be able to produce bytes.
                    //
                    // 2. The buffer specified was 0 bytes in length.
                    //
                    // https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read
                    //
                    // We assume the first scenario since we know our buffer length in not 0.
                    // This means the connection is closed and we can exit.
                    println!("{}: connection with {} is closed (received EOF).",
                             server,
                             peer);
                    return;
                }
            }
            Err(e) => {
                match e.kind() {
                    // We are using a non-blocking socket and there was nothing to read, so Linux
                    // return an EAGAIN error which translates in rust into a "WouldBlock" error.
                    ::std::io::ErrorKind::WouldBlock => continue,
                    _ => {
                        // FIXME: should bail
                        panic!();
                    }
                }
            }
        }
    }
}

pub fn handle_message(buffer: &[u8],
                      length: usize,
                      stream: &mut TcpStream,
                      replies_count: &Arc<Mutex<u32>>) {
    let server = stream.local_addr().unwrap();
    let peer = stream.peer_addr().unwrap();
    if let Ok(msg) = str::from_utf8(&buffer[0..length]) {
        println!("{} <<< {} {}", server, peer, msg);
        match stream.write(&buffer[0..length]) {
            Ok(n) => {
                if length < n {
                    panic!("*** {}: Incomplete answer: {} bytes answered instead of {}",
                           peer,
                           n,
                           length);
                } else {
                    println!("{} >>> {} {}", server, peer, msg);
                    *replies_count.lock().unwrap() += 1;
                }
            }
            Err(e) => {
                panic!("*** {}: {:?}", peer, e);
            }
        }
    } else {
        println!("<<< {} {:?}", peer, buffer);
        println!("Received message is not a valid UTF-8 sequence. Ignoring");
    }
}
<commit_msg>handle server error<commit_after>use std::io::{Write, Read};
use std::net::{TcpListener, TcpStream, SocketAddrV4};
use std::thread::{spawn, JoinHandle};
use std::time::Duration;
use std::str;
use std::sync::{Arc, Mutex};

pub struct Server {
    address: SocketAddrV4,
    expected_connections: Option<u32>,
    expected_echo_replies: Option<u32>,
}

impl Server {
    pub fn new(address: SocketAddrV4,
               expected_connections: Option<u32>,
               expected_replies: Option<u32>)
               -> Self {
        Server {
            address: address,
            expected_connections: expected_connections,
            expected_echo_replies: expected_replies,
        }
    }

    pub fn listen(&self) {
        match TcpListener::bind(&self.address) {
            Ok(listener) => {
                println!("listening started on {}, ready to accept", self.address);
                let replies_count = Arc::new(Mutex::new(0));
                let mut handles = Vec::<JoinHandle<()>>::new();
                for (connection_count, stream) in listener.incoming().enumerate() {
                    let expected_echo_replies = self.expected_echo_replies;
                    let count = replies_count.clone();
                    handles.push(spawn(move || {
                        handle_connection(stream.unwrap(), count, expected_echo_replies);
                    }));
                    if let Some(expected_connections) = self.expected_connections {
                        if connection_count as u32 == expected_connections {
                            // we should probably kill the active threads first
                            break;
                        }
                    }
                }
                for handle in handles {
                    handle.join().unwrap();
                }
            }
            Err(e) => {
                println!("Cannot listen on {}", self.address);
                println!("    caused by: {}", e);
            }
        }
    }
}

fn handle_connection(mut stream: TcpStream,
                     replies_count: Arc<Mutex<u32>>,
                     expected_replies: Option<u32>) {
    let server = stream.local_addr().unwrap();
    let peer = stream.peer_addr().unwrap();
    println!("{}: connection established from {}", server, peer);
    // We'll wait for 100 milliseconds to receive a message.
    // An io:Error (ErrorKind::WouldBlock) is returned after 100ms if there is nothing to read.
    stream.set_read_timeout(Some(Duration::from_millis(100))).unwrap();

    // We'll wait for 100 milliseconds to send a message
    stream.set_write_timeout(Some(Duration::from_millis(100))).unwrap();

    let mut read_buf: [u8; 128] = [0; 128];
    loop {
        match stream.read(&mut read_buf) {
            Ok(n) => {
                if n > 0 {
                    handle_message(&read_buf, n, &mut stream, &replies_count);
                    if let Some(expected_replies) = expected_replies {
                        if *replies_count.lock().unwrap() >= expected_replies {
                            return;
                        }
                    }
                } else {
                    // according to rust docs[1], n == 0 can indicate one of two scenarios:
                    //
                    // 1. This reader has reached its "end of file" and will likely no longer be
                    //    able to produce bytes. Note that this does not mean that the reader will
                    //    always no longer be able to produce bytes.
                    //
                    // 2. The buffer specified was 0 bytes in length.
                    //
                    // https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read
                    //
                    // We assume the first scenario since we know our buffer length in not 0.
                    // This means the connection is closed and we can exit.
                    println!("{}: connection with {} is closed (received EOF).",
                             server,
                             peer);
                    return;
                }
            }
            Err(e) => {
                match e.kind() {
                    // We are using a non-blocking socket and there was nothing to read, so Linux
                    // return an EAGAIN error which translates in rust into a "WouldBlock" error.
                    ::std::io::ErrorKind::WouldBlock => continue,
                    _ => {
                        // FIXME: should bail
                        panic!();
                    }
                }
            }
        }
    }
}

pub fn handle_message(buffer: &[u8],
                      length: usize,
                      stream: &mut TcpStream,
                      replies_count: &Arc<Mutex<u32>>) {
    let server = stream.local_addr().unwrap();
    let peer = stream.peer_addr().unwrap();
    if let Ok(msg) = str::from_utf8(&buffer[0..length]) {
        println!("{} <<< {} {}", server, peer, msg);
        match stream.write(&buffer[0..length]) {
            Ok(n) => {
                if length < n {
                    panic!("*** {}: Incomplete answer: {} bytes answered instead of {}",
                           peer,
                           n,
                           length);
                } else {
                    println!("{} >>> {} {}", server, peer, msg);
                    *replies_count.lock().unwrap() += 1;
                }
            }
            Err(e) => {
                panic!("*** {}: {:?}", peer, e);
            }
        }
    } else {
        println!("<<< {} {:?}", peer, buffer);
        println!("Received message is not a valid UTF-8 sequence. Ignoring");
    }
}
<|endoftext|>


<commit_before>use super::connection::{DBusConnection, DBusReleaseNameReply, DBusRequestNameFlags};
use super::error::DBusError;
use super::interface::DBusInterfaceMap;
use super::message::DBusMessage;
use super::object::DBusObject;
use super::target::DBusTarget;

use std::cell::RefCell;
use std::collections::btree_map::{BTreeMap, Entry};
use std::rc::Rc;

pub type DBusSignalHandler = Box<FnMut(&DBusConnection, &DBusTarget) -> ()>;
type DBusSignalHandlers = Vec<DBusSignalHandler>;
type DBusSignalHandlerMap = BTreeMap<DBusTarget, DBusSignalHandlers>;

fn _add_handler(handlers: &mut DBusSignalHandlerMap, signal: DBusTarget, handler: DBusSignalHandler) {
    match handlers.entry(signal) {
        Entry::Vacant(v)    => { v.insert(vec![handler]); },
        Entry::Occupied(o)  => o.into_mut().push(handler),
    };
}

pub struct DBusServer {
    conn: Rc<DBusConnection>,
    name: String,
    can_handle: bool,

    // TODO: store children information
    objects: BTreeMap<String, DBusObject>,
    signals: DBusSignalHandlerMap,
    namespace_signals: DBusSignalHandlerMap,
}

impl DBusServer {
    pub fn new_listener(conn: Rc<DBusConnection>, name: &str) -> Result<DBusServer, DBusError> {
        Ok(DBusServer {
            conn: conn,
            name: name.to_owned(),
            can_handle: false,

            objects: BTreeMap::new(),
            signals: BTreeMap::new(),
            namespace_signals: BTreeMap::new(),
        })
    }

    pub fn new(conn: Rc<DBusConnection>, name: &str) -> Result<DBusServer, DBusError> {
        try!(conn.request_name(name, DBusRequestNameFlags::DoNotQueue));

        // TODO: add root object
        // TODO: add ObjectManager interface

        Ok(DBusServer {
            conn: conn,
            name: name.to_owned(),
            can_handle: true,

            objects: BTreeMap::new(),
            signals: BTreeMap::new(),
            namespace_signals: BTreeMap::new(),
        })
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn add_object(&mut self, path: &str, iface_map: DBusInterfaceMap) -> Result<&mut Self, DBusError> {
        if !self.can_handle {
            return Err(DBusError::NoServerName);
        }

        match self.objects.entry(path.to_owned()) {
            Entry::Vacant(v)    => {
                // TODO: store this
                let children = Rc::new(RefCell::new(vec![]));
                let obj = try!(DBusObject::new(path, iface_map, children));

                // TODO: emit InterfacesAdded signal

                v.insert(obj);

                Ok(())
            },
            Entry::Occupied(_)  => Err(DBusError::PathAlreadyRegistered(path.to_owned())),
        }.map(|_| self)
    }

    pub fn remove_object(&mut self, path: &str) -> Result<&mut Self, DBusError> {
        if !self.can_handle {
            return Err(DBusError::NoServerName);
        }

        match self.objects.remove(path) {
            Some(_) => {
                //TODO: emit InterfacesRemoved signal

                Ok(self)
            },
            None    => Err(DBusError::NoSuchPath(path.to_owned())),
        }
    }

    pub fn connect(&mut self, signal: DBusTarget, callback: DBusSignalHandler) -> Result<&mut Self, DBusError> {
        let dbus_match = format!("type='signal',interface='{}',path='{}',member='{}'",
                                 signal.interface,
                                 signal.object,
                                 signal.method);
        try!(self.conn.add_match(&dbus_match));

        _add_handler(&mut self.signals, signal, callback);

        Ok(self)
    }

    pub fn connect_namespace(&mut self, signal: DBusTarget, callback: DBusSignalHandler) -> Result<&mut Self, DBusError> {
        let dbus_match = format!("type='signal',interface='{}',path_namespace='{}',member='{}'",
                                 signal.interface,
                                 signal.object,
                                 signal.method);
        try!(self.conn.add_match(&dbus_match));

        _add_handler(&mut self.namespace_signals, signal, callback);

        Ok(self)
    }

    pub fn handle_message<'b>(&mut self, m: &'b mut DBusMessage) -> Option<&'b mut DBusMessage> {
        if m.is_signal() {
            Some(self._match_signal(m))
        } else if m.is_method_call() {
            self._call_method(m)
        } else {
            Some(m)
        }
    }

    fn _call_method<'b>(&mut self, m: &'b mut DBusMessage) -> Option<&'b mut DBusMessage> {
        let conn = self.conn.clone();
        self.objects.iter_mut().fold(Some(m), |opt_m, (_, object)| {
            opt_m.and_then(|mut m| {
                match object.handle_message(&conn, &mut m) {
                    None          => Some(m),
                    Some(Ok(()))  => None,
                    Some(Err(())) => {
                        println!("failed to send a reply for {:?}", m);
                        None
                    },
                }
            })
        })
    }

    fn _match_signal<'b>(&mut self, m: &'b mut DBusMessage) -> &'b mut DBusMessage {
        let conn = self.conn.clone();

        DBusTarget::extract(m).map(|signal| {
            for handlers in self.signals.get_mut(&signal) {
                for handler in handlers.iter_mut() {
                    handler(&conn, &signal);
                }
            }

            let matched_handlers = self.namespace_signals.iter_mut().filter(|&(expect, _)| {
                expect.namespace_eq(&signal)
            });

            for (_, handlers) in matched_handlers {
                for handler in handlers.iter_mut() {
                    handler(&conn, &signal);
                };
            };
        });

        m
    }
}

impl Drop for DBusServer {
    fn drop(&mut self) {
        if !self.can_handle {
            return;
        }

        let res = self.conn.release_name(&self.name);
        match res {
            Ok(reply) =>
                match reply {
                    DBusReleaseNameReply::Released    => (),
                    DBusReleaseNameReply::NonExistent => panic!("internal error: non-existent name {}?!", self.name),
                    DBusReleaseNameReply::NotOwner    => panic!("internal error: not the owner of {}?!", self.name),
                },
            Err(err) => println!("failed to release {}: {:?}", self.name, err),
        }
    }
}
<commit_msg>typo: add missing space in comment<commit_after>use super::connection::{DBusConnection, DBusReleaseNameReply, DBusRequestNameFlags};
use super::error::DBusError;
use super::interface::DBusInterfaceMap;
use super::message::DBusMessage;
use super::object::DBusObject;
use super::target::DBusTarget;

use std::cell::RefCell;
use std::collections::btree_map::{BTreeMap, Entry};
use std::rc::Rc;

pub type DBusSignalHandler = Box<FnMut(&DBusConnection, &DBusTarget) -> ()>;
type DBusSignalHandlers = Vec<DBusSignalHandler>;
type DBusSignalHandlerMap = BTreeMap<DBusTarget, DBusSignalHandlers>;

fn _add_handler(handlers: &mut DBusSignalHandlerMap, signal: DBusTarget, handler: DBusSignalHandler) {
    match handlers.entry(signal) {
        Entry::Vacant(v)    => { v.insert(vec![handler]); },
        Entry::Occupied(o)  => o.into_mut().push(handler),
    };
}

pub struct DBusServer {
    conn: Rc<DBusConnection>,
    name: String,
    can_handle: bool,

    // TODO: store children information
    objects: BTreeMap<String, DBusObject>,
    signals: DBusSignalHandlerMap,
    namespace_signals: DBusSignalHandlerMap,
}

impl DBusServer {
    pub fn new_listener(conn: Rc<DBusConnection>, name: &str) -> Result<DBusServer, DBusError> {
        Ok(DBusServer {
            conn: conn,
            name: name.to_owned(),
            can_handle: false,

            objects: BTreeMap::new(),
            signals: BTreeMap::new(),
            namespace_signals: BTreeMap::new(),
        })
    }

    pub fn new(conn: Rc<DBusConnection>, name: &str) -> Result<DBusServer, DBusError> {
        try!(conn.request_name(name, DBusRequestNameFlags::DoNotQueue));

        // TODO: add root object
        // TODO: add ObjectManager interface

        Ok(DBusServer {
            conn: conn,
            name: name.to_owned(),
            can_handle: true,

            objects: BTreeMap::new(),
            signals: BTreeMap::new(),
            namespace_signals: BTreeMap::new(),
        })
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn add_object(&mut self, path: &str, iface_map: DBusInterfaceMap) -> Result<&mut Self, DBusError> {
        if !self.can_handle {
            return Err(DBusError::NoServerName);
        }

        match self.objects.entry(path.to_owned()) {
            Entry::Vacant(v)    => {
                // TODO: store this
                let children = Rc::new(RefCell::new(vec![]));
                let obj = try!(DBusObject::new(path, iface_map, children));

                // TODO: emit InterfacesAdded signal

                v.insert(obj);

                Ok(())
            },
            Entry::Occupied(_)  => Err(DBusError::PathAlreadyRegistered(path.to_owned())),
        }.map(|_| self)
    }

    pub fn remove_object(&mut self, path: &str) -> Result<&mut Self, DBusError> {
        if !self.can_handle {
            return Err(DBusError::NoServerName);
        }

        match self.objects.remove(path) {
            Some(_) => {
                // TODO: emit InterfacesRemoved signal

                Ok(self)
            },
            None    => Err(DBusError::NoSuchPath(path.to_owned())),
        }
    }

    pub fn connect(&mut self, signal: DBusTarget, callback: DBusSignalHandler) -> Result<&mut Self, DBusError> {
        let dbus_match = format!("type='signal',interface='{}',path='{}',member='{}'",
                                 signal.interface,
                                 signal.object,
                                 signal.method);
        try!(self.conn.add_match(&dbus_match));

        _add_handler(&mut self.signals, signal, callback);

        Ok(self)
    }

    pub fn connect_namespace(&mut self, signal: DBusTarget, callback: DBusSignalHandler) -> Result<&mut Self, DBusError> {
        let dbus_match = format!("type='signal',interface='{}',path_namespace='{}',member='{}'",
                                 signal.interface,
                                 signal.object,
                                 signal.method);
        try!(self.conn.add_match(&dbus_match));

        _add_handler(&mut self.namespace_signals, signal, callback);

        Ok(self)
    }

    pub fn handle_message<'b>(&mut self, m: &'b mut DBusMessage) -> Option<&'b mut DBusMessage> {
        if m.is_signal() {
            Some(self._match_signal(m))
        } else if m.is_method_call() {
            self._call_method(m)
        } else {
            Some(m)
        }
    }

    fn _call_method<'b>(&mut self, m: &'b mut DBusMessage) -> Option<&'b mut DBusMessage> {
        let conn = self.conn.clone();
        self.objects.iter_mut().fold(Some(m), |opt_m, (_, object)| {
            opt_m.and_then(|mut m| {
                match object.handle_message(&conn, &mut m) {
                    None          => Some(m),
                    Some(Ok(()))  => None,
                    Some(Err(())) => {
                        println!("failed to send a reply for {:?}", m);
                        None
                    },
                }
            })
        })
    }

    fn _match_signal<'b>(&mut self, m: &'b mut DBusMessage) -> &'b mut DBusMessage {
        let conn = self.conn.clone();

        DBusTarget::extract(m).map(|signal| {
            for handlers in self.signals.get_mut(&signal) {
                for handler in handlers.iter_mut() {
                    handler(&conn, &signal);
                }
            }

            let matched_handlers = self.namespace_signals.iter_mut().filter(|&(expect, _)| {
                expect.namespace_eq(&signal)
            });

            for (_, handlers) in matched_handlers {
                for handler in handlers.iter_mut() {
                    handler(&conn, &signal);
                };
            };
        });

        m
    }
}

impl Drop for DBusServer {
    fn drop(&mut self) {
        if !self.can_handle {
            return;
        }

        let res = self.conn.release_name(&self.name);
        match res {
            Ok(reply) =>
                match reply {
                    DBusReleaseNameReply::Released    => (),
                    DBusReleaseNameReply::NonExistent => panic!("internal error: non-existent name {}?!", self.name),
                    DBusReleaseNameReply::NotOwner    => panic!("internal error: not the owner of {}?!", self.name),
                },
            Err(err) => println!("failed to release {}: {:?}", self.name, err),
        }
    }
}
<|endoftext|>


<commit_before>use crate::adapters::primary::common::settings::StringQuery;
use geojson::Geometry;
use serde_json::json;

use super::coord::Coord;
use super::{filters, settings};

pub fn build_query(
    q: &str,
    filters: filters::Filters,
    _lang: String,
    settings: &settings::QuerySettings,
) -> serde_json::Value {
    let type_query = build_place_type_boost(&settings.type_query.boosts);
    let string_query = build_string_query(q, _lang.as_str(), &settings.string_query);
    let boosts = build_boosts(q, settings, &filters);
    let mut filters_poi = build_filters(filters.shape, filters.poi_types, filters.zone_types);
    let filters = vec![build_house_number_condition(q), build_matching_condition(q)]
        .append(filters_poi.as_mut());
    json!({
        "query": {
            "bool": {
                "must": [ type_query, string_query ],
                "should": boosts,
                "filter": {
                    "bool": {
                        "must": filters
                    }
                }
            }
        }
    })
}

fn build_string_query(q: &str, _lang: &str, settings: &StringQuery) -> serde_json::Value {
    json!({
        "bool": {
            "boost": settings.global,
            "should": [
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["name", format!("names.{}", _lang)],
                        "boost": settings.boosts.name
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label", format!("label.{}", _lang)],
                        "boost": settings.boosts.label
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label.prefix", format!("label.prefix.{}", _lang)],
                        "boost": settings.boosts.label_prefix
                    }
                },
                {
                    "match": {
                        "zip_codes": {
                        "query": q,
                        "boost": settings.boosts.zip_codes
                        }
                    }

                },
                {
                    "match": {
                        "house_number": {
                         "query": q,
                         "boost": settings.boosts.house_number
                        }
                    }
                }
            ]
        }
    })
}

fn build_filters(
    shape: Option<(Geometry, Vec<String>)>,
    poi_types: Option<Vec<String>>,
    zone_types: Option<Vec<String>>,
) -> Vec<serde_json::Value> {
    let mut filters: Vec<Option<serde_json::Value>> = Vec::new();
    let geoshape_filter = shape.map(|(geometry, scope)| build_shape_query(geometry, scope));
    filters.push(geoshape_filter);
    let poi_types_filter = poi_types.map(build_poi_types_filter);
    filters.push(poi_types_filter);
    let zone_types_filter = zone_types.map(build_zone_types_filter);
    filters.push(zone_types_filter);
    filters.into_iter().flatten().collect()
}

fn build_boosts(
    _q: &str,
    settings: &settings::QuerySettings,
    filters: &filters::Filters,
) -> Vec<serde_json::Value> {
    let mut boosts: Vec<Option<serde_json::Value>> = Vec::new();
    // TODO: in production, admins are boosted by their weight only in prefix mode.
    let admin_weight_boost = Some(build_admin_weight_query(&settings.importance_query));
    boosts.push(admin_weight_boost);
    let mut decay = settings.importance_query.proximity.decay.clone();
    if let Some(proximity) = &filters.proximity {
        decay.scale = proximity.scale;
        decay.offset = proximity.offset;
        decay.decay = proximity.decay;
    }
    let proximity_boost = filters
        .coord
        .clone()
        .map(|coord| build_proximity_boost(coord, &decay));
    boosts.push(proximity_boost);
    boosts.into_iter().flatten().collect()
}

fn build_house_number_condition(q: &str) -> serde_json::Value {
    if q.split_whitespace().count() > 1 {
        // Filter to handle house number.
        // We either want:
        // * to exactly match the document house_number
        // * or that the document has no house_number
        json!({
            "bool": {
                "should": [
                {
                    "bool": {
                        "must_not": {
                            "exists": {
                              "field": "house_number"
                            }
                        },
                    }
                },
                {
                    "match": {
                        "house_number": {
                            "query": q
                        }
                    }
                }
                ]
            }
        })
    } else {
        // If the query contains a single word, we don't exact any house number in the result.
        json!({
            "bool": {
                "must_not": {
                    "exists": {
                        "field": "house_number"
                    }
                },
            }
        })
    }
}

fn build_matching_condition(q: &str) -> serde_json::Value {
    // Filter to handle house number.
    // We either want:
    // * to exactly match the document house_number
    // * or that the document has no house_number
    json!({
        "match": {
            "full_label.prefix": {
                "query": q,
                "operator": "and"
            }
        }
    })
}

fn build_admin_weight_query(settings: &settings::ImportanceQueryBoosts) -> serde_json::Value {
    json!({
        "function_score": {
            "query": { "term": { "type": "admin" } },
            "boost_mode": "replace",
            "functions": [
                {
                    "field_value_factor": {
                        "field": "weight",
                        "factor": 1e6,
                        "modifier": "log1p",
                        "missing": 0
                    }
                },
                {
                    // TODO: in production, this weight depends of the focus radius
                    "weight": settings.weights.max_radius.admin
                }
            ]
        }
    })
}

fn build_place_type_boost(settings: &settings::Types) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
                { "term": { "type": { "value": "admin", "boost": settings.admin } } },
                { "term": { "type": { "value": "addr", "boost": settings.address } } },
                { "term": { "type": { "value": "stop", "boost": settings.stop } } },
                { "term": { "type": { "value": "poi", "boost": settings.poi } } },
                { "term": { "type": { "value": "street", "boost": settings.street } } },
            ]
        }
    })
}

fn build_proximity_boost(coord: Coord, settings_decay: &settings::Decay) -> serde_json::Value {
    let settings::Decay {
        func,
        scale,
        offset,
        decay,
    } = settings_decay;

    json!({
        "function_score": {
            func.clone(): {
                "coord": {
                    "origin": {
                        "lat": coord.lat,
                        "lon": coord.lon
                    },
                    "scale": format!("{}km", scale),
                    "offset": format!("{}km", offset),
                    "decay": decay
                }
            }
        }
    })
}

pub fn build_reverse_query(distance: &str, lat: f64, lon: f64) -> serde_json::Value {
    json!({
    "query": {
        "bool": {
            "must": {
                "match_all": {}
            },
            "filter": {
                "geo_distance": {
                    "distance": distance,
                    "coord": {
                        "lat": lat,
                        "lon": lon
                    }
                }
            }
        }
    },
    "sort": [{
         "_geo_distance": {
             "coord": {
                 "lat": lat,
                 "lon": lon
             },
             "order": "asc",
             "unit": "m",
             "mode": "min",
             "distance_type": "arc",
             "ignore_unmapped": true
         }
     }]
    })
}

// If there is a shape, all the places listed in shape_scope are restricted to the shape.
// and the places that are not listed are not restricted.
// So if shape_scope = {A, B}, we should end up with something like
// should [
//   must {               => filwer_w_shape
//     term: _type in {A, B}
//     filter: geoshape
//   },
//   must_not {            => filter_wo_shape
//      term: _type in {A, B}
//   }
// ]
//
pub fn build_shape_query(shape: Geometry, scope: Vec<String>) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
            {
                "bool": {
                    "must": {
                        "terms": {
                            "type": scope
                        }
                    },
                    "filter": {
                        "geo_shape": {
                            "approx_coord": {
                                "shape": shape,
                            "relation": "intersects"
                            }
                        }
                    }
                }
            },
            {
                "bool": {
                    "must_not": {
                        "terms": {
                            "type": scope
                        }
                    }
                }
            }
            ]
        }
    })
}

// If we search for POIs and we specify poi_types, then we add a filter that should say something
// like:
// If the place is a POI, then its poi_type must be part of the given list
// So if poi_types = {A, B}, we should end up with something like
// should [
//   must {               => for pois, filter their poi types
//     type: poi
//     filter: poi_types = {A, B}
//   },
//   must_not {            => or don't filter for poi types on other places
//      type: poi
//   }
// ]
pub fn build_poi_types_filter(poi_types: Vec<String>) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
            {
                "bool": {
                    "must": {
                        "term": {
                            "_source.type": "poi"
                        }
                    },
                    "filter": {
                        "terms": {
                            "_source.poi_type.id": poi_types
                        }
                    }
                }
            },
            {
                "bool": {
                    "must_not": {
                        "term": {
                            "_source.type": "poi"
                        }
                    }
                }
            }
            ]
        }
    })
}

// If we search for administrative regions and we specify zone_types, then we add a filter that should say something
// like:
// If the place is an administrative region, then its zone_type must be part of the given list
// So if zone_type = {A, B}, we should end up with something like
// should [
//   must {               => for admins, make sure they have the right zone type
//     type: admin
//     filter: zone_types
//   },
//   must_not {            => no filter on zone types for other places
//      type: admin
//   }
// ]
//
//
pub fn build_zone_types_filter(zone_types: Vec<String>) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
                {
                    "bool": {
                        "must": {
                            "term": {
                                "type": "admin"
                            }
                        },
                        "filter": {
                            "terms": {
                                "zone_type":zone_types
                            }
                        }
                    }
                },
                {
                    "bool": {
                        "must_not": {
                            "term": {
                                "type": "admin"
                            }
                        }
                    }
                }
            ]
        }
    })
}

pub fn build_features_query(indices: &[String], doc_id: &str) -> serde_json::Value {
    let vec: Vec<BTreeMap<&str, &str>> = indices
        .iter()
        .map(|index| {
            vec![("_index", index.as_str()), ("_id", doc_id)]
                .into_iter()
                .collect()
        })
        .collect();
    json!({ "docs": vec })
}

//
// fn build_coverage_condition() -> serde_json::Value {
//     // filter to handle PT coverages
//     // we either want:
//     // * to get objects with no coverage at all (non-PT objects)
//     // * or the objects with coverage matching the ones we're allowed to get
//     json!({
//             "bool": {
//                 "should": [
//                 {
//                     "bool": {
//                         "must_not": {
//                             "exists": {
//                               "field": "coverages"
//                             }
//                         },
//                     }
//                 },
//                 {
//                     "term": {
//                         "coverages": []
//                     }
//                 }
//             ]
//         }
//     })
// }

// fn build_search_as_you_type_query(q: &str, settings: &settings::StringQuery) -> serde_json::Value {
//     json!({
//         "bool": {
//             "boost": settings.global,
//             "should": [
//                 {
//                     "multi_match": {
//                         "query": q,
//                         "type": "bool_prefix", // match_phrase_prefix query match terms order
//                         "fields": [
//                             "label", "label._2gram", "label._3gram", "name"
//                         ]
//                     }
//                 }
//             ]
//         }
//     })
// }
<commit_msg>compute weight with radius + add in comment unused function that were call end with ElasticSearch2 -> I need to ask/test first to understand theirs purposes<commit_after>use crate::adapters::primary::common::settings::{BuildWeight, ImportanceQueryBoosts, StringQuery};
use geojson::Geometry;
use serde_json::json;

use super::coord::Coord;
use super::{filters, settings};

pub fn build_query(
    q: &str,
    filters: filters::Filters,
    _lang: String,
    settings: &settings::QuerySettings,
) -> serde_json::Value {
    let type_query = build_place_type_boost(&settings.type_query.boosts);
    let string_query = build_string_query(q, _lang.as_str(), &settings.string_query);
    let boosts = build_boosts(q, settings, &filters);
    let mut filters_poi = build_filters(filters.shape, filters.poi_types, filters.zone_types);
    let filters = vec![build_house_number_condition(q), build_matching_condition(q)]
        .append(filters_poi.as_mut());
    json!({
        "query": {
            "bool": {
                "must": [ type_query, string_query ],
                "should": boosts,
                "filter": {
                    "bool": {
                        "must": filters
                    }
                }
            }
        }
    })
}

fn build_string_query(q: &str, _lang: &str, settings: &StringQuery) -> serde_json::Value {
    json!({
        "bool": {
            "boost": settings.global,
            "should": [
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["name", format!("names.{}", _lang)],
                        "boost": settings.boosts.name
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label", format!("label.{}", _lang)],
                        "boost": settings.boosts.label
                    }
                },
                {
                    "multi_match": {
                        "query": q,
                        "fields": ["label.prefix", format!("label.prefix.{}", _lang)],
                        "boost": settings.boosts.label_prefix
                    }
                },
                {
                    "match": {
                        "zip_codes": {
                        "query": q,
                        "boost": settings.boosts.zip_codes
                        }
                    }

                },
                {
                    "match": {
                        "house_number": {
                         "query": q,
                         "boost": settings.boosts.house_number
                        }
                    }
                }
            ]
        }
    })
}

fn build_boosts(
    _q: &str,
    settings: &settings::QuerySettings,
    filters: &filters::Filters,
) -> Vec<serde_json::Value> {
    let mut boosts: Vec<Option<serde_json::Value>> = Vec::new();
    // TODO: in production, admins are boosted by their weight only in prefix mode.
    let admin_weight_boost = Some(build_admin_weight_query(
        &settings.importance_query,
        &filters.coord,
    ));
    boosts.push(admin_weight_boost);
    let mut decay = settings.importance_query.proximity.decay.clone();
    if let Some(proximity) = &filters.proximity {
        decay.scale = proximity.scale;
        decay.offset = proximity.offset;
        decay.decay = proximity.decay;
    }
    let proximity_boost = filters
        .coord
        .clone()
        .map(|coord| build_proximity_boost(coord, &decay));
    boosts.push(proximity_boost);
    boosts.into_iter().flatten().collect()
}

fn build_filters(
    shape: Option<(Geometry, Vec<String>)>,
    poi_types: Option<Vec<String>>,
    zone_types: Option<Vec<String>>,
) -> Vec<serde_json::Value> {
    let mut filters: Vec<Option<serde_json::Value>> = Vec::new();
    let geoshape_filter = shape.map(|(geometry, scope)| build_shape_query(geometry, scope));
    filters.push(geoshape_filter);
    let poi_types_filter = poi_types.map(build_poi_types_filter);
    filters.push(poi_types_filter);
    let zone_types_filter = zone_types.map(build_zone_types_filter);
    filters.push(zone_types_filter);
    filters.into_iter().flatten().collect()
}

fn build_weight_depending_on_radius(
    importance_query_settings: &ImportanceQueryBoosts,
    coord: &Option<Coord>,
) -> BuildWeight {
    let settings_weight = importance_query_settings.clone().weights;

    // Weights for minimal radius
    let min_weights = settings_weight.clone().min_radius_prefix;

    // Weights for maximal radius
    let max_weights = settings_weight.clone().max_radius;

    // Compute a linear combination of `min_weights` and `max_weights` depending of
    // the level of zoom.
    let zoom_ratio = match coord {
        None => 1.,
        Some(_) => {
            let (min_radius, max_radius) = settings_weight.radius_range;
            let curve = importance_query_settings.clone().proximity.decay;
            let radius = (curve.offset + curve.scale).min(max_radius).max(min_radius);
            (radius.ln_1p() - min_radius.ln_1p()) / (max_radius.ln_1p() - min_radius.ln_1p())
        }
    };

    BuildWeight {
        admin: (1. - zoom_ratio) * min_weights.admin + zoom_ratio * max_weights.admin,
        factor: (1. - zoom_ratio) * min_weights.factor + zoom_ratio * max_weights.factor,
        missing: (1. - zoom_ratio) * min_weights.missing + zoom_ratio * max_weights.missing,
    }
}

fn build_house_number_condition(q: &str) -> serde_json::Value {
    if q.split_whitespace().count() > 1 {
        // Filter to handle house number.
        // We either want:
        // * to exactly match the document house_number
        // * or that the document has no house_number
        json!({
            "bool": {
                "should": [
                {
                    "bool": {
                        "must_not": {
                            "exists": {
                              "field": "house_number"
                            }
                        },
                    }
                },
                {
                    "match": {
                        "house_number": {
                            "query": q
                        }
                    }
                }
                ]
            }
        })
    } else {
        // If the query contains a single word, we don't exact any house number in the result.
        json!({
            "bool": {
                "must_not": {
                    "exists": {
                        "field": "house_number"
                    }
                },
            }
        })
    }
}

fn build_matching_condition(q: &str) -> serde_json::Value {
    // Filter to handle house number.
    // We either want:
    // * to exactly match the document house_number
    // * or that the document has no house_number
    json!({
        "match": {
            "full_label.prefix": {
                "query": q,
                "operator": "and"
            }
        }
    })
}

fn build_admin_weight_query(
    settings: &settings::ImportanceQueryBoosts,
    coord: &Option<Coord>,
) -> serde_json::Value {
    let weights = build_weight_depending_on_radius(settings, coord);
    json!({
        "function_score": {
            "query": { "term": { "type": "admin" } },
            "boost_mode": "replace",
            "functions": [
                {
                    "field_value_factor": {
                        "field": "weight",
                        "factor": 1e6,
                        "modifier": "log1p",
                        "missing": 0
                    }
                },
                {
                    "weight": weights.admin
                }
            ]
        }
    })
}

fn build_place_type_boost(settings: &settings::Types) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
                { "term": { "type": { "value": "admin", "boost": settings.admin } } },
                { "term": { "type": { "value": "addr", "boost": settings.address } } },
                { "term": { "type": { "value": "stop", "boost": settings.stop } } },
                { "term": { "type": { "value": "poi", "boost": settings.poi } } },
                { "term": { "type": { "value": "street", "boost": settings.street } } },
            ]
        }
    })
}

fn build_proximity_boost(coord: Coord, settings_decay: &settings::Decay) -> serde_json::Value {
    let settings::Decay {
        func,
        scale,
        offset,
        decay,
    } = settings_decay;

    json!({
        "function_score": {
            func.clone(): {
                "coord": {
                    "origin": {
                        "lat": coord.lat,
                        "lon": coord.lon
                    },
                    "scale": format!("{}km", scale),
                    "offset": format!("{}km", offset),
                    "decay": decay
                }
            }
        }
    })
}

pub fn build_reverse_query(distance: &str, lat: f64, lon: f64) -> serde_json::Value {
    json!({
    "query": {
        "bool": {
            "must": {
                "match_all": {}
            },
            "filter": {
                "geo_distance": {
                    "distance": distance,
                    "coord": {
                        "lat": lat,
                        "lon": lon
                    }
                }
            }
        }
    },
    "sort": [{
         "_geo_distance": {
             "coord": {
                 "lat": lat,
                 "lon": lon
             },
             "order": "asc",
             "unit": "m",
             "mode": "min",
             "distance_type": "arc",
             "ignore_unmapped": true
         }
     }]
    })
}

// If there is a shape, all the places listed in shape_scope are restricted to the shape.
// and the places that are not listed are not restricted.
// So if shape_scope = {A, B}, we should end up with something like
// should [
//   must {               => filwer_w_shape
//     term: _type in {A, B}
//     filter: geoshape
//   },
//   must_not {            => filter_wo_shape
//      term: _type in {A, B}
//   }
// ]
//
pub fn build_shape_query(shape: Geometry, scope: Vec<String>) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
            {
                "bool": {
                    "must": {
                        "terms": {
                            "type": scope
                        }
                    },
                    "filter": {
                        "geo_shape": {
                            "approx_coord": {
                                "shape": shape,
                            "relation": "intersects"
                            }
                        }
                    }
                }
            },
            {
                "bool": {
                    "must_not": {
                        "terms": {
                            "type": scope
                        }
                    }
                }
            }
            ]
        }
    })
}

// If we search for POIs and we specify poi_types, then we add a filter that should say something
// like:
// If the place is a POI, then its poi_type must be part of the given list
// So if poi_types = {A, B}, we should end up with something like
// should [
//   must {               => for pois, filter their poi types
//     type: poi
//     filter: poi_types = {A, B}
//   },
//   must_not {            => or don't filter for poi types on other places
//      type: poi
//   }
// ]
pub fn build_poi_types_filter(poi_types: Vec<String>) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
            {
                "bool": {
                    "must": {
                        "term": {
                            "_source.type": "poi"
                        }
                    },
                    "filter": {
                        "terms": {
                            "_source.poi_type.id": poi_types
                        }
                    }
                }
            },
            {
                "bool": {
                    "must_not": {
                        "term": {
                            "_source.type": "poi"
                        }
                    }
                }
            }
            ]
        }
    })
}

// If we search for administrative regions and we specify zone_types, then we add a filter that should say something
// like:
// If the place is an administrative region, then its zone_type must be part of the given list
// So if zone_type = {A, B}, we should end up with something like
// should [
//   must {               => for admins, make sure they have the right zone type
//     type: admin
//     filter: zone_types
//   },
//   must_not {            => no filter on zone types for other places
//      type: admin
//   }
// ]
//
//
pub fn build_zone_types_filter(zone_types: Vec<String>) -> serde_json::Value {
    json!({
        "bool": {
            "should": [
                {
                    "bool": {
                        "must": {
                            "term": {
                                "type": "admin"
                            }
                        },
                        "filter": {
                            "terms": {
                                "zone_type":zone_types
                            }
                        }
                    }
                },
                {
                    "bool": {
                        "must_not": {
                            "term": {
                                "type": "admin"
                            }
                        }
                    }
                }
            ]
        }
    })
}

pub fn build_features_query(indices: &[String], doc_id: &str) -> serde_json::Value {
    let vec: Vec<BTreeMap<&str, &str>> = indices
        .iter()
        .map(|index| {
            vec![("_index", index.as_str()), ("_id", doc_id)]
                .into_iter()
                .collect()
        })
        .collect();
    json!({ "docs": vec })
}

// fn build_with_weight(build_weight: BuildWeight, types: &Types) -> serde_json::Value {
//     json!({
//         "function_score": {
//             "boost_mode": "replace",
//             "functions": [
//                 {
//                         "query": { "term": { "_type": "admin" } },
//                         "field_value_factor": {
//                             "field": "weight",
//                             "factor": build_weight.factor,
//                             "missing": build_weight.missing
//                     }
//                 },
//                 {
//                         "query": { "term": { "_type": "address" } },
//                         "field_value_factor": {
//                             "field": "weight",
//                             "factor": build_weight.factor,
//                             "missing": build_weight.missing
//                         }
//                 },
//                                 {
//                         "query": { "term": { "_type": "admin" } },
//                         "field_value_factor": {
//                             "field": "weight",
//                             "factor": build_weight.factor,
//                             "missing": build_weight.missing
//                         }
//                 },
//                                 {
//                         "query": { "term": { "_type": "poi" } },
//                         "field_value_factor": {
//                             "field": "weight",
//                             "factor": build_weight.factor,
//                             "missing": build_weight.missing
//                     }
//                 },
//                 {
//                         "query": { "term": { "_type": "street" } },
//                         "field_value_factor": {
//                             "field": "weight",
//                             "factor": build_weight.factor,
//                             "missing": build_weight.missing
//                     }
//                 }
//             ]
//         }
//     })
// }

//
// fn build_coverage_condition() -> serde_json::Value {
//     // filter to handle PT coverages
//     // we either want:
//     // * to get objects with no coverage at all (non-PT objects)
//     // * or the objects with coverage matching the ones we're allowed to get
//     json!({
//             "bool": {
//                 "should": [
//                 {
//                     "bool": {
//                         "must_not": {
//                             "exists": {
//                               "field": "coverages"
//                             }
//                         },
//                     }
//                 },
//                 {
//                     "term": {
//                         "coverages": []
//                     }
//                 }
//             ]
//         }
//     })
// }

// fn build_search_as_you_type_query(q: &str, settings: &settings::StringQuery) -> serde_json::Value {
//     json!({
//         "bool": {
//             "boost": settings.global,
//             "should": [
//                 {
//                     "multi_match": {
//                         "query": q,
//                         "type": "bool_prefix", // match_phrase_prefix query match terms order
//                         "fields": [
//                             "label", "label._2gram", "label._3gram", "name"
//                         ]
//                     }
//                 }
//             ]
//         }
//     })
// }
<|endoftext|>