From 70502081ddaef03bb8088576a470a4857fed762c Mon Sep 17 00:00:00 2001 From: Benedikt Terhechte Date: Sat, 18 Dec 2021 13:45:32 +0100 Subject: [PATCH] Implement custom queries over fake data --- postsack-web/Cargo.toml | 1 + postsack-web/src/database.rs | 311 +++++++++++++++++++++++++++++++++-- 2 files changed, 296 insertions(+), 16 deletions(-) diff --git a/postsack-web/Cargo.toml b/postsack-web/Cargo.toml index 180acad..a4bef60 100644 --- a/postsack-web/Cargo.toml +++ b/postsack-web/Cargo.toml @@ -12,5 +12,6 @@ crate-type = ["cdylib"] [dependencies] ps-gui = { path = "../ps-gui" } ps-core = { path = "../ps-core" } +serde = { version = "1.0.131", features = ["derive"]} wasm-bindgen = "*" console_error_panic_hook = "0.1.7" diff --git a/postsack-web/src/database.rs b/postsack-web/src/database.rs index 8e075bd..aef308c 100644 --- a/postsack-web/src/database.rs +++ b/postsack-web/src/database.rs @@ -1,46 +1,220 @@ -use std::path::Path; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; use std::thread::JoinHandle; +use std::{ops::Range, path::Path}; use ps_core::{ crossbeam_channel::Sender, eyre::{bail, Result}, - Config, DBMessage, DatabaseLike, DatabaseQuery, Field, Query, QueryResult, ValueField, + Config, DBMessage, DatabaseLike, DatabaseQuery, Field, Filter, Query, QueryResult, Value, + ValueField, }; +use ps_core::{OtherQuery, QueryRow}; -pub struct FakeDatabase {} +#[derive(Default, Clone)] +pub struct Entry { + pub sender_domain: &'static str, + pub sender_local_part: &'static str, + pub sender_name: &'static str, + pub year: usize, + pub month: usize, + pub day: usize, + pub timestamp: usize, + pub subject: &'static str, + pub to_name: &'static str, + pub to_address: &'static str, + pub is_reply: bool, + pub is_send: bool, +} + +impl Entry { + fn value(&self, field: &Field) -> Value { + match field { + Field::Path => Value::String("".to_string()), + Field::SenderDomain => Value::String(self.sender_domain.to_string()), + Field::SenderLocalPart => Value::String(self.sender_local_part.to_string()), + Field::SenderName => Value::String(self.sender_name.to_string()), + Field::Subject => Value::String(self.subject.to_string()), + Field::ToName => Value::String(self.to_name.to_string()), + Field::ToAddress => Value::String(self.to_address.to_string()), + Field::ToGroup => Value::String("".to_string()), + + Field::Year => Value::Number(self.year.into()), + Field::Month => Value::Number(self.month.into()), + Field::Day => Value::Number(self.day.into()), + Field::Timestamp => Value::Number(self.timestamp.into()), + + Field::IsReply => Value::Bool(self.is_reply), + Field::IsSend => Value::Bool(self.is_send), + + Field::MetaIsSeen => Value::Bool(false), + Field::MetaTags => Value::Array(Vec::new()), + } + } + + fn as_row(&self, fields: &[Field]) -> QueryRow { + let mut row = QueryRow::new(); + for field in fields { + let value = self.value(&field); + let value_field = ValueField::new(&field, value); + row.insert(*field, value_field); + } + row + } +} + +#[derive(Debug, PartialEq, Eq)] +struct HashedValue(Value); + +impl Hash for HashedValue { + fn hash(&self, state: &mut H) { + match &self.0 { + Value::String(s) => s.hash(state), + Value::Number(s) => s.hash(state), + Value::Array(s) => { + format!("{:?}", s).hash(state); + } + Value::Bool(s) => s.hash(state), + _ => { + format!("{:?}", &self.0).hash(state); + } + } + } +} + +pub struct FakeDatabase; impl FakeDatabase { - pub fn total_item_count() -> usize { - 33 + fn query_normal( + &self, + fields: &Vec, + filters: &Vec, + range: &Range, + ) -> Vec { + let entries = self.filtered(filters); + let mut result = Vec::new(); + for entry in entries.skip(range.start).take(range.end) { + result.push(QueryResult::Normal(entry.as_row(fields))); + } + result + } + + fn query_grouped(&self, filters: &Vec, group_by: &Field) -> Vec { + let mut map = HashMap::::new(); + for entry in self + .filtered(filters) + .map(|e| HashedValue(e.value(group_by))) + { + let entry = map.entry(entry).or_insert(0); + *entry += 1; + } + + let mut result = Vec::new(); + for (key, value) in map { + result.push(QueryResult::Grouped { + value: ValueField::new(group_by, key.0), + count: value, + }) + } + result + } + + fn query_other(&self, field: &Field) -> Vec { + let mut set = HashSet::::new(); + for entry in &ENTRIES { + let hashed_entry = HashedValue(entry.value(field)); + if !set.contains(&hashed_entry) { + set.insert(hashed_entry); + } + } + + let mut result = Vec::new(); + for value in set { + result.push(QueryResult::Other(ValueField::new(field, value.0))); + } + result + } + + fn filtered<'a>(&'a self, filters: &'a Vec) -> impl Iterator { + ENTRIES.iter().filter(move |entry| { + for filter in filters { + // Go through all filters and escape early if they don't match + match filter { + Filter::Like(vf) => { + let other = entry.value(vf.field()); + if vf.value() != &other { + return false; + } + } + Filter::NotLike(vf) => { + let other = entry.value(vf.field()); + if vf.value() == &other { + return false; + } + } + Filter::Contains(vf) => { + let other = entry.value(vf.field()); + match (&other, vf.value()) { + (Value::String(a), Value::String(b)) => { + if !a.contains(b) { + return false; + } + } + _ => { + let s1 = format!("{}", vf.value()); + let s2 = format!("{}", &other); + if !s2.contains(&s1) { + return false; + } + } + } + } + Filter::Is(vf) => { + let other = entry.value(vf.field()); + if vf.value() != &other { + return false; + } + } + } + } + true + }) + //.cloned() + //.collect() } } impl Clone for FakeDatabase { fn clone(&self) -> Self { - FakeDatabase {} + FakeDatabase } } impl DatabaseQuery for FakeDatabase { fn query(&self, query: &Query) -> Result> { - Ok((0..50) - .map(|e| QueryResult::Grouped { - count: e as usize + 30, - value: ValueField::usize(&Field::Month, e as usize), - }) - .collect()) + match query { + Query::Normal { + fields, + filters, + range, + } => Ok(self.query_normal(fields, filters, range)), + Query::Grouped { filters, group_by } => Ok(self.query_grouped(filters, group_by)), + Query::Other { + query: OtherQuery::All(q), + } => Ok(self.query_other(q)), + } } } impl DatabaseLike for FakeDatabase { - fn new(path: impl AsRef) -> Result + fn new(_path: impl AsRef) -> Result where Self: Sized, { Ok(FakeDatabase {}) } - fn config(path: impl AsRef) -> Result + fn config(_path: impl AsRef) -> Result where Self: Sized, { @@ -48,13 +222,118 @@ impl DatabaseLike for FakeDatabase { } fn total_mails(&self) -> Result { - Ok(0) + Ok(ENTRIES.len()) } fn import(self) -> (Sender, JoinHandle>) { panic!() } - fn save_config(&self, config: Config) -> Result<()> { + fn save_config(&self, _config: Config) -> Result<()> { Ok(()) } } + +#[cfg(target_arch = "wasm32")] +use super::generated::ENTRIES; + +#[cfg(not(target_arch = "wasm32"))] +const ENTRIES: [Entry; 7] = [ + Entry { + sender_local_part: "tellus.non.magna", + is_send: true, + to_address: "john@doe.com", + sender_name: "Sybill Fleming", + timestamp: 1625731134, + month: 12, + to_name: "", + is_reply: false, + year: 2013, + sender_domain: "protonmail.edu", + day: 28, + subject: "libero et tristique pellentesque, tellus sem mollis dui,", + }, + Entry { + sender_local_part: "mauris.sapien", + is_send: true, + to_address: "john@doe.com", + sender_name: "Ignatius Reed", + timestamp: 1645571678, + month: 10, + to_name: "", + is_reply: true, + year: 2020, + sender_domain: "icloud.com", + day: 26, + subject: "nisi magna sed dui. Fusce aliquam,", + }, + Entry { + sender_local_part: "magna.nam", + is_send: false, + to_address: "john@doe.com", + sender_name: "Geraldine Gay", + timestamp: 1631684202, + month: 8, + to_name: "", + is_reply: true, + year: 2016, + sender_domain: "aol.org", + day: 18, + subject: "semper auctor. Mauris vel turpis. Aliquam adipiscing", + }, + Entry { + sender_local_part: "tortor", + is_send: true, + to_address: "john@doe.com", + sender_name: "Colt Clark", + timestamp: 1640866204, + month: 4, + to_name: "", + is_reply: true, + year: 2012, + sender_domain: "aol.ca", + day: 2, + subject: "hendrerit id, ante. Nunc mauris sapien, cursus", + }, + Entry { + sender_local_part: "urna.convallis.erat", + is_send: true, + to_address: "john@doe.com", + sender_name: "Joy Clark", + timestamp: 1646836804, + month: 2, + to_name: "", + is_reply: true, + year: 2020, + sender_domain: "protonmail.ca", + day: 10, + subject: "dui nec urna suscipit nonummy. Fusce fermentum fermentum arcu. Vestibulum", + }, + Entry { + sender_local_part: "amet.luctus", + is_send: false, + to_address: "john@doe.com", + sender_name: "Ray Bowers", + timestamp: 1609958850, + month: 6, + to_name: "", + is_reply: false, + year: 2015, + sender_domain: "protonmail.org", + day: 30, + subject: "turpis egestas. Aliquam fringilla cursus", + }, + Entry { + sender_local_part: "vehicula.et", + is_send: true, + to_address: "john@doe.com", + sender_name: "Maris Shaw", + timestamp: 1612463990, + month: 10, + to_name: "", + is_reply: false, + year: 2018, + sender_domain: "hotmail.ca", + day: 30, + subject: "molestie orci tincidunt", + }, +];