From b53b3af8c00c05b845bbd2d2401ae0b2ceae2145 Mon Sep 17 00:00:00 2001 From: CodeSteak Date: Tue, 7 May 2019 18:36:23 +0200 Subject: [PATCH] implement query_filter stuff. --- .../src/persistency/search/mod.rs | 129 +++++++---------- .../src/persistency/search/query_filter.rs | 135 ++++++++++++++++++ 2 files changed, 187 insertions(+), 77 deletions(-) create mode 100644 server/texture-sync-server/src/persistency/search/query_filter.rs diff --git a/server/texture-sync-server/src/persistency/search/mod.rs b/server/texture-sync-server/src/persistency/search/mod.rs index fcf0df7..70f03ed 100644 --- a/server/texture-sync-server/src/persistency/search/mod.rs +++ b/server/texture-sync-server/src/persistency/search/mod.rs @@ -1,93 +1,68 @@ -#![allow(unused_variables)] -#![allow(dead_code)] - use crate::model::*; +mod query_filter; +pub use self::query_filter::QueryFilterSyntaxError; +use self::query_filter::*; + pub struct Query { filters: Vec, } -pub type QueryParserResult = Result; -pub enum QuerySyntaxError { - UnknownFilter, -} - impl Query { - pub fn parse(input: &[String]) -> QueryParserResult { + pub fn search(&self, input: &mut Iterator) -> Vec { + let mut results: Vec<(u64, &Texture)> = Vec::new(); + + // We use pseudo decimal fixed point numbers here. + // 1.000_000 = 1_000_000; + + // This is done, since algorithms like quicksort can fail on floats. + let required_score = self.required_score() * 1_000_000u64; + + for texture in input { + let mut score = 0u64; + + for (pos, filter) in self.filters.iter().enumerate() { + match filter.score(texture) { + Score::RequiredMatch(true) => (), + Score::RequiredMatch(false) => { + // skip this texture + continue; + } + Score::Match(true) => { + score += 1_000_000u64 * (1 + (0.1 / f64::sqrt(pos as f64 + 1.0)) as u64); + } + Score::Match(false) => (), + } + } + + if score >= required_score { + results.push((score, texture)) + } + } + + results.sort_by_key(|(score, _)| *score); + + results + .iter() + .map(|(_, texture)| Texture::clone(texture)) + .collect::>() + } + + fn required_score(&self) -> u64 { + let non_special = self.filters.iter().filter(|f| !f.is_special()).count() as u64; + + // ceil(non_special / 2) + (non_special + 1) / 2 + } + + pub fn parse(input: &[String]) -> Result { let mut result = Query { filters: vec![] }; for fltr in input { - let qryfltr = Self::parse_single(fltr)?; - + let qryfltr = fltr.parse::()?; result.filters.push(qryfltr); } Ok(result) } - - fn parse_single(input: &str) -> Result { - unimplemented!() - } -} - -pub fn search(input: &[Texture], query: &Query) -> Vec { - unimplemented!() -} - -enum QueryFilter { - Not(Box), - Tag(String), - SpecialInName(String), - SpecialBeforeDate(Date), - SpecialAfterDate(Date), - SpecialMinResolution(u64), -} - -enum Score { - RequiredMatch(bool), - Match(bool), -} - -use std::ops::Not; -impl Not for Score { - type Output = Score; - - fn not(self) -> Score { - match self { - Score::RequiredMatch(b) => Score::RequiredMatch(!b), - Score::Match(b) => Score::Match(!b), - } - } -} - -impl QueryFilter { - pub fn score(&self, texture: &Texture) -> Score { - use QueryFilter::*; - match self { - Not(inner) => inner.score(texture).not(), - - Tag(tag) => Score::Match( - texture - .tags - .iter() - .find(|tt| tt.to_lowercase() == tag.to_lowercase()) - .is_some(), - ), - - SpecialInName(name) => Score::RequiredMatch( - // - texture.name.contains(name), - ), - - SpecialBeforeDate(date) => Score::RequiredMatch(texture.added_on <= *date), - - SpecialAfterDate(date) => Score::RequiredMatch(texture.added_on > *date), - - SpecialMinResolution(required) => { - let smaller_resolution = u64::min(texture.resolution.0, texture.resolution.1); - - Score::RequiredMatch(smaller_resolution >= *required) - } - } - } } diff --git a/server/texture-sync-server/src/persistency/search/query_filter.rs b/server/texture-sync-server/src/persistency/search/query_filter.rs new file mode 100644 index 0000000..0a43db6 --- /dev/null +++ b/server/texture-sync-server/src/persistency/search/query_filter.rs @@ -0,0 +1,135 @@ +use crate::model::*; +use std::str::FromStr; + +pub enum QueryFilter { + Not(Box), + Tag(String), + SpecialInName(String), + SpecialBeforeDate(Date), + SpecialAfterDate(Date), + SpecialMinResolution(u64), +} + +pub enum Score { + RequiredMatch(bool), + Match(bool), +} + +pub enum QueryFilterSyntaxError { + UnknownSpecialFilter, + ResolutionArgumentInvalid, + DateArgumentInvalid, +} + +use std::ops::Not; +impl Not for Score { + type Output = Score; + + fn not(self) -> Score { + match self { + Score::RequiredMatch(b) => Score::RequiredMatch(!b), + Score::Match(b) => Score::Match(!b), + } + } +} + +impl QueryFilter { + pub fn is_special(&self) -> bool { + use QueryFilter::*; + match self { + Not(inner) => inner.is_special(), + Tag(_) => false, + SpecialInName(_) => true, + SpecialBeforeDate(_) => true, + SpecialAfterDate(_) => true, + SpecialMinResolution(_) => true, + } + } + + pub fn score(&self, texture: &Texture) -> Score { + use QueryFilter::*; + match self { + Not(inner) => inner.score(texture).not(), + + Tag(tag) => Score::Match( + texture + .tags + .iter() + .find(|tt| tt.to_lowercase() == tag.to_lowercase()) + .is_some(), + ), + + SpecialInName(name) => Score::RequiredMatch( + // + texture.name.contains(name), + ), + + SpecialBeforeDate(date) => Score::RequiredMatch(texture.added_on <= *date), + + SpecialAfterDate(date) => Score::RequiredMatch(texture.added_on > *date), + + SpecialMinResolution(required) => { + let smaller_resolution = u64::min(texture.resolution.0, texture.resolution.1); + + Score::RequiredMatch(smaller_resolution >= *required) + } + } + } +} + +impl FromStr for QueryFilter { + type Err = QueryFilterSyntaxError; + + fn from_str(input: &str) -> Result { + const NEGATION_CHAR: char = '!'; + const SPLIT_CHAR: char = ':'; + + if input.starts_with(NEGATION_CHAR) { + // A Not. + let inner = Self::from_str(&input[1..])?; + return Ok(QueryFilter::Not(Box::new(inner))); + } else if input.contains(SPLIT_CHAR) { + // A Special Filter + let mut parts = input.splitn(2, SPLIT_CHAR); + let filter_name = parts.next().unwrap().to_lowercase(); + let filter_arg = parts.next().unwrap(); + + match filter_name.as_str() { + "n" | "name" => Ok(QueryFilter::SpecialInName(filter_name.to_string())), + "r" | "res" | "resolution" => { + let num: u64; + + if filter_arg.ends_with("k") { + num = *&filter_arg[0..(filter_arg.len() - 1)] + .parse::() + .map(|n| (n as u64) * 1024) + .map_err(|_e| QueryFilterSyntaxError::ResolutionArgumentInvalid)?; + } else { + num = filter_arg + .parse() + .map_err(|_e| QueryFilterSyntaxError::ResolutionArgumentInvalid)?; + } + + Ok(QueryFilter::SpecialMinResolution(num)) + } + "a" | "after" => { + let date = Date::from_str(filter_arg) + .ok_or(QueryFilterSyntaxError::DateArgumentInvalid)?; + + Ok(QueryFilter::SpecialAfterDate(date)) + } + "b" | "bef" | "before" => { + let date = Date::from_str(filter_arg) + .ok_or(QueryFilterSyntaxError::DateArgumentInvalid)?; + + Ok(QueryFilter::SpecialBeforeDate(date)) + } + + _ => Err(QueryFilterSyntaxError::UnknownSpecialFilter), + } + } else { + // A Tag + Ok(QueryFilter::Tag(input.to_string())) + } + } +}