Ported the database crate over

pull/7/head
Benedikt Terhechte 3 years ago
parent 812d635bc5
commit 2a505f0d83

@ -1,4 +0,0 @@
mod database;
mod importer;
mod model;
mod types;

@ -348,7 +348,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]]
name = "postsack-core"
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro2"
version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "ps-core"
version = "0.2.0"
dependencies = [
"chrono",
@ -373,21 +388,6 @@ dependencies = [
"treemap",
]
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro2"
version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.10"

@ -1,5 +1,5 @@
[package]
name = "postsack-core"
name = "ps-core"
version = "0.2.0"
edition = "2021"
description = "Provides a high level visual overview of swaths of email"

@ -0,0 +1,10 @@
mod database;
mod importer;
mod model;
mod types;
pub use database::query::{Field, OtherQuery, Query, ValueField, AMOUNT_FIELD_NAME};
pub use database::query_result::QueryResult;
pub use types::{Config, EmailEntry, EmailMeta, FormatType};
pub use crossbeam_channel;

@ -0,0 +1,50 @@
use chrono::prelude::*;
use std::path::PathBuf;
pub type Tag = String;
/// This is based on additional information in some systems such as
/// Gmail labels or Apple Mail tags or Apple XML
#[derive(Debug, Default)]
pub struct EmailMeta {
pub tags: Vec<Tag>,
pub is_seen: bool,
}
const TAG_SEP: &str = ":|:";
impl EmailMeta {
pub fn tags_from_string(tag_string: &str) -> Vec<String> {
tag_string.split(TAG_SEP).map(|e| e.to_string()).collect()
}
pub fn from(is_seen: bool, tag_string: &str) -> Self {
let tags = EmailMeta::tags_from_string(tag_string);
EmailMeta { tags, is_seen }
}
pub fn tags_string(&self) -> String {
self.tags.join(TAG_SEP)
}
}
/// Representation of an email
#[derive(Debug)]
pub struct EmailEntry {
pub path: PathBuf,
pub sender_domain: String,
pub sender_local_part: String,
pub sender_name: String,
pub datetime: chrono::DateTime<Utc>,
pub subject: String,
/// The amount of `to:` adresses
pub to_count: usize,
/// When this email was send to a group, the group name
pub to_group: Option<String>,
/// The first address and name in `To`, if any
pub to_first: Option<(String, String)>,
pub is_reply: bool,
/// Was this email send from the account we're importing?
pub is_send: bool,
pub meta: Option<EmailMeta>,
}

@ -1,4 +1,5 @@
mod config;
mod email;
mod format_type;
pub use config::{Config, FormatType};
// pub use format_type::ImporterFormatType;
pub use email::{EmailEntry, EmailMeta};

@ -0,0 +1,844 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "arrayvec"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
dependencies = [
"nodrop",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi",
]
[[package]]
name = "crc32fast"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
dependencies = [
"cfg-if",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
dependencies = [
"cfg-if",
"lazy_static",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "eyre"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "221239d1d5ea86bf5d6f91c9d6bc3646ffe471b08ff9b0f91c44f115ac969d2b"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "flate2"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
dependencies = [
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
dependencies = [
"hashbrown",
]
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "itoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
[[package]]
name = "libsqlite3-sys"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cafc7c74096c336d9d27145f7ebd4f4b6f95ba16aa5a282387267e6925cb58"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "lru"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c748cfe47cb8da225c37595b3108bea1c198c84aaae8ea0ba76d01dda9fc803"
dependencies = [
"hashbrown",
]
[[package]]
name = "memchr"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "nodrop"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]]
name = "num-format"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465"
dependencies = [
"arrayvec",
"itoa 0.4.8",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
[[package]]
name = "pin-project-lite"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
[[package]]
name = "pkg-config"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe"
[[package]]
name = "ppv-lite86"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
[[package]]
name = "proc-macro2"
version = "1.0.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f84e92c0f7c9d58328b85a78557813e4bd845130db68d7184635344399423b1"
dependencies = [
"unicode-xid",
]
[[package]]
name = "ps-core"
version = "0.2.0"
dependencies = [
"chrono",
"crossbeam-channel",
"eyre",
"flate2",
"lru",
"num-format",
"once_cell",
"rand",
"rayon",
"regex",
"rsql_builder",
"serde",
"serde_json",
"shellexpand",
"strum",
"strum_macros",
"thiserror",
"tracing",
"tracing-subscriber",
"treemap",
]
[[package]]
name = "ps-database"
version = "0.2.0"
dependencies = [
"chrono",
"eyre",
"ps-core",
"rsql_builder",
"rusqlite",
"serde",
"serde_json",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "quote"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core",
]
[[package]]
name = "rayon"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "rsql_builder"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dbd5712883cef396d13516bb52b300fd97a29d52ca20361f0a4905bd38a2355"
dependencies = [
"serde",
"serde_json",
]
[[package]]
name = "rusqlite"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7"
dependencies = [
"bitflags",
"chrono",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"memchr",
"serde_json",
"smallvec",
]
[[package]]
name = "rustversion"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
[[package]]
name = "ryu"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.131"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ad69dfbd3e45369132cc64e6748c2d65cdfb001a2b1c232d128b4ad60561c1"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.131"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b710a83c4e0dff6a3d511946b95274ad9ca9e5d3ae497b63fda866ac955358d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
dependencies = [
"itoa 1.0.1",
"ryu",
"serde",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "shellexpand"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829"
dependencies = [
"dirs-next",
]
[[package]]
name = "smallvec"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "strum"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb"
[[package]]
name = "strum_macros"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
[[package]]
name = "syn"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi",
"winapi",
]
[[package]]
name = "tracing"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
dependencies = [
"lazy_static",
]
[[package]]
name = "tracing-log"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245da694cc7fc4729f3f418b304cb57789f1bed2a78c575407ab8a23f53cb4d3"
dependencies = [
"ansi_term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "treemap"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1571f89da27a5e1aa83304ee1ab9519ea8c6432b4c8903aaaa6c9a9eecb6f36"
[[package]]
name = "unicode-segmentation"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -0,0 +1,16 @@
[package]
name = "ps-database"
version = "0.2.0"
edition = "2021"
[dependencies]
eyre = "0.6.5"
thiserror = "1.0.29"
tracing = "0.1.29"
tracing-subscriber = "0.3.0"
rusqlite = {version = "0.26.1", features = ["chrono", "trace", "serde_json", "bundled"]}
chrono = "0.4.19"
serde_json = "1.0.70"
serde = { version = "1.0.130", features = ["derive"]}
rsql_builder = "0.1.2"
ps-core = { path = "../ps-core" }

@ -0,0 +1,142 @@
use std::collections::HashMap;
use std::convert::TryInto;
use std::str::FromStr;
use chrono::prelude::*;
use eyre::{bail, eyre, Result};
use rusqlite::{self, types, Row};
use serde_json::Value;
use ps_core::{EmailEntry, EmailMeta, Field, QueryResult, ValueField, AMOUNT_FIELD_NAME};
/// rusqlite does offer Serde to Value conversion, but it
/// converts everything to strings!
pub fn json_to_value(input: &Value) -> Result<types::Value> {
let ok = match input {
Value::Number(n) if n.is_i64() => {
types::Value::Integer(n.as_i64().ok_or_else(|| eyre!("Invalid Number {:?}", n))?)
}
Value::Number(n) if n.is_u64() => {
let value = n.as_u64().ok_or_else(|| eyre!("Invalid Number {:?}", n))?;
let converted: i64 = value.try_into()?;
types::Value::Integer(converted)
}
Value::Number(n) if n.is_f64() => {
types::Value::Real(n.as_f64().ok_or_else(|| eyre!("Invalid Number {:?}", n))?)
}
Value::Bool(n) => types::Value::Integer(*n as i64),
Value::String(n) => types::Value::Text(n.clone()),
_ => bail!("Invalid type: {}", &input),
};
Ok(ok)
}
pub trait RowConversion<'a>: Sized {
fn grouped_from_row<'stmt>(field: &'a Field, row: &Row<'stmt>) -> Result<Self>;
fn from_row<'stmt>(fields: &'a [Field], row: &Row<'stmt>) -> Result<Self>;
}
impl<'a> RowConversion<'a> for QueryResult {
fn grouped_from_row<'stmt>(field: &'a Field, row: &Row<'stmt>) -> Result<Self> {
let amount: usize = row.get(AMOUNT_FIELD_NAME)?;
let values = values_from_fields(&[*field], row)?;
Ok(QueryResult::Grouped {
count: amount,
value: values[field].clone(),
})
}
fn from_row<'stmt>(fields: &'a [Field], row: &Row<'stmt>) -> Result<Self> {
let values = values_from_fields(fields, row)?;
Ok(QueryResult::Normal(values))
}
}
fn values_from_fields<'stmt>(
fields: &[Field],
row: &Row<'stmt>,
) -> Result<HashMap<Field, ValueField>> {
let mut values: HashMap<Field, ValueField> = HashMap::default();
for field in fields {
values.insert(*field, value_from_field(field, row)?);
}
Ok(values)
}
pub fn value_from_field<'stmt>(field: &Field, row: &Row<'stmt>) -> Result<ValueField> {
use Field::*;
// Use type safety when unpacking
match field {
Path | SenderDomain | SenderLocalPart | SenderName | ToGroup | ToName | ToAddress
| Subject => {
let string: String = row.get::<&str, String>(field.as_str())?;
Ok(ValueField::string(field, &string))
}
Year | Month | Day | Timestamp => {
return Ok(ValueField::usize(
field,
row.get::<&str, usize>(field.as_str())?,
));
}
MetaTags => {
let tag_string = row.get::<&str, String>(field.as_str())?;
let tags = EmailMeta::tags_from_string(&tag_string);
Ok(ValueField::array(
field,
tags.into_iter().map(Value::String).collect(),
))
}
IsReply | IsSend | MetaIsSeen => {
return Ok(ValueField::bool(
field,
row.get::<&str, bool>(field.as_str())?,
));
}
}
}
// impl EmailEntry {
// #[allow(unused)]
// fn from_row(row: &Row<'_>) -> Result<Self> {
// let path: String = row.get("path")?;
// let path = std::path::PathBuf::from_str(&path)?;
// let sender_domain: String = row.get("sender_domain")?;
// let sender_local_part: String = row.get("sender_local_part")?;
// let sender_name: String = row.get("sender_name")?;
// let timestamp: i64 = row.get("timestamp")?;
// let datetime = Utc.timestamp(timestamp, 0);
// let subject: String = row.get("subject")?;
// let to_count: usize = row.get("to_count")?;
// let to_group: Option<String> = row.get("to_group")?;
// let to_name: Option<String> = row.get("to_name")?;
// let to_address: Option<String> = row.get("to_address")?;
// let to_first = to_address.map(|a| (to_name.unwrap_or_default(), a));
// let is_reply: bool = row.get("is_reply")?;
// let is_send: bool = row.get("is_send")?;
// // Parse EmailMeta
// let meta_tags: Option<String> = row.get("meta_tags")?;
// let meta_is_seen: Option<bool> = row.get("meta_is_seen")?;
// let meta = match (meta_tags, meta_is_seen) {
// (Some(a), Some(b)) => Some(EmailMeta::from(b, &a)),
// _ => None,
// };
// Ok(EmailEntry {
// path,
// sender_domain,
// sender_local_part,
// sender_name,
// datetime,
// subject,
// to_count,
// to_group,
// to_first,
// is_reply,
// is_send,
// meta,
// })
// }
// }

