implement query_filter stuff.
This commit is contained in:
		@ -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<QueryFilter>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub type QueryParserResult = Result<Query, QuerySyntaxError>;
 | 
			
		||||
pub enum QuerySyntaxError {
 | 
			
		||||
    UnknownFilter,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Query {
 | 
			
		||||
    pub fn parse(input: &[String]) -> QueryParserResult {
 | 
			
		||||
    pub fn search(&self, input: &mut Iterator<Item = &Texture>) -> Vec<Texture> {
 | 
			
		||||
        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::<Vec<Texture>>()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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<Query, QueryFilterSyntaxError> {
 | 
			
		||||
        let mut result = Query { filters: vec![] };
 | 
			
		||||
 | 
			
		||||
        for fltr in input {
 | 
			
		||||
            let qryfltr = Self::parse_single(fltr)?;
 | 
			
		||||
 | 
			
		||||
            let qryfltr = fltr.parse::<QueryFilter>()?;
 | 
			
		||||
            result.filters.push(qryfltr);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(result)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn parse_single(input: &str) -> Result<QueryFilter, QuerySyntaxError> {
 | 
			
		||||
        unimplemented!()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn search(input: &[Texture], query: &Query) -> Vec<Texture> {
 | 
			
		||||
    unimplemented!()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum QueryFilter {
 | 
			
		||||
    Not(Box<QueryFilter>),
 | 
			
		||||
    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)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,135 @@
 | 
			
		||||
use crate::model::*;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
pub enum QueryFilter {
 | 
			
		||||
    Not(Box<QueryFilter>),
 | 
			
		||||
    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<QueryFilter, QueryFilterSyntaxError> {
 | 
			
		||||
        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::<u32>()
 | 
			
		||||
                            .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()))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user