From a712bf6c3cde7c2a027be42dca18161f43692ac0 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 7 Aug 2020 13:51:44 +0300 Subject: [PATCH] melib/jmap: make backend async Replace reqwest with isahc which supports async IO --- Cargo.lock | 483 +++++------------- melib/Cargo.toml | 14 +- melib/src/backends.rs | 6 +- melib/src/backends/jmap.rs | 198 +++---- melib/src/backends/jmap/connection.rs | 123 ++--- melib/src/backends/jmap/operations.rs | 75 ++- melib/src/backends/jmap/protocol.rs | 111 ++-- melib/src/backends/jmap/rfc8620.rs | 14 +- melib/src/backends/jmap/rfc8620/comparator.rs | 2 +- melib/src/backends/jmap/rfc8620/filters.rs | 2 +- melib/src/error.rs | 6 +- 11 files changed, 395 insertions(+), 639 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cdd387fb..8fae87cf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,12 +124,6 @@ dependencies = [ "waker-fn", ] -[[package]] -name = "bumpalo" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" - [[package]] name = "byteorder" version = "1.3.4" @@ -293,6 +287,37 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "curl" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9447ad28eee2a5cfb031c329d46bef77487244fff6a724b378885b8691a35f78" +dependencies = [ + "curl-sys", + "libc", + "openssl-probe", + "openssl-sys", + "schannel", + "socket2", + "winapi 0.3.8", +] + +[[package]] +name = "curl-sys" +version = "0.4.33+curl-7.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9818ea018327f79c811612f29b9834d2abddbe7db81460a2d5c7e12946b337" +dependencies = [ + "cc", + "libc", + "libnghttp2-sys", + "libz-sys", + "openssl-sys", + "pkg-config", + "vcpkg", + "winapi 0.3.8", +] + [[package]] name = "data-encoding" version = "2.2.1" @@ -320,12 +345,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "dtoa" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" - [[package]] name = "encoding" version = "0.2.33" @@ -613,25 +632,6 @@ dependencies = [ "wasi", ] -[[package]] -name = "h2" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "log", - "slab", - "tokio", - "tokio-util", -] - [[package]] name = "heck" version = "0.3.1" @@ -661,79 +661,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "httparse" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" - -[[package]] -name = "hyper" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e7655b9594024ad0ee439f3b5a7299369dc2a3f459b47c696f9ff676f9aa1f" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "itoa", - "log", - "pin-project", - "socket2", - "time", - "tokio", - "tower-service", - "want", -] - -[[package]] -name = "hyper-tls" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-tls", -] - -[[package]] -name = "idna" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "indexmap" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" -dependencies = [ - "autocfg", -] - [[package]] name = "inotify" version = "0.7.1" @@ -763,21 +690,39 @@ dependencies = [ "libc", ] +[[package]] +name = "isahc" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b95d2708cacabb9e638a36e232ee3f2949c576b11150669835183af419dbaca" +dependencies = [ + "bytes", + "crossbeam-channel", + "crossbeam-utils", + "curl", + "curl-sys", + "encoding_rs", + "futures-channel", + "futures-io", + "futures-util", + "http", + "lazy_static", + "log", + "mime", + "serde", + "serde_json", + "slab", + "sluice", + "tracing", + "tracing-futures", +] + [[package]] name = "itoa" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" -[[package]] -name = "js-sys" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -837,6 +782,16 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "libnghttp2-sys" +version = "0.1.4+1.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03624ec6df166e79e139a2310ca213283d6b3c30810c54844f307086d4488df1" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "libsqlite3-sys" version = "0.16.0" @@ -847,6 +802,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "linked-hash-map" version = "0.5.3" @@ -901,12 +868,6 @@ dependencies = [ "libc", ] -[[package]] -name = "matches" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" - [[package]] name = "maybe-uninit" version = "2.0.0" @@ -965,6 +926,7 @@ dependencies = [ "encoding", "flate2", "futures", + "isahc", "libc", "libloading", "memmap", @@ -972,7 +934,6 @@ dependencies = [ "nix", "nom", "notify", - "reqwest", "rusqlite", "serde", "serde_derive", @@ -1017,16 +978,6 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" -[[package]] -name = "mime_guess" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "miniz_oxide" version = "0.4.0" @@ -1298,12 +1249,6 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - [[package]] name = "pin-project" version = "0.4.20" @@ -1324,12 +1269,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pin-project-lite" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" - [[package]] name = "pin-utils" version = "0.1.0" @@ -1480,41 +1419,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "reqwest" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b82c9238b305f26f53443e3a4bc8528d64b8d0bee408ec949eb7bf5635ec680" -dependencies = [ - "base64 0.12.3", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "http", - "http-body", - "hyper", - "hyper-tls", - "js-sys", - "lazy_static", - "log", - "mime", - "mime_guess", - "native-tls", - "percent-encoding", - "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", - "tokio", - "tokio-tls", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - [[package]] name = "rmp" version = "0.8.9" @@ -1699,18 +1603,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_urlencoded" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" -dependencies = [ - "dtoa", - "itoa", - "serde", - "url", -] - [[package]] name = "signal-hook" version = "0.1.15" @@ -1737,6 +1629,18 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "sluice" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed13b7cb46f13a15db2c4740f087a848acc8b31af89f95844d40137451f89b1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", +] + [[package]] name = "smallvec" version = "1.4.1" @@ -1898,48 +1802,6 @@ dependencies = [ "winapi 0.3.8", ] -[[package]] -name = "tokio" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "slab", -] - -[[package]] -name = "tokio-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - [[package]] name = "toml" version = "0.5.6" @@ -1950,42 +1812,45 @@ dependencies = [ ] [[package]] -name = "tower-service" -version = "0.3.0" +name = "tracing" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" - -[[package]] -name = "try-lock" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f0aae59226cf195d8e74d4b34beae1859257efb4e5fed3f147d2dc2c7d372178" dependencies = [ - "version_check", + "cfg-if", + "log", + "tracing-attributes", + "tracing-core", ] [[package]] -name = "unicode-bidi" -version = "0.3.4" +name = "tracing-attributes" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +checksum = "f0693bf8d6f2bf22c690fc61a9d21ac69efdbb894a17ed596b9af0f01e64b84b" dependencies = [ - "matches", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "unicode-normalization" -version = "0.1.12" +name = "tracing-core" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" +checksum = "d593f98af59ebc017c0648f0117525db358745a8894a8d684e185ba3f45954f9" dependencies = [ - "smallvec", + "lazy_static", +] + +[[package]] +name = "tracing-futures" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab7bb6f14721aa00656086e9335d363c5c8747bae02ebe32ea2c7dece5689b4c" +dependencies = [ + "pin-project", + "tracing", ] [[package]] @@ -2006,17 +1871,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" -[[package]] -name = "url" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" -dependencies = [ - "idna", - "matches", - "percent-encoding", -] - [[package]] name = "uuid" version = "0.8.1" @@ -2062,100 +1916,12 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" -[[package]] -name = "wasm-bindgen" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0" -dependencies = [ - "cfg-if", - "serde", - "serde_json", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64487204d863f109eb77e8462189d111f27cb5712cc9fdb3461297a76963a2f6" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" - -[[package]] -name = "web-sys" -version = "0.3.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "wepoll-sys-stjepang" version = "1.0.6" @@ -2208,15 +1974,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winreg" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" -dependencies = [ - "winapi 0.3.8", -] - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/melib/Cargo.toml b/melib/Cargo.toml index 878d86b40..591e33e63 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -35,7 +35,7 @@ uuid = { version = "0.8.1", features = ["serde", "v4"] } unicode-segmentation = { version = "1.2.1", optional = true } libc = {version = "0.2.59", features = ["extra_traits",]} -reqwest = { version ="0.10.0-alpha.2", optional=true, features = ["json", "blocking" ]} +isahc = { version = "0.9.7", optional = true, default-features = false, features = ["http2", "json", "text-decoding"]} serde_json = { version = "1.0", optional = true, features = ["raw_value",] } smallvec = { version = "^1.4.0", features = ["serde", ] } nix = "0.17.0" @@ -52,13 +52,15 @@ flate2 = { version = "1.0.16", optional = true } default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backend", "vcard", "sqlite3", "smtp", "deflate_compression"] debug-tracing = [] -unicode_algorithms = ["unicode-segmentation"] +deflate_compression = ["flate2", ] +http = ["isahc"] +http-static = ["isahc", "isahc/static-curl"] imap_backend = ["native-tls"] +jmap_backend = ["http", "serde_json"] maildir_backend = ["notify", "memmap"] mbox_backend = ["notify", "memmap"] notmuch_backend = [] -jmap_backend = ["reqwest", "serde_json" ] -vcard = [] -sqlite3 = ["rusqlite", ] smtp = ["native-tls", "base64"] -deflate_compression = ["flate2", ] +sqlite3 = ["rusqlite", ] +unicode_algorithms = ["unicode-segmentation"] +vcard = [] diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 90ec5d430..f97d67f0d 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -47,8 +47,6 @@ pub mod maildir; pub mod mbox; #[cfg(feature = "imap_backend")] pub use self::imap::ImapType; -#[cfg(feature = "jmap_backend")] -pub use self::jmap::JmapType; #[cfg(feature = "imap_backend")] pub use self::nntp::NntpType; use crate::async_workers::*; @@ -173,8 +171,8 @@ impl Backends { 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), + create_fn: Box::new(|| Box::new(|f, i| jmap::JmapType::new(f, i))), + validate_conf_fn: Box::new(jmap::JmapType::validate_config), }, ); } diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 613477297..c2b930613 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -19,12 +19,14 @@ * along with meli. If not, see . */ -use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; +use crate::async_workers::{Async, WorkContext}; use crate::backends::*; use crate::conf::AccountSettings; use crate::email::*; use crate::error::{MeliError, Result}; -use reqwest::blocking::Client; +use futures::lock::Mutex as FutureMutex; +use isahc::prelude::HttpClient; +use isahc::ResponseExt; use std::collections::{BTreeMap, HashMap}; use std::convert::TryFrom; use std::str::FromStr; @@ -181,10 +183,10 @@ pub struct Store { #[derive(Debug)] pub struct JmapType { account_name: String, - online: Arc)>>, + online: Arc)>>, is_subscribed: Arc, server_conf: JmapServerConf, - connection: Arc, + connection: Arc>, store: Arc>, tag_index: Arc>>, mailboxes: Arc>>, @@ -193,7 +195,7 @@ pub struct JmapType { impl MailBackend for JmapType { fn capabilities(&self) -> MailBackendCapabilities { const CAPABILITIES: MailBackendCapabilities = MailBackendCapabilities { - is_async: false, + is_async: true, is_remote: true, supports_search: true, extensions: None, @@ -202,61 +204,67 @@ impl MailBackend for JmapType { CAPABILITIES } - fn is_online(&self) -> Result<()> { - if self.online.lock().unwrap().1.is_err() - && Instant::now().duration_since(self.online.lock().unwrap().0) - >= std::time::Duration::new(2, 0) - { - let _ = self.mailboxes(); - } - self.online.lock().unwrap().1.clone() + fn is_online_async(&self) -> ResultFuture<()> { + let online = self.online.clone(); + Ok(Box::pin(async move { + //match timeout(std::time::Duration::from_secs(3), connection.lock()).await { + let online_lck = online.lock().await; + if online_lck.1.is_err() + && Instant::now().duration_since(online_lck.0) >= std::time::Duration::new(2, 0) + { + //let _ = self.mailboxes(); + } + online_lck.1.clone() + })) } - fn fetch(&mut self, mailbox_hash: MailboxHash) -> Result>>> { - let mut w = AsyncBuilder::new(); + fn fetch_async( + &mut self, + mailbox_hash: MailboxHash, + ) -> Result>> + Send + 'static>>> { let mailboxes = self.mailboxes.clone(); let store = self.store.clone(); let tag_index = self.tag_index.clone(); let connection = self.connection.clone(); - let handle = { - let tx = w.tx(); - let closure = move |_work_context| { - tx.send(AsyncStatus::Payload(protocol::get( - &connection, - &store, - &tag_index, - &mailboxes.read().unwrap()[&mailbox_hash], - ))) - .unwrap(); - tx.send(AsyncStatus::Finished).unwrap(); - }; - Box::new(closure) - }; - Ok(w.build(handle)) + Ok(Box::pin(async_stream::try_stream! { + let mut conn = connection.lock().await; + conn.connect().await?; + let res = protocol::fetch( + &conn, + &store, + &tag_index, + &mailboxes, + mailbox_hash, + ).await?; + yield res; + })) } - fn watch( - &self, - _sender: RefreshEventConsumer, - _work_context: WorkContext, - ) -> Result { + fn watch_async(&self, _sender: RefreshEventConsumer) -> ResultFuture<()> { Err(MeliError::from("JMAP watch for updates is unimplemented")) } - fn mailboxes(&self) -> Result> { - if self.mailboxes.read().unwrap().is_empty() { - let mailboxes = debug!(protocol::get_mailboxes(&self.connection))?; - *self.mailboxes.write().unwrap() = mailboxes; - } + fn mailboxes_async(&self) -> ResultFuture> { + let mailboxes = self.mailboxes.clone(); + let connection = self.connection.clone(); + Ok(Box::pin(async move { + let mut conn = connection.lock().await; + conn.connect().await?; + if mailboxes.read().unwrap().is_empty() { + let new_mailboxes = debug!(protocol::get_mailboxes(&conn).await)?; + *mailboxes.write().unwrap() = new_mailboxes; + } - Ok(self - .mailboxes - .read() - .unwrap() - .iter() - .filter(|(_, f)| f.is_subscribed) - .map(|(&h, f)| (h, BackendMailbox::clone(f) as Mailbox)) - .collect()) + let ret = mailboxes + .read() + .unwrap() + .iter() + .filter(|(_, f)| f.is_subscribed) + .map(|(&h, f)| (h, BackendMailbox::clone(f) as Mailbox)) + .collect(); + + Ok(ret) + })) } fn operation(&self, hash: EnvelopeHash) -> Result> { @@ -289,7 +297,7 @@ impl MailBackend for JmapType { q: crate::search::Query, mailbox_hash: Option, ) -> ResultFuture> { - let conn = self.connection.clone(); + let connection = self.connection.clone(); let filter = if let Some(mailbox_hash) = mailbox_hash { let mailbox_id = self.mailboxes.read().unwrap()[&mailbox_hash].id.clone(); @@ -304,44 +312,57 @@ impl MailBackend for JmapType { Filter::::from(q) }; - let email_call: EmailQuery = EmailQuery::new( - Query::new() - .account_id(conn.mail_account_id().to_string()) - .filter(Some(filter)) - .position(0), - ) - .collapse_threads(false); - - let mut req = Request::new(conn.request_no.clone()); - req.add_call(&email_call); - - let res = conn - .client - .lock() - .unwrap() - .post(&conn.session.api_url) - .basic_auth( - &conn.server_conf.server_username, - Some(&conn.server_conf.server_password), + Ok(Box::pin(async move { + let mut conn = connection.lock().await; + conn.connect().await?; + let email_call: EmailQuery = EmailQuery::new( + Query::new() + .account_id(conn.mail_account_id().to_string()) + .filter(Some(filter)) + .position(0), ) - .json(&req) - .send(); + .collapse_threads(false); - let res_text = res?.text()?; - let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); - *conn.online_status.lock().unwrap() = (std::time::Instant::now(), Ok(())); - let m = QueryResponse::::try_from(v.method_responses.remove(0))?; - let QueryResponse:: { ids, .. } = m; - let ret = ids - .into_iter() - .map(|id| { - use std::hash::Hasher; - let mut h = std::collections::hash_map::DefaultHasher::new(); - h.write(id.as_bytes()); - h.finish() - }) - .collect(); - Ok(Box::pin(async move { Ok(ret) })) + let mut req = Request::new(conn.request_no.clone()); + req.add_call(&email_call); + + let mut res = conn + .client + .post_async(&conn.session.api_url, serde_json::to_string(&req)?) + .await?; + + let res_text = res.text_async().await?; + let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); + *conn.online_status.lock().await = (std::time::Instant::now(), Ok(())); + let m = QueryResponse::::try_from(v.method_responses.remove(0))?; + let QueryResponse:: { ids, .. } = m; + let ret = ids + .into_iter() + .map(|id| { + use std::hash::Hasher; + let mut h = std::collections::hash_map::DefaultHasher::new(); + h.write(id.as_bytes()); + h.finish() + }) + .collect(); + Ok(ret) + })) + } + + fn fetch(&mut self, _mailbox_hash: MailboxHash) -> Result>>> { + Err(MeliError::new("Unimplemented.")) + } + + fn watch( + &self, + _sender: RefreshEventConsumer, + _work_context: WorkContext, + ) -> Result { + Err(MeliError::new("Unimplemented.")) + } + + fn mailboxes(&self) -> Result> { + Err(MeliError::new("Unimplemented.")) } } @@ -350,14 +371,17 @@ impl JmapType { s: &AccountSettings, is_subscribed: Box bool + Send + Sync>, ) -> Result> { - let online = Arc::new(Mutex::new(( + let online = Arc::new(FutureMutex::new(( std::time::Instant::now(), Err(MeliError::new("Account is uninitialised.")), ))); let server_conf = JmapServerConf::new(s)?; Ok(Box::new(JmapType { - connection: Arc::new(JmapConnection::new(&server_conf, online.clone())?), + connection: Arc::new(FutureMutex::new(JmapConnection::new( + &server_conf, + online.clone(), + )?)), store: Arc::new(RwLock::new(Store::default())), tag_index: Arc::new(RwLock::new(Default::default())), mailboxes: Arc::new(RwLock::new(HashMap::default())), diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs index b6a70514d..f3e3ab38d 100644 --- a/melib/src/backends/jmap/connection.rs +++ b/melib/src/backends/jmap/connection.rs @@ -20,13 +20,14 @@ */ use super::*; +use isahc::config::Configurable; #[derive(Debug)] pub struct JmapConnection { pub session: JmapSession, pub request_no: Arc>, - pub client: Arc>, - pub online_status: Arc)>>, + pub client: Arc, + pub online_status: Arc)>>, pub server_conf: JmapServerConf, pub account_id: Arc>, pub method_call_states: Arc>>, @@ -35,70 +36,21 @@ pub struct JmapConnection { impl JmapConnection { pub fn new( server_conf: &JmapServerConf, - online_status: Arc)>>, + online_status: Arc)>>, ) -> Result { - use reqwest::header; - let mut headers = header::HeaderMap::new(); - 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 mut jmap_session_resource_url = if server_conf.server_hostname.starts_with("https://") { - server_conf.server_hostname.to_string() - } else { - format!("https://{}", &server_conf.server_hostname) - }; - if server_conf.server_port != 443 { - jmap_session_resource_url.push(':'); - jmap_session_resource_url.push_str(&server_conf.server_port.to_string()); - } - jmap_session_resource_url.push_str("/.well-known/jmap"); - - let req = client - .get(&jmap_session_resource_url) - .basic_auth( + let client = HttpClient::builder() + .timeout(std::time::Duration::from_secs(10)) + .authentication(isahc::auth::Authentication::basic()) + .credentials(isahc::auth::Credentials::new( &server_conf.server_username, - Some(&server_conf.server_password), - ) - .send()?; - let res_text = req.text()?; - - let session: JmapSession = serde_json::from_str(&res_text).map_err(|_| { - let err = MeliError::new(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)\nReply from server: {}", &server_conf.server_hostname, &res_text)); - *online_status.lock().unwrap() = (Instant::now(), Err(err.clone())); - err - })?; - if !session - .capabilities - .contains_key("urn:ietf:params:jmap:core") - { - let 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(", "))); - *online_status.lock().unwrap() = (Instant::now(), Err(err.clone())); - return Err(err); - } - if !session - .capabilities - .contains_key("urn:ietf:params:jmap:mail") - { - let 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(", "))); - *online_status.lock().unwrap() = (Instant::now(), Err(err.clone())); - return Err(err); - } - - *online_status.lock().unwrap() = (Instant::now(), Ok(())); + &server_conf.server_password, + )) + .build()?; let server_conf = server_conf.clone(); Ok(JmapConnection { - session, + session: Default::default(), request_no: Arc::new(Mutex::new(0)), - client: Arc::new(Mutex::new(client)), + client: Arc::new(client), online_status, server_conf, account_id: Arc::new(Mutex::new(String::new())), @@ -106,6 +58,55 @@ impl JmapConnection { }) } + pub async fn connect(&mut self) -> Result<()> { + if self.online_status.lock().await.1.is_ok() { + return Ok(()); + } + let mut jmap_session_resource_url = + if self.server_conf.server_hostname.starts_with("https://") { + self.server_conf.server_hostname.to_string() + } else { + format!("https://{}", &self.server_conf.server_hostname) + }; + if self.server_conf.server_port != 443 { + jmap_session_resource_url.push(':'); + jmap_session_resource_url.push_str(&self.server_conf.server_port.to_string()); + } + jmap_session_resource_url.push_str("/.well-known/jmap"); + + let mut req = self.client.get_async(&jmap_session_resource_url).await?; + let res_text = req.text_async().await?; + + let session: JmapSession = match serde_json::from_str(&res_text) { + Err(err) => { + let err = MeliError::new(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)\nReply from server: {}", &self.server_conf.server_hostname, &res_text)).set_source(Some(Arc::new(err))); + *self.online_status.lock().await = (Instant::now(), Err(err.clone())); + return Err(err); + } + Ok(s) => s, + }; + if !session + .capabilities + .contains_key("urn:ietf:params:jmap:core") + { + let err = MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &self.server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", "))); + *self.online_status.lock().await = (Instant::now(), Err(err.clone())); + return Err(err); + } + if !session + .capabilities + .contains_key("urn:ietf:params:jmap:mail") + { + let err = MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &self.server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", "))); + *self.online_status.lock().await = (Instant::now(), Err(err.clone())); + return Err(err); + } + + *self.online_status.lock().await = (Instant::now(), Ok(())); + self.session = session; + Ok(()) + } + pub fn mail_account_id(&self) -> &Id { &self.session.primary_accounts["urn:ietf:params:jmap:mail"] } diff --git a/melib/src/backends/jmap/operations.rs b/melib/src/backends/jmap/operations.rs index 4b093ba44..0605b484b 100644 --- a/melib/src/backends/jmap/operations.rs +++ b/melib/src/backends/jmap/operations.rs @@ -20,72 +20,69 @@ */ use super::*; -use std::cell::Cell; use std::sync::{Arc, RwLock}; /// `BackendOp` implementor for Imap #[derive(Debug, Clone)] pub struct JmapOp { hash: EnvelopeHash, - connection: Arc, + connection: Arc>, store: Arc>, - bytes: Option, - flags: Cell>, - headers: Option, - body: Option, } impl JmapOp { pub fn new( hash: EnvelopeHash, - connection: Arc, + connection: Arc>, store: Arc>, ) -> Self { JmapOp { hash, connection, store, - bytes: None, - headers: None, - body: None, - flags: Cell::new(None), } } } impl BackendOp for JmapOp { fn as_bytes(&mut self) -> ResultFuture> { - 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 store_lck = self.store.read().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(&download_request_format( - &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()?; - - store_lck.byte_cache.entry(self.hash).or_default().bytes = Some(res_text); + let ret = store_lck.byte_cache[&self.hash].bytes.clone().unwrap(); + return Ok(Box::pin(async move { Ok(ret.into_bytes()) })); } - self.bytes = store_lck.byte_cache[&self.hash].bytes.clone(); } - let ret = self.bytes.as_ref().unwrap().as_bytes().to_vec(); - Ok(Box::pin(async move { Ok(ret) })) + let store = self.store.clone(); + let hash = self.hash; + let connection = self.connection.clone(); + Ok(Box::pin(async move { + let blob_id = store.read().unwrap().blob_id_store[&hash].clone(); + let mut conn = connection.lock().await; + conn.connect().await?; + let mut res = conn + .client + .get_async(&download_request_format( + &conn.session, + conn.mail_account_id(), + &blob_id, + None, + )) + .await?; + + let res_text = res.text_async().await?; + + store + .write() + .unwrap() + .byte_cache + .entry(hash) + .or_default() + .bytes = Some(res_text.clone()); + Ok(res_text.into_bytes()) + })) } fn fetch_flags(&self) -> ResultFuture { diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs index 53a8edaa9..be982c0d5 100644 --- a/melib/src/backends/jmap/protocol.rs +++ b/melib/src/backends/jmap/protocol.rs @@ -95,29 +95,32 @@ impl Request { } } -pub fn get_mailboxes(conn: &JmapConnection) -> Result> { - let seq = get_request_no!(conn.request_no); - let res = conn - .client - .lock() - .unwrap() - .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", { - "accountId": conn.mail_account_id() - }, - format!("#m{}",seq).as_str()]], - })) - .send(); +#[derive(Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct JsonResponse<'a> { + #[serde(borrow)] + method_responses: Vec>, +} - let res_text = res?.text()?; +pub async fn get_mailboxes(conn: &JmapConnection) -> Result> { + let seq = get_request_no!(conn.request_no); + let mut res = conn + .client + .post_async( + &conn.session.api_url, + serde_json::to_string(&json!({ + "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"], + "methodCalls": [["Mailbox/get", { + "accountId": conn.mail_account_id() + }, + format!("#m{}",seq).as_str()]], + }))?, + ) + .await?; + + let res_text = res.text_async().await?; let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); - *conn.online_status.lock().unwrap() = (std::time::Instant::now(), Ok(())); + *conn.online_status.lock().await = (std::time::Instant::now(), Ok(())); let m = GetResponse::::try_from(v.method_responses.remove(0))?; let GetResponse:: { list, account_id, .. @@ -164,14 +167,7 @@ pub fn get_mailboxes(conn: &JmapConnection) -> Result { - #[serde(borrow)] - method_responses: Vec>, -} - -pub fn get_message_list(conn: &JmapConnection, mailbox: &JmapMailbox) -> Result> { +pub async fn get_message_list(conn: &JmapConnection, mailbox: &JmapMailbox) -> Result> { let email_call: EmailQuery = EmailQuery::new( Query::new() .account_id(conn.mail_account_id().to_string()) @@ -187,27 +183,20 @@ pub fn get_message_list(conn: &JmapConnection, mailbox: &JmapMailbox) -> Result< let mut req = Request::new(conn.request_no.clone()); req.add_call(&email_call); - let res = conn + let mut res = conn .client - .lock() - .unwrap() - .post(&conn.session.api_url) - .basic_auth( - &conn.server_conf.server_username, - Some(&conn.server_conf.server_password), - ) - .json(&req) - .send(); + .post_async(&conn.session.api_url, serde_json::to_string(&req)?) + .await?; - let res_text = res?.text()?; + let res_text = res.text_async().await?; let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); - *conn.online_status.lock().unwrap() = (std::time::Instant::now(), Ok(())); + *conn.online_status.lock().await = (std::time::Instant::now(), Ok(())); let m = QueryResponse::::try_from(v.method_responses.remove(0))?; let QueryResponse:: { ids, .. } = m; Ok(ids) } -pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result> { +pub async fn get_message(conn: &JmapConnection, ids: &[String]) -> Result> { let email_call: EmailGet = EmailGet::new( Get::new() .ids(Some(JmapArgument::value(ids.to_vec()))) @@ -216,19 +205,12 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result::try_from(v.method_responses.remove(0))?; let GetResponse:: { list, .. } = e; @@ -238,18 +220,20 @@ pub fn get_message(conn: &JmapConnection, ids: &[String]) -> Result>()) } -pub fn get( +pub async fn fetch( conn: &JmapConnection, store: &Arc>, tag_index: &Arc>>, - mailbox: &JmapMailbox, + mailboxes: &Arc>>, + mailbox_hash: MailboxHash, ) -> Result> { + let mailbox_id = mailboxes.read().unwrap()[&mailbox_hash].id.clone(); let email_query_call: EmailQuery = EmailQuery::new( Query::new() .account_id(conn.mail_account_id().to_string()) .filter(Some(Filter::Condition( EmailFilterCondition::new() - .in_mailbox(Some(mailbox.id.clone())) + .in_mailbox(Some(mailbox_id)) .into(), ))) .position(0), @@ -270,19 +254,12 @@ pub fn get( req.add_call(&email_call); - let res = conn + let mut res = conn .client - .lock() - .unwrap() - .post(&conn.session.api_url) - .basic_auth( - &conn.server_conf.server_username, - Some(&conn.server_conf.server_password), - ) - .json(&req) - .send(); + .post_async(&conn.session.api_url, serde_json::to_string(&req)?) + .await?; - let res_text = res?.text()?; + let res_text = res.text_async().await?; let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap(); let e = GetResponse::::try_from(v.method_responses.pop().unwrap())?; diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs index 88fbda8c8..787dcb2d8 100644 --- a/melib/src/backends/jmap/rfc8620.rs +++ b/melib/src/backends/jmap/rfc8620.rs @@ -39,7 +39,7 @@ pub trait Object { const NAME: &'static str; } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Default)] #[serde(rename_all = "camelCase")] pub struct JmapSession { pub capabilities: HashMap, @@ -112,7 +112,7 @@ where #[serde(skip_serializing_if = "Option::is_none")] pub properties: Option>, #[serde(skip)] - _ph: PhantomData<*const OBJ>, + _ph: PhantomData OBJ>, } impl Get @@ -279,7 +279,7 @@ where #[serde(default = "bool_false")] calculate_total: bool, #[serde(skip)] - _ph: PhantomData<*const OBJ>, + _ph: PhantomData OBJ>, } impl, OBJ: Object> Query @@ -336,7 +336,7 @@ pub struct QueryResponse { #[serde(default)] pub limit: u64, #[serde(skip)] - _ph: PhantomData<*const OBJ>, + _ph: PhantomData OBJ>, } impl std::convert::TryFrom<&RawValue> for QueryResponse { @@ -354,7 +354,7 @@ impl QueryResponse { pub struct ResultField, OBJ: Object> { pub field: &'static str, - pub _ph: PhantomData<*const (OBJ, M)>, + pub _ph: PhantomData (OBJ, M)>, } // error[E0723]: trait bounds other than `Sized` on const fn parameters are unstable @@ -407,7 +407,7 @@ where #[serde(skip_serializing_if = "Option::is_none")] pub max_changes: Option, #[serde(skip)] - _ph: PhantomData<*const OBJ>, + _ph: PhantomData OBJ>, } impl Changes @@ -463,7 +463,7 @@ pub struct ChangesResponse { pub updated: Vec, pub destroyed: Vec, #[serde(skip)] - _ph: PhantomData<*const OBJ>, + _ph: PhantomData OBJ>, } impl std::convert::TryFrom<&RawValue> for ChangesResponse { diff --git a/melib/src/backends/jmap/rfc8620/comparator.rs b/melib/src/backends/jmap/rfc8620/comparator.rs index 7506d0242..f5170f3d8 100644 --- a/melib/src/backends/jmap/rfc8620/comparator.rs +++ b/melib/src/backends/jmap/rfc8620/comparator.rs @@ -32,7 +32,7 @@ pub struct Comparator { //#[serde(flatten)] additional_properties: Vec, - _ph: PhantomData<*const OBJ>, + _ph: PhantomData OBJ>, } impl Comparator { diff --git a/melib/src/backends/jmap/rfc8620/filters.rs b/melib/src/backends/jmap/rfc8620/filters.rs index dbe0cf27c..7d393b24d 100644 --- a/melib/src/backends/jmap/rfc8620/filters.rs +++ b/melib/src/backends/jmap/rfc8620/filters.rs @@ -41,7 +41,7 @@ pub struct FilterCondition, OBJ: Object> { #[serde(flatten)] pub cond: F, #[serde(skip)] - pub _ph: PhantomData<*const OBJ>, + pub _ph: PhantomData OBJ>, } #[derive(Serialize, Debug, PartialEq)] diff --git a/melib/src/error.rs b/melib/src/error.rs index dcd103994..890abdc37 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -240,10 +240,10 @@ impl From for MeliError { } #[cfg(feature = "jmap_backend")] -impl From for MeliError { +impl From for MeliError { #[inline] - fn from(kind: reqwest::Error) -> MeliError { - MeliError::new(format!("{}", kind)).set_source(Some(Arc::new(kind))) + fn from(kind: isahc::Error) -> MeliError { + MeliError::new(kind.to_string()).set_source(Some(Arc::new(kind))) } }