@ -0,0 +1,252 @@
use chrono::Datelike;
use eyre::{bail, Report, Result};
use rusqlite::{self, params, Connection, Statement};
use std::{collections::HashMap, path::Path, thread::JoinHandle};
use super::{sql::*, DBMessage};
use super::{value_from_field, RowConversion};
use ps_core::{
crossbeam_channel::{unbounded, Sender},
Config, EmailEntry, OtherQuery, Query, QueryResult,
};
#[derive(Debug)]
pub struct Database {
connection: Option<Connection>,
}
impl Database {
/// Open a database and try to retrieve a config from the information stored in there
pub fn config<P: AsRef<Path>>(path: P) -> Result<Config> {
let database = Self::new(path.as_ref())?;
let fields = database.select_config_fields()?;
Config::from_fields(path.as_ref(), fields)
}
/// Open database at path `Path`.
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
#[allow(unused_mut)]
let mut connection = Connection::open(path.as_ref())?;
// Improve the insertion performance.
connection.pragma_update(None, "journal_mode", &"memory")?;
connection.pragma_update(None, "synchronous", &"OFF")?;
Self::create_tables(&connection)?;
#[cfg(feature = "trace-sql")]
connection.trace(Some(|query| {
tracing::trace!("SQL: {}", &query);
}));
Ok(Database {
connection: Some(connection),
})
}
pub fn total_mails(&self) -> Result<usize> {
let connection = match &self.connection {
Some(n) => n,
None => bail!("No connection to database available in query"),
};
let mut stmt = connection.prepare(QUERY_COUNT_MAILS)?;
let count: usize = stmt.query_row([], |q| q.get(0))?;
Ok(count)
}
pub fn save_config(&self, config: Config) -> Result<()> {
let fields = config
.into_fields()
.ok_or_else(|| eyre::eyre!("Could not create fields from config"))?;
self.insert_config_fields(fields)
}
pub fn query(&self, query: &Query) -> Result<Vec<QueryResult>> {
use rusqlite::params_from_iter;
let c = match &self.connection {
Some(n) => n,
None => bail!("No connection to database available in query"),
};
let (sql, values) = query.to_sql();
let mut stmt = c.prepare(&sql)?;
let mut query_results = Vec::new();
let mut converted = Vec::new();
for value in values {
converted.push(super::conversion::json_to_value(&value)?);
}
let p = params_from_iter(converted.iter());
let mut rows = stmt.query(p)?;
while let Some(row) = rows.next()? {
match query {
Query::Grouped { group_by, .. } => {
let result = QueryResult::grouped_from_row(group_by, row)?;
query_results.push(result);
}
Query::Normal { fields, .. } => {
let result = QueryResult::from_row(fields, row)?;
query_results.push(result);
}
Query::Other {
query: OtherQuery::All(field),
} => query_results.push(QueryResult::Other(value_from_field(field, row)?)),
}
}
Ok(query_results)
}
/// Begin the data import.
/// This will consume the `Database`. A new one has to be opened
/// afterwards in order to support multi-threading.
/// Returns an input `Sender` and a `JoinHandle`.
/// The `Sender` is used to submit work to the database via `DBMessage`
/// cases. The `JoinHandle` is used to wait for database completion.
///
/// # Examples
///
/// ``` ignore
/// let db = Database::new("db.sqlite").unwrap();
/// let (sender, handle) = db.import();
/// sender.send(DBMessage::Mail(m1)).unwrap();
/// sender.send(DBMessage::Mail(m2)).unwrap();
/// handle.join().unwrap();
/// ```
pub fn import(mut self) -> (Sender<DBMessage>, JoinHandle<Result<usize>>) {
let (sender, receiver) = unbounded();
// Import can only be called *once* on a database created with `new`.
// Therefore there should always be a value to unwrap;
let mut connection = self.connection.take().unwrap();
let handle = std::thread::spawn(move || {
let mut counter = 0;
{
let transaction = connection.transaction()?;
{
let mut mail_prepared = transaction.prepare(QUERY_EMAILS)?;
let mut error_prepared = transaction.prepare(QUERY_ERRORS)?;
loop {
let next = match receiver.recv() {
Ok(n) => n,
Err(e) => {
println!("Receiver error: {:?}", &e);
panic!("should not happen");
}
};
match next {
DBMessage::Mail(mail) => {
counter += 1;
insert_mail(&mut mail_prepared, &mail)
}
DBMessage::Error(report) => insert_error(&mut error_prepared, &report),
DBMessage::Done => {
tracing::trace!("Received DBMessage::Done");
break;
}
}?;
}
}
if let Err(e) = transaction.commit() {
return Err(eyre::eyre!("Transaction Error: {:?}", &e));
}
}
// In case closing the database fails, we try again until we succeed
let mut c = connection;
loop {
tracing::trace!("Attempting close");
match c.close() {
Ok(_n) => break,
Err((a, _b)) => c = a,
}
}
tracing::trace!("Finished SQLITE: {}", &counter);
Ok(counter)
});
(sender, handle)
}
fn create_tables(connection: &Connection) -> Result<()> {
connection.execute(TBL_EMAILS, params![])?;
connection.execute(TBL_ERRORS, params![])?;
connection.execute(TBL_META, params![])?;
Ok(())
}
fn select_config_fields(&self) -> Result<HashMap<String, serde_json::Value>> {
let connection = match &self.connection {
Some(n) => n,
None => bail!("No connection to database available in query"),
};
let mut stmt = connection.prepare(QUERY_SELECT_META)?;
let mut query_results = HashMap::new();
let mut rows = stmt.query([])?;
while let Some(row) = rows.next()? {
let (k, v) = match (
row.get::<_, String>("key"),
row.get::<_, serde_json::Value>("value"),
) {
(Ok(k), Ok(v)) => (k, v),
(a, b) => {
tracing::error!("Invalid row data. Missing fields key and or value:\nkey: {:?}\nvalue: {:?}\n", a, b);
continue;
}
};
query_results.insert(k, v);
}
Ok(query_results)
}
fn insert_config_fields(&self, fields: HashMap<String, serde_json::Value>) -> Result<()> {
let connection = match &self.connection {
Some(n) => n,
None => bail!("No connection to database available in query"),
};
let mut stmt = connection.prepare(QUERY_INSERT_META)?;
for (key, value) in fields {
stmt.execute(params![key, value])?;
}
Ok(())
}
}
fn insert_mail(statement: &mut Statement, entry: &EmailEntry) -> Result<()> {
let path = entry.path.display().to_string();
let year = entry.datetime.date().year();
let month = entry.datetime.date().month();
let day = entry.datetime.date().day();
let timestamp = entry.datetime.timestamp();
let e = entry;
let to_name = e.to_first.as_ref().map(|e| &e.0);
let to_address = e.to_first.as_ref().map(|e| &e.1);
let meta_tags = e.meta.as_ref().map(|e| e.tags_string());
let meta_is_seen = e.meta.as_ref().map(|e| e.is_seen);
let p = params![
path,
e.sender_domain,
e.sender_local_part,
e.sender_name,
year,
month,
day,
timestamp,
e.subject,
e.to_count,
e.to_group,
to_name,
to_address,
e.is_reply,
e.is_send,
meta_tags,
meta_is_seen
];
statement.execute(p)?;
tracing::trace!("Insert Mail {}", &path);
Ok(())
}
fn insert_error(statement: &mut Statement, message: &Report) -> Result<()> {
statement.execute(params![message.to_string()])?;
tracing::trace!("Insert Error {}", message);
Ok(())
}

