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, Filter, Query, QueryResult, Value, ValueField, }; use ps_core::{OtherQuery, QueryRow}; #[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); #[allow(clippy::derive_hash_xor_eq)] 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 { #[allow(unused)] pub fn total_item_count() -> usize { ENTRIES.len() } fn query_normal( &self, fields: &[Field], filters: &[Filter], 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: &[Filter], 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 [Filter]) -> 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 }) } } impl Clone for FakeDatabase { fn clone(&self) -> Self { FakeDatabase } } impl DatabaseQuery for FakeDatabase { fn query(&self, query: &Query) -> Result> { 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 where Self: Sized, { Ok(FakeDatabase {}) } fn config(_path: impl AsRef) -> Result where Self: Sized, { bail!("Na") } fn total_mails(&self) -> Result { Ok(ENTRIES.len()) } fn import(self) -> (Sender, JoinHandle>) { panic!() } 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", }, ];