From a43f6919cc74cb86df3ca5cbc4121df621fa4f03 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 3 Dec 2019 13:25:49 +0200 Subject: [PATCH 01/11] JMAP WIP --- Cargo.lock | 842 +++++++++++++++++++++++++++- melib/Cargo.toml | 5 +- melib/src/backends.rs | 14 + melib/src/backends/jmap.rs | 295 ++++++++++ melib/src/backends/jmap/folder.rs | 78 +++ melib/src/backends/jmap/protocol.rs | 261 +++++++++ melib/src/error.rs | 16 + 7 files changed, 1496 insertions(+), 15 deletions(-) create mode 100644 melib/src/backends/jmap.rs create mode 100644 melib/src/backends/jmap/folder.rs create mode 100644 melib/src/backends/jmap/protocol.rs diff --git a/Cargo.lock b/Cargo.lock index 1cc74b5c..42da72a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,10 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "anyhow" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "arc-swap" version = "0.4.2" @@ -48,6 +53,11 @@ dependencies = [ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bincode" version = "1.2.0" @@ -77,11 +87,25 @@ name = "block" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bumpalo" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "c2-chacha" version = "0.2.2" @@ -232,6 +256,11 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dtoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "encoding" version = "0.2.33" @@ -289,6 +318,14 @@ name = "encoding_index_tests" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "encoding_rs" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.5" @@ -392,6 +429,43 @@ name = "fuchsia-zircon-sys" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "futures-channel-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures-core-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-io-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-sink-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-util-preview" +version = "0.3.0-alpha.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-io-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "getrandom" version = "0.1.6" @@ -401,6 +475,121 @@ dependencies = [ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "h2" +version = "0.2.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hermit-abi" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http-body" +version = "0.2.0-alpha.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hyper" +version = "0.13.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "http-body 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-net 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)", + "tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", + "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper-tls" +version = "0.4.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hyper 0.13.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "indexmap" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "inotify" version = "0.6.1" @@ -433,6 +622,14 @@ name = "itoa" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "js-sys" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -490,6 +687,14 @@ dependencies = [ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "lock_api" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "log" version = "0.4.6" @@ -525,13 +730,23 @@ dependencies = [ "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "meli" version = "0.4.1" dependencies = [ "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "melib 0.4.1", - "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "ui 0.4.1", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -554,8 +769,10 @@ dependencies = [ "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)", "notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.10.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "text_processing 0.4.1", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -594,11 +811,25 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mime" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "mime_apps" version = "0.2.0" source = "git+https://git.meli.delivery/meli/mime_apps#fdd4f43a843eb729ef0d1f58a52b0e74bab7b9d6" +[[package]] +name = "mime_guess" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mio" version = "0.6.19" @@ -627,6 +858,16 @@ dependencies = [ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mio-uds" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "miow" version = "0.2.1" @@ -665,18 +906,6 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "nix" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", - "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "nix" version = "0.15.0" @@ -702,6 +931,15 @@ dependencies = [ "memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "notify" version = "4.0.12" @@ -746,6 +984,15 @@ dependencies = [ "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "num_cpus" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "numtoa" version = "0.1.0" @@ -807,6 +1054,58 @@ dependencies = [ "vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pin-project" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pin-utils" +version = "0.1.0-alpha.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "pkg-config" version = "0.3.14" @@ -825,6 +1124,14 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "0.6.12" @@ -833,6 +1140,14 @@ dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "quote" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rand" version = "0.6.5" @@ -1006,6 +1321,42 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "reqwest" +version = "0.10.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", + "http-body 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.13.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.4.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rusqlite" version = "0.20.0" @@ -1122,6 +1473,17 @@ dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde_urlencoded" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "signal-hook" version = "0.1.10" @@ -1145,11 +1507,37 @@ name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "smallvec" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smallvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "sourcefile" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "string" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "0.15.35" @@ -1160,6 +1548,16 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synstructure" version = "0.10.2" @@ -1220,6 +1618,133 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tokio" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-macros 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-net 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-codec" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-executor" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-macros" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-net" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-sync" +version = "0.2.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-timer" +version = "0.3.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tls" +version = "0.3.0-alpha.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "toml" version = "0.5.3" @@ -1228,6 +1753,55 @@ dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tower-make" +version = "0.3.0-alpha.2a" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tower-service" +version = "0.3.0-alpha.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tracing" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-attributes 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tracing-core" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "try-lock" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ui" version = "0.4.1" @@ -1255,6 +1829,30 @@ dependencies = [ "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-segmentation" version = "1.3.0" @@ -1265,6 +1863,21 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "url" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "uuid" version = "0.7.4" @@ -1279,6 +1892,16 @@ name = "vcpkg" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -1294,6 +1917,112 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "wasm-bindgen-webidl" +version = "0.2.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "web-sys" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)", + "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "weedle" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "winapi" version = "0.2.8" @@ -1331,6 +2060,14 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ws2_32-sys" version = "0.2.1" @@ -1346,17 +2083,21 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] +"checksum anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14" "checksum arc-swap 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "854ede29f7a0ce90519fb2439d030320c6201119b87dab0ee96044603e1130b9" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" "checksum backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)" = "ada4c783bb7e7443c14e0480f429ae2cc99da95065aeab7ee1b81ada0419404f" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" "checksum bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8ab639324e3ee8774d296864fbc0dbbb256cf1a41c490b94cba90c082915f92" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" +"checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" "checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" @@ -1374,6 +2115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum data-encoding 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f47ca1860a761136924ddd2422ba77b2ea54fe8cc75b9040804a0d9d32ad97" "checksum dbus 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e1b39f3f6aa3d4a1522c4f0f9f1e9e9167bd93740a8690874caa7cf8ce47d7" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" "checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" "checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" "checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" @@ -1381,6 +2123,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" +"checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fallible-iterator 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" @@ -1395,11 +2138,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures-channel-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e5f4df964fa9c1c2f8bddeb5c3611631cacd93baf810fc8bb2fb4b495c263a" +"checksum futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b35b6263fb1ef523c3056565fa67b1d16f0a8604ff12b11b08c25f28a734c60a" +"checksum futures-io-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "f4914ae450db1921a56c91bde97a27846287d062087d4a652efc09bb3a01ebda" +"checksum futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "86f148ef6b69f75bb610d4f9a2336d4fc88c4b5b67129d1a340dd0fd362efeec" +"checksum futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)" = "5ce968633c17e5f97936bd2797b6e38fb56cf16a7422319f7ec2e30d3c470e8d" "checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55" +"checksum h2 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f107db1419ef8271686187b1a5d47c6431af4a7f4d98b495e7b7fc249bb0a78" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hermit-abi 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "307c3c9f937f38e3534b1d6447ecf090cafcc9744e4a6360e8b037b2cf5af120" +"checksum http 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "2790658cddc82e82b08e25176c431d7015a0adeb1718498715cbd20138a0bf68" +"checksum http-body 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f3aef6f3de2bd8585f5b366f3f550b5774500b4764d00cf00f903c95749eec3" +"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +"checksum hyper 0.13.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2d05aa523087ac0b9d8b93dd80d5d482a697308ed3b0dca7b0667511a7fa7cdc" +"checksum hyper-tls 0.4.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "47cb3975f80cc809efe5dfcc52b73c9b281fde33f2df35a2e5f79f35e384ae7f" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +"checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" "checksum inotify 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40b54539f3910d6f84fbf9a643efd6e3aa6e4f001426c0329576128255994718" "checksum inotify-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum js-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "1c840fdb2167497b0bd0db43d6dfe61e91637fa72f9d061f8bd17ddc44ba6414" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" @@ -1408,28 +2167,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum libsqlite3-sys 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25" "checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83" "checksum linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ce9439c6f4a1092dc1861272bef01034891da39f13aa1cdcf40ca3e4081de5f" +"checksum lock_api 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e57b3997725d2b60dbec1297f6c2e2957cc383db1cebd6be812163f969c7d586" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" "checksum mac-notification-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3dfb6b71a9a89cd38b395d994214297447e8e63b1ba5708a9a2b0b1048ceda76" "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum memchr 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "148fab2e51b4f1cfc66da2a7c32981d1d3c083a803978268bb11fe4b86925e7a" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "46f3c7359028b31999287dae4e5047ddfe90a23b7dca2282ce759b491080c99b" "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" +"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" "checksum mime_apps 0.2.0 (git+https://git.meli.delivery/meli/mime_apps)" = "" +"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-extras 2.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "46e73a04c2fa6250b8d802134d56d554a9ec2922bf977777c805ea5def61ce40" +"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" -"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" +"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3572d71f13ea8ed41867accd971fd564aa75934cf7a1fae03ddb8c74a8a49943" "checksum notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2956515e032b56214c2712b79f41ad39519af8bf3584d064b307f578b7f88c5e" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" "checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" "checksum objc 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "31d20fd2b37e07cf5125be68357b588672e8cefe9a96f8c17a9d46053b3e590d" "checksum objc-foundation 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" @@ -1437,10 +2203,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum openssl 0.10.24 (registry+https://github.com/rust-lang/crates.io-index)" = "8152bb5a9b5b721538462336e3bef9a539f892715e5037fda0f984577311af15" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.49 (registry+https://github.com/rust-lang/crates.io-index)" = "f4fad9e54bd23bd4cbbe48fdc08a1b8091707ac869ef8508edea2fec77dcc884" +"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +"checksum pin-project 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "94b90146c7216e4cb534069fb91366de4ea0ea353105ee45ed297e2d1619e469" +"checksum pin-project-internal 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "44ca92f893f0656d3cba8158dd0f2b99b94de256a4a54e870bd6922fcc6c8355" +"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" "checksum pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "676e8eb2b1b4c9043511a9b7bea0915320d7e502b0a079fb03f9635a5252b18c" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" +"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" @@ -1460,6 +2234,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +"checksum reqwest 0.10.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e83b47defcad97ddbe592fd5fe49e16661f754b0ba5847cf41bcd870a2d338d7" "checksum rusqlite 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051" "checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" @@ -1475,27 +2250,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" "checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" +"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" "checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68" "checksum signal-hook-registry 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1797d48f38f91643908bb14e35e79928f9f4b3cefb2420a564dde0991b4358dc" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6" +"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" +"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" "checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" +"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" +"checksum tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1f17f5d6ab0f35c1506678b28fb1798bdf74fcb737e9843c7b17b73e426eba38" +"checksum tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9f5d22fd1e84bd4045d28813491cb7d7caae34d45c80517c2213f09a85e8787a" +"checksum tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9ee9ceecf69145923834ea73f32ba40c790fd877b74a7817dd0b089f1eb9c7c8" +"checksum tokio-io 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "112784d5543df30660b04a72ca423bfbd90e8bb32f94dcf610f15401218b22c5" +"checksum tokio-macros 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "86b616374bcdadd95974e1f0dfca07dc913f1163c53840c0d664aca35114964e" +"checksum tokio-net 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "a441682cd32f3559383112c4a7f372f5c9fa1950c5cf8c8dd05274a2ce8c2654" +"checksum tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4f1aaeb685540f7407ea0e27f1c9757d258c7c6bf4e3eb19da6fc59b747239d2" +"checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" +"checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" +"checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" +"checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" +"checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" +"checksum tracing-attributes 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a4263b12c3d3c403274493eb805966093b53214124796552d674ca1dd5d27c2b" +"checksum tracing-core 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bc913647c520c959b6d21e35ed8fa6984971deca9f0a2fcb8c51207e0c56af1d" +"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" "checksum vcpkg 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "33dd455d0f96e90a75803cfeb7f948768c08d70a6de9a8d2362461935698bf95" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7904a7e2bb3cdf0cf5e783f44204a85a37a93151738fa349f06680f59a98b45" +"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +"checksum wasm-bindgen 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "29ae32af33bacd663a9a28241abecf01f2be64e6a185c6139b04f18b6385c5f2" +"checksum wasm-bindgen-backend 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "1845584bd3593442dc0de6e6d9f84454a59a057722f36f005e44665d6ab19d85" +"checksum wasm-bindgen-futures 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1458706aa1b8fe6898d19433c9f110d93a05d1f22ae6adf55810409a94df34b4" +"checksum wasm-bindgen-macro 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "87fcc747e6b73c93d22c947a6334644d22cfec5abd8b66238484dc2b0aeb9fe4" +"checksum wasm-bindgen-macro-support 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3dc4b3f2c4078c8c4a5f363b92fcf62604c5913cbd16c6ff5aaf0f74ec03f570" +"checksum wasm-bindgen-shared 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "ca0b78d6d3be8589b95d1d49cdc0794728ca734adf36d7c9f07e6459508bb53d" +"checksum wasm-bindgen-webidl 0.2.55 (registry+https://github.com/rust-lang/crates.io-index)" = "3126356474ceb717c8fb5549ae387c9fbf4872818454f4d87708bee997214bb5" +"checksum web-sys 0.3.32 (registry+https://github.com/rust-lang/crates.io-index)" = "98405c0a2e722ed3db341b4c5b70eb9fe0021621f7350bab76df93b09b649bbf" +"checksum weedle 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bb43f70885151e629e2a19ce9e50bd730fd436cfd4b666894c9ce4de9141164" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" "checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" diff --git a/melib/Cargo.toml b/melib/Cargo.toml index 35d4526a..ed83f7ad 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -26,9 +26,11 @@ bincode = "1.2.0" uuid = { version = "0.7.4", features = ["serde", "v4"] } text_processing = { path = "../text_processing", version = "*", optional= true } libc = {version = "0.2.59", features = ["extra_traits",]} +reqwest = { version ="0.10.0-alpha.2", optional=true, features = ["json", "blocking" ]} +serde_json = { version = "1.0", optional = true } [features] -default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard"] +default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "jmap_backend", "vcard"] debug-tracing = [] unicode_algorithms = ["text_processing"] @@ -36,4 +38,5 @@ imap_backend = ["native-tls"] maildir_backend = ["notify", "notify-rust", "memmap"] mbox_backend = ["notify", "notify-rust", "memmap"] notmuch_backend = [] +jmap_backend = ["reqwest", "serde_json" ] vcard = [] diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 656cc465..ec008f04 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -38,6 +38,10 @@ pub mod mbox; pub mod notmuch; #[cfg(feature = "notmuch_backend")] pub use self::notmuch::NotmuchDb; +#[cfg(feature = "jmap_backend")] +pub mod jmap; +#[cfg(feature = "jmap_backend")] +pub use self::jmap::JmapType; #[cfg(feature = "imap_backend")] pub use self::imap::ImapType; @@ -129,6 +133,16 @@ impl Backends { }, ); } + #[cfg(feature = "jmap_backend")] + { + b.register( + "jmap".to_string(), + Backend { + create_fn: Box::new(|| Box::new(|f, i| JmapType::new(f, i))), + validate_conf_fn: Box::new(JmapType::validate_config), + }, + ); + } b } diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs new file mode 100644 index 00000000..ecb162d9 --- /dev/null +++ b/melib/src/backends/jmap.rs @@ -0,0 +1,295 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; +use crate::backends::BackendOp; +use crate::backends::FolderHash; +use crate::backends::RefreshEvent; +use crate::backends::RefreshEventKind::{self, *}; +use crate::backends::{BackendFolder, Folder, FolderOperation, MailBackend, RefreshEventConsumer}; +use crate::conf::AccountSettings; +use crate::email::*; +use crate::error::{MeliError, Result}; +use fnv::{FnvHashMap, FnvHashSet}; +use reqwest::blocking::Client; +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use std::str::FromStr; +use std::sync::{Arc, Mutex, RwLock}; + +pub mod protocol; + +use protocol::*; + +pub mod folder; + +use folder::*; + +#[derive(Debug, Default)] +pub struct EnvelopeCache { + bytes: Option, + headers: Option, + body: Option, + flags: Option, +} + +#[derive(Debug, Clone)] +pub struct JmapServerConf { + pub server_hostname: String, + pub server_username: String, + pub server_password: String, + pub server_port: u16, + pub danger_accept_invalid_certs: bool, +} + +macro_rules! get_conf_val { + ($s:ident[$var:literal]) => { + $s.extra.get($var).ok_or_else(|| { + MeliError::new(format!( + "Configuration error ({}): JMAP connection requires the field `{}` set", + $s.name.as_str(), + $var + )) + }) + }; + ($s:ident[$var:literal], $default:expr) => { + $s.extra + .get($var) + .map(|v| { + <_>::from_str(v).map_err(|e| { + MeliError::new(format!( + "Configuration error ({}): Invalid value for field `{}`: {}\n{}", + $s.name.as_str(), + $var, + v, + e + )) + }) + }) + .unwrap_or_else(|| Ok($default)) + }; +} + +impl JmapServerConf { + pub fn new(s: &AccountSettings) -> Result { + Ok(JmapServerConf { + server_hostname: get_conf_val!(s["server_hostname"])?.to_string(), + server_username: get_conf_val!(s["server_username"])?.to_string(), + server_password: get_conf_val!(s["server_password"])?.to_string(), + server_port: get_conf_val!(s["server_port"], 443)?, + danger_accept_invalid_certs: get_conf_val!(s["danger_accept_invalid_certs"], false)?, + }) + } +} + +struct IsSubscribedFn(Box bool + Send + Sync>); + +impl std::fmt::Debug for IsSubscribedFn { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "IsSubscribedFn Box") + } +} + +impl std::ops::Deref for IsSubscribedFn { + type Target = Box bool + Send + Sync>; + fn deref(&self) -> &Box bool + Send + Sync> { + &self.0 + } +} +macro_rules! get_conf_val { + ($s:ident[$var:literal]) => { + $s.extra.get($var).ok_or_else(|| { + MeliError::new(format!( + "Configuration error ({}): IMAP connection requires the field `{}` set", + $s.name.as_str(), + $var + )) + }) + }; + ($s:ident[$var:literal], $default:expr) => { + $s.extra + .get($var) + .map(|v| { + <_>::from_str(v).map_err(|e| { + MeliError::new(format!( + "Configuration error ({}): Invalid value for field `{}`: {}\n{}", + $s.name.as_str(), + $var, + v, + e + )) + }) + }) + .unwrap_or_else(|| Ok($default)) + }; +} + +#[derive(Debug)] +pub struct JmapType { + account_name: String, + online: Arc>, + is_subscribed: Arc, + server_conf: JmapServerConf, + connection: Arc>, + folders: Arc>>, +} + +impl MailBackend for JmapType { + fn is_online(&self) -> bool { + *self.online.lock().unwrap() + } + fn get(&mut self, folder: &Folder) -> Async>> { + let mut w = AsyncBuilder::new(); + let folders = self.folders.clone(); + let connection = self.connection.clone(); + let folder_hash = folder.hash(); + let handle = { + let tx = w.tx(); + let closure = move |_work_context| { + let mut conn_lck = connection.lock().unwrap(); + tx.send(AsyncStatus::Payload( + protocol::get_message_list( + &mut conn_lck, + &folders.read().unwrap()[&folder_hash], + ) + .and_then(|ids| { + protocol::get_message(&mut conn_lck, std::dbg!(&ids).as_slice()) + }), + )) + .unwrap(); + tx.send(AsyncStatus::Finished).unwrap(); + }; + Box::new(closure) + }; + w.build(handle) + } + + fn watch( + &self, + sender: RefreshEventConsumer, + work_context: WorkContext, + ) -> Result { + Err(MeliError::from("sadfsa")) + } + + fn folders(&self) -> Result> { + if self.folders.read().unwrap().is_empty() { + let folders = std::dbg!(protocol::get_mailboxes( + &mut self.connection.lock().unwrap() + ))?; + let ret = Ok(folders + .iter() + .map(|(&h, f)| (h, BackendFolder::clone(f) as Folder)) + .collect()); + *self.folders.write().unwrap() = folders; + ret + } else { + Ok(self + .folders + .read() + .unwrap() + .iter() + .map(|(&h, f)| (h, BackendFolder::clone(f) as Folder)) + .collect()) + } + } + + fn operation(&self, hash: EnvelopeHash) -> Box { + unimplemented!() + } + + fn save(&self, bytes: &[u8], folder: &str, flags: Option) -> Result<()> { + Ok(()) + } + + fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> { + Ok(()) + } + + fn as_any(&self) -> &dyn::std::any::Any { + self + } +} + +impl JmapType { + pub fn new( + s: &AccountSettings, + is_subscribed: Box bool + Send + Sync>, + ) -> Result> { + let online = Arc::new(Mutex::new(false)); + let server_conf = JmapServerConf::new(s)?; + + Ok(Box::new(JmapType { + connection: Arc::new(Mutex::new(JmapConnection::new( + &server_conf, + online.clone(), + )?)), + folders: Arc::new(RwLock::new(FnvHashMap::default())), + account_name: s.name.clone(), + online, + is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), + server_conf, + })) + } + + pub fn validate_config(s: &AccountSettings) -> Result<()> { + get_conf_val!(s["server_hostname"])?; + get_conf_val!(s["server_username"])?; + get_conf_val!(s["server_password"])?; + get_conf_val!(s["server_port"], 443)?; + get_conf_val!(s["danger_accept_invalid_certs"], false)?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct JmapConnection { + request_no: usize, + client: Client, + online_status: Arc>, +} + +impl JmapConnection { + pub fn new(server_conf: &JmapServerConf, online_status: Arc>) -> Result { + use reqwest::header; + let mut headers = header::HeaderMap::new(); + headers.insert( + header::AUTHORIZATION, + header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"), + ); + headers.insert( + header::ACCEPT, + header::HeaderValue::from_static("application/json"), + ); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ); + Ok(JmapConnection { + request_no: 0, + client: reqwest::blocking::ClientBuilder::new() + .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs) + .default_headers(headers) + .build()?, + online_status, + }) + } +} diff --git a/melib/src/backends/jmap/folder.rs b/melib/src/backends/jmap/folder.rs new file mode 100644 index 00000000..1ce7da99 --- /dev/null +++ b/melib/src/backends/jmap/folder.rs @@ -0,0 +1,78 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; +use crate::backends::{FolderPermissions, SpecialUsageMailbox}; + +#[derive(Debug, Clone)] +pub struct JmapFolder { + pub name: String, + pub path: String, + pub hash: FolderHash, + pub v: Vec, + pub id: String, + pub is_subscribed: bool, + pub my_rights: JmapRights, + pub parent_id: Option, + pub role: Option, + pub sort_order: u64, + pub total_emails: u64, + pub total_threads: u64, + pub unread_emails: u64, + pub unread_threads: u64, + pub usage: SpecialUsageMailbox, +} + +impl BackendFolder for JmapFolder { + fn hash(&self) -> FolderHash { + self.hash + } + + fn name(&self) -> &str { + &self.name + } + + fn path(&self) -> &str { + &self.path + } + + fn change_name(&mut self, _s: &str) {} + + fn clone(&self) -> Folder { + Box::new(std::clone::Clone::clone(self)) + } + + fn children(&self) -> &[FolderHash] { + &self.v + } + + fn parent(&self) -> Option { + None + } + + fn permissions(&self) -> FolderPermissions { + FolderPermissions::default() + } + + fn special_usage(&self) -> SpecialUsageMailbox { + self.usage + } +} diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs new file mode 100644 index 00000000..91caf923 --- /dev/null +++ b/melib/src/backends/jmap/protocol.rs @@ -0,0 +1,261 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::folder::JmapFolder; +use super::*; +use serde_json::{json, Value}; + +macro_rules! get_path_hash { + ($path:expr) => {{ + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + let mut hasher = DefaultHasher::new(); + $path.hash(&mut hasher); + hasher.finish() + }}; +} + +static USING: &'static [&'static str] = &["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"]; + +pub fn get_mailboxes(conn: &mut JmapConnection) -> Result> { + let res = conn + .client + .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") + .json(&json!({ + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + "methodCalls": [["Mailbox/get", {}, + format!("#m{}", conn.request_no + 1).as_str()]], + })) + .send(); + conn.request_no += 1; + + let mut v: JsonResponse = + serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap())).unwrap(); + *conn.online_status.lock().unwrap() = true; + std::dbg!(&v); + assert_eq!("Mailbox/get", v.method_responses[0].0); + Ok( + if let Response::MailboxGet { list, .. } = v.method_responses.remove(0).1 { + list.into_iter().map(|r| { + if let MailboxResponse { + id, + is_subscribed, + my_rights, + name, + parent_id, + role, + sort_order, + total_emails, + total_threads, + unread_emails, + unread_threads, + } = r + { + let hash = get_path_hash!(&name); + ( + hash, + JmapFolder { + name: name.clone(), + hash, + path: name, + v: Vec::new(), + id, + is_subscribed, + my_rights, + parent_id, + role, + usage: Default::default(), + sort_order, + total_emails, + total_threads, + unread_emails, + unread_threads, + }, + ) + } else { + panic!() + } + }) + } else { + panic!() + } + .collect(), + ) +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct JsonResponse { + method_responses: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MethodResponse(String, Response, String); + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum Response { + #[serde(rename_all = "camelCase")] + MailboxGet { + account_id: String, + list: Vec, + not_found: Vec, + state: String, + }, + #[serde(rename_all = "camelCase")] + EmailQuery { + account_id: String, + can_calculate_changes: bool, + collapse_threads: bool, + filter: Value, + ids: Vec, + position: u64, + query_state: String, + sort: Option, + total: usize, + }, + Empty {}, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MailboxResponse { + id: String, + is_subscribed: bool, + my_rights: JmapRights, + name: String, + parent_id: Option, + role: Option, + sort_order: u64, + total_emails: u64, + total_threads: u64, + unread_emails: u64, + unread_threads: u64, +} +#[derive(Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct JmapRights { + may_add_items: bool, + may_create_child: bool, + may_delete: bool, + may_read_items: bool, + may_remove_items: bool, + may_rename: bool, + may_set_keywords: bool, + may_set_seen: bool, + may_submit: bool, +} + +// [ +// [ "getMessageList", { +// filter: { +// inMailboxes: [ "mailbox1" ] +// }, +// sort: [ "date desc", "id desc" ] +// collapseThreads: true, +// position: 0, +// limit: 10, +// fetchThreads: true, +// fetchMessages: true, +// fetchMessageProperties: [ +// "threadId", +// "mailboxId", +// "isUnread", +// "isFlagged", +// "isAnswered", +// "isDraft", +// "hasAttachment", +// "from", +// "to", +// "subject", +// "date", +// "preview" +// ], +// fetchSearchSnippets: false +// }, "call1"] +// ] +pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Result> { + let res = conn + .client + .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") + .json(&json!({ + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + "methodCalls": [["Email/query", { "filter": { + "inMailboxes": [ folder.id ] + }, + "collapseThreads": false, + "position": 0, + "fetchThreads": true, + "fetchMessages": true, + "fetchMessageProperties": [ + "threadId", + "mailboxId", + "isUnread", + "isFlagged", + "isAnswered", + "isDraft", + "hasAttachment", + "from", + "to", + "subject", + "date", + "preview" + ], + }, format!("#m{}", conn.request_no + 1).as_str()]], + })) + .send(); + + conn.request_no += 1; + let mut v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?; + + let result: Response = v.method_responses.remove(0).1; + if let Response::EmailQuery { ids, .. } = result { + Ok(ids) + } else { + Err(MeliError::new(format!("response was {:#?}", &result))) + } +} + +pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result> { + let res = conn + .client + .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") + .json(&json!({ + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + "methodCalls": [["Email/get", { + "ids": ids, + "properties": [ "threadId", "mailboxIds", "from", "subject", + "receivedAt", + "htmlBody", "bodyValues" ], + "bodyProperties": [ "partId", "blobId", "size", "type" ], + "fetchHTMLBodyValues": true, + "maxBodyValueBytes": 256 + }, format!("#m{}", conn.request_no + 1).as_str()]], + })) + .send(); + conn.request_no += 1; + + let v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?; + std::dbg!(&v); + Ok(vec![]) +} diff --git a/melib/src/error.rs b/melib/src/error.rs index 25182a65..47fd2203 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -151,6 +151,22 @@ impl From for MeliError { } } +#[cfg(feature = "jmap_backend")] +impl From for MeliError { + #[inline] + fn from(kind: reqwest::Error) -> MeliError { + MeliError::new(format!("{}", kind)) + } +} + +#[cfg(feature = "jmap_backend")] +impl From for MeliError { + #[inline] + fn from(kind: serde_json::error::Error) -> MeliError { + MeliError::new(format!("{}", kind)) + } +} + impl From<&str> for MeliError { #[inline] fn from(kind: &str) -> MeliError { From e8611cca2f43c6027a934d6a995324d6b0012d0e Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 3 Dec 2019 21:29:26 +0200 Subject: [PATCH 02/11] JMAP WIP #2 --- melib/src/backends/jmap.rs | 8 +- melib/src/backends/jmap/objects.rs | 25 ++ melib/src/backends/jmap/objects/email.rs | 289 +++++++++++++ melib/src/backends/jmap/protocol.rs | 133 ++++++ melib/src/backends/jmap/rfc8620.rs | 408 ++++++++++++++++++ melib/src/backends/jmap/rfc8620/comparator.rs | 44 ++ melib/src/backends/jmap/rfc8620/filters.rs | 50 +++ 7 files changed, 955 insertions(+), 2 deletions(-) create mode 100644 melib/src/backends/jmap/objects.rs create mode 100644 melib/src/backends/jmap/objects/email.rs create mode 100644 melib/src/backends/jmap/rfc8620.rs create mode 100644 melib/src/backends/jmap/rfc8620/comparator.rs create mode 100644 melib/src/backends/jmap/rfc8620/filters.rs diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index ecb162d9..26d850d0 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -36,11 +36,15 @@ use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; pub mod protocol; - use protocol::*; -pub mod folder; +pub mod rfc8620; +use rfc8620::*; +pub mod objects; +use objects::*; + +pub mod folder; use folder::*; #[derive(Debug, Default)] diff --git a/melib/src/backends/jmap/objects.rs b/melib/src/backends/jmap/objects.rs new file mode 100644 index 00000000..cc29d5b5 --- /dev/null +++ b/melib/src/backends/jmap/objects.rs @@ -0,0 +1,25 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +mod email; +pub use email::*; diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs new file mode 100644 index 00000000..851e41c0 --- /dev/null +++ b/melib/src/backends/jmap/objects/email.rs @@ -0,0 +1,289 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; +use crate::backends::jmap::protocol::*; +use std::collections::HashMap; + +// 4.1.1. +// Metadata +// These properties represent metadata about the message in the mail +// store and are not derived from parsing the message itself. +// +// o id: "Id" (immutable; server-set) +// +// The id of the Email object. Note that this is the JMAP object id, +// NOT the Message-ID header field value of the message [RFC5322]. +// +// o blobId: "Id" (immutable; server-set) +// +// The id representing the raw octets of the message [RFC5322] for +// this Email. This may be used to download the raw original message +// or to attach it directly to another Email, etc. +// +// o threadId: "Id" (immutable; server-set) +// +// The id of the Thread to which this Email belongs. +// +// o mailboxIds: "Id[Boolean]" +// +// The set of Mailbox ids this Email belongs to. An Email in the +// mail store MUST belong to one or more Mailboxes at all times +// (until it is destroyed). The set is represented as an object, +// with each key being a Mailbox id. The value for each key in the +// object MUST be true. +// +// o keywords: "String[Boolean]" (default: {}) +// +// A set of keywords that apply to the Email. The set is represented +// as an object, with the keys being the keywords. The value for +// each key in the object MUST be true. +// +// Keywords are shared with IMAP. The six system keywords from IMAP +// get special treatment. The following four keywords have their +// first character changed from "\" in IMAP to "$" in JMAP and have +// particular semantic meaning: +// +// * "$draft": The Email is a draft the user is composing. +// +// * "$seen": The Email has been read. +// +// * "$flagged": The Email has been flagged for urgent/special +// attention. +// +// * "$answered": The Email has been replied to. +// +// The IMAP "\Recent" keyword is not exposed via JMAP. The IMAP +// "\Deleted" keyword is also not present: IMAP uses a delete+expunge +// model, which JMAP does not. Any message with the "\Deleted" +// keyword MUST NOT be visible via JMAP (and so are not counted in +// the "totalEmails", "unreadEmails", "totalThreads", and +// "unreadThreads" Mailbox properties). +// +// Users may add arbitrary keywords to an Email. For compatibility +// with IMAP, a keyword is a case-insensitive string of 1-255 +// characters in the ASCII subset %x21-%x7e (excludes control chars +// and space), and it MUST NOT include any of these characters: +// +// ( ) { ] % * " \ +// +// Because JSON is case sensitive, servers MUST return keywords in +// lowercase. +// +// The IANA "IMAP and JMAP Keywords" registry at +// as +// established in [RFC5788] assigns semantic meaning to some other +// keywords in common use. New keywords may be established here in +// the future. In particular, note: +// +// * "$forwarded": The Email has been forwarded. +// +// * "$phishing": The Email is highly likely to be phishing. +// Clients SHOULD warn users to take care when viewing this Email +// and disable links and attachments. +// +// * "$junk": The Email is definitely spam. Clients SHOULD set this +// flag when users report spam to help train automated spam- +// detection systems. +// +// * "$notjunk": The Email is definitely not spam. Clients SHOULD +// set this flag when users indicate an Email is legitimate, to +// help train automated spam-detection systems. +// +// o size: "UnsignedInt" (immutable; server-set) +// +// The size, in octets, of the raw data for the message [RFC5322] (as +// referenced by the "blobId", i.e., the number of octets in the file +// the user would download). +// +// o receivedAt: "UTCDate" (immutable; default: time of creation on +// server) +// +// The date the Email was received by the message store. This is the +// "internal date" in IMAP [RFC3501]./ + +#[derive(Deserialize, Serialize, Debug)] +pub struct EmailObject { + pub id: Id, + pub blob_id: Id, + pub thread_id: Id, + pub mailbox_ids: HashMap, + pub keywords: HashMap, + pub size: u64, + pub received_at: String, +} + +impl Object for EmailObject {} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct EmailQueryResponse { + pub account_id: Id, + pub can_calculate_changes: bool, + pub collapse_threads: bool, + // FIXME + pub filter: String, + pub ids: Vec, + pub position: u64, + pub query_state: String, + pub sort: Option, + pub total: usize, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct EmailQueryCall { + pub filter: Vec, /* "inMailboxes": [ folder.id ] },*/ + pub collapse_threads: bool, + pub position: u64, + pub fetch_threads: bool, + pub fetch_messages: bool, + pub fetch_message_properties: Vec, +} + +impl Method for EmailQueryCall { + const NAME: &'static str = "Email/query"; +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct EmailGetCall { + pub filter: Vec, /* "inMailboxes": [ folder.id ] },*/ + pub collapse_threads: bool, + pub position: u64, + pub fetch_threads: bool, + pub fetch_messages: bool, + pub fetch_message_properties: Vec, +} + +impl Method for EmailGetCall { + const NAME: &'static str = "Email/get"; +} + +#[derive(Serialize, Deserialize, Default, Debug)] +#[serde(rename_all = "camelCase")] +pub struct EmailFilterCondition { + #[serde(skip_serializing_if = "Vec::is_empty")] + pub in_mailboxes: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub in_mailbox_other_than: Vec, + #[serde(skip_serializing_if = "String::is_empty")] + pub before: UtcDate, + #[serde(skip_serializing_if = "String::is_empty")] + pub after: UtcDate, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub min_size: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub max_size: Option, + #[serde(skip_serializing_if = "String::is_empty")] + pub all_in_thread_have_keyword: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub some_in_thread_have_keyword: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub none_in_thread_have_keyword: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub has_keyword: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub not_keyword: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub has_attachment: Option, + #[serde(skip_serializing_if = "String::is_empty")] + pub text: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub from: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub to: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub cc: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub bcc: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub subject: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub body: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub header: Vec, +} + +impl FilterTrait for EmailFilterCondition {} + +// The following convenience properties are also specified for the Email +// object: +// +// o messageId: "String[]|null" (immutable) +// +// The value is identical to the value of "header:Message- +// ID:asMessageIds". For messages conforming to RFC 5322, this will +// be an array with a single entry. +// +// o inReplyTo: "String[]|null" (immutable) +// +// The value is identical to the value of "header:In-Reply- +// To:asMessageIds". +// +// o references: "String[]|null" (immutable) +// +// The value is identical to the value of +// "header:References:asMessageIds". +// +// o sender: "EmailAddress[]|null" (immutable) +// +// The value is identical to the value of +// "header:Sender:asAddresses". +// +// o from: "EmailAddress[]|null" (immutable) +// +// The value is identical to the value of "header:From:asAddresses". +// +// o to: "EmailAddress[]|null" (immutable) +// +// The value is identical to the value of "header:To:asAddresses". +// +// o cc: "EmailAddress[]|null" (immutable) +// +// The value is identical to the value of "header:Cc:asAddresses". +// +// o bcc: "EmailAddress[]|null" (immutable) +// +// The value is identical to the value of "header:Bcc:asAddresses". +// +// o replyTo: "EmailAddress[]|null" (immutable) +// +// The value is identical to the value of "header:Reply- +// To:asAddresses". +// +// o subject: "String|null" (immutable) +// +// The value is identical to the value of "header:Subject:asText". +// +// +// +// Jenkins & Newman Standards Track [Page 34] +// +// RFC 8621 JMAP Mail August 2019 +// +// +// o sentAt: "Date|null" (immutable; default on creation: current +// server time) +// +// The value is identical to the value of "header:Date:asDate". diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 91caf923..125e9366 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -21,8 +21,34 @@ use super::folder::JmapFolder; use super::*; +use serde::{de::DeserializeOwned, Serialize}; use serde_json::{json, Value}; +pub type Id = String; +pub type UtcDate = String; + +use super::rfc8620::Object; + +pub trait Method: Serialize { + const NAME: &'static str; +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum MessageProperty { + ThreadId, + MailboxId, + IsUnread, + IsFlagged, + IsAnswered, + IsDraft, + HasAttachment, + From, + To, + Subject, + Date, + Preview, +} macro_rules! get_path_hash { ($path:expr) => {{ use std::collections::hash_map::DefaultHasher; @@ -35,6 +61,45 @@ macro_rules! get_path_hash { static USING: &'static [&'static str] = &["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"]; +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Request { + using: &'static [&'static str], + /* Why is this Value instead of Box>? The Method trait cannot be made into a + * Trait object because its serialize() will be generic. */ + method_calls: Vec, +} + +impl Request { + pub fn new() -> Self { + Request { + using: USING, + method_calls: Vec::new(), + } + } + + pub fn add_call, O: Object>(&mut self, call: M) { + self.method_calls + .push(serde_json::to_value((M::NAME, call, "f")).unwrap()); + } +} + +#[derive(Serialize, Debug)] +#[serde(untagged)] +pub enum MethodCall { + #[serde(rename_all = "camelCase")] + EmailQuery { + filter: Vec, /* "inMailboxes": [ folder.id ] },*/ + collapse_threads: bool, + position: u64, + fetch_threads: bool, + fetch_messages: bool, + fetch_message_properties: Vec, + }, + MailboxGet {}, + Empty {}, +} + pub fn get_mailboxes(conn: &mut JmapConnection) -> Result> { let res = conn .client @@ -195,6 +260,74 @@ pub struct JmapRights { // }, "call1"] // ] pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Result> { + let email_call: EmailQueryCall = EmailQueryCall { + filter: vec![EmailFilterCondition { + in_mailboxes: vec![folder.id.clone()], + ..Default::default() + }], + collapse_threads: false, + position: 0, + fetch_threads: true, + fetch_messages: true, + fetch_message_properties: vec![ + MessageProperty::ThreadId, + MessageProperty::MailboxId, + MessageProperty::IsUnread, + MessageProperty::IsFlagged, + MessageProperty::IsAnswered, + MessageProperty::IsDraft, + MessageProperty::HasAttachment, + MessageProperty::From, + MessageProperty::To, + MessageProperty::Subject, + MessageProperty::Date, + MessageProperty::Preview, + ], + }; + + let mut req = Request::new(); + req.add_call(email_call); + std::dbg!(serde_json::to_string(&req)); + + /* + { + "using": [ + "urn:ietf:params:jmap:core", + "urn:ietf:params:jmap:mail" + ], + "methodCalls": [[ + "Email/query", + { + "collapseThreads": false, + "fetchMessageProperties": [ + "threadId", + "mailboxId", + "isUnread", + "isFlagged", + "isAnswered", + "isDraft", + "hasAttachment", + "from", + "to", + "subject", + "date", + "preview" + ], + "fetchMessages": true, + "fetchThreads": true, + "filter": [ + { + "inMailboxes": [ + "fde49e47-14e7-11ea-a277-2477037a1804" + ] + } + ], + "position": 0 + }, + "f" + ]] + } + */ let res = conn .client .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs new file mode 100644 index 00000000..55863b6c --- /dev/null +++ b/melib/src/backends/jmap/rfc8620.rs @@ -0,0 +1,408 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use core::marker::PhantomData; +use serde::{de::DeserializeOwned, Serialize}; +mod filters; +pub use filters::*; +mod comparator; +pub use comparator::*; + +use super::protocol::Method; +pub trait Object {} + +// 5.1. /get +// +// Objects of type Foo are fetched via a call to "Foo/get". +// +// It takes the following arguments: +// +// o accountId: "Id" +// +// The id of the account to use. +// +// o ids: "Id[]|null" +// +// The ids of the Foo objects to return. If null, then *all* records +// of the data type are returned, if this is supported for that data +// type and the number of records does not exceed the +// "maxObjectsInGet" limit. +// +// o properties: "String[]|null" +// +// If supplied, only the properties listed in the array are returned +// for each Foo object. If null, all properties of the object are +// returned. The id property of the object is *always* returned, +// even if not explicitly requested. If an invalid property is +// requested, the call MUST be rejected with an "invalidArguments" +// error. + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetCall> +where + OBJ: std::fmt::Debug + Serialize, +{ + #[serde(skip_serializing_if = "String::is_empty")] + account_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + ids: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + properties: Option>, + _ph: PhantomData<*const CALL>, + __ph: PhantomData<*const OBJ>, +} + +// The response has the following arguments: +// +// o accountId: "Id" +// +// The id of the account used for the call. +// +// o state: "String" +// +// A (preferably short) string representing the state on the server +// for *all* the data of this type in the account (not just the +// objects returned in this call). If the data changes, this string +// MUST change. If the Foo data is unchanged, servers SHOULD return +// the same state string on subsequent requests for this data type. +// When a client receives a response with a different state string to +// a previous call, it MUST either throw away all currently cached +// objects for the type or call "Foo/changes" to get the exact +// changes. +// +// o list: "Foo[]" +// +// An array of the Foo objects requested. This is the *empty array* +// if no objects were found or if the "ids" argument passed in was +// also an empty array. The results MAY be in a different order to +// the "ids" in the request arguments. If an identical id is +// included more than once in the request, the server MUST only +// include it once in either the "list" or the "notFound" argument of +// the response. +// +// o notFound: "Id[]" +// +// This array contains the ids passed to the method for records that +// do not exist. The array is empty if all requested ids were found +// or if the "ids" argument passed in was either null or an empty +// array. +// +// The following additional error may be returned instead of the "Foo/ +// get" response: +// +// "requestTooLarge": The number of ids requested by the client exceeds +// the maximum number the server is willing to process in a single +// method call. + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetResponse { + #[serde(skip_serializing_if = "String::is_empty")] + account_id: String, + state: String, + list: Vec, + not_found: Vec, +} + +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +enum JmapError { + RequestTooLarge, + InvalidArguments, +} + +// 5.5. /query +// +// For data sets where the total amount of data is expected to be very +// small, clients can just fetch the complete set of data and then do +// any sorting/filtering locally. However, for large data sets (e.g., +// multi-gigabyte mailboxes), the client needs to be able to +// search/sort/window the data type on the server. +// +// A query on the set of Foos in an account is made by calling "Foo/ +// query". This takes a number of arguments to determine which records +// to include, how they should be sorted, and which part of the result +// should be returned (the full list may be *very* long). The result is +// returned as a list of Foo ids. +// +// A call to "Foo/query" takes the following arguments: +// +// o accountId: "Id" +// +// The id of the account to use. +// +// o filter: "FilterOperator|FilterCondition|null" +// +// Determines the set of Foos returned in the results. If null, all +// objects in the account of this type are included in the results. +// A *FilterOperator* object has the following properties: +// +// * operator: "String" +// +// This MUST be one of the following strings: +// +// + "AND": All of the conditions must match for the filter to +// match. +// +// + "OR": At least one of the conditions must match for the +// filter to match. +// +// + "NOT": None of the conditions must match for the filter to +// match. +// +// * conditions: "(FilterOperator|FilterCondition)[]" +// +// The conditions to evaluate against each record. +// +// A *FilterCondition* is an "object" whose allowed properties and +// semantics depend on the data type and is defined in the /query +// method specification for that type. It MUST NOT have an +// "operator" property. +// +// o sort: "Comparator[]|null" +// +// Lists the names of properties to compare between two Foo records, +// and how to compare them, to determine which comes first in the +// sort. If two Foo records have an identical value for the first +// comparator, the next comparator will be considered, and so on. If +// all comparators are the same (this includes the case where an +// empty array or null is given as the "sort" argument), the sort +// order is server dependent, but it MUST be stable between calls to +// "Foo/query". A *Comparator* has the following properties: +// +// * property: "String" +// +// The name of the property on the Foo objects to compare. +// +// * isAscending: "Boolean" (optional; default: true) +// +// If true, sort in ascending order. If false, reverse the +// comparator's results to sort in descending order. +// +// * collation: "String" (optional; default is server-dependent) +// +// The identifier, as registered in the collation registry defined +// in [RFC4790], for the algorithm to use when comparing the order +// of strings. The algorithms the server supports are advertised +// in the capabilities object returned with the Session object +// (see Section 2). +// +// If omitted, the default algorithm is server dependent, but: +// +// 1. It MUST be unicode-aware. +// +// 2. It MAY be selected based on an Accept-Language header in +// the request (as defined in [RFC7231], Section 5.3.5) or +// out-of-band information about the user's language/locale. +// +// 3. It SHOULD be case insensitive where such a concept makes +// sense for a language/locale. Where the user's language is +// unknown, it is RECOMMENDED to follow the advice in +// Section 5.2.3 of [RFC8264]. +// +// The "i;unicode-casemap" collation [RFC5051] and the Unicode +// Collation Algorithm () +// are two examples that fulfil these criterion and provide +// reasonable behaviour for a large number of languages. +// +// When the property being compared is not a string, the +// "collation" property is ignored, and the following comparison +// rules apply based on the type. In ascending order: +// +// + "Boolean": false comes before true. +// +// + "Number": A lower number comes before a higher number. +// +// + "Date"/"UTCDate": The earlier date comes first. +// +// The Comparator object may also have additional properties as +// required for specific sort operations defined in a type's /query +// method. +// +// o position: "Int" (default: 0) +// +// The zero-based index of the first id in the full list of results +// to return. +// +// If a negative value is given, it is an offset from the end of the +// list. Specifically, the negative value MUST be added to the total +// number of results given the filter, and if still negative, it's +// clamped to "0". This is now the zero-based index of the first id +// to return. +// +// If the index is greater than or equal to the total number of +// objects in the results list, then the "ids" array in the response +// will be empty, but this is not an error. +// +// o anchor: "Id|null" +// +// A Foo id. If supplied, the "position" argument is ignored. The +// index of this id in the results will be used in combination with +// the "anchorOffset" argument to determine the index of the first +// result to return (see below for more details). +// +// o anchorOffset: "Int" (default: 0) +// +// The index of the first result to return relative to the index of +// the anchor, if an anchor is given. This MAY be negative. For +// example, "-1" means the Foo immediately preceding the anchor is +// the first result in the list returned (see below for more +// details). +// +// o limit: "UnsignedInt|null" +// +// The maximum number of results to return. If null, no limit +// presumed. The server MAY choose to enforce a maximum "limit" +// argument. In this case, if a greater value is given (or if it is +// null), the limit is clamped to the maximum; the new limit is +// returned with the response so the client is aware. If a negative +// value is given, the call MUST be rejected with an +// "invalidArguments" error. +// +// o calculateTotal: "Boolean" (default: false) +// +// Does the client wish to know the total number of results in the +// query? This may be slow and expensive for servers to calculate, +// particularly with complex filters, so clients should take care to +// only request the total when needed. +// +// If an "anchor" argument is given, the anchor is looked for in the +// results after filtering and sorting. If found, the "anchorOffset" is +// then added to its index. If the resulting index is now negative, it +// is clamped to 0. This index is now used exactly as though it were +// supplied as the "position" argument. If the anchor is not found, the +// call is rejected with an "anchorNotFound" error. +// +// If an "anchor" is specified, any position argument supplied by the +// client MUST be ignored. If no "anchor" is supplied, any +// "anchorOffset" argument MUST be ignored. +// +// A client can use "anchor" instead of "position" to find the index of +// an id within a large set of results. + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct QueryCall, OBJ: Object> +where + OBJ: std::fmt::Debug + Serialize, +{ + account_id: String, + filter: Option>, + sort: Option>, + #[serde(default)] + position: u64, + #[serde(skip_serializing_if = "Option::is_none")] + anchor: Option, + #[serde(default)] + anchor_offset: u64, + #[serde(skip_serializing_if = "Option::is_none")] + limit: Option, + #[serde(default = "bool_false")] + calculate_total: bool, + _ph: PhantomData<*const OBJ>, +} + +fn bool_false() -> bool { + false +} + +fn bool_true() -> bool { + true +} + +// The response has the following arguments: +// +// o accountId: "Id" +// +// The id of the account used for the call. +// +// o queryState: "String" +// +// A string encoding the current state of the query on the server. +// This string MUST change if the results of the query (i.e., the +// matching ids and their sort order) have changed. The queryState +// string MAY change if something has changed on the server, which +// means the results may have changed but the server doesn't know for +// sure. +// +// The queryState string only represents the ordered list of ids that +// match the particular query (including its sort/filter). There is +// no requirement for it to change if a property on an object +// matching the query changes but the query results are unaffected +// (indeed, it is more efficient if the queryState string does not +// change in this case). The queryState string only has meaning when +// compared to future responses to a query with the same type/sort/ +// filter or when used with /queryChanges to fetch changes. +// +// Should a client receive back a response with a different +// queryState string to a previous call, it MUST either throw away +// the currently cached query and fetch it again (note, this does not +// require fetching the records again, just the list of ids) or call +// "Foo/queryChanges" to get the difference. +// +// o canCalculateChanges: "Boolean" +// +// This is true if the server supports calling "Foo/queryChanges" +// with these "filter"/"sort" parameters. Note, this does not +// guarantee that the "Foo/queryChanges" call will succeed, as it may +// only be possible for a limited time afterwards due to server +// internal implementation details. +// +// o position: "UnsignedInt" +// +// The zero-based index of the first result in the "ids" array within +// the complete list of query results. +// +// o ids: "Id[]" +// +// The list of ids for each Foo in the query results, starting at the +// index given by the "position" argument of this response and +// continuing until it hits the end of the results or reaches the +// "limit" number of ids. If "position" is >= "total", this MUST be +// the empty list. +// +// o total: "UnsignedInt" (only if requested) +// +// The total number of Foos in the results (given the "filter"). +// This argument MUST be omitted if the "calculateTotal" request +// argument is not true. +// +// o limit: "UnsignedInt" (if set by the server) +// +// The limit enforced by the server on the maximum number of results +// to return. This is only returned if the server set a limit or +// used a different limit than that given in the request. +// +// The following additional errors may be returned instead of the "Foo/ +// query" response: +// +// "anchorNotFound": An anchor argument was supplied, but it cannot be +// found in the results of the query. +// +// "unsupportedSort": The "sort" is syntactically valid, but it includes +// a property the server does not support sorting on or a collation +// method it does not recognise. +// +// "unsupportedFilter": The "filter" is syntactically valid, but the +// server cannot process it. If the filter was the result of a user's +// search input, the client SHOULD suggest that the user simplify their +// search. diff --git a/melib/src/backends/jmap/rfc8620/comparator.rs b/melib/src/backends/jmap/rfc8620/comparator.rs new file mode 100644 index 00000000..c11ad266 --- /dev/null +++ b/melib/src/backends/jmap/rfc8620/comparator.rs @@ -0,0 +1,44 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Comparator { + property: String, + #[serde(default = "bool_true")] + is_ascending: bool, + //FIXME + collation: Option, + //#[serde(flatten)] + additional_properties: Vec, + + _ph: PhantomData<*const OBJ>, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +pub enum FilterOperator { + And, + Or, + Not, +} diff --git a/melib/src/backends/jmap/rfc8620/filters.rs b/melib/src/backends/jmap/rfc8620/filters.rs new file mode 100644 index 00000000..61697d0d --- /dev/null +++ b/melib/src/backends/jmap/rfc8620/filters.rs @@ -0,0 +1,50 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +pub trait FilterTrait {} +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum Filter, OBJ: Object> { + Operator { + operator: FilterOperator, + conditions: Vec>, + }, + Condition(FilterCondition), +} + +#[derive(Serialize, Debug)] +pub struct FilterCondition, OBJ: Object> { + #[serde(flatten)] + cond: F, + #[serde(skip)] + _ph: PhantomData<*const OBJ>, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "UPPERCASE")] +pub enum FilterOperator { + And, + Or, + Not, +} From a1efeed34352e60434e511edc5d35ffb4c6b4276 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 4 Dec 2019 01:04:38 +0200 Subject: [PATCH 03/11] JMAP WIP #3 --- melib/src/backends/jmap.rs | 72 +++------- melib/src/backends/jmap/connection.rs | 64 +++++++++ melib/src/backends/jmap/objects/email.rs | 64 ++++++++- melib/src/backends/jmap/protocol.rs | 134 ++++++++++++------ melib/src/backends/jmap/rfc8620.rs | 105 ++++++++++++-- melib/src/backends/jmap/rfc8620/comparator.rs | 17 +++ 6 files changed, 344 insertions(+), 112 deletions(-) create mode 100644 melib/src/backends/jmap/connection.rs diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 26d850d0..8d25e982 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -35,6 +35,19 @@ use std::hash::Hasher; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; +#[macro_export] +macro_rules! _impl { + ($field:ident : $t:ty) => { + pub fn $field(mut self, new_val: $t) -> Self { + self.$field = new_val; + self + } + } + } + +pub mod connection; +use connection::*; + pub mod protocol; use protocol::*; @@ -122,7 +135,7 @@ macro_rules! get_conf_val { ($s:ident[$var:literal]) => { $s.extra.get($var).ok_or_else(|| { MeliError::new(format!( - "Configuration error ({}): IMAP connection requires the field `{}` set", + "Configuration error ({}): JMAP connection requires the field `{}` set", $s.name.as_str(), $var )) @@ -152,7 +165,7 @@ pub struct JmapType { online: Arc>, is_subscribed: Arc, server_conf: JmapServerConf, - connection: Arc>, + connection: Arc, folders: Arc>>, } @@ -168,15 +181,11 @@ impl MailBackend for JmapType { let handle = { let tx = w.tx(); let closure = move |_work_context| { - let mut conn_lck = connection.lock().unwrap(); tx.send(AsyncStatus::Payload( - protocol::get_message_list( - &mut conn_lck, - &folders.read().unwrap()[&folder_hash], - ) - .and_then(|ids| { - protocol::get_message(&mut conn_lck, std::dbg!(&ids).as_slice()) - }), + protocol::get_message_list(&connection, &folders.read().unwrap()[&folder_hash]) + .and_then(|ids| { + protocol::get_message(&connection, std::dbg!(&ids).as_slice()) + }), )) .unwrap(); tx.send(AsyncStatus::Finished).unwrap(); @@ -196,9 +205,7 @@ impl MailBackend for JmapType { fn folders(&self) -> Result> { if self.folders.read().unwrap().is_empty() { - let folders = std::dbg!(protocol::get_mailboxes( - &mut self.connection.lock().unwrap() - ))?; + let folders = std::dbg!(protocol::get_mailboxes(&self.connection))?; let ret = Ok(folders .iter() .map(|(&h, f)| (h, BackendFolder::clone(f) as Folder)) @@ -242,10 +249,7 @@ impl JmapType { let server_conf = JmapServerConf::new(s)?; Ok(Box::new(JmapType { - connection: Arc::new(Mutex::new(JmapConnection::new( - &server_conf, - online.clone(), - )?)), + connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?), folders: Arc::new(RwLock::new(FnvHashMap::default())), account_name: s.name.clone(), online, @@ -263,37 +267,3 @@ impl JmapType { Ok(()) } } - -#[derive(Debug)] -pub struct JmapConnection { - request_no: usize, - client: Client, - online_status: Arc>, -} - -impl JmapConnection { - pub fn new(server_conf: &JmapServerConf, online_status: Arc>) -> Result { - use reqwest::header; - let mut headers = header::HeaderMap::new(); - headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"), - ); - headers.insert( - header::ACCEPT, - header::HeaderValue::from_static("application/json"), - ); - headers.insert( - header::CONTENT_TYPE, - header::HeaderValue::from_static("application/json"), - ); - Ok(JmapConnection { - request_no: 0, - client: reqwest::blocking::ClientBuilder::new() - .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs) - .default_headers(headers) - .build()?, - online_status, - }) - } -} diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs new file mode 100644 index 00000000..036abd82 --- /dev/null +++ b/melib/src/backends/jmap/connection.rs @@ -0,0 +1,64 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +#[derive(Debug)] +pub struct JmapConnection { + pub request_no: Arc>, + pub client: Arc>, + pub online_status: Arc>, + pub server_conf: JmapServerConf, +} + +impl JmapConnection { + pub fn new(server_conf: &JmapServerConf, online_status: Arc>) -> Result { + use reqwest::header; + let mut headers = header::HeaderMap::new(); + headers.insert( + header::AUTHORIZATION, + header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"), + ); + headers.insert( + header::ACCEPT, + header::HeaderValue::from_static("application/json"), + ); + headers.insert( + header::CONTENT_TYPE, + header::HeaderValue::from_static("application/json"), + ); + let client = reqwest::blocking::ClientBuilder::new() + .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs) + .default_headers(headers) + .build()?; + + let res_text = client.get(&server_conf.server_hostname).send()?.text()?; + debug!(&res_text); + + let server_conf = server_conf.clone(); + Ok(JmapConnection { + request_no: Arc::new(Mutex::new(0)), + client: Arc::new(Mutex::new(client)), + online_status, + server_conf, + }) + } +} diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 851e41c0..d10d40e4 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -21,6 +21,7 @@ use super::*; use crate::backends::jmap::protocol::*; +use crate::backends::jmap::rfc8620::bool_false; use std::collections::HashMap; // 4.1.1. @@ -151,7 +152,7 @@ pub struct EmailQueryResponse { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailQueryCall { - pub filter: Vec, /* "inMailboxes": [ folder.id ] },*/ + pub filter: EmailFilterCondition, /* "inMailboxes": [ folder.id ] },*/ pub collapse_threads: bool, pub position: u64, pub fetch_threads: bool, @@ -166,18 +167,44 @@ impl Method for EmailQueryCall { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailGetCall { - pub filter: Vec, /* "inMailboxes": [ folder.id ] },*/ - pub collapse_threads: bool, - pub position: u64, - pub fetch_threads: bool, - pub fetch_messages: bool, - pub fetch_message_properties: Vec, + #[serde(flatten)] + pub get_call: GetCall, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub body_properties: Vec, + #[serde(default = "bool_false")] + pub fetch_text_body_values: bool, + #[serde(default = "bool_false")] + pub fetch_html_body_values: bool, + #[serde(default = "bool_false")] + pub fetch_all_body_values: bool, + #[serde(default)] + pub max_body_value_bytes: u64, } impl Method for EmailGetCall { const NAME: &'static str = "Email/get"; } +impl EmailGetCall { + pub fn new(get_call: GetCall) -> Self { + EmailGetCall { + get_call, + body_properties: Vec::new(), + fetch_text_body_values: false, + fetch_html_body_values: false, + fetch_all_body_values: false, + max_body_value_bytes: 0, + } + } + + _impl!(get_call: GetCall); + _impl!(body_properties: Vec); + _impl!(fetch_text_body_values: bool); + _impl!(fetch_html_body_values: bool); + _impl!(fetch_all_body_values: bool); + _impl!(max_body_value_bytes: u64); +} + #[derive(Serialize, Deserialize, Default, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailFilterCondition { @@ -225,6 +252,29 @@ pub struct EmailFilterCondition { pub header: Vec, } +impl EmailFilterCondition { + _impl!(in_mailboxes: Vec); + _impl!(in_mailbox_other_than: Vec); + _impl!(before: UtcDate); + _impl!(after: UtcDate); + _impl!(min_size: Option); + _impl!(max_size: Option); + _impl!(all_in_thread_have_keyword: String); + _impl!(some_in_thread_have_keyword: String); + _impl!(none_in_thread_have_keyword: String); + _impl!(has_keyword: String); + _impl!(not_keyword: String); + _impl!(has_attachment: Option); + _impl!(text: String); + _impl!(from: String); + _impl!(to: String); + _impl!(cc: String); + _impl!(bcc: String); + _impl!(subject: String); + _impl!(body: String); + _impl!(header: Vec); +} + impl FilterTrait for EmailFilterCondition {} // The following convenience properties are also specified for the Email diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 125e9366..8557f253 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -29,6 +29,15 @@ pub type UtcDate = String; use super::rfc8620::Object; +macro_rules! get_request_no { + ($lock:expr) => {{ + let mut lck = $lock.lock().unwrap(); + let ret = *lck; + *lck += 1; + ret + }}; +} + pub trait Method: Serialize { const NAME: &'static str; } @@ -68,19 +77,25 @@ pub struct Request { /* Why is this Value instead of Box>? The Method trait cannot be made into a * Trait object because its serialize() will be generic. */ method_calls: Vec, + + #[serde(skip_serializing)] + request_no: Arc>, } impl Request { - pub fn new() -> Self { + pub fn new(request_no: Arc>) -> Self { Request { using: USING, method_calls: Vec::new(), + request_no, } } - pub fn add_call, O: Object>(&mut self, call: M) { + pub fn add_call, O: Object>(&mut self, call: M) -> usize { + let seq = get_request_no!(self.request_no); self.method_calls - .push(serde_json::to_value((M::NAME, call, "f")).unwrap()); + .push(serde_json::to_value((M::NAME, call, &format!("m{}", seq))).unwrap()); + seq } } @@ -100,17 +115,19 @@ pub enum MethodCall { Empty {}, } -pub fn get_mailboxes(conn: &mut JmapConnection) -> Result> { +pub fn get_mailboxes(conn: &JmapConnection) -> Result> { + let seq = get_request_no!(conn.request_no); let res = conn .client + .lock() + .unwrap() .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") .json(&json!({ "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], "methodCalls": [["Mailbox/get", {}, - format!("#m{}", conn.request_no + 1).as_str()]], + format!("#m{}",seq).as_str()]], })) .send(); - conn.request_no += 1; let mut v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap())).unwrap(); @@ -259,12 +276,13 @@ pub struct JmapRights { // fetchSearchSnippets: false // }, "call1"] // ] -pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Result> { +pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result> { + let seq = get_request_no!(conn.request_no); let email_call: EmailQueryCall = EmailQueryCall { - filter: vec![EmailFilterCondition { + filter: EmailFilterCondition { in_mailboxes: vec![folder.id.clone()], ..Default::default() - }], + }, collapse_threads: false, position: 0, fetch_threads: true, @@ -285,9 +303,8 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul ], }; - let mut req = Request::new(); + let mut req = Request::new(conn.request_no.clone()); req.add_call(email_call); - std::dbg!(serde_json::to_string(&req)); /* { @@ -328,37 +345,43 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul ]] } */ + /* + r#" + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + "methodCalls": [["Email/query", { "filter": { + "inMailboxes": [ folder.id ] + }, + "collapseThreads": false, + "position": 0, + "fetchThreads": true, + "fetchMessages": true, + "fetchMessageProperties": [ + "threadId", + "mailboxId", + "isUnread", + "isFlagged", + "isAnswered", + "isDraft", + "hasAttachment", + "from", + "to", + "subject", + "date", + "preview" + ], + }, format!("m{}", seq).as_str()]], + });" + );*/ + + std::dbg!(serde_json::to_string(&req)); let res = conn .client + .lock() + .unwrap() .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") - .json(&json!({ - "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], - "methodCalls": [["Email/query", { "filter": { - "inMailboxes": [ folder.id ] - }, - "collapseThreads": false, - "position": 0, - "fetchThreads": true, - "fetchMessages": true, - "fetchMessageProperties": [ - "threadId", - "mailboxId", - "isUnread", - "isFlagged", - "isAnswered", - "isDraft", - "hasAttachment", - "from", - "to", - "subject", - "date", - "preview" - ], - }, format!("#m{}", conn.request_no + 1).as_str()]], - })) + .json(&req) .send(); - conn.request_no += 1; let mut v: JsonResponse = serde_json::from_str(&std::dbg!(res.unwrap().text().unwrap()))?; let result: Response = v.method_responses.remove(0).1; @@ -369,11 +392,35 @@ pub fn get_message_list(conn: &mut JmapConnection, folder: &JmapFolder) -> Resul } } -pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result> { +pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result> { + let seq = get_request_no!(conn.request_no); + let email_call: EmailGetCall = + EmailGetCall::new(GetCall::new().ids(Some(ids.iter().cloned().collect::>()))); + + let mut req = Request::new(conn.request_no.clone()); + req.add_call(email_call); let res = conn .client + .lock() + .unwrap() .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") - .json(&json!({ + .json(&req) + .send(); + + let res_text = res?.text()?; + let v: JsonResponse = serde_json::from_str(&res_text)?; + let mut f = std::fs::File::create(std::dbg!(format!("/tmp/asdfsa{}", seq))).unwrap(); + use std::io::Write; + f.write_all( + serde_json::to_string_pretty(&serde_json::from_str::(&res_text)?)?.as_bytes(), + ) + .unwrap(); + Ok(vec![]) +} + +/* + * + *json!({ "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], "methodCalls": [["Email/get", { "ids": ids, @@ -383,12 +430,7 @@ pub fn get_message(conn: &mut JmapConnection, ids: &[String]) -> Result. */ +use super::Id; use core::marker::PhantomData; use serde::{de::DeserializeOwned, Serialize}; +use serde_json::Value; + mod filters; pub use filters::*; mod comparator; pub use comparator::*; use super::protocol::Method; +use std::collections::HashMap; pub trait Object {} // 5.1. /get @@ -57,20 +61,76 @@ pub trait Object {} #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct GetCall> +pub struct JmapSession { + capabilities: HashMap, + accounts: HashMap, + primary_accounts: Vec, + username: String, + api_url: String, + download_url: String, + + upload_url: String, + event_source_url: String, + state: String, + #[serde(flatten)] + extra_properties: HashMap, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct CapabilitiesObject { + max_size_upload: u64, + max_concurrent_upload: u64, + max_size_request: u64, + max_concurrent_requests: u64, + max_calls_in_request: u64, + max_objects_in_get: u64, + max_objects_in_set: u64, + collation_algorithms: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Account { + name: String, + is_personal: bool, + is_read_only: bool, + account_capabilities: HashMap, + #[serde(flatten)] + extra_properties: HashMap, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetCall where OBJ: std::fmt::Debug + Serialize, { #[serde(skip_serializing_if = "String::is_empty")] - account_id: String, + pub account_id: String, #[serde(skip_serializing_if = "Option::is_none")] - ids: Option>, + pub ids: Option>, #[serde(skip_serializing_if = "Option::is_none")] - properties: Option>, - _ph: PhantomData<*const CALL>, - __ph: PhantomData<*const OBJ>, + pub properties: Option>, + _ph: PhantomData<*const OBJ>, } +impl GetCall +where + OBJ: std::fmt::Debug + Serialize, +{ + pub fn new() -> Self { + Self { + account_id: String::new(), + ids: None, + properties: None, + _ph: PhantomData, + } + } + _impl!(account_id: String); + _impl!(ids: Option>); + _impl!(properties: Option>); +} // The response has the following arguments: // // o accountId: "Id" @@ -128,6 +188,7 @@ pub struct GetResponse { enum JmapError { RequestTooLarge, InvalidArguments, + InvalidResultReference, } // 5.5. /query @@ -321,11 +382,39 @@ where _ph: PhantomData<*const OBJ>, } -fn bool_false() -> bool { +impl, OBJ: Object> QueryCall +where + OBJ: std::fmt::Debug + Serialize, +{ + pub fn new() -> Self { + Self { + account_id: String::new(), + filter: None, + sort: None, + position: 0, + anchor: None, + anchor_offset: 0, + limit: None, + calculate_total: false, + _ph: PhantomData, + } + } + + _impl!(account_id: String); + _impl!(filter: Option>); + _impl!(sort: Option>); + _impl!(position: u64); + _impl!(anchor: Option); + _impl!(anchor_offset: u64); + _impl!(limit: Option); + _impl!(calculate_total: bool); +} + +pub fn bool_false() -> bool { false } -fn bool_true() -> bool { +pub fn bool_true() -> bool { true } diff --git a/melib/src/backends/jmap/rfc8620/comparator.rs b/melib/src/backends/jmap/rfc8620/comparator.rs index c11ad266..e1bad283 100644 --- a/melib/src/backends/jmap/rfc8620/comparator.rs +++ b/melib/src/backends/jmap/rfc8620/comparator.rs @@ -35,6 +35,23 @@ pub struct Comparator { _ph: PhantomData<*const OBJ>, } +impl Comparator { + pub fn new() -> Self { + Self { + property: String::new(), + is_ascending: true, + collation: None, + additional_properties: Vec::new(), + _ph: PhantomData, + } + } + + _impl!(property: String); + _impl!(is_ascending: bool); + _impl!(collation: Option); + _impl!(additional_properties: Vec); +} + #[derive(Serialize, Debug)] #[serde(rename_all = "UPPERCASE")] pub enum FilterOperator { From 1ee8ef7a055d41de9aae0b078b5422cc9bf24456 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Wed, 4 Dec 2019 19:42:31 +0200 Subject: [PATCH 04/11] JMAP WIP #4 --- melib/Cargo.toml | 2 +- melib/src/backends/jmap.rs | 9 + melib/src/backends/jmap/connection.rs | 2 + melib/src/backends/jmap/objects.rs | 3 + melib/src/backends/jmap/objects/email.rs | 312 ++++++++++++++++----- melib/src/backends/jmap/objects/mailbox.rs | 66 +++++ melib/src/backends/jmap/protocol.rs | 229 +++++---------- melib/src/backends/jmap/rfc8620.rs | 83 +++++- melib/src/email.rs | 3 +- melib/src/email/address.rs | 31 ++ melib/src/email/parser.rs | 30 -- 11 files changed, 484 insertions(+), 286 deletions(-) create mode 100644 melib/src/backends/jmap/objects/mailbox.rs diff --git a/melib/Cargo.toml b/melib/Cargo.toml index ed83f7ad..1a8e8b2a 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -27,7 +27,7 @@ uuid = { version = "0.7.4", features = ["serde", "v4"] } text_processing = { path = "../text_processing", version = "*", optional= true } libc = {version = "0.2.59", features = ["extra_traits",]} reqwest = { version ="0.10.0-alpha.2", optional=true, features = ["json", "blocking" ]} -serde_json = { version = "1.0", optional = true } +serde_json = { version = "1.0", optional = true, features = ["raw_value",] } [features] default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "jmap_backend", "vcard"] diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 8d25e982..890e09b7 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -45,6 +45,15 @@ macro_rules! _impl { } } +#[macro_export] +macro_rules! _impl_get_mut { + ($method:ident, $field:ident : $t:ty) => { + pub fn $method(&mut self) -> &mut $t { + &mut self.$field + } + } + } + pub mod connection; use connection::*; diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs index 036abd82..8a40bb8d 100644 --- a/melib/src/backends/jmap/connection.rs +++ b/melib/src/backends/jmap/connection.rs @@ -27,6 +27,7 @@ pub struct JmapConnection { pub client: Arc>, pub online_status: Arc>, pub server_conf: JmapServerConf, + pub account_id: Arc>, } impl JmapConnection { @@ -59,6 +60,7 @@ impl JmapConnection { client: Arc::new(Mutex::new(client)), online_status, server_conf, + account_id: Arc::new(Mutex::new(String::new())), }) } } diff --git a/melib/src/backends/jmap/objects.rs b/melib/src/backends/jmap/objects.rs index cc29d5b5..b4ec2ea6 100644 --- a/melib/src/backends/jmap/objects.rs +++ b/melib/src/backends/jmap/objects.rs @@ -23,3 +23,6 @@ use super::*; mod email; pub use email::*; + +mod mailbox; +pub use mailbox::*; diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index d10d40e4..a3ef32dd 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -22,7 +22,11 @@ use super::*; use crate::backends::jmap::protocol::*; use crate::backends::jmap::rfc8620::bool_false; +use serde::de::{Deserialize, Deserializer}; +use serde_json::Value; +use std::collections::hash_map::DefaultHasher; use std::collections::HashMap; +use std::hash::Hasher; // 4.1.1. // Metadata @@ -122,17 +126,215 @@ use std::collections::HashMap; // "internal date" in IMAP [RFC3501]./ #[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] pub struct EmailObject { - pub id: Id, - pub blob_id: Id, - pub thread_id: Id, - pub mailbox_ids: HashMap, - pub keywords: HashMap, - pub size: u64, - pub received_at: String, + #[serde(default)] + id: Id, + #[serde(default)] + mailbox_ids: HashMap, + #[serde(default)] + size: u64, + #[serde(default)] + received_at: String, + #[serde(default)] + to: Vec, + #[serde(default)] + bcc: Vec, + #[serde(default)] + reply_to: Option, + #[serde(default)] + cc: Vec, + #[serde(default)] + from: Vec, + #[serde(default)] + in_reply_to_email_id: Id, + #[serde(default)] + keywords: Value, + #[serde(default)] + attached_emails: Option, + #[serde(default)] + attachments: Vec, + #[serde(default)] + blob_id: String, + #[serde(default)] + has_attachment: bool, + #[serde(default)] + #[serde(deserialize_with = "deserialize_header")] + headers: HashMap, + #[serde(default)] + html_body: Vec, + #[serde(default)] + preview: Option, + #[serde(default)] + sent_at: String, + #[serde(default)] + subject: String, + #[serde(default)] + text_body: Vec, + #[serde(default)] + thread_id: Id, } -impl Object for EmailObject {} +#[derive(Deserialize, Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +struct Header { + name: String, + value: String, +} + +fn deserialize_header<'de, D>( + deserializer: D, +) -> std::result::Result, D::Error> +where + D: Deserializer<'de>, +{ + let v = >::deserialize(deserializer)?; + Ok(v.into_iter().map(|t| (t.name, t.value)).collect()) +} + +#[derive(Deserialize, Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +struct EmailAddress { + email: String, + name: Option, +} + +impl Into for EmailAddress { + fn into(self) -> crate::email::Address { + let Self { email, mut name } = self; + crate::make_address!((name.take().unwrap_or_default()), email) + } +} + +impl std::fmt::Display for EmailAddress { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + if self.name.is_some() { + write!(f, "{} <{}>", self.name.as_ref().unwrap(), &self.email) + } else { + write!(f, "{}", &self.email) + } + } +} + +impl std::convert::From for crate::Envelope { + fn from(mut t: EmailObject) -> crate::Envelope { + let mut env = crate::Envelope::new(0); + env.set_date(std::mem::replace(&mut t.sent_at, String::new()).as_bytes()); + if let Ok(d) = crate::email::parser::date(env.date_as_str().as_bytes()) { + env.set_datetime(d); + } + + if let Some(v) = t.headers.get("Message-ID").or(t.headers.get("Message-Id")) { + env.set_message_id(v.as_bytes()); + } + if let Some(v) = t.headers.get("In-Reply-To") { + env.set_in_reply_to(v.as_bytes()); + env.push_references(v.as_bytes()); + } + if let Some(v) = t.headers.get("References") { + let parse_result = crate::email::parser::references(v.as_bytes()); + if parse_result.is_done() { + for v in parse_result.to_full_result().unwrap() { + env.push_references(v); + } + } + env.set_references(v.as_bytes()); + } + if let Some(v) = t.headers.get("Date") { + env.set_date(v.as_bytes()); + if let Ok(d) = crate::email::parser::date(v.as_bytes()) { + env.set_datetime(d); + } + } + env.set_has_attachments(t.has_attachment); + env.set_subject(std::mem::replace(&mut t.subject, String::new()).into_bytes()); + + env.set_from( + std::mem::replace(&mut t.from, Vec::new()) + .into_iter() + .map(|addr| addr.into()) + .collect::>(), + ); + env.set_to( + std::mem::replace(&mut t.to, Vec::new()) + .into_iter() + .map(|addr| addr.into()) + .collect::>(), + ); + + env.set_cc( + std::mem::replace(&mut t.cc, Vec::new()) + .into_iter() + .map(|addr| addr.into()) + .collect::>(), + ); + + env.set_bcc( + std::mem::replace(&mut t.bcc, Vec::new()) + .into_iter() + .map(|addr| addr.into()) + .collect::>(), + ); + + if env.references.is_some() { + if let Some(pos) = env + .references + .as_ref() + .map(|r| &r.refs) + .unwrap() + .iter() + .position(|r| r == env.message_id()) + { + env.references.as_mut().unwrap().refs.remove(pos); + } + } + + let mut h = DefaultHasher::new(); + h.write(t.id.as_bytes()); + env.set_hash(h.finish()); + env + } +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +struct HtmlBody { + blob_id: Id, + cid: Option, + disposition: String, + headers: Value, + language: Option>, + location: Option, + name: Option, + part_id: Option, + size: u64, + #[serde(alias = "type")] + content_type: String, + #[serde(default)] + sub_parts: Vec, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +struct TextBody { + blob_id: Id, + cid: Option, + disposition: String, + headers: Value, + language: Option>, + location: Option, + name: Option, + part_id: Option, + size: u64, + #[serde(alias = "type")] + content_type: String, + #[serde(default)] + sub_parts: Vec, +} + +impl Object for EmailObject { + const NAME: &'static str = "Email"; +} #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -166,9 +368,9 @@ impl Method for EmailQueryCall { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct EmailGetCall { +pub struct EmailGet { #[serde(flatten)] - pub get_call: GetCall, + pub get_call: Get, #[serde(skip_serializing_if = "Vec::is_empty")] pub body_properties: Vec, #[serde(default = "bool_false")] @@ -181,13 +383,13 @@ pub struct EmailGetCall { pub max_body_value_bytes: u64, } -impl Method for EmailGetCall { +impl Method for EmailGet { const NAME: &'static str = "Email/get"; } -impl EmailGetCall { - pub fn new(get_call: GetCall) -> Self { - EmailGetCall { +impl EmailGet { + pub fn new(get_call: Get) -> Self { + EmailGet { get_call, body_properties: Vec::new(), fetch_text_body_values: false, @@ -197,7 +399,7 @@ impl EmailGetCall { } } - _impl!(get_call: GetCall); + _impl!(get_call: Get); _impl!(body_properties: Vec); _impl!(fetch_text_body_values: bool); _impl!(fetch_html_body_values: bool); @@ -249,7 +451,7 @@ pub struct EmailFilterCondition { #[serde(skip_serializing_if = "String::is_empty")] pub body: String, #[serde(skip_serializing_if = "Vec::is_empty")] - pub header: Vec, + pub header: Vec, } impl EmailFilterCondition { @@ -272,68 +474,24 @@ impl EmailFilterCondition { _impl!(bcc: String); _impl!(subject: String); _impl!(body: String); - _impl!(header: Vec); + _impl!(header: Vec); } impl FilterTrait for EmailFilterCondition {} -// The following convenience properties are also specified for the Email -// object: -// -// o messageId: "String[]|null" (immutable) -// -// The value is identical to the value of "header:Message- -// ID:asMessageIds". For messages conforming to RFC 5322, this will -// be an array with a single entry. -// -// o inReplyTo: "String[]|null" (immutable) -// -// The value is identical to the value of "header:In-Reply- -// To:asMessageIds". -// -// o references: "String[]|null" (immutable) -// -// The value is identical to the value of -// "header:References:asMessageIds". -// -// o sender: "EmailAddress[]|null" (immutable) -// -// The value is identical to the value of -// "header:Sender:asAddresses". -// -// o from: "EmailAddress[]|null" (immutable) -// -// The value is identical to the value of "header:From:asAddresses". -// -// o to: "EmailAddress[]|null" (immutable) -// -// The value is identical to the value of "header:To:asAddresses". -// -// o cc: "EmailAddress[]|null" (immutable) -// -// The value is identical to the value of "header:Cc:asAddresses". -// -// o bcc: "EmailAddress[]|null" (immutable) -// -// The value is identical to the value of "header:Bcc:asAddresses". -// -// o replyTo: "EmailAddress[]|null" (immutable) -// -// The value is identical to the value of "header:Reply- -// To:asAddresses". -// -// o subject: "String|null" (immutable) -// -// The value is identical to the value of "header:Subject:asText". -// -// -// -// Jenkins & Newman Standards Track [Page 34] -// -// RFC 8621 JMAP Mail August 2019 -// -// -// o sentAt: "Date|null" (immutable; default on creation: current -// server time) -// -// The value is identical to the value of "header:Date:asDate". +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum MessageProperty { + ThreadId, + MailboxId, + IsUnread, + IsFlagged, + IsAnswered, + IsDraft, + HasAttachment, + From, + To, + Subject, + Date, + Preview, +} diff --git a/melib/src/backends/jmap/objects/mailbox.rs b/melib/src/backends/jmap/objects/mailbox.rs new file mode 100644 index 00000000..f3d828cf --- /dev/null +++ b/melib/src/backends/jmap/objects/mailbox.rs @@ -0,0 +1,66 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MailboxObject { + pub id: String, + pub is_subscribed: bool, + pub my_rights: JmapRights, + pub name: String, + pub parent_id: Option, + pub role: Option, + pub sort_order: u64, + pub total_emails: u64, + pub total_threads: u64, + pub unread_emails: u64, + pub unread_threads: u64, +} + +impl Object for MailboxObject { + const NAME: &'static str = "Mailbox"; +} + +#[derive(Deserialize, Serialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct JmapRights { + pub may_add_items: bool, + pub may_create_child: bool, + pub may_delete: bool, + pub may_read_items: bool, + pub may_remove_items: bool, + pub may_rename: bool, + pub may_set_keywords: bool, + pub may_set_seen: bool, + pub may_submit: bool, +} +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MailboxGet { + #[serde(flatten)] + pub get_call: Get, +} + +impl Method for MailboxGet { + const NAME: &'static str = "Mailbox/query"; +} diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 8557f253..01be8765 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -23,6 +23,7 @@ use super::folder::JmapFolder; use super::*; use serde::{de::DeserializeOwned, Serialize}; use serde_json::{json, Value}; +use std::convert::TryFrom; pub type Id = String; pub type UtcDate = String; @@ -38,26 +39,14 @@ macro_rules! get_request_no { }}; } +pub trait Response { + const NAME: &'static str; +} + pub trait Method: Serialize { const NAME: &'static str; } -#[derive(Deserialize, Serialize, Debug)] -#[serde(rename_all = "camelCase")] -pub enum MessageProperty { - ThreadId, - MailboxId, - IsUnread, - IsFlagged, - IsAnswered, - IsDraft, - HasAttachment, - From, - To, - Subject, - Date, - Preview, -} macro_rules! get_path_hash { ($path:expr) => {{ use std::collections::hash_map::DefaultHasher; @@ -78,7 +67,7 @@ pub struct Request { * Trait object because its serialize() will be generic. */ method_calls: Vec, - #[serde(skip_serializing)] + #[serde(skip)] request_no: Arc>, } @@ -129,153 +118,62 @@ pub fn get_mailboxes(conn: &JmapConnection) -> Result::try_from(v.method_responses.remove(0))?; + let GetResponse:: { + list, account_id, .. + } = m; + *conn.account_id.lock().unwrap() = account_id; + Ok(list + .into_iter() + .map(|r| { + let MailboxObject { + id, + is_subscribed, + my_rights, + name, + parent_id, + role, + sort_order, + total_emails, + total_threads, + unread_emails, + unread_threads, + } = r; + let hash = get_path_hash!(&name); + ( + hash, + JmapFolder { + name: name.clone(), + hash, + path: name, + v: Vec::new(), id, is_subscribed, my_rights, - name, parent_id, role, + usage: Default::default(), sort_order, total_emails, total_threads, unread_emails, unread_threads, - } = r - { - let hash = get_path_hash!(&name); - ( - hash, - JmapFolder { - name: name.clone(), - hash, - path: name, - v: Vec::new(), - id, - is_subscribed, - my_rights, - parent_id, - role, - usage: Default::default(), - sort_order, - total_emails, - total_threads, - unread_emails, - unread_threads, - }, - ) - } else { - panic!() - } - }) - } else { - panic!() - } - .collect(), - ) + }, + ) + }) + .collect()) } #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct JsonResponse { - method_responses: Vec, +pub struct JsonResponse<'a> { + #[serde(borrow)] + method_responses: Vec>, } -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct MethodResponse(String, Response, String); - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(untagged)] -pub enum Response { - #[serde(rename_all = "camelCase")] - MailboxGet { - account_id: String, - list: Vec, - not_found: Vec, - state: String, - }, - #[serde(rename_all = "camelCase")] - EmailQuery { - account_id: String, - can_calculate_changes: bool, - collapse_threads: bool, - filter: Value, - ids: Vec, - position: u64, - query_state: String, - sort: Option, - total: usize, - }, - Empty {}, -} - -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct MailboxResponse { - id: String, - is_subscribed: bool, - my_rights: JmapRights, - name: String, - parent_id: Option, - role: Option, - sort_order: u64, - total_emails: u64, - total_threads: u64, - unread_emails: u64, - unread_threads: u64, -} -#[derive(Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct JmapRights { - may_add_items: bool, - may_create_child: bool, - may_delete: bool, - may_read_items: bool, - may_remove_items: bool, - may_rename: bool, - may_set_keywords: bool, - may_set_seen: bool, - may_submit: bool, -} - -// [ -// [ "getMessageList", { -// filter: { -// inMailboxes: [ "mailbox1" ] -// }, -// sort: [ "date desc", "id desc" ] -// collapseThreads: true, -// position: 0, -// limit: 10, -// fetchThreads: true, -// fetchMessages: true, -// fetchMessageProperties: [ -// "threadId", -// "mailboxId", -// "isUnread", -// "isFlagged", -// "isAnswered", -// "isDraft", -// "hasAttachment", -// "from", -// "to", -// "subject", -// "date", -// "preview" -// ], -// fetchSearchSnippets: false -// }, "call1"] -// ] pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result> { let seq = get_request_no!(conn.request_no); let email_call: EmailQueryCall = EmailQueryCall { @@ -373,7 +271,6 @@ pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result Result::try_from(v.method_responses.remove(0))?; + let QueryResponse:: { ids, .. } = m; + Ok(ids) } pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result> { let seq = get_request_no!(conn.request_no); - let email_call: EmailGetCall = - EmailGetCall::new(GetCall::new().ids(Some(ids.iter().cloned().collect::>()))); + let email_call: EmailGet = EmailGet::new( + Get::new() + .ids(Some(ids.iter().cloned().collect::>())) + .account_id(conn.account_id.lock().unwrap().clone()), + ); let mut req = Request::new(conn.request_no.clone()); req.add_call(email_call); @@ -408,14 +306,13 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result(&res_text)?)?.as_bytes(), - ) - .unwrap(); - Ok(vec![]) + let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); + let e = GetResponse::::try_from(v.method_responses.remove(0))?; + let GetResponse:: { list, .. } = e; + Ok(list + .into_iter() + .map(std::convert::Into::into) + .collect::>()) } /* diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index f8cb0ce4..14f6d0cc 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -22,16 +22,18 @@ use super::Id; use core::marker::PhantomData; use serde::{de::DeserializeOwned, Serialize}; -use serde_json::Value; +use serde_json::{value::RawValue, Value}; mod filters; pub use filters::*; mod comparator; pub use comparator::*; -use super::protocol::Method; +use super::protocol::{Method, Response}; use std::collections::HashMap; -pub trait Object {} +pub trait Object { + const NAME: &'static str; +} // 5.1. /get // @@ -102,7 +104,7 @@ pub struct Account { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct GetCall +pub struct Get where OBJ: std::fmt::Debug + Serialize, { @@ -112,10 +114,11 @@ where pub ids: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>, + #[serde(skip)] _ph: PhantomData<*const OBJ>, } -impl GetCall +impl Get where OBJ: std::fmt::Debug + Serialize, { @@ -173,14 +176,41 @@ where // the maximum number the server is willing to process in a single // method call. -#[derive(Serialize, Debug)] +#[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct GetResponse { +pub struct MethodResponse<'a> { + #[serde(borrow)] + pub method_responses: Vec<&'a RawValue>, + #[serde(default)] + pub created_ids: HashMap, + #[serde(default)] + pub session_state: String, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct GetResponse { #[serde(skip_serializing_if = "String::is_empty")] - account_id: String, - state: String, - list: Vec, - not_found: Vec, + pub account_id: String, + pub state: String, + pub list: Vec, + pub not_found: Vec, +} + +impl std::convert::TryFrom<&RawValue> for GetResponse { + type Error = crate::error::MeliError; + fn try_from(t: &RawValue) -> Result, crate::error::MeliError> { + let res: (String, GetResponse, String) = serde_json::from_str(t.get())?; + assert_eq!(&res.0, &format!("{}/get", OBJ::NAME)); + Ok(res.1) + } +} + +impl GetResponse { + _impl_get_mut!(account_id_mut, account_id: String); + _impl_get_mut!(state_mut, state: String); + _impl_get_mut!(list_mut, list: Vec); + _impl_get_mut!(not_found_mut, not_found: Vec); } #[derive(Deserialize, Debug)] @@ -379,6 +409,7 @@ where limit: Option, #[serde(default = "bool_false")] calculate_total: bool, + #[serde(skip)] _ph: PhantomData<*const OBJ>, } @@ -495,3 +526,33 @@ pub fn bool_true() -> bool { // server cannot process it. If the filter was the result of a user's // search input, the client SHOULD suggest that the user simplify their // search. + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct QueryResponse { + #[serde(skip_serializing_if = "String::is_empty", default)] + pub account_id: String, + pub query_state: String, + pub can_calculate_changes: bool, + pub position: u64, + pub ids: Vec, + #[serde(default)] + pub total: u64, + #[serde(default)] + pub limit: u64, + #[serde(skip)] + _ph: PhantomData<*const OBJ>, +} + +impl std::convert::TryFrom<&RawValue> for QueryResponse { + type Error = crate::error::MeliError; + fn try_from(t: &RawValue) -> Result, crate::error::MeliError> { + let res: (String, QueryResponse, String) = serde_json::from_str(t.get())?; + assert_eq!(&res.0, &format!("{}/query", OBJ::NAME)); + Ok(res.1) + } +} + +impl QueryResponse { + _impl_get_mut!(ids_mut, ids: Vec); +} diff --git a/melib/src/email.rs b/melib/src/email.rs index 0dff6cb7..8c7e577b 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -125,7 +125,7 @@ pub struct Envelope { subject: Option, message_id: MessageID, in_reply_to: Option, - references: Option, + pub references: Option, other_headers: FnvHashMap, timestamp: UnixTimestamp, @@ -557,6 +557,7 @@ impl Envelope { None => Vec::new(), } } + pub fn other_headers(&self) -> &FnvHashMap { &self.other_headers } diff --git a/melib/src/email/address.rs b/melib/src/email/address.rs index 85231418..400738a2 100644 --- a/melib/src/email/address.rs +++ b/melib/src/email/address.rs @@ -257,3 +257,34 @@ impl fmt::Debug for References { write!(f, "{:#?}", self.refs) } } + +#[macro_export] +macro_rules! make_address { + ($d:expr, $a:expr) => { + Address::Mailbox(if $d.is_empty() { + MailboxAddress { + raw: format!("{}", $a).into_bytes(), + display_name: StrBuilder { + offset: 0, + length: 0, + }, + address_spec: StrBuilder { + offset: 0, + length: $a.len(), + }, + } + } else { + MailboxAddress { + raw: format!("{} <{}>", $d, $a).into_bytes(), + display_name: StrBuilder { + offset: 0, + length: $d.len(), + }, + address_spec: StrBuilder { + offset: $d.len() + 2, + length: $a.len(), + }, + } + }) + }; +} diff --git a/melib/src/email/parser.rs b/melib/src/email/parser.rs index 4176dce9..cad0dd32 100644 --- a/melib/src/email/parser.rs +++ b/melib/src/email/parser.rs @@ -1102,36 +1102,6 @@ mod tests { ); } - macro_rules! make_address { - ($d:literal, $a:literal) => { - Address::Mailbox(if $d.is_empty() { - MailboxAddress { - raw: format!("<{}>", $a).into_bytes(), - display_name: StrBuilder { - offset: 0, - length: 0, - }, - address_spec: StrBuilder { - offset: 1, - length: $a.len(), - }, - } - } else { - MailboxAddress { - raw: format!("{} <{}>", $d, $a).into_bytes(), - display_name: StrBuilder { - offset: 0, - length: $d.len(), - }, - address_spec: StrBuilder { - offset: $d.len() + 2, - length: $a.len(), - }, - } - }) - }; - } - #[test] fn test_address_list() { let s = b"Obit Oppidum , From a41dc6c38a95ce7c23ebf2adf086209dbd36145a Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 5 Dec 2019 00:04:03 +0200 Subject: [PATCH 05/11] JMAP WIP #5 --- melib/src/backends/jmap.rs | 10 ++- melib/src/backends/jmap/objects/email.rs | 11 ++-- melib/src/backends/jmap/objects/mailbox.rs | 7 +- melib/src/backends/jmap/protocol.rs | 72 ++++++++++++++++++--- melib/src/backends/jmap/rfc8620.rs | 69 ++++++++++++++++++-- melib/src/backends/jmap/rfc8620/argument.rs | 54 ++++++++++++++++ 6 files changed, 200 insertions(+), 23 deletions(-) create mode 100644 melib/src/backends/jmap/rfc8620/argument.rs diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 890e09b7..0e4603a5 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -190,12 +190,10 @@ impl MailBackend for JmapType { let handle = { let tx = w.tx(); let closure = move |_work_context| { - tx.send(AsyncStatus::Payload( - protocol::get_message_list(&connection, &folders.read().unwrap()[&folder_hash]) - .and_then(|ids| { - protocol::get_message(&connection, std::dbg!(&ids).as_slice()) - }), - )) + tx.send(AsyncStatus::Payload(protocol::get( + &connection, + &folders.read().unwrap()[&folder_hash], + ))) .unwrap(); tx.send(AsyncStatus::Finished).unwrap(); }; diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index a3ef32dd..37cb8f6d 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -399,7 +399,6 @@ impl EmailGet { } } - _impl!(get_call: Get); _impl!(body_properties: Vec); _impl!(fetch_text_body_values: bool); _impl!(fetch_html_body_values: bool); @@ -410,8 +409,8 @@ impl EmailGet { #[derive(Serialize, Deserialize, Default, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailFilterCondition { - #[serde(skip_serializing_if = "Vec::is_empty")] - pub in_mailboxes: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub in_mailbox: Option, #[serde(skip_serializing_if = "Vec::is_empty")] pub in_mailbox_other_than: Vec, #[serde(skip_serializing_if = "String::is_empty")] @@ -455,7 +454,11 @@ pub struct EmailFilterCondition { } impl EmailFilterCondition { - _impl!(in_mailboxes: Vec); + pub fn new() -> Self { + Self::default() + } + + _impl!(in_mailbox: Option); _impl!(in_mailbox_other_than: Vec); _impl!(before: UtcDate); _impl!(after: UtcDate); diff --git a/melib/src/backends/jmap/objects/mailbox.rs b/melib/src/backends/jmap/objects/mailbox.rs index f3d828cf..1c41e392 100644 --- a/melib/src/backends/jmap/objects/mailbox.rs +++ b/melib/src/backends/jmap/objects/mailbox.rs @@ -60,7 +60,12 @@ pub struct MailboxGet { #[serde(flatten)] pub get_call: Get, } +impl MailboxGet { + pub fn new(get_call: Get) -> Self { + MailboxGet { get_call } + } +} impl Method for MailboxGet { - const NAME: &'static str = "Mailbox/query"; + const NAME: &'static str = "Mailbox/get"; } diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 01be8765..3f393ab6 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -80,7 +80,7 @@ impl Request { } } - pub fn add_call, O: Object>(&mut self, call: M) -> usize { + pub fn add_call, O: Object>(&mut self, call: &M) -> usize { let seq = get_request_no!(self.request_no); self.method_calls .push(serde_json::to_value((M::NAME, call, &format!("m{}", seq))).unwrap()); @@ -177,10 +177,7 @@ pub struct JsonResponse<'a> { pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result> { let seq = get_request_no!(conn.request_no); let email_call: EmailQueryCall = EmailQueryCall { - filter: EmailFilterCondition { - in_mailboxes: vec![folder.id.clone()], - ..Default::default() - }, + filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), collapse_threads: false, position: 0, fetch_threads: true, @@ -202,7 +199,7 @@ pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result Result>())) + .ids(Some(JmapArgument::value( + ids.iter().cloned().collect::>(), + ))) .account_id(conn.account_id.lock().unwrap().clone()), ); let mut req = Request::new(conn.request_no.clone()); - req.add_call(email_call); + req.add_call(&email_call); let res = conn .client .lock() @@ -331,3 +330,60 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result Result> { + let email_query_call: EmailQueryCall = EmailQueryCall { + filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), + collapse_threads: false, + position: 0, + fetch_threads: true, + fetch_messages: true, + fetch_message_properties: vec![ + MessageProperty::ThreadId, + MessageProperty::MailboxId, + MessageProperty::IsUnread, + MessageProperty::IsFlagged, + MessageProperty::IsAnswered, + MessageProperty::IsDraft, + MessageProperty::HasAttachment, + MessageProperty::From, + MessageProperty::To, + MessageProperty::Subject, + MessageProperty::Date, + MessageProperty::Preview, + ], + }; + + let mut req = Request::new(conn.request_no.clone()); + let prev_seq = req.add_call(&email_query_call); + + let email_call: EmailGet = EmailGet::new( + Get::new() + .ids(Some(JmapArgument::reference( + prev_seq, + &email_query_call, + "/ids", + ))) + .account_id(conn.account_id.lock().unwrap().clone()), + ); + + req.add_call(&email_call); + + let res = conn + .client + .lock() + .unwrap() + .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") + .json(&req) + .send(); + + let res_text = res?.text()?; + + let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); + let e = GetResponse::::try_from(v.method_responses.pop().unwrap())?; + let GetResponse:: { list, .. } = e; + Ok(list + .into_iter() + .map(std::convert::Into::into) + .collect::>()) +} diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 14f6d0cc..3bdac1bc 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -21,13 +21,16 @@ use super::Id; use core::marker::PhantomData; -use serde::{de::DeserializeOwned, Serialize}; +use serde::de::DeserializeOwned; +use serde::ser::{Serialize, SerializeStruct, Serializer}; use serde_json::{value::RawValue, Value}; mod filters; pub use filters::*; mod comparator; pub use comparator::*; +mod argument; +pub use argument::*; use super::protocol::{Method, Response}; use std::collections::HashMap; @@ -102,7 +105,7 @@ pub struct Account { extra_properties: HashMap, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Get where @@ -111,7 +114,8 @@ where #[serde(skip_serializing_if = "String::is_empty")] pub account_id: String, #[serde(skip_serializing_if = "Option::is_none")] - pub ids: Option>, + #[serde(flatten)] + pub ids: Option>>, #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>, #[serde(skip)] @@ -131,9 +135,65 @@ where } } _impl!(account_id: String); - _impl!(ids: Option>); + _impl!(ids: Option>>); _impl!(properties: Option>); } + +impl Serialize for Get { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut fields_no = 0; + if !self.account_id.is_empty() { + fields_no += 1; + } + if !self.ids.is_none() { + fields_no += 1; + } + if !self.properties.is_none() { + fields_no += 1; + } + + let mut state = serializer.serialize_struct("Get", fields_no)?; + if !self.account_id.is_empty() { + state.serialize_field("accountId", &self.account_id)?; + } + match self.ids.as_ref() { + None => {} + Some(JmapArgument::Value(ref v)) => state.serialize_field("ids", v)?, + Some(JmapArgument::ResultReference { + ref result_of, + ref name, + ref path, + }) => { + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct A<'a> { + result_of: &'a str, + name: &'a str, + path: &'a str, + } + + state.serialize_field( + "#ids", + &A { + result_of, + name, + path, + }, + )?; + } + } + + if !self.properties.is_none() { + state.serialize_field("properties", self.properties.as_ref().unwrap()); + } + + state.end() + } +} + // The response has the following arguments: // // o accountId: "Id" @@ -192,6 +252,7 @@ pub struct MethodResponse<'a> { pub struct GetResponse { #[serde(skip_serializing_if = "String::is_empty")] pub account_id: String, + #[serde(default)] pub state: String, pub list: Vec, pub not_found: Vec, diff --git a/melib/src/backends/jmap/rfc8620/argument.rs b/melib/src/backends/jmap/rfc8620/argument.rs new file mode 100644 index 00000000..498208fe --- /dev/null +++ b/melib/src/backends/jmap/rfc8620/argument.rs @@ -0,0 +1,54 @@ +/* + * meli - jmap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use crate::backends::jmap::protocol::Method; +use crate::backends::jmap::rfc8620::Object; +use serde::de::DeserializeOwned; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub enum JmapArgument { + Value(T), + ResultReference { + result_of: String, + name: String, + path: String, + }, +} + +impl JmapArgument { + pub fn value(v: T) -> Self { + JmapArgument::Value(v) + } + + pub fn reference(result_of: usize, method: &M, path: &str) -> Self + where + M: Method, + OBJ: Object, + { + JmapArgument::ResultReference { + result_of: format!("m{}", result_of), + name: M::NAME.to_string(), + path: path.to_string(), + } + } +} From 791033d2fc09f81681ee52ca6a9dadc36531d5fc Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 6 Dec 2019 10:06:15 +0200 Subject: [PATCH 06/11] melib/jmap: add byte operations --- melib/src/backends/jmap.rs | 20 ++++- melib/src/backends/jmap/objects/email.rs | 4 +- melib/src/backends/jmap/operations.rs | 102 +++++++++++++++++++++++ melib/src/backends/jmap/protocol.rs | 38 ++++----- 4 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 melib/src/backends/jmap/operations.rs diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 0e4603a5..c3b1832d 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -54,6 +54,9 @@ macro_rules! _impl_get_mut { } } +pub mod operations; +use operations::*; + pub mod connection; use connection::*; @@ -168,6 +171,13 @@ macro_rules! get_conf_val { }; } +#[derive(Debug, Default)] +pub struct Store { + byte_cache: FnvHashMap, + id_store: FnvHashMap, + blob_id_store: FnvHashMap, +} + #[derive(Debug)] pub struct JmapType { account_name: String, @@ -175,6 +185,7 @@ pub struct JmapType { is_subscribed: Arc, server_conf: JmapServerConf, connection: Arc, + store: Arc>, folders: Arc>>, } @@ -185,6 +196,7 @@ impl MailBackend for JmapType { fn get(&mut self, folder: &Folder) -> Async>> { let mut w = AsyncBuilder::new(); let folders = self.folders.clone(); + let store = self.store.clone(); let connection = self.connection.clone(); let folder_hash = folder.hash(); let handle = { @@ -192,6 +204,7 @@ impl MailBackend for JmapType { let closure = move |_work_context| { tx.send(AsyncStatus::Payload(protocol::get( &connection, + &store, &folders.read().unwrap()[&folder_hash], ))) .unwrap(); @@ -231,7 +244,11 @@ impl MailBackend for JmapType { } fn operation(&self, hash: EnvelopeHash) -> Box { - unimplemented!() + Box::new(JmapOp::new( + hash, + self.connection.clone(), + self.store.clone(), + )) } fn save(&self, bytes: &[u8], folder: &str, flags: Option) -> Result<()> { @@ -257,6 +274,7 @@ impl JmapType { Ok(Box::new(JmapType { connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?), + store: Arc::new(RwLock::new(Store::default())), folders: Arc::new(RwLock::new(FnvHashMap::default())), account_name: s.name.clone(), online, diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 37cb8f6d..8340ca5b 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -129,7 +129,7 @@ use std::hash::Hasher; #[serde(rename_all = "camelCase")] pub struct EmailObject { #[serde(default)] - id: Id, + pub id: Id, #[serde(default)] mailbox_ids: HashMap, #[serde(default)] @@ -155,7 +155,7 @@ pub struct EmailObject { #[serde(default)] attachments: Vec, #[serde(default)] - blob_id: String, + pub blob_id: String, #[serde(default)] has_attachment: bool, #[serde(default)] diff --git a/melib/src/backends/jmap/operations.rs b/melib/src/backends/jmap/operations.rs new file mode 100644 index 00000000..db001b4a --- /dev/null +++ b/melib/src/backends/jmap/operations.rs @@ -0,0 +1,102 @@ +/* + * meli - jmap module. + * + * Copyright 2017 - 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +use super::*; + +use crate::backends::BackendOp; +use crate::email::*; +use crate::error::{MeliError, Result}; +use std::cell::Cell; +use std::sync::{Arc, RwLock}; + +/// `BackendOp` implementor for Imap +#[derive(Debug, Clone)] +pub struct JmapOp { + hash: EnvelopeHash, + connection: Arc, + store: Arc>, + bytes: Option, + flags: Cell>, + headers: Option, + body: Option, +} + +impl JmapOp { + pub fn new( + hash: EnvelopeHash, + connection: Arc, + store: Arc>, + ) -> Self { + JmapOp { + hash, + connection, + store, + bytes: None, + headers: None, + body: None, + flags: Cell::new(None), + } + } +} + +impl BackendOp for JmapOp { + fn description(&self) -> String { + self.store + .try_read() + .and_then(|store_lck| Ok(store_lck.id_store[&self.hash].clone())) + .unwrap_or(String::new()) + } + + fn as_bytes(&mut self) -> Result<&[u8]> { + if self.bytes.is_none() { + let mut store_lck = self.store.write().unwrap(); + if !(store_lck.byte_cache.contains_key(&self.hash) + && store_lck.byte_cache[&self.hash].bytes.is_some()) + { + let blob_id = &store_lck.blob_id_store[&self.hash]; + let res = self.connection + .client + .lock() + .unwrap() + .get(&format!("https://jmap-proxy.local/raw/fc32dffe-14e7-11ea-a277-2477037a1804/{blob_id}/{name}", blob_id = blob_id, name = "")) + .send(); + + let res_text = res?.text()?; + + store_lck.byte_cache.entry(self.hash).or_default().bytes = Some(res_text); + } + self.bytes = store_lck.byte_cache[&self.hash].bytes.clone(); + } + Ok(&self.bytes.as_ref().unwrap().as_bytes()) + } + + fn fetch_flags(&self) -> Flag { + Flag::default() + } + + fn set_flag(&mut self, _envelope: &mut Envelope, f: Flag, value: bool) -> Result<()> { + Ok(()) + } + + fn set_tag(&mut self, _envelope: &mut Envelope, _tag: String, value: bool) -> Result<()> { + Ok(()) + } +} diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 3f393ab6..99f1b820 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -314,24 +314,11 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result>()) } -/* - * - *json!({ - "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], - "methodCalls": [["Email/get", { - "ids": ids, - "properties": [ "threadId", "mailboxIds", "from", "subject", - "receivedAt", - "htmlBody", "bodyValues" ], - "bodyProperties": [ "partId", "blobId", "size", "type" ], - "fetchHTMLBodyValues": true, - "maxBodyValueBytes": 256 - }, format!("m{}", seq).as_str()]], - })) - -*/ - -pub fn get(conn: &JmapConnection, folder: &JmapFolder) -> Result> { +pub fn get( + conn: &JmapConnection, + store: &Arc>, + folder: &JmapFolder, +) -> Result> { let email_query_call: EmailQueryCall = EmailQueryCall { filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), collapse_threads: false, @@ -349,7 +336,6 @@ pub fn get(conn: &JmapConnection, folder: &JmapFolder) -> Result> MessageProperty::From, MessageProperty::To, MessageProperty::Subject, - MessageProperty::Date, MessageProperty::Preview, ], }; @@ -382,8 +368,18 @@ pub fn get(conn: &JmapConnection, folder: &JmapFolder) -> Result> let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); let e = GetResponse::::try_from(v.method_responses.pop().unwrap())?; let GetResponse:: { list, .. } = e; - Ok(list + let ids = list + .iter() + .map(|obj| (obj.id.clone(), obj.blob_id.clone())) + .collect::>(); + let ret = list .into_iter() .map(std::convert::Into::into) - .collect::>()) + .collect::>(); + let mut store_lck = store.write().unwrap(); + for (env, (id, blob_id)) in ret.iter().zip(ids.into_iter()) { + store_lck.id_store.insert(env.hash(), id); + store_lck.blob_id_store.insert(env.hash(), blob_id); + } + Ok(ret) } From 275c9f421f6f82491059462b11c39e815e841518 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 6 Dec 2019 14:12:27 +0200 Subject: [PATCH 07/11] JMAP WIP #6 --- melib/src/backends/jmap.rs | 18 +- melib/src/backends/jmap/connection.rs | 2 + melib/src/backends/jmap/objects/email.rs | 28 +- melib/src/backends/jmap/protocol.rs | 103 +--- melib/src/backends/jmap/rfc8620.rs | 499 +++++++------------- melib/src/backends/jmap/rfc8620/argument.rs | 5 +- 6 files changed, 235 insertions(+), 420 deletions(-) diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index c3b1832d..2770bcd0 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -37,20 +37,24 @@ use std::sync::{Arc, Mutex, RwLock}; #[macro_export] macro_rules! _impl { - ($field:ident : $t:ty) => { + ($(#[$outer:meta])*$field:ident : $t:ty) => { + $(#[$outer])* pub fn $field(mut self, new_val: $t) -> Self { self.$field = new_val; self } - } - } - -#[macro_export] -macro_rules! _impl_get_mut { - ($method:ident, $field:ident : $t:ty) => { + }; + (get_mut $(#[$outer:meta])*$method:ident, $field:ident : $t:ty) => { + $(#[$outer])* pub fn $method(&mut self) -> &mut $t { &mut self.$field } + }; + (get $(#[$outer:meta])*$method:ident, $field:ident : $t:ty) => { + $(#[$outer])* + pub fn $method(&self) -> &$t { + &self.$field + } } } diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs index 8a40bb8d..66e91ca5 100644 --- a/melib/src/backends/jmap/connection.rs +++ b/melib/src/backends/jmap/connection.rs @@ -28,6 +28,7 @@ pub struct JmapConnection { pub online_status: Arc>, pub server_conf: JmapServerConf, pub account_id: Arc>, + pub method_call_states: Arc>>, } impl JmapConnection { @@ -61,6 +62,7 @@ impl JmapConnection { online_status, server_conf, account_id: Arc::new(Mutex::new(String::new())), + method_call_states: Arc::new(Mutex::new(Default::default())), }) } } diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 8340ca5b..b1e437d3 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -22,6 +22,7 @@ use super::*; use crate::backends::jmap::protocol::*; use crate::backends::jmap::rfc8620::bool_false; +use core::marker::PhantomData; use serde::de::{Deserialize, Deserializer}; use serde_json::Value; use std::collections::hash_map::DefaultHasher; @@ -131,6 +132,8 @@ pub struct EmailObject { #[serde(default)] pub id: Id, #[serde(default)] + pub blob_id: String, + #[serde(default)] mailbox_ids: HashMap, #[serde(default)] size: u64, @@ -155,8 +158,6 @@ pub struct EmailObject { #[serde(default)] attachments: Vec, #[serde(default)] - pub blob_id: String, - #[serde(default)] has_attachment: bool, #[serde(default)] #[serde(deserialize_with = "deserialize_header")] @@ -366,6 +367,14 @@ impl Method for EmailQueryCall { const NAME: &'static str = "Email/query"; } +impl EmailQueryCall { + pub const RESULT_FIELD_IDS: ResultField = + ResultField:: { + field: "/ids", + _ph: PhantomData, + }; +} + #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct EmailGet { @@ -486,7 +495,10 @@ impl FilterTrait for EmailFilterCondition {} #[serde(rename_all = "camelCase")] pub enum MessageProperty { ThreadId, - MailboxId, + MailboxIds, + Keywords, + Size, + ReceivedAt, IsUnread, IsFlagged, IsAnswered, @@ -494,7 +506,15 @@ pub enum MessageProperty { HasAttachment, From, To, + Cc, + Bcc, + ReplyTo, Subject, - Date, + SentAt, Preview, + Id, + BlobId, + MessageId, + InReplyTo, + Sender, } diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 99f1b820..4e448d58 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -182,92 +182,12 @@ pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result::try_from(v.method_responses.pop().unwrap())?; - let GetResponse:: { list, .. } = e; + let GetResponse:: { list, state, .. } = e; + { + let mut states_lck = conn.method_call_states.lock().unwrap(); + + if let Some(prev_state) = states_lck.get_mut(&EmailGet::NAME) { + debug!("{:?}: prev_state was {}", EmailGet::NAME, prev_state); + + if *prev_state != state { /* Query Changes. */ } + + *prev_state = state; + debug!("{:?}: curr state is {}", EmailGet::NAME, prev_state); + } else { + debug!("{:?}: inserting state {}", EmailGet::NAME, &state); + states_lck.insert(EmailGet::NAME, state); + } + } let ids = list .iter() .map(|obj| (obj.id.clone(), obj.blob_id.clone())) diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 3bdac1bc..e68241a6 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -38,32 +38,6 @@ pub trait Object { const NAME: &'static str; } -// 5.1. /get -// -// Objects of type Foo are fetched via a call to "Foo/get". -// -// It takes the following arguments: -// -// o accountId: "Id" -// -// The id of the account to use. -// -// o ids: "Id[]|null" -// -// The ids of the Foo objects to return. If null, then *all* records -// of the data type are returned, if this is supported for that data -// type and the number of records does not exceed the -// "maxObjectsInGet" limit. -// -// o properties: "String[]|null" -// -// If supplied, only the properties listed in the array are returned -// for each Foo object. If null, all properties of the object are -// returned. The id property of the object is *always* returned, -// even if not explicitly requested. If an invalid property is -// requested, the call MUST be rejected with an "invalidArguments" -// error. - #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct JmapSession { @@ -105,6 +79,16 @@ pub struct Account { extra_properties: HashMap, } +/// #`get` +/// +/// Objects of type `Foo` are fetched via a call to `Foo/get`. +/// +/// It takes the following arguments: +/// +/// - `account_id`: "Id" +/// +/// The id of the account to use. +/// #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Get @@ -134,9 +118,34 @@ where _ph: PhantomData, } } - _impl!(account_id: String); - _impl!(ids: Option>>); - _impl!(properties: Option>); + _impl!( + /// - accountId: "Id" + /// + /// The id of the account to use. + /// + account_id: String + ); + _impl!( + /// - ids: `Option>>` + /// + /// The ids of the Foo objects to return. If `None`, then *all* records + /// of the data type are returned, if this is supported for that data + /// type and the number of records does not exceed the + /// "max_objects_in_get" limit. + /// + ids: Option>> + ); + _impl!( + /// - properties: Option> + /// + /// If supplied, only the properties listed in the array are returned + /// for each `Foo` object. If `None`, all properties of the object are + /// returned. The `id` property of the object is *always* returned, + /// even if not explicitly requested. If an invalid property is + /// requested, the call WILL be rejected with an "invalid_arguments" + /// error. + properties: Option> + ); } impl Serialize for Get { @@ -194,48 +203,6 @@ impl Serialize for Get { } } -// The response has the following arguments: -// -// o accountId: "Id" -// -// The id of the account used for the call. -// -// o state: "String" -// -// A (preferably short) string representing the state on the server -// for *all* the data of this type in the account (not just the -// objects returned in this call). If the data changes, this string -// MUST change. If the Foo data is unchanged, servers SHOULD return -// the same state string on subsequent requests for this data type. -// When a client receives a response with a different state string to -// a previous call, it MUST either throw away all currently cached -// objects for the type or call "Foo/changes" to get the exact -// changes. -// -// o list: "Foo[]" -// -// An array of the Foo objects requested. This is the *empty array* -// if no objects were found or if the "ids" argument passed in was -// also an empty array. The results MAY be in a different order to -// the "ids" in the request arguments. If an identical id is -// included more than once in the request, the server MUST only -// include it once in either the "list" or the "notFound" argument of -// the response. -// -// o notFound: "Id[]" -// -// This array contains the ids passed to the method for records that -// do not exist. The array is empty if all requested ids were found -// or if the "ids" argument passed in was either null or an empty -// array. -// -// The following additional error may be returned instead of the "Foo/ -// get" response: -// -// "requestTooLarge": The number of ids requested by the client exceeds -// the maximum number the server is willing to process in a single -// method call. - #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct MethodResponse<'a> { @@ -268,10 +235,10 @@ impl std::convert::TryFrom<&RawValue> for GetRes } impl GetResponse { - _impl_get_mut!(account_id_mut, account_id: String); - _impl_get_mut!(state_mut, state: String); - _impl_get_mut!(list_mut, list: Vec); - _impl_get_mut!(not_found_mut, not_found: Vec); + _impl!(get_mut account_id_mut, account_id: String); + _impl!(get_mut state_mut, state: String); + _impl!(get_mut list_mut, list: Vec); + _impl!(get_mut not_found_mut, not_found: Vec); } #[derive(Deserialize, Debug)] @@ -282,175 +249,6 @@ enum JmapError { InvalidResultReference, } -// 5.5. /query -// -// For data sets where the total amount of data is expected to be very -// small, clients can just fetch the complete set of data and then do -// any sorting/filtering locally. However, for large data sets (e.g., -// multi-gigabyte mailboxes), the client needs to be able to -// search/sort/window the data type on the server. -// -// A query on the set of Foos in an account is made by calling "Foo/ -// query". This takes a number of arguments to determine which records -// to include, how they should be sorted, and which part of the result -// should be returned (the full list may be *very* long). The result is -// returned as a list of Foo ids. -// -// A call to "Foo/query" takes the following arguments: -// -// o accountId: "Id" -// -// The id of the account to use. -// -// o filter: "FilterOperator|FilterCondition|null" -// -// Determines the set of Foos returned in the results. If null, all -// objects in the account of this type are included in the results. -// A *FilterOperator* object has the following properties: -// -// * operator: "String" -// -// This MUST be one of the following strings: -// -// + "AND": All of the conditions must match for the filter to -// match. -// -// + "OR": At least one of the conditions must match for the -// filter to match. -// -// + "NOT": None of the conditions must match for the filter to -// match. -// -// * conditions: "(FilterOperator|FilterCondition)[]" -// -// The conditions to evaluate against each record. -// -// A *FilterCondition* is an "object" whose allowed properties and -// semantics depend on the data type and is defined in the /query -// method specification for that type. It MUST NOT have an -// "operator" property. -// -// o sort: "Comparator[]|null" -// -// Lists the names of properties to compare between two Foo records, -// and how to compare them, to determine which comes first in the -// sort. If two Foo records have an identical value for the first -// comparator, the next comparator will be considered, and so on. If -// all comparators are the same (this includes the case where an -// empty array or null is given as the "sort" argument), the sort -// order is server dependent, but it MUST be stable between calls to -// "Foo/query". A *Comparator* has the following properties: -// -// * property: "String" -// -// The name of the property on the Foo objects to compare. -// -// * isAscending: "Boolean" (optional; default: true) -// -// If true, sort in ascending order. If false, reverse the -// comparator's results to sort in descending order. -// -// * collation: "String" (optional; default is server-dependent) -// -// The identifier, as registered in the collation registry defined -// in [RFC4790], for the algorithm to use when comparing the order -// of strings. The algorithms the server supports are advertised -// in the capabilities object returned with the Session object -// (see Section 2). -// -// If omitted, the default algorithm is server dependent, but: -// -// 1. It MUST be unicode-aware. -// -// 2. It MAY be selected based on an Accept-Language header in -// the request (as defined in [RFC7231], Section 5.3.5) or -// out-of-band information about the user's language/locale. -// -// 3. It SHOULD be case insensitive where such a concept makes -// sense for a language/locale. Where the user's language is -// unknown, it is RECOMMENDED to follow the advice in -// Section 5.2.3 of [RFC8264]. -// -// The "i;unicode-casemap" collation [RFC5051] and the Unicode -// Collation Algorithm () -// are two examples that fulfil these criterion and provide -// reasonable behaviour for a large number of languages. -// -// When the property being compared is not a string, the -// "collation" property is ignored, and the following comparison -// rules apply based on the type. In ascending order: -// -// + "Boolean": false comes before true. -// -// + "Number": A lower number comes before a higher number. -// -// + "Date"/"UTCDate": The earlier date comes first. -// -// The Comparator object may also have additional properties as -// required for specific sort operations defined in a type's /query -// method. -// -// o position: "Int" (default: 0) -// -// The zero-based index of the first id in the full list of results -// to return. -// -// If a negative value is given, it is an offset from the end of the -// list. Specifically, the negative value MUST be added to the total -// number of results given the filter, and if still negative, it's -// clamped to "0". This is now the zero-based index of the first id -// to return. -// -// If the index is greater than or equal to the total number of -// objects in the results list, then the "ids" array in the response -// will be empty, but this is not an error. -// -// o anchor: "Id|null" -// -// A Foo id. If supplied, the "position" argument is ignored. The -// index of this id in the results will be used in combination with -// the "anchorOffset" argument to determine the index of the first -// result to return (see below for more details). -// -// o anchorOffset: "Int" (default: 0) -// -// The index of the first result to return relative to the index of -// the anchor, if an anchor is given. This MAY be negative. For -// example, "-1" means the Foo immediately preceding the anchor is -// the first result in the list returned (see below for more -// details). -// -// o limit: "UnsignedInt|null" -// -// The maximum number of results to return. If null, no limit -// presumed. The server MAY choose to enforce a maximum "limit" -// argument. In this case, if a greater value is given (or if it is -// null), the limit is clamped to the maximum; the new limit is -// returned with the response so the client is aware. If a negative -// value is given, the call MUST be rejected with an -// "invalidArguments" error. -// -// o calculateTotal: "Boolean" (default: false) -// -// Does the client wish to know the total number of results in the -// query? This may be slow and expensive for servers to calculate, -// particularly with complex filters, so clients should take care to -// only request the total when needed. -// -// If an "anchor" argument is given, the anchor is looked for in the -// results after filtering and sorting. If found, the "anchorOffset" is -// then added to its index. If the resulting index is now negative, it -// is clamped to 0. This index is now used exactly as though it were -// supplied as the "position" argument. If the anchor is not found, the -// call is rejected with an "anchorNotFound" error. -// -// If an "anchor" is specified, any position argument supplied by the -// client MUST be ignored. If no "anchor" is supplied, any -// "anchorOffset" argument MUST be ignored. -// -// A client can use "anchor" instead of "position" to find the index of -// an id within a large set of results. - #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct QueryCall, OBJ: Object> @@ -510,84 +308,6 @@ pub fn bool_true() -> bool { true } -// The response has the following arguments: -// -// o accountId: "Id" -// -// The id of the account used for the call. -// -// o queryState: "String" -// -// A string encoding the current state of the query on the server. -// This string MUST change if the results of the query (i.e., the -// matching ids and their sort order) have changed. The queryState -// string MAY change if something has changed on the server, which -// means the results may have changed but the server doesn't know for -// sure. -// -// The queryState string only represents the ordered list of ids that -// match the particular query (including its sort/filter). There is -// no requirement for it to change if a property on an object -// matching the query changes but the query results are unaffected -// (indeed, it is more efficient if the queryState string does not -// change in this case). The queryState string only has meaning when -// compared to future responses to a query with the same type/sort/ -// filter or when used with /queryChanges to fetch changes. -// -// Should a client receive back a response with a different -// queryState string to a previous call, it MUST either throw away -// the currently cached query and fetch it again (note, this does not -// require fetching the records again, just the list of ids) or call -// "Foo/queryChanges" to get the difference. -// -// o canCalculateChanges: "Boolean" -// -// This is true if the server supports calling "Foo/queryChanges" -// with these "filter"/"sort" parameters. Note, this does not -// guarantee that the "Foo/queryChanges" call will succeed, as it may -// only be possible for a limited time afterwards due to server -// internal implementation details. -// -// o position: "UnsignedInt" -// -// The zero-based index of the first result in the "ids" array within -// the complete list of query results. -// -// o ids: "Id[]" -// -// The list of ids for each Foo in the query results, starting at the -// index given by the "position" argument of this response and -// continuing until it hits the end of the results or reaches the -// "limit" number of ids. If "position" is >= "total", this MUST be -// the empty list. -// -// o total: "UnsignedInt" (only if requested) -// -// The total number of Foos in the results (given the "filter"). -// This argument MUST be omitted if the "calculateTotal" request -// argument is not true. -// -// o limit: "UnsignedInt" (if set by the server) -// -// The limit enforced by the server on the maximum number of results -// to return. This is only returned if the server set a limit or -// used a different limit than that given in the request. -// -// The following additional errors may be returned instead of the "Foo/ -// query" response: -// -// "anchorNotFound": An anchor argument was supplied, but it cannot be -// found in the results of the query. -// -// "unsupportedSort": The "sort" is syntactically valid, but it includes -// a property the server does not support sorting on or a collation -// method it does not recognise. -// -// "unsupportedFilter": The "filter" is syntactically valid, but the -// server cannot process it. If the filter was the result of a user's -// search input, the client SHOULD suggest that the user simplify their -// search. - #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct QueryResponse { @@ -615,5 +335,138 @@ impl std::convert::TryFrom<&RawValue> for QueryR } impl QueryResponse { - _impl_get_mut!(ids_mut, ids: Vec); + _impl!(get_mut ids_mut, ids: Vec); +} + +pub struct ResultField, OBJ: Object> { + pub field: &'static str, + pub _ph: PhantomData<*const (OBJ, M)>, +} + +// error[E0723]: trait bounds other than `Sized` on const fn parameters are unstable +// --> melib/src/backends/jmap/rfc8620.rs:626:6 +// | +// 626 | impl, OBJ: Object> ResultField { +// | ^ +// | +// = note: for more information, see issue https://github.com/rust-lang/rust/issues/57563 +// = help: add `#![feature(const_fn)]` to the crate attributes to enable +// impl, OBJ: Object> ResultField { +// pub const fn new(field: &'static str) -> Self { +// Self { +// field, +// _ph: PhantomData, +// } +// } +// } + +/// #`changes` +/// +/// The "Foo/changes" method allows a client to efficiently update the state of its Foo cache +/// to match the new state on the server. It takes the following arguments: +/// +/// - accountId: "Id" The id of the account to use. +/// - sinceState: "String" +/// The current state of the client. This is the string that was +/// returned as the "state" argument in the "Foo/get" response. The +/// server will return the changes that have occurred since this +/// state. +/// +/// - maxChanges: "UnsignedInt|null" +/// The maximum number of ids to return in the response. The server +/// MAY choose to return fewer than this value but MUST NOT return +/// more. If not given by the client, the server may choose how many +/// to return. If supplied by the client, the value MUST be a +/// positive integer greater than 0. If a value outside of this range +/// is given, the server MUST re +/// +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +/* ch-ch-ch-ch-ch-Changes */ +pub struct Changes +where + OBJ: std::fmt::Debug + Serialize, +{ + #[serde(skip_serializing_if = "String::is_empty")] + pub account_id: String, + pub since_state: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub max_changes: Option, + #[serde(skip)] + _ph: PhantomData<*const OBJ>, +} + +impl Changes +where + OBJ: std::fmt::Debug + Serialize, +{ + pub fn new() -> Self { + Self { + account_id: String::new(), + since_state: String::new(), + max_changes: None, + _ph: PhantomData, + } + } + _impl!( + /// - accountId: "Id" + /// + /// The id of the account to use. + /// + account_id: String + ); + _impl!( + /// - since_state: "String" + /// The current state of the client. This is the string that was + /// returned as the "state" argument in the "Foo/get" response. The + /// server will return the changes that have occurred since this + /// state. + /// + /// + since_state: String + ); + _impl!( + /// - max_changes: "UnsignedInt|null" + /// The maximum number of ids to return in the response. The server + /// MAY choose to return fewer than this value but MUST NOT return + /// more. If not given by the client, the server may choose how many + /// to return. If supplied by the client, the value MUST be a + /// positive integer greater than 0. If a value outside of this range + /// is given, the server MUST re + max_changes: Option + ); +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ChangesResponse { + #[serde(skip_serializing_if = "String::is_empty")] + pub account_id: String, + pub old_state: String, + pub new_state: String, + pub has_more_changes: bool, + pub created: Vec, + pub updated: Vec, + pub destroyed: Vec, + #[serde(skip)] + _ph: PhantomData<*const OBJ>, +} + +impl std::convert::TryFrom<&RawValue> for ChangesResponse { + type Error = crate::error::MeliError; + fn try_from(t: &RawValue) -> Result, crate::error::MeliError> { + let res: (String, ChangesResponse, String) = serde_json::from_str(t.get())?; + assert_eq!(&res.0, &format!("{}/changes", OBJ::NAME)); + Ok(res.1) + } +} + +impl ChangesResponse { + _impl!(get_mut account_id_mut, account_id: String); + _impl!(get_mut old_state_mut, old_state: String); + _impl!(get_mut new_state_mut, new_state: String); + _impl!(get has_more_changes, has_more_changes: bool); + _impl!(get_mut created_mut, created: Vec); + _impl!(get_mut updated_mut, updated: Vec); + _impl!(get_mut destroyed_mut, destroyed: Vec); } diff --git a/melib/src/backends/jmap/rfc8620/argument.rs b/melib/src/backends/jmap/rfc8620/argument.rs index 498208fe..5c8dccae 100644 --- a/melib/src/backends/jmap/rfc8620/argument.rs +++ b/melib/src/backends/jmap/rfc8620/argument.rs @@ -21,6 +21,7 @@ use crate::backends::jmap::protocol::Method; use crate::backends::jmap::rfc8620::Object; +use crate::backends::jmap::rfc8620::ResultField; use serde::de::DeserializeOwned; use serde::ser::{Serialize, SerializeStruct, Serializer}; @@ -40,7 +41,7 @@ impl JmapArgument { JmapArgument::Value(v) } - pub fn reference(result_of: usize, method: &M, path: &str) -> Self + pub fn reference(result_of: usize, method: &M, path: ResultField) -> Self where M: Method, OBJ: Object, @@ -48,7 +49,7 @@ impl JmapArgument { JmapArgument::ResultReference { result_of: format!("m{}", result_of), name: M::NAME.to_string(), - path: path.to_string(), + path: path.field.to_string(), } } } From 30e9114d9cbb003dbc2bc2e16b210100596379dd Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 7 Dec 2019 14:03:54 +0200 Subject: [PATCH 08/11] jmap: fix warnings --- melib/src/backends/jmap.rs | 14 +++++--------- melib/src/backends/jmap/objects/email.rs | 3 +-- melib/src/backends/jmap/operations.rs | 5 ++--- melib/src/backends/jmap/protocol.rs | 5 +---- melib/src/backends/jmap/rfc8620.rs | 4 ++-- melib/src/backends/jmap/rfc8620/argument.rs | 4 +--- 6 files changed, 12 insertions(+), 23 deletions(-) diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 2770bcd0..64434ba4 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -22,16 +22,12 @@ use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; use crate::backends::BackendOp; use crate::backends::FolderHash; -use crate::backends::RefreshEvent; -use crate::backends::RefreshEventKind::{self, *}; use crate::backends::{BackendFolder, Folder, FolderOperation, MailBackend, RefreshEventConsumer}; use crate::conf::AccountSettings; use crate::email::*; use crate::error::{MeliError, Result}; -use fnv::{FnvHashMap, FnvHashSet}; +use fnv::FnvHashMap; use reqwest::blocking::Client; -use std::collections::hash_map::DefaultHasher; -use std::hash::Hasher; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; @@ -221,8 +217,8 @@ impl MailBackend for JmapType { fn watch( &self, - sender: RefreshEventConsumer, - work_context: WorkContext, + _sender: RefreshEventConsumer, + _work_context: WorkContext, ) -> Result { Err(MeliError::from("sadfsa")) } @@ -255,11 +251,11 @@ impl MailBackend for JmapType { )) } - fn save(&self, bytes: &[u8], folder: &str, flags: Option) -> Result<()> { + fn save(&self, _bytes: &[u8], _folder: &str, _flags: Option) -> Result<()> { Ok(()) } - fn folder_operation(&mut self, path: &str, op: FolderOperation) -> Result<()> { + fn folder_operation(&mut self, _path: &str, _op: FolderOperation) -> Result<()> { Ok(()) } diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index b1e437d3..411aec0a 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -20,7 +20,6 @@ */ use super::*; -use crate::backends::jmap::protocol::*; use crate::backends::jmap::rfc8620::bool_false; use core::marker::PhantomData; use serde::de::{Deserialize, Deserializer}; @@ -152,7 +151,7 @@ pub struct EmailObject { #[serde(default)] in_reply_to_email_id: Id, #[serde(default)] - keywords: Value, + keywords: HashMap, #[serde(default)] attached_emails: Option, #[serde(default)] diff --git a/melib/src/backends/jmap/operations.rs b/melib/src/backends/jmap/operations.rs index db001b4a..29e6f04d 100644 --- a/melib/src/backends/jmap/operations.rs +++ b/melib/src/backends/jmap/operations.rs @@ -22,8 +22,7 @@ use super::*; use crate::backends::BackendOp; -use crate::email::*; -use crate::error::{MeliError, Result}; +use crate::error::Result; use std::cell::Cell; use std::sync::{Arc, RwLock}; @@ -92,7 +91,7 @@ impl BackendOp for JmapOp { Flag::default() } - fn set_flag(&mut self, _envelope: &mut Envelope, f: Flag, value: bool) -> Result<()> { + fn set_flag(&mut self, _envelope: &mut Envelope, _f: Flag, _value: bool) -> Result<()> { Ok(()) } diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 4e448d58..bb0ecfb9 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -21,7 +21,7 @@ use super::folder::JmapFolder; use super::*; -use serde::{de::DeserializeOwned, Serialize}; +use serde::Serialize; use serde_json::{json, Value}; use std::convert::TryFrom; @@ -175,7 +175,6 @@ pub struct JsonResponse<'a> { } pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result> { - let seq = get_request_no!(conn.request_no); let email_call: EmailQueryCall = EmailQueryCall { filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), collapse_threads: false, @@ -205,7 +204,6 @@ pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result Result> { - let seq = get_request_no!(conn.request_no); let email_call: EmailGet = EmailGet::new( Get::new() .ids(Some(JmapArgument::value( @@ -267,7 +265,6 @@ pub fn get( Get::new() .ids(Some(JmapArgument::reference( prev_seq, - &email_query_call, EmailQueryCall::RESULT_FIELD_IDS, ))) .account_id(conn.account_id.lock().unwrap().clone()), diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index e68241a6..69cf7b51 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -32,7 +32,7 @@ pub use comparator::*; mod argument; pub use argument::*; -use super::protocol::{Method, Response}; +use super::protocol::Method; use std::collections::HashMap; pub trait Object { const NAME: &'static str; @@ -196,7 +196,7 @@ impl Serialize for Get { } if !self.properties.is_none() { - state.serialize_field("properties", self.properties.as_ref().unwrap()); + state.serialize_field("properties", self.properties.as_ref().unwrap())?; } state.end() diff --git a/melib/src/backends/jmap/rfc8620/argument.rs b/melib/src/backends/jmap/rfc8620/argument.rs index 5c8dccae..1410d0b8 100644 --- a/melib/src/backends/jmap/rfc8620/argument.rs +++ b/melib/src/backends/jmap/rfc8620/argument.rs @@ -22,8 +22,6 @@ use crate::backends::jmap::protocol::Method; use crate::backends::jmap::rfc8620::Object; use crate::backends::jmap::rfc8620::ResultField; -use serde::de::DeserializeOwned; -use serde::ser::{Serialize, SerializeStruct, Serializer}; #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] @@ -41,7 +39,7 @@ impl JmapArgument { JmapArgument::Value(v) } - pub fn reference(result_of: usize, method: &M, path: ResultField) -> Self + pub fn reference(result_of: usize, path: ResultField) -> Self where M: Method, OBJ: Object, From aa9a6a31283a69a799c95ed3ff6a1a2b427d1891 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 7 Dec 2019 14:04:25 +0200 Subject: [PATCH 09/11] melib: add SpecialUseMailbox::detect_usage method --- melib/src/backends.rs | 22 ++++++++++++++++++++++ ui/src/conf.rs | 23 ++--------------------- ui/src/conf/accounts.rs | 2 +- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/melib/src/backends.rs b/melib/src/backends.rs index ec008f04..a6d0b1ba 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -370,6 +370,28 @@ impl Default for SpecialUsageMailbox { } } +impl SpecialUsageMailbox { + pub fn detect_usage(name: &str) -> Option { + if name.eq_ignore_ascii_case("inbox") { + Some(SpecialUsageMailbox::Inbox) + } else if name.eq_ignore_ascii_case("archive") { + Some(SpecialUsageMailbox::Archive) + } else if name.eq_ignore_ascii_case("drafts") { + Some(SpecialUsageMailbox::Drafts) + } else if name.eq_ignore_ascii_case("junk") { + Some(SpecialUsageMailbox::Junk) + } else if name.eq_ignore_ascii_case("spam") { + Some(SpecialUsageMailbox::Junk) + } else if name.eq_ignore_ascii_case("sent") { + Some(SpecialUsageMailbox::Sent) + } else if name.eq_ignore_ascii_case("trash") { + Some(SpecialUsageMailbox::Trash) + } else { + Some(SpecialUsageMailbox::Normal) + } + } +} + pub trait BackendFolder: Debug { fn hash(&self) -> FolderHash; fn name(&self) -> &str; diff --git a/ui/src/conf.rs b/ui/src/conf.rs index 65af869c..aa255da3 100644 --- a/ui/src/conf.rs +++ b/ui/src/conf.rs @@ -182,7 +182,8 @@ impl From for AccountConf { .split(if s.contains('/') { '/' } else { '.' }) .last() .unwrap_or(""); - folder_confs.get_mut(s).unwrap().folder_conf.usage = usage(name); + folder_confs.get_mut(s).unwrap().folder_conf.usage = + SpecialUsageMailbox::detect_usage(name); } if folder_confs[s].folder_conf().ignore.is_unset() { @@ -564,26 +565,6 @@ impl Serialize for CacheType { } } -pub fn usage(name: &str) -> Option { - if name.eq_ignore_ascii_case("inbox") { - Some(SpecialUsageMailbox::Inbox) - } else if name.eq_ignore_ascii_case("archive") { - Some(SpecialUsageMailbox::Archive) - } else if name.eq_ignore_ascii_case("drafts") { - Some(SpecialUsageMailbox::Drafts) - } else if name.eq_ignore_ascii_case("junk") { - Some(SpecialUsageMailbox::Junk) - } else if name.eq_ignore_ascii_case("spam") { - Some(SpecialUsageMailbox::Junk) - } else if name.eq_ignore_ascii_case("sent") { - Some(SpecialUsageMailbox::Sent) - } else if name.eq_ignore_ascii_case("trash") { - Some(SpecialUsageMailbox::Trash) - } else { - Some(SpecialUsageMailbox::Normal) - } -} - pub fn create_config_file(p: &Path) -> Result<()> { let mut file = OpenOptions::new() .write(true) diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs index f20166cb..bbe6cdef 100644 --- a/ui/src/conf/accounts.rs +++ b/ui/src/conf/accounts.rs @@ -353,7 +353,7 @@ impl Account { } else { let mut new = FileFolderConf::default(); new.folder_conf.subscribe = super::ToggleFlag::InternalVal(true); - new.folder_conf.usage = super::usage(f.name()); + new.folder_conf.usage = SpecialUsageMailbox::detect_usage(f.name()); folder_confs.insert(f.hash(), new); } folder_names.insert(f.hash(), f.path().to_string()); From d44a453aed5cecd10ec5419007ec67b3d78b0a92 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sat, 7 Dec 2019 16:44:29 +0200 Subject: [PATCH 10/11] jmap: add keyword->tag support --- melib/src/backends/jmap.rs | 9 +++ melib/src/backends/jmap/objects/email.rs | 4 ++ melib/src/backends/jmap/protocol.rs | 71 ++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 64434ba4..1537b338 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -28,6 +28,7 @@ use crate::email::*; use crate::error::{MeliError, Result}; use fnv::FnvHashMap; use reqwest::blocking::Client; +use std::collections::BTreeMap; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; @@ -186,6 +187,7 @@ pub struct JmapType { server_conf: JmapServerConf, connection: Arc, store: Arc>, + tag_index: Arc>>, folders: Arc>>, } @@ -197,6 +199,7 @@ impl MailBackend for JmapType { let mut w = AsyncBuilder::new(); let folders = self.folders.clone(); let store = self.store.clone(); + let tag_index = self.tag_index.clone(); let connection = self.connection.clone(); let folder_hash = folder.hash(); let handle = { @@ -205,6 +208,7 @@ impl MailBackend for JmapType { tx.send(AsyncStatus::Payload(protocol::get( &connection, &store, + &tag_index, &folders.read().unwrap()[&folder_hash], ))) .unwrap(); @@ -262,6 +266,10 @@ impl MailBackend for JmapType { fn as_any(&self) -> &dyn::std::any::Any { self } + + fn tags(&self) -> Option>>> { + Some(self.tag_index.clone()) + } } impl JmapType { @@ -275,6 +283,7 @@ impl JmapType { Ok(Box::new(JmapType { connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?), store: Arc::new(RwLock::new(Store::default())), + tag_index: Arc::new(RwLock::new(Default::default())), folders: Arc::new(RwLock::new(FnvHashMap::default())), account_name: s.name.clone(), online, diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index 411aec0a..ef252884 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -175,6 +175,10 @@ pub struct EmailObject { thread_id: Id, } +impl EmailObject { + _impl!(get keywords, keywords: HashMap); +} + #[derive(Deserialize, Serialize, Debug, Default)] #[serde(rename_all = "camelCase")] struct Header { diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index bb0ecfb9..6ffc5316 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -21,9 +21,12 @@ use super::folder::JmapFolder; use super::*; +use crate::structs::StackVec; use serde::Serialize; use serde_json::{json, Value}; +use std::collections::hash_map::DefaultHasher; use std::convert::TryFrom; +use std::hash::{Hash, Hasher}; pub type Id = String; pub type UtcDate = String; @@ -39,6 +42,19 @@ macro_rules! get_request_no { }}; } +macro_rules! tag_hash { + ($t:ident) => {{ + let mut hasher = DefaultHasher::default(); + $t.hash(&mut hasher); + hasher.finish() + }}; + ($t:literal) => {{ + let mut hasher = DefaultHasher::default(); + $t.hash(&mut hasher); + hasher.finish() + }}; +} + pub trait Response { const NAME: &'static str; } @@ -235,6 +251,7 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result>, + tag_index: &Arc>>, folder: &JmapFolder, ) -> Result> { let email_query_call: EmailQueryCall = EmailQueryCall { @@ -291,7 +308,7 @@ pub fn get( if let Some(prev_state) = states_lck.get_mut(&EmailGet::NAME) { debug!("{:?}: prev_state was {}", EmailGet::NAME, prev_state); - if *prev_state != state { /* Query Changes. */ } + if *prev_state != state { /* FIXME Query Changes. */ } *prev_state = state; debug!("{:?}: curr state is {}", EmailGet::NAME, prev_state); @@ -300,18 +317,62 @@ pub fn get( states_lck.insert(EmailGet::NAME, state); } } + let mut tag_lck = tag_index.write().unwrap(); let ids = list .iter() - .map(|obj| (obj.id.clone(), obj.blob_id.clone())) - .collect::>(); - let ret = list + .map(|obj| { + let tags = obj + .keywords() + .keys() + .map(|tag| { + let tag_hash = { + let mut hasher = DefaultHasher::default(); + tag.hash(&mut hasher); + hasher.finish() + }; + if !tag_lck.contains_key(&tag_hash) { + tag_lck.insert(tag_hash, tag.to_string()); + } + tag_hash + }) + .collect::>(); + (tags, obj.id.clone(), obj.blob_id.clone()) + }) + .collect::, Id, Id)>>(); + drop(tag_lck); + let mut ret = list .into_iter() .map(std::convert::Into::into) .collect::>(); + let mut store_lck = store.write().unwrap(); - for (env, (id, blob_id)) in ret.iter().zip(ids.into_iter()) { + debug_assert_eq!(tag_hash!("$draft"), 6613915297903591176); + debug_assert_eq!(tag_hash!("$seen"), 1683863812294339685); + debug_assert_eq!(tag_hash!("$flagged"), 2714010747478170100); + debug_assert_eq!(tag_hash!("$answered"), 8940855303929342213); + debug_assert_eq!(tag_hash!("$junk"), 2656839745430720464); + debug_assert_eq!(tag_hash!("$notjunk"), 4091323799684325059); + for (env, (tags, id, blob_id)) in ret.iter_mut().zip(ids.into_iter()) { store_lck.id_store.insert(env.hash(), id); store_lck.blob_id_store.insert(env.hash(), blob_id); + for t in tags { + match t { + 6613915297903591176 => { + env.set_flags(env.flags() | Flag::DRAFT); + } + 1683863812294339685 => { + env.set_flags(env.flags() | Flag::SEEN); + } + 2714010747478170100 => { + env.set_flags(env.flags() | Flag::FLAGGED); + } + 8940855303929342213 => { + env.set_flags(env.flags() | Flag::REPLIED); + } + 2656839745430720464 | 4091323799684325059 => { /* ignore */ } + _ => env.labels_mut().push(t), + } + } } Ok(ret) } From 14eb99f515da2c3627148a2ac2920a200140edff Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 13 Dec 2019 00:01:59 +0200 Subject: [PATCH 11/11] JMAP WIP #7 --- melib/src/backends/jmap/connection.rs | 35 +++++-- melib/src/backends/jmap/folder.rs | 18 +++- melib/src/backends/jmap/objects/email.rs | 117 ++++++++++++++++------- melib/src/backends/jmap/operations.rs | 22 +++-- melib/src/backends/jmap/protocol.rs | 96 +++++++++---------- melib/src/backends/jmap/rfc8620.rs | 100 ++++++++++++++++--- melib/src/email.rs | 14 +-- 7 files changed, 279 insertions(+), 123 deletions(-) diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs index 66e91ca5..321aeb26 100644 --- a/melib/src/backends/jmap/connection.rs +++ b/melib/src/backends/jmap/connection.rs @@ -23,6 +23,7 @@ use super::*; #[derive(Debug)] pub struct JmapConnection { + pub session: JmapSession, pub request_no: Arc>, pub client: Arc>, pub online_status: Arc>, @@ -35,10 +36,6 @@ impl JmapConnection { pub fn new(server_conf: &JmapServerConf, online_status: Arc>) -> Result { use reqwest::header; let mut headers = header::HeaderMap::new(); - headers.insert( - header::AUTHORIZATION, - header::HeaderValue::from_static("fc32dffe-14e7-11ea-a277-2477037a1804"), - ); headers.insert( header::ACCEPT, header::HeaderValue::from_static("application/json"), @@ -51,12 +48,34 @@ impl JmapConnection { .danger_accept_invalid_certs(server_conf.danger_accept_invalid_certs) .default_headers(headers) .build()?; - - let res_text = client.get(&server_conf.server_hostname).send()?.text()?; + let req = client + .get(&server_conf.server_hostname) + .basic_auth( + &server_conf.server_username, + Some(&server_conf.server_password), + ) + .send()?; + let res_text = req.text()?; debug!(&res_text); + let session: JmapSession = serde_json::from_str(&res_text)?; + + if !session + .capabilities + .contains_key("urn:ietf:params:jmap:core") + { + return Err(MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", ")))); + } + if !session + .capabilities + .contains_key("urn:ietf:params:jmap:mail") + { + return Err(MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", ")))); + } + let server_conf = server_conf.clone(); Ok(JmapConnection { + session, request_no: Arc::new(Mutex::new(0)), client: Arc::new(Mutex::new(client)), online_status, @@ -65,4 +84,8 @@ impl JmapConnection { method_call_states: Arc::new(Mutex::new(Default::default())), }) } + + pub fn mail_account_id(&self) -> &Id { + &self.session.primary_accounts["urn:ietf:params:jmap:mail"] + } } diff --git a/melib/src/backends/jmap/folder.rs b/melib/src/backends/jmap/folder.rs index 1ce7da99..87e34c91 100644 --- a/melib/src/backends/jmap/folder.rs +++ b/melib/src/backends/jmap/folder.rs @@ -73,6 +73,22 @@ impl BackendFolder for JmapFolder { } fn special_usage(&self) -> SpecialUsageMailbox { - self.usage + match self.role.as_ref().map(String::as_str) { + Some("inbox") => SpecialUsageMailbox::Inbox, + Some("archive") => SpecialUsageMailbox::Archive, + Some("junk") => SpecialUsageMailbox::Junk, + Some("trash") => SpecialUsageMailbox::Trash, + Some("drafts") => SpecialUsageMailbox::Drafts, + Some("sent") => SpecialUsageMailbox::Sent, + Some(other) => { + debug!( + "unknown JMAP mailbox role for folder {}: {}", + self.path(), + other + ); + SpecialUsageMailbox::Normal + } + None => SpecialUsageMailbox::Normal, + } } } diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs index ef252884..49a1ef60 100644 --- a/melib/src/backends/jmap/objects/email.rs +++ b/melib/src/backends/jmap/objects/email.rs @@ -139,17 +139,23 @@ pub struct EmailObject { #[serde(default)] received_at: String, #[serde(default)] + message_id: Vec, + #[serde(default)] to: Vec, #[serde(default)] - bcc: Vec, + bcc: Option>, #[serde(default)] - reply_to: Option, + reply_to: Option>, #[serde(default)] - cc: Vec, + cc: Option>, + #[serde(default)] + sender: Option>, #[serde(default)] from: Vec, #[serde(default)] - in_reply_to_email_id: Id, + in_reply_to: Option>, + #[serde(default)] + references: Option>, #[serde(default)] keywords: HashMap, #[serde(default)] @@ -166,13 +172,15 @@ pub struct EmailObject { #[serde(default)] preview: Option, #[serde(default)] - sent_at: String, + sent_at: Option, #[serde(default)] - subject: String, + subject: Option, #[serde(default)] text_body: Vec, #[serde(default)] thread_id: Id, + #[serde(flatten)] + extra: HashMap, } impl EmailObject { @@ -223,17 +231,19 @@ impl std::fmt::Display for EmailAddress { impl std::convert::From for crate::Envelope { fn from(mut t: EmailObject) -> crate::Envelope { let mut env = crate::Envelope::new(0); - env.set_date(std::mem::replace(&mut t.sent_at, String::new()).as_bytes()); + if let Some(ref mut sent_at) = t.sent_at { + env.set_date(std::mem::replace(sent_at, String::new()).as_bytes()); + } if let Ok(d) = crate::email::parser::date(env.date_as_str().as_bytes()) { env.set_datetime(d); } - if let Some(v) = t.headers.get("Message-ID").or(t.headers.get("Message-Id")) { + if let Some(v) = t.message_id.get(0) { env.set_message_id(v.as_bytes()); } - if let Some(v) = t.headers.get("In-Reply-To") { - env.set_in_reply_to(v.as_bytes()); - env.push_references(v.as_bytes()); + if let Some(ref in_reply_to) = t.in_reply_to { + env.set_in_reply_to(in_reply_to[0].as_bytes()); + env.push_references(in_reply_to[0].as_bytes()); } if let Some(v) = t.headers.get("References") { let parse_result = crate::email::parser::references(v.as_bytes()); @@ -251,7 +261,9 @@ impl std::convert::From for crate::Envelope { } } env.set_has_attachments(t.has_attachment); - env.set_subject(std::mem::replace(&mut t.subject, String::new()).into_bytes()); + if let Some(ref mut subject) = t.subject { + env.set_subject(std::mem::replace(subject, String::new()).into_bytes()); + } env.set_from( std::mem::replace(&mut t.from, Vec::new()) @@ -266,19 +278,23 @@ impl std::convert::From for crate::Envelope { .collect::>(), ); - env.set_cc( - std::mem::replace(&mut t.cc, Vec::new()) - .into_iter() - .map(|addr| addr.into()) - .collect::>(), - ); + if let Some(ref mut cc) = t.cc { + env.set_cc( + std::mem::replace(cc, Vec::new()) + .into_iter() + .map(|addr| addr.into()) + .collect::>(), + ); + } - env.set_bcc( - std::mem::replace(&mut t.bcc, Vec::new()) - .into_iter() - .map(|addr| addr.into()) - .collect::>(), - ); + if let Some(ref mut bcc) = t.bcc { + env.set_bcc( + std::mem::replace(bcc, Vec::new()) + .into_iter() + .map(|addr| addr.into()) + .collect::>(), + ); + } if env.references.is_some() { if let Some(pos) = env @@ -304,12 +320,21 @@ impl std::convert::From for crate::Envelope { #[serde(rename_all = "camelCase")] struct HtmlBody { blob_id: Id, + #[serde(default)] + charset: String, + #[serde(default)] cid: Option, - disposition: String, + #[serde(default)] + disposition: Option, + #[serde(default)] headers: Value, + #[serde(default)] language: Option>, + #[serde(default)] location: Option, + #[serde(default)] name: Option, + #[serde(default)] part_id: Option, size: u64, #[serde(alias = "type")] @@ -322,12 +347,21 @@ struct HtmlBody { #[serde(rename_all = "camelCase")] struct TextBody { blob_id: Id, + #[serde(default)] + charset: String, + #[serde(default)] cid: Option, - disposition: String, + #[serde(default)] + disposition: Option, + #[serde(default)] headers: Value, + #[serde(default)] language: Option>, + #[serde(default)] location: Option, + #[serde(default)] name: Option, + #[serde(default)] part_id: Option, size: u64, #[serde(alias = "type")] @@ -355,27 +389,34 @@ pub struct EmailQueryResponse { pub total: usize, } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct EmailQueryCall { - pub filter: EmailFilterCondition, /* "inMailboxes": [ folder.id ] },*/ +pub struct EmailQuery { + #[serde(flatten)] + pub query_call: Query, + //pub filter: EmailFilterCondition, /* "inMailboxes": [ folder.id ] },*/ pub collapse_threads: bool, - pub position: u64, - pub fetch_threads: bool, - pub fetch_messages: bool, - pub fetch_message_properties: Vec, } -impl Method for EmailQueryCall { +impl Method for EmailQuery { const NAME: &'static str = "Email/query"; } -impl EmailQueryCall { - pub const RESULT_FIELD_IDS: ResultField = - ResultField:: { +impl EmailQuery { + pub const RESULT_FIELD_IDS: ResultField = + ResultField:: { field: "/ids", _ph: PhantomData, }; + + pub fn new(query_call: Query) -> Self { + EmailQuery { + query_call, + collapse_threads: false, + } + } + + _impl!(collapse_threads: bool); } #[derive(Deserialize, Serialize, Debug)] @@ -388,10 +429,12 @@ pub struct EmailGet { #[serde(default = "bool_false")] pub fetch_text_body_values: bool, #[serde(default = "bool_false")] + #[serde(rename = "fetchHTMLBodyValues")] pub fetch_html_body_values: bool, #[serde(default = "bool_false")] pub fetch_all_body_values: bool, #[serde(default)] + #[serde(skip_serializing_if = "u64_zero")] pub max_body_value_bytes: u64, } diff --git a/melib/src/backends/jmap/operations.rs b/melib/src/backends/jmap/operations.rs index 29e6f04d..8ec3712f 100644 --- a/melib/src/backends/jmap/operations.rs +++ b/melib/src/backends/jmap/operations.rs @@ -71,12 +71,22 @@ impl BackendOp for JmapOp { && store_lck.byte_cache[&self.hash].bytes.is_some()) { let blob_id = &store_lck.blob_id_store[&self.hash]; - let res = self.connection - .client - .lock() - .unwrap() - .get(&format!("https://jmap-proxy.local/raw/fc32dffe-14e7-11ea-a277-2477037a1804/{blob_id}/{name}", blob_id = blob_id, name = "")) - .send(); + let res = self + .connection + .client + .lock() + .unwrap() + .get(&downloadRequestFormat( + &self.connection.session, + self.connection.mail_account_id(), + blob_id, + None, + )) + .basic_auth( + &self.connection.server_conf.server_username, + Some(&self.connection.server_conf.server_password), + ) + .send(); let res_text = res?.text()?; diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 6ffc5316..4029e48c 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -104,32 +104,22 @@ impl Request { } } -#[derive(Serialize, Debug)] -#[serde(untagged)] -pub enum MethodCall { - #[serde(rename_all = "camelCase")] - EmailQuery { - filter: Vec, /* "inMailboxes": [ folder.id ] },*/ - collapse_threads: bool, - position: u64, - fetch_threads: bool, - fetch_messages: bool, - fetch_message_properties: Vec, - }, - MailboxGet {}, - Empty {}, -} - pub fn get_mailboxes(conn: &JmapConnection) -> Result> { let seq = get_request_no!(conn.request_no); let res = conn .client .lock() .unwrap() - .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") + .post(&conn.session.api_url) + .basic_auth( + &conn.server_conf.server_username, + Some(&conn.server_conf.server_password), + ) .json(&json!({ "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], - "methodCalls": [["Mailbox/get", {}, + "methodCalls": [["Mailbox/get", { + "accountId": conn.mail_account_id() + }, format!("#m{}",seq).as_str()]], })) .send(); @@ -191,14 +181,15 @@ pub struct JsonResponse<'a> { } pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result> { - let email_call: EmailQueryCall = EmailQueryCall { - filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), - collapse_threads: false, - position: 0, - fetch_threads: true, - fetch_messages: true, - fetch_message_properties: vec![], - }; + let email_call: EmailQuery = EmailQuery::new( + Query::new() + .account_id(conn.mail_account_id().to_string()) + .filter(Some( + EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), + )) + .position(0), + ) + .collapse_threads(false); let mut req = Request::new(conn.request_no.clone()); req.add_call(&email_call); @@ -207,7 +198,11 @@ pub fn get_message_list(conn: &JmapConnection, folder: &JmapFolder) -> Result Result>(), ))) - .account_id(conn.account_id.lock().unwrap().clone()), + .account_id(conn.mail_account_id().to_string()), ); let mut req = Request::new(conn.request_no.clone()); @@ -234,7 +229,11 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result>>, folder: &JmapFolder, ) -> Result> { - let email_query_call: EmailQueryCall = EmailQueryCall { - filter: EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), - collapse_threads: false, - position: 0, - fetch_threads: true, - fetch_messages: true, - fetch_message_properties: vec![ - MessageProperty::ThreadId, - MessageProperty::MailboxIds, - MessageProperty::IsUnread, - MessageProperty::IsFlagged, - MessageProperty::IsAnswered, - MessageProperty::IsDraft, - MessageProperty::HasAttachment, - MessageProperty::From, - MessageProperty::To, - MessageProperty::Subject, - MessageProperty::Preview, - ], - }; + let email_query_call: EmailQuery = EmailQuery::new( + Query::new() + .account_id(conn.mail_account_id().to_string()) + .filter(Some( + EmailFilterCondition::new().in_mailbox(Some(folder.id.clone())), + )) + .position(0), + ) + .collapse_threads(false); let mut req = Request::new(conn.request_no.clone()); let prev_seq = req.add_call(&email_query_call); @@ -282,9 +270,9 @@ pub fn get( Get::new() .ids(Some(JmapArgument::reference( prev_seq, - EmailQueryCall::RESULT_FIELD_IDS, + EmailQuery::RESULT_FIELD_IDS, ))) - .account_id(conn.account_id.lock().unwrap().clone()), + .account_id(conn.mail_account_id().to_string()), ); req.add_call(&email_call); @@ -293,7 +281,11 @@ pub fn get( .client .lock() .unwrap() - .post("https://jmap-proxy.local/jmap/fc32dffe-14e7-11ea-a277-2477037a1804/") + .post(&conn.session.api_url) + .basic_auth( + &conn.server_conf.server_username, + Some(&conn.server_conf.server_password), + ) .json(&req) .send(); diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 69cf7b51..7f668418 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -20,6 +20,7 @@ */ use super::Id; +use crate::email::parser::BytesExt; use core::marker::PhantomData; use serde::de::DeserializeOwned; use serde::ser::{Serialize, SerializeStruct, Serializer}; @@ -41,30 +42,38 @@ pub trait Object { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct JmapSession { - capabilities: HashMap, - accounts: HashMap, - primary_accounts: Vec, - username: String, - api_url: String, - download_url: String, + pub capabilities: HashMap, + pub accounts: HashMap, + pub primary_accounts: HashMap, + pub username: String, + pub api_url: String, + pub download_url: String, - upload_url: String, - event_source_url: String, - state: String, + pub upload_url: String, + pub event_source_url: String, + pub state: String, #[serde(flatten)] - extra_properties: HashMap, + pub extra_properties: HashMap, } #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct CapabilitiesObject { + #[serde(default)] max_size_upload: u64, + #[serde(default)] max_concurrent_upload: u64, + #[serde(default)] max_size_request: u64, + #[serde(default)] max_concurrent_requests: u64, + #[serde(default)] max_calls_in_request: u64, + #[serde(default)] max_objects_in_get: u64, + #[serde(default)] max_objects_in_set: u64, + #[serde(default)] collation_algorithms: Vec, } @@ -251,18 +260,19 @@ enum JmapError { #[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] -pub struct QueryCall, OBJ: Object> +pub struct Query, OBJ: Object> where OBJ: std::fmt::Debug + Serialize, { account_id: String, - filter: Option>, + filter: Option, sort: Option>, #[serde(default)] position: u64, #[serde(skip_serializing_if = "Option::is_none")] anchor: Option, #[serde(default)] + #[serde(skip_serializing_if = "u64_zero")] anchor_offset: u64, #[serde(skip_serializing_if = "Option::is_none")] limit: Option, @@ -272,7 +282,7 @@ where _ph: PhantomData<*const OBJ>, } -impl, OBJ: Object> QueryCall +impl, OBJ: Object> Query where OBJ: std::fmt::Debug + Serialize, { @@ -291,7 +301,7 @@ where } _impl!(account_id: String); - _impl!(filter: Option>); + _impl!(filter: Option); _impl!(sort: Option>); _impl!(position: u64); _impl!(anchor: Option); @@ -300,6 +310,10 @@ where _impl!(calculate_total: bool); } +pub fn u64_zero(num: &u64) -> bool { + *num == 0 +} + pub fn bool_false() -> bool { false } @@ -470,3 +484,61 @@ impl ChangesResponse { _impl!(get_mut updated_mut, updated: Vec); _impl!(get_mut destroyed_mut, destroyed: Vec); } + +pub fn downloadRequestFormat( + session: &JmapSession, + account_id: &Id, + blob_id: &Id, + name: Option, +) -> String { + // https://jmap.fastmail.com/download/{accountId}/{blobId}/{name} + let mut ret = String::with_capacity( + session.download_url.len() + + blob_id.len() + + name.as_ref().map(|n| n.len()).unwrap_or(0) + + account_id.len(), + ); + let mut prev_pos = 0; + + while let Some(pos) = session.download_url.as_bytes()[prev_pos..].find(b"{") { + ret.push_str(&session.download_url[prev_pos..prev_pos + pos]); + prev_pos += pos; + if session.download_url[prev_pos..].starts_with("{accountId}") { + ret.push_str(account_id); + prev_pos += "{accountId}".len(); + } else if session.download_url[prev_pos..].starts_with("{blobId}") { + ret.push_str(blob_id); + prev_pos += "{blobId}".len(); + } else if session.download_url[prev_pos..].starts_with("{name}") { + ret.push_str(name.as_ref().map(String::as_str).unwrap_or("")); + prev_pos += "{name}".len(); + } + } + if prev_pos != session.download_url.len() { + ret.push_str(&session.download_url[prev_pos..]); + } + ret +} + +pub fn uploadRequestFormat(session: &JmapSession, account_id: &Id) -> String { + //"uploadUrl": "https://jmap.fastmail.com/upload/{accountId}/", + let mut ret = String::with_capacity(session.upload_url.len() + account_id.len()); + let mut prev_pos = 0; + + while let Some(pos) = session.upload_url.as_bytes()[prev_pos..].find(b"{") { + ret.push_str(&session.upload_url[prev_pos..prev_pos + pos]); + prev_pos += pos; + if session.upload_url[prev_pos..].starts_with("{accountId}") { + ret.push_str(account_id); + prev_pos += "{accountId}".len(); + break; + } else { + ret.push('{'); + prev_pos += 1; + } + } + if prev_pos != session.upload_url.len() { + ret.push_str(&session.upload_url[prev_pos..]); + } + ret +} diff --git a/melib/src/email.rs b/melib/src/email.rs index 8c7e577b..48f7c76b 100644 --- a/melib/src/email.rs +++ b/melib/src/email.rs @@ -490,14 +490,14 @@ impl Envelope { self.subject = Some(new_val); } pub fn set_message_id(&mut self, new_val: &[u8]) { - let slice = match parser::message_id(new_val).to_full_result() { - Ok(v) => v, - Err(e) => { - debug!(e); - return; + match parser::message_id(new_val).to_full_result() { + Ok(slice) => { + self.message_id = MessageID::new(new_val, slice); } - }; - self.message_id = MessageID::new(new_val, slice); + Err(_) => { + self.message_id = MessageID::new(new_val, new_val); + } + } } pub fn push_references(&mut self, new_val: &[u8]) { let slice = match parser::message_id(new_val).to_full_result() {