@ -0,0 +1,14 @@
use eyre::Report;
use ps_core::EmailEntry;
/// Parameter for sending work to the database during `import`.
pub enum DBMessage {
/// Send for a successfuly parsed mail
Mail(Box<EmailEntry>),
/// Send for any kind of error during reading / parsing
Error(Report),
/// Send once all parsing is done.
/// This is used to break out of the receiving loop
Done,
}

@ -0,0 +1,8 @@
mod conversion;
mod db;
mod db_message;
mod sql;
pub use conversion::{value_from_field, RowConversion};
pub use db::Database;
pub use db_message::DBMessage;

@ -0,0 +1,71 @@
pub const TBL_EMAILS: &str = r#"
CREATE TABLE IF NOT EXISTS emails (
path TEXT NOT NULL,
sender_domain TEXT NOT NULL,
sender_local_part TEXT NOT NULL,
sender_name TEXT NOT NULL,
year INTEGER NOT NULL,
month INTEGER NOT NULL,
day INTEGER NOT NULL,
timestamp INTEGER NOT NULL,
subject TEXT NOT NULL,
to_count INTEGER NOT NULL,
to_group TEXT NULL,
to_name TEXT NULL,
to_address TEXT NULL,
is_reply BOOL,
is_send BOOL,
meta_tags TEXT NULL,
meta_is_seen BOOL NULL
);"#;
pub const QUERY_EMAILS: &str = r#"
INSERT INTO emails
(
path, sender_domain, sender_local_part, sender_name,
year, month, day, timestamp, subject,
to_count, to_group, to_name, to_address,
is_reply, is_send,
meta_tags, meta_is_seen
)
VALUES
(
?, ?, ?, ?,
?, ?, ?, ?, ?,
?, ?, ?, ?,
?, ?,
?, ?
)
"#;
pub const TBL_ERRORS: &str = r#"
CREATE TABLE IF NOT EXISTS errors (
message TEXT NOT NULL
);"#;
pub const QUERY_ERRORS: &str = r#"
INSERT INTO errors
(message)
VALUES
(?)
"#;
pub const TBL_META: &str = r#"
CREATE TABLE IF NOT EXISTS meta (
key TEXT NOT NULL,
value TEXT NOT NULL
);"#;
pub const QUERY_INSERT_META: &str = r#"
INSERT INTO meta
(key, value)
VALUES
(?, ?)
"#;
pub const QUERY_SELECT_META: &str = r#"
SELECT key, value FROM meta"#;
pub const QUERY_COUNT_MAILS: &str = r#"
SELECT count(path) FROM emails
"#;
Loading…
Cancel
Save