Perf measurement & bottleneck fix #102

Merged
quentin merged 16 commits from perf/cpu-ram-bottleneck into main 2024-02-23 17:32:39 +00:00
30 changed files with 3816 additions and 794 deletions

478
Cargo.lock generated
View file

@ -28,13 +28,15 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aerogramme" name = "aerogramme"
version = "0.2.1" version = "0.2.2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
"async-trait", "async-trait",
"aws-config", "aws-config",
"aws-sdk-s3", "aws-sdk-s3",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"backtrace", "backtrace",
"base64 0.21.7", "base64 0.21.7",
"chrono", "chrono",
@ -44,7 +46,8 @@ dependencies = [
"eml-codec", "eml-codec",
"futures", "futures",
"hex", "hex",
"hyper-rustls", "hyper-rustls 0.26.0",
"hyper-util",
"im", "im",
"imap-codec", "imap-codec",
"imap-flow", "imap-flow",
@ -455,28 +458,27 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "aws-config" name = "aws-config"
version = "1.1.2" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e64b72d4bdbb41a73d27709c65a25b6e4bfc8321bf70fa3a8b19ce7d4eb81b0" checksum = "3182c19847238b50b62ae0383a6dbfc14514e552eb5e307e1ea83ccf5840b8a6"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-http",
"aws-runtime", "aws-runtime",
"aws-sdk-sso", "aws-sdk-sso",
"aws-sdk-ssooidc", "aws-sdk-ssooidc",
"aws-sdk-sts", "aws-sdk-sts",
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-json", "aws-smithy-json",
"aws-smithy-runtime", "aws-smithy-runtime",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"aws-types", "aws-types",
"bytes", "bytes",
"fastrand 2.0.1", "fastrand 2.0.1",
"hex", "hex",
"http", "http 0.2.11",
"hyper", "hyper 0.14.28",
"ring 0.17.7", "ring 0.17.7",
"time", "time",
"tokio", "tokio",
@ -486,77 +488,84 @@ dependencies = [
[[package]] [[package]]
name = "aws-credential-types" name = "aws-credential-types"
version = "1.1.2" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a7cb3510b95492bd9014b60e2e3bee3e48bc516e220316f8e6b60df18b47331" checksum = "e5635d8707f265c773282a22abe1ecd4fbe96a8eb2f0f14c0796f8016f11a41a"
dependencies = [ dependencies = [
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"zeroize", "zeroize",
] ]
[[package]]
name = "aws-http"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a95d41abe4e941399fdb4bc2f54713eac3c839d98151875948bb24e66ab658f2"
dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types 1.1.3",
"aws-types",
"bytes",
"http",
"http-body",
"pin-project-lite 0.2.13",
"tracing",
]
[[package]] [[package]]
name = "aws-runtime" name = "aws-runtime"
version = "1.1.2" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233cca219c6705d525ace011d6f9bc51aaf32fce5b4c41661d2d7ff22d9b4d49" checksum = "6f82b9ae2adfd9d6582440d0eeb394c07f74d21b4c0cc72bdb73735c9e1a9c0e"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-http", "aws-sigv4",
"aws-sigv4 1.1.2",
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-eventstream", "aws-smithy-eventstream",
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"aws-types", "aws-types",
"bytes",
"fastrand 2.0.1", "fastrand 2.0.1",
"http", "http 0.2.11",
"http-body 0.4.6",
"percent-encoding", "percent-encoding",
"pin-project-lite 0.2.13",
"tracing", "tracing",
"uuid", "uuid",
] ]
[[package]] [[package]]
name = "aws-sdk-s3" name = "aws-sdk-config"
version = "1.12.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "634fbe5b6591ee2e281cd2ba8641e9bd752dbf5bf338924d6ad4bd5a3304fe31" checksum = "0cb71960e3e197c3f512f3bf0f47f444acd708db59733416107ec2ff161ff5c4"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-http",
"aws-runtime", "aws-runtime",
"aws-sigv4 1.1.2",
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-checksums", "aws-smithy-http",
"aws-smithy-eventstream",
"aws-smithy-http 0.60.3",
"aws-smithy-json", "aws-smithy-json",
"aws-smithy-runtime", "aws-smithy-runtime",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"aws-types",
"bytes",
"http 0.2.11",
"once_cell",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-s3"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5076637347e7d0218e61facae853110682ae58efabd2f4e2a9e530c203d5fa7b"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-sigv4",
"aws-smithy-async",
"aws-smithy-checksums",
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-json",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-smithy-xml", "aws-smithy-xml",
"aws-types", "aws-types",
"bytes", "bytes",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"regex-lite", "regex-lite",
@ -566,22 +575,21 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sso" name = "aws-sdk-sso"
version = "1.10.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee41005e0f3a19ae749c7953d9e1f1ef8d2183f76f64966e346fa41c1ba0ed44" checksum = "ca7e8097448832fcd22faf6bb227e97d76b40e354509d1307653a885811c7151"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-http",
"aws-runtime", "aws-runtime",
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-json", "aws-smithy-json",
"aws-smithy-runtime", "aws-smithy-runtime",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"aws-types", "aws-types",
"bytes", "bytes",
"http", "http 0.2.11",
"once_cell", "once_cell",
"regex-lite", "regex-lite",
"tracing", "tracing",
@ -589,22 +597,21 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-ssooidc" name = "aws-sdk-ssooidc"
version = "1.10.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa08168f8a27505e7b90f922c32a489feb1f2133878981a15138bebc849ac09c" checksum = "a75073590e23d63044606771afae309fada8eb10ded54a1ce4598347221d3fef"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-http",
"aws-runtime", "aws-runtime",
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-json", "aws-smithy-json",
"aws-smithy-runtime", "aws-smithy-runtime",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"aws-types", "aws-types",
"bytes", "bytes",
"http", "http 0.2.11",
"once_cell", "once_cell",
"regex-lite", "regex-lite",
"tracing", "tracing",
@ -612,23 +619,22 @@ dependencies = [
[[package]] [[package]]
name = "aws-sdk-sts" name = "aws-sdk-sts"
version = "1.10.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29102eff04d50ef70f11a48823db33e33c6cc5f027bfb6ff4864efbd5f1f66f3" checksum = "650e4aaae41547151dea4d8142f7ffcc8ab8ba76d5dccc8933936ef2102c3356"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-http",
"aws-runtime", "aws-runtime",
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-json", "aws-smithy-json",
"aws-smithy-query", "aws-smithy-query",
"aws-smithy-runtime", "aws-smithy-runtime",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"aws-smithy-xml", "aws-smithy-xml",
"aws-types", "aws-types",
"http", "http 0.2.11",
"once_cell", "once_cell",
"regex-lite", "regex-lite",
"tracing", "tracing",
@ -636,40 +642,22 @@ dependencies = [
[[package]] [[package]]
name = "aws-sigv4" name = "aws-sigv4"
version = "0.55.3" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d2ce6f507be68e968a33485ced670111d1cbad161ddbbab1e313c03d37d8f4c" checksum = "404c64a104188ac70dd1684718765cb5559795458e446480e41984e68e57d888"
dependencies = [
"aws-smithy-http 0.55.3",
"form_urlencoded",
"hex",
"hmac",
"http",
"once_cell",
"percent-encoding",
"regex",
"sha2",
"time",
"tracing",
]
[[package]]
name = "aws-sigv4"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b92384b39aedb258aa734fe0e7b2ffcd13f33e68227251a72cd2635e0acc8f1a"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-smithy-eventstream", "aws-smithy-eventstream",
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"bytes", "bytes",
"crypto-bigint 0.5.5", "crypto-bigint 0.5.5",
"form_urlencoded", "form_urlencoded",
"hex", "hex",
"hmac", "hmac",
"http", "http 0.2.11",
"http 1.0.0",
"once_cell", "once_cell",
"p256", "p256",
"percent-encoding", "percent-encoding",
@ -683,9 +671,9 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-async" name = "aws-smithy-async"
version = "1.1.3" version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eac0bb78e9e2765699999a02d7bfb4e6ad8f13e0962ebb9f5202b1d8cd76006" checksum = "fcf7f09a27286d84315dfb9346208abb3b0973a692454ae6d0bc8d803fcce3b4"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
@ -694,18 +682,18 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-checksums" name = "aws-smithy-checksums"
version = "0.60.3" version = "0.60.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "535a2d5f1e459bc7709580a77152c8d493982db083236c2b1d1c51dc6217e8a3" checksum = "0fd4b66f2a8e7c84d7e97bda2666273d41d2a2e25302605bcf906b7b2661ae5e"
dependencies = [ dependencies = [
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"bytes", "bytes",
"crc32c", "crc32c",
"crc32fast", "crc32fast",
"hex", "hex",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"md-5", "md-5",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
"sha1", "sha1",
@ -715,49 +703,29 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-eventstream" name = "aws-smithy-eventstream"
version = "0.60.3" version = "0.60.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "682371561562d08ab437766903c6bc28f4f95d7ab2ecfb389bda7849dd98aefe" checksum = "e6363078f927f612b970edf9d1903ef5cef9a64d1e8423525ebb1f0a1633c858"
dependencies = [ dependencies = [
"aws-smithy-types 1.1.3", "aws-smithy-types",
"bytes", "bytes",
"crc32fast", "crc32fast",
] ]
[[package]] [[package]]
name = "aws-smithy-http" name = "aws-smithy-http"
version = "0.55.3" version = "0.60.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b3b693869133551f135e1f2c77cb0b8277d9e3e17feaf2213f735857c4f0d28" checksum = "b6ca214a6a26f1b7ebd63aa8d4f5e2194095643023f9608edf99a58247b9d80d"
dependencies = [
"aws-smithy-types 0.55.3",
"bytes",
"bytes-utils",
"futures-core",
"http",
"http-body",
"hyper",
"once_cell",
"percent-encoding",
"pin-project-lite 0.2.13",
"pin-utils",
"tracing",
]
[[package]]
name = "aws-smithy-http"
version = "0.60.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "365ca49744b2bda2f1e2dc03b856da3fa5a28ca5b0a41e41d7ff5305a8fae190"
dependencies = [ dependencies = [
"aws-smithy-eventstream", "aws-smithy-eventstream",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"bytes", "bytes",
"bytes-utils", "bytes-utils",
"futures-core", "futures-core",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
@ -767,40 +735,40 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-json" name = "aws-smithy-json"
version = "0.60.3" version = "0.60.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733ccdb727ac63370836aa3b3c483d75ad2ef7bc6507db3efe1d01e8d2e50367" checksum = "1af80ecf3057fb25fe38d1687e94c4601a7817c6a1e87c1b0635f7ecb644ace5"
dependencies = [ dependencies = [
"aws-smithy-types 1.1.3", "aws-smithy-types",
] ]
[[package]] [[package]]
name = "aws-smithy-query" name = "aws-smithy-query"
version = "0.60.3" version = "0.60.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aff02ae2ee7968bbce2983ffb5ce529d24f4848532300f398347bde8c2196974" checksum = "eb27084f72ea5fc20033efe180618677ff4a2f474b53d84695cfe310a6526cbc"
dependencies = [ dependencies = [
"aws-smithy-types 1.1.3", "aws-smithy-types",
"urlencoding", "urlencoding",
] ]
[[package]] [[package]]
name = "aws-smithy-runtime" name = "aws-smithy-runtime"
version = "1.1.3" version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ab9cb6fee50680af8ceaa293ae79eba32095ca117161cb323f9ee30dd87d139" checksum = "fbb5fca54a532a36ff927fbd7407a7c8eb9c3b4faf72792ba2965ea2cad8ed55"
dependencies = [ dependencies = [
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-http 0.60.3", "aws-smithy-http",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"bytes", "bytes",
"fastrand 2.0.1", "fastrand 2.0.1",
"h2", "h2 0.3.24",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"hyper", "hyper 0.14.28",
"hyper-rustls", "hyper-rustls 0.24.2",
"once_cell", "once_cell",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
"pin-utils", "pin-utils",
@ -811,14 +779,15 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-runtime-api" name = "aws-smithy-runtime-api"
version = "1.1.3" version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02ca2da7619517310bfead6d18abcdde90f1439224d887d608503cfacff46dff" checksum = "22389cb6f7cac64f266fb9f137745a9349ced7b47e0d2ba503e9e40ede4f7060"
dependencies = [ dependencies = [
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"bytes", "bytes",
"http", "http 0.2.11",
"http 1.0.0",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
"tokio", "tokio",
"tracing", "tracing",
@ -827,29 +796,16 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-types" name = "aws-smithy-types"
version = "0.55.3" version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16a3d0bf4f324f4ef9793b86a1701d9700fbcdbd12a846da45eed104c634c6e8" checksum = "f081da5481210523d44ffd83d9f0740320050054006c719eae0232d411f024d3"
dependencies = [
"base64-simd",
"itoa",
"num-integer",
"ryu",
"time",
]
[[package]]
name = "aws-smithy-types"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d4bb944488536cd2fef43212d829bc7e9a8bfc4afa079d21170441e7be8d2d0"
dependencies = [ dependencies = [
"base64-simd", "base64-simd",
"bytes", "bytes",
"bytes-utils", "bytes-utils",
"futures-core", "futures-core",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"itoa", "itoa",
"num-integer", "num-integer",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
@ -863,24 +819,24 @@ dependencies = [
[[package]] [[package]]
name = "aws-smithy-xml" name = "aws-smithy-xml"
version = "0.60.3" version = "0.60.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef796feaf894d7fd03869235237aeffe73ed1b29a3927cceeee2eecadf876eba" checksum = "0fccd8f595d0ca839f9f2548e66b99514a85f92feb4c01cf2868d93eb4888a42"
dependencies = [ dependencies = [
"xmlparser", "xmlparser",
] ]
[[package]] [[package]]
name = "aws-types" name = "aws-types"
version = "1.1.2" version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8549aa62c5b7db5c57ab915200ee214b4f5d8f19b29a4a8fa0b3ad3bca1380e3" checksum = "8fbb5d48aae496f628e7aa2e41991dd4074f606d9e3ade1ce1059f293d40f9a2"
dependencies = [ dependencies = [
"aws-credential-types", "aws-credential-types",
"aws-smithy-async", "aws-smithy-async",
"aws-smithy-runtime-api", "aws-smithy-runtime-api",
"aws-smithy-types 1.1.3", "aws-smithy-types",
"http", "http 0.2.11",
"rustc_version", "rustc_version",
"tracing", "tracing",
] ]
@ -896,9 +852,9 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"hyper", "hyper 0.14.28",
"itoa", "itoa",
"matchit", "matchit",
"memchr", "memchr",
@ -922,8 +878,8 @@ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"mime", "mime",
"rustversion", "rustversion",
"tower-layer", "tower-layer",
@ -1242,9 +1198,9 @@ dependencies = [
[[package]] [[package]]
name = "crc32c" name = "crc32c"
version = "0.6.4" version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8f48d60e5b4d2c53d5c2b1d8a58c849a70ae5e5509b08a48d047e3b65714a74" checksum = "89254598aa9b9fa608de44b3ae54c810f0f06d755e24c50177f1f8f31ff50ce2"
dependencies = [ dependencies = [
"rustc_version", "rustc_version",
] ]
@ -1743,7 +1699,26 @@ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
"futures-util", "futures-util",
"http", "http 0.2.11",
"indexmap 2.1.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "h2"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 1.0.0",
"indexmap 2.1.0", "indexmap 2.1.0",
"slab", "slab",
"tokio", "tokio",
@ -1823,6 +1798,17 @@ dependencies = [
"itoa", "itoa",
] ]
[[package]]
name = "http"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "0.4.6" version = "0.4.6"
@ -1830,7 +1816,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http 0.2.11",
"pin-project-lite 0.2.13",
]
[[package]]
name = "http-body"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
dependencies = [
"bytes",
"http 1.0.0",
]
[[package]]
name = "http-body-util"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840"
dependencies = [
"bytes",
"futures-util",
"http 1.0.0",
"http-body 1.0.0",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
] ]
@ -1862,9 +1871,9 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-util", "futures-util",
"h2", "h2 0.3.24",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"httparse", "httparse",
"httpdate", "httpdate",
"itoa", "itoa",
@ -1876,6 +1885,27 @@ dependencies = [
"want", "want",
] ]
[[package]]
name = "hyper"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"h2 0.4.2",
"http 1.0.0",
"http-body 1.0.0",
"httparse",
"httpdate",
"itoa",
"pin-project-lite 0.2.13",
"smallvec",
"tokio",
"want",
]
[[package]] [[package]]
name = "hyper-rustls" name = "hyper-rustls"
version = "0.24.2" version = "0.24.2"
@ -1883,27 +1913,66 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"http", "http 0.2.11",
"hyper", "hyper 0.14.28",
"log", "log",
"rustls 0.21.10", "rustls 0.21.10",
"rustls-native-certs", "rustls-native-certs 0.6.3",
"tokio", "tokio",
"tokio-rustls 0.24.1", "tokio-rustls 0.24.1",
] ]
[[package]]
name = "hyper-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
dependencies = [
"futures-util",
"http 1.0.0",
"hyper 1.2.0",
"hyper-util",
"log",
"rustls 0.22.2",
"rustls-native-certs 0.7.0",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.25.0",
"tower-service",
]
[[package]] [[package]]
name = "hyper-timeout" name = "hyper-timeout"
version = "0.4.1" version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1"
dependencies = [ dependencies = [
"hyper", "hyper 0.14.28",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
"tokio", "tokio",
"tokio-io-timeout", "tokio-io-timeout",
] ]
[[package]]
name = "hyper-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http 1.0.0",
"http-body 1.0.0",
"hyper 1.2.0",
"pin-project-lite 0.2.13",
"socket2 0.5.5",
"tokio",
"tower",
"tower-service",
"tracing",
]
[[package]] [[package]]
name = "iana-time-zone" name = "iana-time-zone"
version = "0.1.59" version = "0.1.59"
@ -1965,7 +2034,7 @@ dependencies = [
[[package]] [[package]]
name = "imap-codec" name = "imap-codec"
version = "2.0.0" version = "2.0.0"
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0adcc244282c64cc7874ffa9cd22e4a451ee19f8" source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#d8a5afc03fb771232e94c73af6a05e79dc80bbed"
dependencies = [ dependencies = [
"abnf-core", "abnf-core",
"base64 0.21.7", "base64 0.21.7",
@ -1980,19 +2049,21 @@ dependencies = [
[[package]] [[package]]
name = "imap-flow" name = "imap-flow"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/superboum/imap-flow.git?branch=custom/aerogramme#60ff9e082ccfcd10a042b616d8038a578fa0c8ff" source = "git+https://github.com/duesee/imap-flow.git?branch=main#68c1da5d1c56dbe543d9736de9683259d1d28191"
dependencies = [ dependencies = [
"bounded-static", "bounded-static",
"bytes", "bytes",
"imap-codec", "imap-codec",
"imap-types",
"thiserror", "thiserror",
"tokio", "tokio",
"tracing",
] ]
[[package]] [[package]]
name = "imap-types" name = "imap-types"
version = "2.0.0" version = "2.0.0"
source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#0adcc244282c64cc7874ffa9cd22e4a451ee19f8" source = "git+https://github.com/superboum/imap-codec?branch=custom/aerogramme#d8a5afc03fb771232e94c73af6a05e79dc80bbed"
dependencies = [ dependencies = [
"base64 0.21.7", "base64 0.21.7",
"bounded-static", "bounded-static",
@ -2076,14 +2147,17 @@ dependencies = [
[[package]] [[package]]
name = "k2v-client" name = "k2v-client"
version = "0.0.4" version = "0.0.4"
source = "git+https://git.deuxfleurs.fr/Deuxfleurs/garage.git?tag=v0.9.1#ee57dd922b9c396298473b41e4046c8d00ee77d5" source = "git+https://git.deuxfleurs.fr/Deuxfleurs/garage.git?branch=k2v/shared_http_client#8b35a946d9f6b31b26b9783acbfab984316051f4"
dependencies = [ dependencies = [
"aws-sigv4 0.55.3", "aws-sdk-config",
"aws-sigv4",
"base64 0.21.7", "base64 0.21.7",
"hex", "hex",
"http", "http 1.0.0",
"hyper", "http-body-util",
"hyper-rustls", "hyper 1.2.0",
"hyper-rustls 0.26.0",
"hyper-util",
"log", "log",
"percent-encoding", "percent-encoding",
"serde", "serde",
@ -2136,7 +2210,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"ring 0.16.20", "ring 0.16.20",
"rustls 0.20.9", "rustls 0.20.9",
"rustls-native-certs", "rustls-native-certs 0.6.3",
"thiserror", "thiserror",
"tokio", "tokio",
"tokio-rustls 0.23.4", "tokio-rustls 0.23.4",
@ -2905,6 +2979,19 @@ dependencies = [
"security-framework", "security-framework",
] ]
[[package]]
name = "rustls-native-certs"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
dependencies = [
"openssl-probe",
"rustls-pemfile 2.0.0",
"rustls-pki-types",
"schannel",
"security-framework",
]
[[package]] [[package]]
name = "rustls-pemfile" name = "rustls-pemfile"
version = "1.0.4" version = "1.0.4"
@ -3520,10 +3607,10 @@ dependencies = [
"axum", "axum",
"base64 0.21.7", "base64 0.21.7",
"bytes", "bytes",
"h2", "h2 0.3.24",
"http", "http 0.2.11",
"http-body", "http-body 0.4.6",
"hyper", "hyper 0.14.28",
"hyper-timeout", "hyper-timeout",
"percent-encoding", "percent-encoding",
"pin-project", "pin-project",
@ -3574,6 +3661,7 @@ version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [ dependencies = [
"log",
"pin-project-lite 0.2.13", "pin-project-lite 0.2.13",
"tracing-attributes", "tracing-attributes",
"tracing-core", "tracing-core",

684
Cargo.nix vendored

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
[package] [package]
name = "aerogramme" name = "aerogramme"
version = "0.2.1" version = "0.2.2"
authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"] authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
edition = "2021" edition = "2021"
license = "EUPL-1.2" license = "EUPL-1.2"
@ -48,23 +48,26 @@ rand = "0.8.5"
rustls = "0.22" rustls = "0.22"
rustls-pemfile = "2.0" rustls-pemfile = "2.0"
tokio-rustls = "0.25" tokio-rustls = "0.25"
hyper-rustls = { version = "0.24", features = ["http2"] } hyper-rustls = { version = "0.26", features = ["http2"] }
hyper-util = { version = "0.1", features = ["full"] }
rpassword = "7.0" rpassword = "7.0"
# login # login
ldap3 = { version = "0.10", default-features = false, features = ["tls-rustls"] } ldap3 = { version = "0.10", default-features = false, features = ["tls-rustls"] }
# storage # storage
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", tag = "v0.9.1" } k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "k2v/shared_http_client" }
aws-config = { version = "1.1.1", features = ["behavior-version-latest"] } aws-config = { version = "1", features = ["behavior-version-latest"] }
aws-sdk-s3 = "1.9.0" aws-sdk-s3 = "1"
aws-smithy-runtime = "1"
aws-smithy-runtime-api = "1"
# email protocols # email protocols
eml-codec = "0.1.2" eml-codec = "0.1.2"
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" } smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" } smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
imap-codec = { version = "2.0.0", features = ["bounded-static", "ext_condstore_qresync"] } imap-codec = { version = "2.0.0", features = ["bounded-static", "ext_condstore_qresync"] }
imap-flow = { git = "https://github.com/superboum/imap-flow.git", branch = "custom/aerogramme" } imap-flow = { git = "https://github.com/duesee/imap-flow.git", branch = "main" }
thiserror = "1.0.56" thiserror = "1.0.56"
[dev-dependencies] [dev-dependencies]

30
flake.lock vendored
View file

@ -48,11 +48,11 @@
"rust-analyzer-src": "rust-analyzer-src" "rust-analyzer-src": "rust-analyzer-src"
}, },
"locked": { "locked": {
"lastModified": 1688484237, "lastModified": 1706768574,
"narHash": "sha256-qFUn2taHGe203wm7Oio4UGFz1sAiq+kitRexY3sQ1CA=", "narHash": "sha256-4o6TMpzBHO659EiJTzd/EGQGUDdbgwKwhqf3u6b23U8=",
"owner": "nix-community", "owner": "nix-community",
"repo": "fenix", "repo": "fenix",
"rev": "626a9e0a84010728b335f14d3982e11b99af7dc6", "rev": "668102037129923cd0fc239d864fce71eabdc6a3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -116,11 +116,11 @@
"systems": "systems_2" "systems": "systems_2"
}, },
"locked": { "locked": {
"lastModified": 1689068808, "lastModified": 1705309234,
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -162,11 +162,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1688231357, "lastModified": 1706550542,
"narHash": "sha256-ZOn16X5jZ6X5ror58gOJAxPfFLAQhZJ6nOUeS4tfFwo=", "narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
"owner": "nixos", "owner": "nixos",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "645ff62e09d294a30de823cb568e9c6d68e92606", "rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -178,11 +178,11 @@
}, },
"nixpkgs_4": { "nixpkgs_4": {
"locked": { "locked": {
"lastModified": 1690294827, "lastModified": 1708673722,
"narHash": "sha256-JV53dEaMM566e+6R4Wj58jBAkFg7HaZr3SsXZ9hdh40=", "narHash": "sha256-FPbPhA727wuVkmR21Va6scRjAmj4pk3U8blteaXB/Hg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "7ce0abe77d2ace6d6fc43ff7077019e62a77e741", "rev": "92cf4feb2b9091466a82b27e4bb045cbccc2ba09",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -204,11 +204,11 @@
"rust-analyzer-src": { "rust-analyzer-src": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1688410727, "lastModified": 1706735270,
"narHash": "sha256-TqKZO9D64UDBCMY2sUP2ebAKP0oY7S9enrHfZaDiqBQ=", "narHash": "sha256-IJk+UitcJsxzMQWm9pa1ZbJBriQ4ginXOlPyVq+Cu40=",
"owner": "rust-lang", "owner": "rust-lang",
"repo": "rust-analyzer", "repo": "rust-analyzer",
"rev": "45272efec5fcb8bc46e303d6ced8bd2ba095a667", "rev": "42cb1a2bd79af321b0cc503d2960b73f34e2f92b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -61,8 +61,8 @@
packageFun = import ./Cargo.nix; packageFun = import ./Cargo.nix;
target = rustTarget; target = rustTarget;
release = true; release = true;
rustcLinkFlags = [ "--cfg" "tokio_unstable" ]; #rustcLinkFlags = [ "--cfg" "tokio_unstable" ];
rustcBuildFlags = [ "--cfg" "tokio_unstable" ]; #rustcBuildFlags = [ "--cfg" "tokio_unstable" ];
rustToolchain = with fenix.packages.x86_64-linux; combine [ rustToolchain = with fenix.packages.x86_64-linux; combine [
minimal.cargo minimal.cargo
minimal.rustc minimal.rustc

View file

@ -1,5 +1,5 @@
use imap_codec::imap_types::command::{FetchModifier, SelectExamineModifier, StoreModifier}; use imap_codec::imap_types::command::{FetchModifier, SelectExamineModifier, StoreModifier};
use imap_codec::imap_types::core::NonEmptyVec; use imap_codec::imap_types::core::Vec1;
use imap_codec::imap_types::extensions::enable::{CapabilityEnable, Utf8Kind}; use imap_codec::imap_types::extensions::enable::{CapabilityEnable, Utf8Kind};
use imap_codec::imap_types::response::Capability; use imap_codec::imap_types::response::Capability;
use std::collections::HashSet; use std::collections::HashSet;
@ -49,7 +49,7 @@ impl Default for ServerCapability {
} }
impl ServerCapability { impl ServerCapability {
pub fn to_vec(&self) -> NonEmptyVec<Capability<'static>> { pub fn to_vec(&self) -> Vec1<Capability<'static>> {
self.0 self.0
.iter() .iter()
.map(|v| v.clone()) .map(|v| v.clone())

View file

@ -6,7 +6,7 @@ use anyhow::{anyhow, bail, Result};
use imap_codec::imap_types::command::{ use imap_codec::imap_types::command::{
Command, CommandBody, ListReturnItem, SelectExamineModifier, Command, CommandBody, ListReturnItem, SelectExamineModifier,
}; };
use imap_codec::imap_types::core::{Atom, Literal, NonEmptyVec, QuotedChar}; use imap_codec::imap_types::core::{Atom, Literal, QuotedChar, Vec1};
use imap_codec::imap_types::datetime::DateTime; use imap_codec::imap_types::datetime::DateTime;
use imap_codec::imap_types::extensions::enable::CapabilityEnable; use imap_codec::imap_types::extensions::enable::CapabilityEnable;
use imap_codec::imap_types::flag::{Flag, FlagNameAttribute}; use imap_codec::imap_types::flag::{Flag, FlagNameAttribute};
@ -17,10 +17,10 @@ use imap_codec::imap_types::status::{StatusDataItem, StatusDataItemName};
use crate::imap::capability::{ClientCapability, ServerCapability}; use crate::imap::capability::{ClientCapability, ServerCapability};
use crate::imap::command::{anystate, MailboxName}; use crate::imap::command::{anystate, MailboxName};
use crate::imap::flow; use crate::imap::flow;
use crate::imap::mailbox_view::MailboxView; use crate::imap::mailbox_view::{MailboxView, UpdateParameters};
use crate::imap::response::Response; use crate::imap::response::Response;
use crate::imap::Body;
use crate::mail::mailbox::Mailbox;
use crate::mail::uidindex::*; use crate::mail::uidindex::*;
use crate::mail::user::{User, MAILBOX_HIERARCHY_DELIMITER as MBX_HIER_DELIM_RAW}; use crate::mail::user::{User, MAILBOX_HIERARCHY_DELIMITER as MBX_HIER_DELIM_RAW};
use crate::mail::IMF; use crate::mail::IMF;
@ -549,6 +549,8 @@ impl<'a> AuthenticatedContext<'a> {
)) ))
} }
//@FIXME we should write a specific version for the "selected" state
//that returns some unsollicited responses
async fn append( async fn append(
self, self,
mailbox: &MailboxCodec<'a>, mailbox: &MailboxCodec<'a>,
@ -558,7 +560,7 @@ impl<'a> AuthenticatedContext<'a> {
) -> Result<(Response<'static>, flow::Transition)> { ) -> Result<(Response<'static>, flow::Transition)> {
let append_tag = self.req.tag.clone(); let append_tag = self.req.tag.clone();
match self.append_internal(mailbox, flags, date, message).await { match self.append_internal(mailbox, flags, date, message).await {
Ok((_mb, uidvalidity, uid, _modseq)) => Ok(( Ok((_mb_view, uidvalidity, uid, _modseq)) => Ok((
Response::build() Response::build()
.tag(append_tag) .tag(append_tag)
.message("APPEND completed") .message("APPEND completed")
@ -580,7 +582,7 @@ impl<'a> AuthenticatedContext<'a> {
fn enable( fn enable(
self, self,
cap_enable: &NonEmptyVec<CapabilityEnable<'static>>, cap_enable: &Vec1<CapabilityEnable<'static>>,
) -> Result<(Response<'static>, flow::Transition)> { ) -> Result<(Response<'static>, flow::Transition)> {
let mut response_builder = Response::build().to_req(self.req); let mut response_builder = Response::build().to_req(self.req);
let capabilities = self.client_capabilities.try_enable(cap_enable.as_ref()); let capabilities = self.client_capabilities.try_enable(cap_enable.as_ref());
@ -593,13 +595,14 @@ impl<'a> AuthenticatedContext<'a> {
)) ))
} }
//@FIXME should be refactored and integrated to the mailbox view
pub(crate) async fn append_internal( pub(crate) async fn append_internal(
self, self,
mailbox: &MailboxCodec<'a>, mailbox: &MailboxCodec<'a>,
flags: &[Flag<'a>], flags: &[Flag<'a>],
date: &Option<DateTime>, date: &Option<DateTime>,
message: &Literal<'a>, message: &Literal<'a>,
) -> Result<(Arc<Mailbox>, ImapUidvalidity, ImapUid, ModSeq)> { ) -> Result<(MailboxView, ImapUidvalidity, ImapUid, ModSeq)> {
let name: &str = MailboxName(mailbox).try_into()?; let name: &str = MailboxName(mailbox).try_into()?;
let mb_opt = self.user.open_mailbox(&name).await?; let mb_opt = self.user.open_mailbox(&name).await?;
@ -607,6 +610,7 @@ impl<'a> AuthenticatedContext<'a> {
Some(mb) => mb, Some(mb) => mb,
None => bail!("Mailbox does not exist"), None => bail!("Mailbox does not exist"),
}; };
let mut view = MailboxView::new(mb, self.client_capabilities.condstore.is_enabled()).await;
if date.is_some() { if date.is_some() {
tracing::warn!("Cannot set date when appending message"); tracing::warn!("Cannot set date when appending message");
@ -617,9 +621,11 @@ impl<'a> AuthenticatedContext<'a> {
let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>(); let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
// TODO: filter allowed flags? ping @Quentin // TODO: filter allowed flags? ping @Quentin
let (uidvalidity, uid, modseq) = mb.append(msg, None, &flags[..]).await?; let (uidvalidity, uid, modseq) =
view.internal.mailbox.append(msg, None, &flags[..]).await?;
//let unsollicited = view.update(UpdateParameters::default()).await?;
Ok((mb, uidvalidity, uid, modseq)) Ok((view, uidvalidity, uid, modseq))
} }
} }

View file

@ -3,7 +3,7 @@ use std::sync::Arc;
use anyhow::Result; use anyhow::Result;
use imap_codec::imap_types::command::{Command, CommandBody, FetchModifier, StoreModifier}; use imap_codec::imap_types::command::{Command, CommandBody, FetchModifier, StoreModifier};
use imap_codec::imap_types::core::Charset; use imap_codec::imap_types::core::{Charset, Vec1};
use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames; use imap_codec::imap_types::fetch::MacroOrMessageDataItemNames;
use imap_codec::imap_types::flag::{Flag, StoreResponse, StoreType}; use imap_codec::imap_types::flag::{Flag, StoreResponse, StoreType};
use imap_codec::imap_types::mailbox::Mailbox as MailboxCodec; use imap_codec::imap_types::mailbox::Mailbox as MailboxCodec;
@ -54,11 +54,15 @@ pub async fn dispatch<'a>(
ctx.fetch(sequence_set, macro_or_item_names, modifiers, uid) ctx.fetch(sequence_set, macro_or_item_names, modifiers, uid)
.await .await
} }
//@FIXME SearchKey::And is a legacy hack, should be refactored
CommandBody::Search { CommandBody::Search {
charset, charset,
criteria, criteria,
uid, uid,
} => ctx.search(charset, criteria, uid).await, } => {
ctx.search(charset, &SearchKey::And(criteria.clone()), uid)
.await
}
CommandBody::Expunge { CommandBody::Expunge {
// UIDPLUS (rfc4315) // UIDPLUS (rfc4315)
uid_sequence_set, uid_sequence_set,
@ -88,15 +92,6 @@ pub async fn dispatch<'a>(
// UNSELECT extension (rfc3691) // UNSELECT extension (rfc3691)
CommandBody::Unselect => ctx.unselect().await, CommandBody::Unselect => ctx.unselect().await,
// IDLE extension (rfc2177)
CommandBody::Idle => Ok((
Response::build()
.to_req(ctx.req)
.message("DUMMY command due to anti-pattern in the code")
.ok()?,
flow::Transition::Idle(ctx.req.tag.clone(), tokio::sync::Notify::new()),
)),
// In selected mode, we fallback to authenticated when needed // In selected mode, we fallback to authenticated when needed
_ => { _ => {
authenticated::dispatch(authenticated::AuthenticatedContext { authenticated::dispatch(authenticated::AuthenticatedContext {

View file

@ -1,11 +1,12 @@
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
use std::sync::Arc; use std::sync::Arc;
use imap_codec::imap_types::core::Tag;
use tokio::sync::Notify; use tokio::sync::Notify;
use crate::imap::mailbox_view::MailboxView; use crate::imap::mailbox_view::MailboxView;
use crate::mail::user::User; use crate::mail::user::User;
use imap_codec::imap_types::core::Tag;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
@ -31,6 +32,14 @@ pub enum State {
), ),
Logout, Logout,
} }
impl State {
pub fn notify(&self) -> Option<Arc<Notify>> {
match self {
Self::Idle(_, _, _, _, anotif) => Some(anotif.clone()),
_ => None,
}
}
}
impl fmt::Display for State { impl fmt::Display for State {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use State::*; use State::*;

View file

@ -4,9 +4,9 @@ use std::sync::Arc;
use anyhow::{anyhow, Error, Result}; use anyhow::{anyhow, Error, Result};
use futures::stream::{FuturesOrdered, StreamExt}; use futures::stream::{StreamExt, TryStreamExt};
use imap_codec::imap_types::core::Charset; use imap_codec::imap_types::core::{Charset, Vec1};
use imap_codec::imap_types::fetch::MessageDataItem; use imap_codec::imap_types::fetch::MessageDataItem;
use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType}; use imap_codec::imap_types::flag::{Flag, FlagFetch, FlagPerm, StoreResponse, StoreType};
use imap_codec::imap_types::response::{Code, CodeOther, Data, Status}; use imap_codec::imap_types::response::{Code, CodeOther, Data, Status};
@ -362,46 +362,36 @@ impl MailboxView {
.iter() .iter()
.map(|midx| midx.uuid) .map(|midx| midx.uuid)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let query_result = self.internal.query(&uuids, query_scope).fetch().await?;
// [3/6] Derive an IMAP-specific view from the results, apply the filters let query = self.internal.query(&uuids, query_scope);
let views = query_result //let query_result = self.internal.query(&uuids, query_scope).fetch().await?;
.iter()
.zip(mail_idx_list.into_iter())
.map(|(qr, midx)| MailView::new(qr, midx))
.collect::<Result<Vec<_>, _>>()?;
// [4/6] Apply the IMAP transformation, bubble up any error let query_stream = query
// We get 2 results: .fetch()
// - The one we send to the client .zip(futures::stream::iter(mail_idx_list))
// - The \Seen flags we must set internally // [3/6] Derive an IMAP-specific view from the results, apply the filters
let (flag_mgmt, imap_ret): (Vec<_>, Vec<_>) = views .map(|(maybe_qr, midx)| match maybe_qr {
.iter() Ok(qr) => Ok((MailView::new(&qr, midx)?.filter(&ap)?, midx)),
.map(|mv| mv.filter(&ap).map(|(body, seen)| ((mv, seen), body))) Err(e) => Err(e),
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.unzip();
// [5/6] Register the \Seen flags
flag_mgmt
.iter()
.filter(|(_mv, seen)| matches!(seen, SeenFlag::MustAdd))
.map(|(mv, _seen)| async move {
let seen_flag = Flag::Seen.to_string();
self.internal
.mailbox
.add_flags(*mv.query_result.uuid(), &[seen_flag])
.await?;
Ok::<_, anyhow::Error>(())
}) })
.collect::<FuturesOrdered<_>>() // [4/6] Apply the IMAP transformation
.collect::<Vec<_>>() .then(|maybe_ret| async move {
.await let ((body, seen), midx) = maybe_ret?;
.into_iter()
.collect::<Result<_, _>>()?; // [5/6] Register the \Seen flags
if matches!(seen, SeenFlag::MustAdd) {
let seen_flag = Flag::Seen.to_string();
self.internal
.mailbox
.add_flags(midx.uuid, &[seen_flag])
.await?;
}
Ok::<_, anyhow::Error>(body)
});
// [6/6] Build the final result that will be sent to the client. // [6/6] Build the final result that will be sent to the client.
Ok(imap_ret) query_stream.try_collect().await
} }
/// A naive search implementation... /// A naive search implementation...
@ -423,39 +413,54 @@ impl MailboxView {
// 3. Filter the selection based on the ID / UID / Flags // 3. Filter the selection based on the ID / UID / Flags
let (kept_idx, to_fetch) = crit.filter_on_idx(&selection); let (kept_idx, to_fetch) = crit.filter_on_idx(&selection);
// 4. Fetch additional info about the emails // 4.a Fetch additional info about the emails
let query_scope = crit.query_scope(); let query_scope = crit.query_scope();
let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::<Vec<_>>(); let uuids = to_fetch.iter().map(|midx| midx.uuid).collect::<Vec<_>>();
let query_result = self.internal.query(&uuids, query_scope).fetch().await?; let query = self.internal.query(&uuids, query_scope);
// 5. If needed, filter the selection based on the body // 4.b We don't want to keep all data in memory, so we do the computing in a stream
let kept_query = crit.filter_on_query(&to_fetch, &query_result)?; let query_stream = query
.fetch()
.zip(futures::stream::iter(&to_fetch))
// 5.a Build a mailview with the body, might fail with an error
// 5.b If needed, filter the selection based on the body, but keep the errors
// 6. Drop the query+mailbox, keep only the mail index
// Here we release a lot of memory, this is the most important part ^^
.filter_map(|(maybe_qr, midx)| {
let r = match maybe_qr {
Ok(qr) => match MailView::new(&qr, midx).map(|mv| crit.is_keep_on_query(&mv)) {
Ok(true) => Some(Ok(*midx)),
Ok(_) => None,
Err(e) => Some(Err(e)),
},
Err(e) => Some(Err(e)),
};
futures::future::ready(r)
});
// 6. Format the result according to the client's taste: // 7. Chain both streams (part resolved from index, part resolved from metadata+body)
// either return UID or ID. let main_stream = futures::stream::iter(kept_idx)
let final_selection = kept_idx.iter().chain(kept_query.iter()); .map(Ok)
let selection_fmt = match uid { .chain(query_stream)
true => final_selection.map(|in_idx| in_idx.uid).collect(), .map_ok(|idx| match uid {
_ => final_selection.map(|in_idx| in_idx.i).collect(), true => (idx.uid, idx.modseq),
}; _ => (idx.i, idx.modseq),
});
// 7. Add the modseq entry if needed // 8. Do the actual computation
let is_modseq = crit.is_modseq(); let internal_result: Vec<_> = main_stream.try_collect().await?;
let maybe_modseq = match is_modseq { let (selection, modseqs): (Vec<_>, Vec<_>) = internal_result.into_iter().unzip();
true => {
let final_selection = kept_idx.iter().chain(kept_query.iter()); // 9. Aggregate the maximum modseq value
final_selection let maybe_modseq = match crit.is_modseq() {
.map(|in_idx| in_idx.modseq) true => modseqs.into_iter().max(),
.max()
.map(|r| NonZeroU64::try_from(r))
.transpose()?
}
_ => None, _ => None,
}; };
// 10. Return the final result
Ok(( Ok((
vec![Body::Data(Data::Search(selection_fmt, maybe_modseq))], vec![Body::Data(Data::Search(selection, maybe_modseq))],
is_modseq, maybe_modseq.is_some(),
)) ))
} }
@ -626,7 +631,7 @@ impl MailboxView {
mod tests { mod tests {
use super::*; use super::*;
use imap_codec::encode::Encoder; use imap_codec::encode::Encoder;
use imap_codec::imap_types::core::NonEmptyVec; use imap_codec::imap_types::core::Vec1;
use imap_codec::imap_types::fetch::Section; use imap_codec::imap_types::fetch::Section;
use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItemName}; use imap_codec::imap_types::fetch::{MacroOrMessageDataItemNames, MessageDataItemName};
use imap_codec::imap_types::response::Response; use imap_codec::imap_types::response::Response;
@ -746,7 +751,7 @@ mod tests {
let test_repr = Response::Data(Data::Fetch { let test_repr = Response::Data(Data::Fetch {
seq: NonZeroU32::new(1).unwrap(), seq: NonZeroU32::new(1).unwrap(),
items: NonEmptyVec::from(MessageDataItem::Body(mime_view::bodystructure( items: Vec1::from(MessageDataItem::Body(mime_view::bodystructure(
&message.child, &message.child,
false, false,
)?)), )?)),

View file

@ -8,7 +8,7 @@ use imap_codec::imap_types::body::{
BasicFields, Body as FetchBody, BodyStructure, MultiPartExtensionData, SinglePartExtensionData, BasicFields, Body as FetchBody, BodyStructure, MultiPartExtensionData, SinglePartExtensionData,
SpecificFields, SpecificFields,
}; };
use imap_codec::imap_types::core::{AString, IString, NString, NonEmptyVec}; use imap_codec::imap_types::core::{AString, IString, NString, Vec1};
use imap_codec::imap_types::fetch::{Part as FetchPart, Section as FetchSection}; use imap_codec::imap_types::fetch::{Part as FetchPart, Section as FetchSection};
use eml_codec::{ use eml_codec::{
@ -141,8 +141,8 @@ impl<'a> NodeMime<'a> {
enum SubsettedSection<'a> { enum SubsettedSection<'a> {
Part, Part,
Header, Header,
HeaderFields(&'a NonEmptyVec<AString<'a>>), HeaderFields(&'a Vec1<AString<'a>>),
HeaderFieldsNot(&'a NonEmptyVec<AString<'a>>), HeaderFieldsNot(&'a Vec1<AString<'a>>),
Text, Text,
Mime, Mime,
} }
@ -238,7 +238,7 @@ impl<'a> SelectedMime<'a> {
/// case-insensitive but otherwise exact. /// case-insensitive but otherwise exact.
fn header_fields( fn header_fields(
&self, &self,
fields: &'a NonEmptyVec<AString<'a>>, fields: &'a Vec1<AString<'a>>,
invert: bool, invert: bool,
) -> Result<ExtractedFull<'a>> { ) -> Result<ExtractedFull<'a>> {
// Build a lowercase ascii hashset with the fields to fetch // Build a lowercase ascii hashset with the fields to fetch
@ -398,8 +398,8 @@ impl<'a> NodeMult<'a> {
.filter_map(|inner| NodeMime(&inner).structure(is_ext).ok()) .filter_map(|inner| NodeMime(&inner).structure(is_ext).ok())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
NonEmptyVec::validate(&inner_bodies)?; Vec1::validate(&inner_bodies)?;
let bodies = NonEmptyVec::unvalidated(inner_bodies); let bodies = Vec1::unvalidated(inner_bodies);
Ok(BodyStructure::Multi { Ok(BodyStructure::Multi {
bodies, bodies,

View file

@ -15,7 +15,7 @@ mod session;
use std::net::SocketAddr; use std::net::SocketAddr;
use anyhow::{bail, Result}; use anyhow::{anyhow, bail, Context, Result};
use futures::stream::{FuturesUnordered, StreamExt}; use futures::stream::{FuturesUnordered, StreamExt};
use tokio::net::TcpListener; use tokio::net::TcpListener;
@ -144,13 +144,6 @@ use tokio_util::bytes::BytesMut;
const PIPELINABLE_COMMANDS: usize = 64; const PIPELINABLE_COMMANDS: usize = 64;
#[derive(Debug)]
enum LoopMode {
Quit,
Interactive,
Idle(BytesMut, Arc<Notify>),
}
// @FIXME a full refactor of this part of the code will be needed sooner or later // @FIXME a full refactor of this part of the code will be needed sooner or later
struct NetLoop { struct NetLoop {
ctx: ClientContext, ctx: ClientContext,
@ -163,7 +156,7 @@ impl NetLoop {
async fn handler(ctx: ClientContext, sock: AnyStream) { async fn handler(ctx: ClientContext, sock: AnyStream) {
let addr = ctx.addr.clone(); let addr = ctx.addr.clone();
let nl = match Self::new(ctx, sock).await { let mut nl = match Self::new(ctx, sock).await {
Ok(nl) => { Ok(nl) => {
tracing::debug!(addr=?addr, "netloop successfully initialized"); tracing::debug!(addr=?addr, "netloop successfully initialized");
nl nl
@ -185,15 +178,15 @@ impl NetLoop {
} }
async fn new(ctx: ClientContext, sock: AnyStream) -> Result<Self> { async fn new(ctx: ClientContext, sock: AnyStream) -> Result<Self> {
let mut opts = ServerFlowOptions::default();
opts.crlf_relaxed = false;
opts.literal_accept_text = Text::unvalidated("OK");
opts.literal_reject_text = Text::unvalidated("Literal rejected");
// Send greeting // Send greeting
let (server, _) = ServerFlow::send_greeting( let (server, _) = ServerFlow::send_greeting(
sock, sock,
ServerFlowOptions { opts,
crlf_relaxed: false,
literal_accept_text: Text::unvalidated("OK"),
literal_reject_text: Text::unvalidated("Literal rejected"),
..ServerFlowOptions::default()
},
Greeting::ok( Greeting::ok(
Some(Code::Capability(ctx.server_capabilities.to_vec())), Some(Code::Capability(ctx.server_capabilities.to_vec())),
"Aerogramme", "Aerogramme",
@ -241,85 +234,111 @@ impl NetLoop {
tracing::info!("runner is quitting"); tracing::info!("runner is quitting");
} }
async fn core(mut self) -> Result<()> { async fn core(&mut self) -> Result<()> {
tracing::trace!("Starting the core loop"); let mut maybe_idle: Option<Arc<Notify>> = None;
let mut mode = LoopMode::Interactive;
loop { loop {
tracing::trace!(mode=?mode, "Core loop iter"); tokio::select! {
mode = match mode { // Managing imap_flow stuff
LoopMode::Interactive => self.interactive_mode().await?, srv_evt = self.server.progress() => match srv_evt? {
LoopMode::Idle(buff, stop) => self.idle_mode(buff, stop).await?, ServerFlowEvent::ResponseSent { handle: _handle, response } => {
LoopMode::Quit => break, match response {
} Response::Status(Status::Bye(_)) => return Ok(()),
_ => tracing::trace!("sent to {} content {:?}", self.ctx.addr, response),
}
},
ServerFlowEvent::CommandReceived { command } => {
match self.cmd_tx.try_send(Request::ImapCommand(command)) {
Ok(_) => (),
Err(mpsc::error::TrySendError::Full(_)) => {
self.server.enqueue_status(Status::bye(None, "Too fast").unwrap());
tracing::error!("client {:?} is sending commands too fast, closing.", self.ctx.addr);
}
_ => {
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
}
}
},
ServerFlowEvent::IdleCommandReceived { tag } => {
match self.cmd_tx.try_send(Request::IdleStart(tag)) {
Ok(_) => (),
Err(mpsc::error::TrySendError::Full(_)) => {
self.server.enqueue_status(Status::bye(None, "Too fast").unwrap());
tracing::error!("client {:?} is sending commands too fast, closing.", self.ctx.addr);
}
_ => {
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
}
}
}
ServerFlowEvent::IdleDoneReceived => {
tracing::trace!("client sent DONE and want to stop IDLE");
maybe_idle.ok_or(anyhow!("Received IDLE done but not idling currently"))?.notify_one();
maybe_idle = None;
}
flow => {
self.server.enqueue_status(Status::bye(None, "Unsupported server flow event").unwrap());
tracing::error!("session task exited for {:?} due to unsupported flow {:?}", self.ctx.addr, flow);
}
},
// Managing response generated by Aerogramme
maybe_msg = self.resp_rx.recv() => match maybe_msg {
Some(ResponseOrIdle::Response(response)) => {
tracing::trace!("Interactive, server has a response for the client");
for body_elem in response.body.into_iter() {
let _handle = match body_elem {
Body::Data(d) => self.server.enqueue_data(d),
Body::Status(s) => self.server.enqueue_status(s),
};
}
self.server.enqueue_status(response.completion);
},
Some(ResponseOrIdle::IdleAccept(stop)) => {
tracing::trace!("Interactive, server agreed to switch in idle mode");
let cr = CommandContinuationRequest::basic(None, "Idling")?;
self.server.idle_accept(cr).or(Err(anyhow!("refused continuation for idle accept")))?;
self.cmd_tx.try_send(Request::IdlePoll)?;
if maybe_idle.is_some() {
bail!("Can't start IDLE if already idling");
}
maybe_idle = Some(stop);
},
Some(ResponseOrIdle::IdleEvent(elems)) => {
tracing::trace!("server imap session has some change to communicate to the client");
for body_elem in elems.into_iter() {
let _handle = match body_elem {
Body::Data(d) => self.server.enqueue_data(d),
Body::Status(s) => self.server.enqueue_status(s),
};
}
self.cmd_tx.try_send(Request::IdlePoll)?;
},
Some(ResponseOrIdle::IdleReject(response)) => {
tracing::trace!("inform client that session rejected idle");
self.server
.idle_reject(response.completion)
.or(Err(anyhow!("wrong reject command")))?;
},
None => {
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
},
Some(_) => unreachable!(),
},
// When receiving a CTRL+C
_ = self.ctx.must_exit.changed() => {
tracing::trace!("Interactive, CTRL+C, exiting");
self.server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap());
},
};
} }
Ok(())
}
async fn interactive_mode(&mut self) -> Result<LoopMode> {
tokio::select! {
// Managing imap_flow stuff
srv_evt = self.server.progress() => match srv_evt? {
ServerFlowEvent::ResponseSent { handle: _handle, response } => {
match response {
Response::Status(Status::Bye(_)) => return Ok(LoopMode::Quit),
_ => tracing::trace!("sent to {} content {:?}", self.ctx.addr, response),
}
},
ServerFlowEvent::CommandReceived { command } => {
match self.cmd_tx.try_send(Request::ImapCommand(command)) {
Ok(_) => (),
Err(mpsc::error::TrySendError::Full(_)) => {
self.server.enqueue_status(Status::bye(None, "Too fast").unwrap());
tracing::error!("client {:?} is sending commands too fast, closing.", self.ctx.addr);
}
_ => {
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
}
}
},
flow => {
self.server.enqueue_status(Status::bye(None, "Unsupported server flow event").unwrap());
tracing::error!("session task exited for {:?} due to unsupported flow {:?}", self.ctx.addr, flow);
}
},
// Managing response generated by Aerogramme
maybe_msg = self.resp_rx.recv() => match maybe_msg {
Some(ResponseOrIdle::Response(response)) => {
tracing::trace!("Interactive, server has a response for the client");
for body_elem in response.body.into_iter() {
let _handle = match body_elem {
Body::Data(d) => self.server.enqueue_data(d),
Body::Status(s) => self.server.enqueue_status(s),
};
}
self.server.enqueue_status(response.completion);
},
Some(ResponseOrIdle::StartIdle(stop)) => {
tracing::trace!("Interactive, server agreed to switch in idle mode");
let cr = CommandContinuationRequest::basic(None, "Idling")?;
self.server.enqueue_continuation(cr);
self.cmd_tx.try_send(Request::Idle)?;
return Ok(LoopMode::Idle(BytesMut::new(), stop))
},
None => {
self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
tracing::error!("session task exited for {:?}, quitting", self.ctx.addr);
},
Some(_) => unreachable!(),
},
// When receiving a CTRL+C
_ = self.ctx.must_exit.changed() => {
tracing::trace!("Interactive, CTRL+C, exiting");
self.server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap());
},
};
Ok(LoopMode::Interactive)
} }
/*
async fn idle_mode(&mut self, mut buff: BytesMut, stop: Arc<Notify>) -> Result<LoopMode> { async fn idle_mode(&mut self, mut buff: BytesMut, stop: Arc<Notify>) -> Result<LoopMode> {
// Flush send // Flush send
loop { loop {
@ -398,5 +417,5 @@ impl NetLoop {
return Ok(LoopMode::Interactive) return Ok(LoopMode::Interactive)
}, },
}; };
} }*/
} }

View file

@ -1,7 +1,9 @@
use imap_codec::imap_types::command::Command; use imap_codec::imap_types::command::Command;
use imap_codec::imap_types::core::Tag;
#[derive(Debug)] #[derive(Debug)]
pub enum Request { pub enum Request {
ImapCommand(Command<'static>), ImapCommand(Command<'static>),
Idle, IdleStart(Tag<'static>),
IdlePoll,
} }

View file

@ -118,6 +118,7 @@ impl<'a> Response<'a> {
#[derive(Debug)] #[derive(Debug)]
pub enum ResponseOrIdle { pub enum ResponseOrIdle {
Response(Response<'static>), Response(Response<'static>),
StartIdle(Arc<Notify>), IdleAccept(Arc<Notify>),
IdleReject(Response<'static>),
IdleEvent(Vec<Body<'static>>), IdleEvent(Vec<Body<'static>>),
} }

View file

@ -1,13 +1,12 @@
use std::num::{NonZeroU32, NonZeroU64}; use std::num::{NonZeroU32, NonZeroU64};
use anyhow::Result; use imap_codec::imap_types::core::Vec1;
use imap_codec::imap_types::core::NonEmptyVec;
use imap_codec::imap_types::search::{MetadataItemSearch, SearchKey}; use imap_codec::imap_types::search::{MetadataItemSearch, SearchKey};
use imap_codec::imap_types::sequence::{SeqOrUid, Sequence, SequenceSet}; use imap_codec::imap_types::sequence::{SeqOrUid, Sequence, SequenceSet};
use crate::imap::index::MailIndex; use crate::imap::index::MailIndex;
use crate::imap::mail_view::MailView; use crate::imap::mail_view::MailView;
use crate::mail::query::{QueryResult, QueryScope}; use crate::mail::query::QueryScope;
pub enum SeqType { pub enum SeqType {
Undefined, Undefined,
@ -49,7 +48,7 @@ impl<'a> Criteria<'a> {
let mut new_vec = base.0.into_inner(); let mut new_vec = base.0.into_inner();
new_vec.extend_from_slice(ext.0.as_ref()); new_vec.extend_from_slice(ext.0.as_ref());
let seq = SequenceSet( let seq = SequenceSet(
NonEmptyVec::try_from(new_vec) Vec1::try_from(new_vec)
.expect("merging non empty vec lead to non empty vec"), .expect("merging non empty vec lead to non empty vec"),
); );
(seq, x) (seq, x)
@ -145,22 +144,6 @@ impl<'a> Criteria<'a> {
(to_keep, to_fetch) (to_keep, to_fetch)
} }
pub fn filter_on_query<'b>(
&self,
midx_list: &[&'b MailIndex<'b>],
query_result: &'b Vec<QueryResult>,
) -> Result<Vec<&'b MailIndex<'b>>> {
Ok(midx_list
.iter()
.zip(query_result.iter())
.map(|(midx, qr)| MailView::new(qr, midx))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.filter(|mail_view| self.is_keep_on_query(mail_view))
.map(|mail_view| mail_view.in_idx)
.collect())
}
// ---- // ----
/// Here we are doing a partial filtering: we do not have access /// Here we are doing a partial filtering: we do not have access
@ -213,7 +196,7 @@ impl<'a> Criteria<'a> {
/// the email, as body(x) might be false. So we need to check it. But as seqid(x) is true, /// the email, as body(x) might be false. So we need to check it. But as seqid(x) is true,
/// we could simplify the request to just body(x) and truncate the first OR. Today, we are /// we could simplify the request to just body(x) and truncate the first OR. Today, we are
/// not doing that, and thus we reevaluate everything. /// not doing that, and thus we reevaluate everything.
fn is_keep_on_query(&self, mail_view: &MailView) -> bool { pub fn is_keep_on_query(&self, mail_view: &MailView) -> bool {
use SearchKey::*; use SearchKey::*;
match self.0 { match self.0 {
// Combinator logic // Combinator logic

View file

@ -4,8 +4,8 @@ use crate::imap::flow;
use crate::imap::request::Request; use crate::imap::request::Request;
use crate::imap::response::{Response, ResponseOrIdle}; use crate::imap::response::{Response, ResponseOrIdle};
use crate::login::ArcLoginProvider; use crate::login::ArcLoginProvider;
use anyhow::{anyhow, bail, Result}; use anyhow::{anyhow, bail, Context, Result};
use imap_codec::imap_types::command::Command; use imap_codec::imap_types::{command::Command, core::Tag};
//----- //-----
pub struct Instance { pub struct Instance {
@ -27,13 +27,48 @@ impl Instance {
pub async fn request(&mut self, req: Request) -> ResponseOrIdle { pub async fn request(&mut self, req: Request) -> ResponseOrIdle {
match req { match req {
Request::Idle => self.idle().await, Request::IdleStart(tag) => self.idle_init(tag),
Request::IdlePoll => self.idle_poll().await,
Request::ImapCommand(cmd) => self.command(cmd).await, Request::ImapCommand(cmd) => self.command(cmd).await,
} }
} }
pub async fn idle(&mut self) -> ResponseOrIdle { pub fn idle_init(&mut self, tag: Tag<'static>) -> ResponseOrIdle {
match self.idle_happy().await { // Build transition
//@FIXME the notifier should be hidden inside the state and thus not part of the transition!
let transition = flow::Transition::Idle(tag.clone(), tokio::sync::Notify::new());
// Try to apply the transition and get the stop notifier
let maybe_stop = self
.state
.apply(transition)
.context("IDLE transition failed")
.and_then(|_| {
self.state
.notify()
.ok_or(anyhow!("IDLE state has no Notify object"))
});
// Build an appropriate response
match maybe_stop {
Ok(stop) => ResponseOrIdle::IdleAccept(stop),
Err(e) => {
tracing::error!(err=?e, "unable to init idle due to a transition error");
//ResponseOrIdle::IdleReject(tag)
let no = Response::build()
.tag(tag)
.message(
"Internal error, processing command triggered an illegal IMAP state transition",
)
.no()
.unwrap();
ResponseOrIdle::IdleReject(no)
}
}
}
pub async fn idle_poll(&mut self) -> ResponseOrIdle {
match self.idle_poll_happy().await {
Ok(r) => r, Ok(r) => r,
Err(e) => { Err(e) => {
tracing::error!(err=?e, "something bad happened in idle"); tracing::error!(err=?e, "something bad happened in idle");
@ -42,7 +77,7 @@ impl Instance {
} }
} }
pub async fn idle_happy(&mut self) -> Result<ResponseOrIdle> { pub async fn idle_poll_happy(&mut self) -> Result<ResponseOrIdle> {
let (mbx, tag, stop) = match &mut self.state { let (mbx, tag, stop) = match &mut self.state {
flow::State::Idle(_, ref mut mbx, _, tag, stop) => (mbx, tag.clone(), stop.clone()), flow::State::Idle(_, ref mut mbx, _, tag, stop) => (mbx, tag.clone(), stop.clone()),
_ => bail!("Invalid session state, can't idle"), _ => bail!("Invalid session state, can't idle"),
@ -128,10 +163,11 @@ impl Instance {
.bad() .bad()
.unwrap()); .unwrap());
} }
ResponseOrIdle::Response(resp)
match &self.state { /*match &self.state {
flow::State::Idle(_, _, _, _, n) => ResponseOrIdle::StartIdle(n.clone()), flow::State::Idle(_, _, _, _, n) => ResponseOrIdle::StartIdle(n.clone()),
_ => ResponseOrIdle::Response(resp), _ => ResponseOrIdle::Response(resp),
} }*/
} }
} }

View file

@ -21,6 +21,7 @@ pub struct LdapLoginProvider {
storage_specific: StorageSpecific, storage_specific: StorageSpecific,
in_memory_store: storage::in_memory::MemDb, in_memory_store: storage::in_memory::MemDb,
garage_store: storage::garage::GarageRoot,
} }
enum BucketSource { enum BucketSource {
@ -91,7 +92,11 @@ impl LdapLoginProvider {
mail_attr: config.mail_attr, mail_attr: config.mail_attr,
crypto_root_attr: config.crypto_root_attr, crypto_root_attr: config.crypto_root_attr,
storage_specific: specific, storage_specific: specific,
//@FIXME should be created outside of the login provider
//Login provider should return only a cryptoroot + a storage URI
//storage URI that should be resolved outside...
in_memory_store: storage::in_memory::MemDb::new(), in_memory_store: storage::in_memory::MemDb::new(),
garage_store: storage::garage::GarageRoot::new()?,
}) })
} }
@ -114,7 +119,7 @@ impl LdapLoginProvider {
BucketSource::Attr(a) => get_attr(user, &a)?, BucketSource::Attr(a) => get_attr(user, &a)?,
}; };
storage::garage::GarageBuilder::new(storage::garage::GarageConf { self.garage_store.user(storage::garage::GarageConf {
region: from_config.aws_region.clone(), region: from_config.aws_region.clone(),
s3_endpoint: from_config.s3_endpoint.clone(), s3_endpoint: from_config.s3_endpoint.clone(),
k2v_endpoint: from_config.k2v_endpoint.clone(), k2v_endpoint: from_config.k2v_endpoint.clone(),

View file

@ -25,6 +25,7 @@ pub struct UserDatabase {
pub struct StaticLoginProvider { pub struct StaticLoginProvider {
user_db: watch::Receiver<UserDatabase>, user_db: watch::Receiver<UserDatabase>,
in_memory_store: storage::in_memory::MemDb, in_memory_store: storage::in_memory::MemDb,
garage_store: storage::garage::GarageRoot,
} }
pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>) -> Result<()> { pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>) -> Result<()> {
@ -84,6 +85,7 @@ impl StaticLoginProvider {
Ok(Self { Ok(Self {
user_db: rx, user_db: rx,
in_memory_store: storage::in_memory::MemDb::new(), in_memory_store: storage::in_memory::MemDb::new(),
garage_store: storage::garage::GarageRoot::new()?,
}) })
} }
} }
@ -109,7 +111,7 @@ impl LoginProvider for StaticLoginProvider {
let storage: storage::Builder = match &user.config.storage { let storage: storage::Builder = match &user.config.storage {
StaticStorage::InMemory => self.in_memory_store.builder(username).await, StaticStorage::InMemory => self.in_memory_store.builder(username).await,
StaticStorage::Garage(grgconf) => { StaticStorage::Garage(grgconf) => {
storage::garage::GarageBuilder::new(storage::garage::GarageConf { self.garage_store.user(storage::garage::GarageConf {
region: grgconf.aws_region.clone(), region: grgconf.aws_region.clone(),
k2v_endpoint: grgconf.k2v_endpoint.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(),
s3_endpoint: grgconf.s3_endpoint.clone(), s3_endpoint: grgconf.s3_endpoint.clone(),
@ -140,7 +142,7 @@ impl LoginProvider for StaticLoginProvider {
let storage: storage::Builder = match &user.config.storage { let storage: storage::Builder = match &user.config.storage {
StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await, StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await,
StaticStorage::Garage(grgconf) => { StaticStorage::Garage(grgconf) => {
storage::garage::GarageBuilder::new(storage::garage::GarageConf { self.garage_store.user(storage::garage::GarageConf {
region: grgconf.aws_region.clone(), region: grgconf.aws_region.clone(),
k2v_endpoint: grgconf.k2v_endpoint.clone(), k2v_endpoint: grgconf.k2v_endpoint.clone(),
s3_endpoint: grgconf.s3_endpoint.clone(), s3_endpoint: grgconf.s3_endpoint.clone(),

View file

@ -498,7 +498,7 @@ fn dump(uid_index: &Bayou<UidIndex>) {
/// The metadata of a message that is stored in K2V /// The metadata of a message that is stored in K2V
/// at pk = mail/<mailbox uuid>, sk = <message uuid> /// at pk = mail/<mailbox uuid>, sk = <message uuid>
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MailMeta { pub struct MailMeta {
/// INTERNALDATE field (milliseconds since epoch) /// INTERNALDATE field (milliseconds since epoch)
pub internaldate: u64, pub internaldate: u64,

View file

@ -2,7 +2,8 @@ use super::mailbox::MailMeta;
use super::snapshot::FrozenMailbox; use super::snapshot::FrozenMailbox;
use super::unique_ident::UniqueIdent; use super::unique_ident::UniqueIdent;
use anyhow::Result; use anyhow::Result;
use futures::stream::{FuturesOrdered, StreamExt}; use futures::future::FutureExt;
use futures::stream::{BoxStream, Stream, StreamExt};
/// Query is in charge of fetching efficiently /// Query is in charge of fetching efficiently
/// requested data for a list of emails /// requested data for a list of emails
@ -28,64 +29,62 @@ impl QueryScope {
} }
} }
//type QueryResultStream = Box<dyn Stream<Item = Result<QueryResult>>>;
impl<'a, 'b> Query<'a, 'b> { impl<'a, 'b> Query<'a, 'b> {
pub async fn fetch(&self) -> Result<Vec<QueryResult>> { pub fn fetch(&self) -> BoxStream<Result<QueryResult>> {
match self.scope { match self.scope {
QueryScope::Index => Ok(self QueryScope::Index => Box::pin(
.emails futures::stream::iter(self.emails)
.iter() .map(|&uuid| Ok(QueryResult::IndexResult { uuid })),
.map(|&uuid| QueryResult::IndexResult { uuid }) ),
.collect()), QueryScope::Partial => Box::pin(self.partial()),
QueryScope::Partial => self.partial().await, QueryScope::Full => Box::pin(self.full()),
QueryScope::Full => self.full().await,
} }
} }
// --- functions below are private *for reasons* // --- functions below are private *for reasons*
fn partial<'d>(&'d self) -> impl Stream<Item = Result<QueryResult>> + 'd + Send {
async move {
let maybe_meta_list: Result<Vec<MailMeta>> =
self.frozen.mailbox.fetch_meta(self.emails).await;
let list_res = maybe_meta_list
.map(|meta_list| {
meta_list
.into_iter()
.zip(self.emails)
.map(|(metadata, &uuid)| Ok(QueryResult::PartialResult { uuid, metadata }))
.collect()
})
.unwrap_or_else(|e| vec![Err(e)]);
async fn partial(&self) -> Result<Vec<QueryResult>> { futures::stream::iter(list_res)
let meta = self.frozen.mailbox.fetch_meta(self.emails).await?; }
let result = meta .flatten_stream()
.into_iter()
.zip(self.emails.iter())
.map(|(metadata, &uuid)| QueryResult::PartialResult { uuid, metadata })
.collect::<Vec<_>>();
Ok(result)
} }
/// @FIXME WARNING: THIS CAN ALLOCATE A LOT OF MEMORY fn full<'d>(&'d self) -> impl Stream<Item = Result<QueryResult>> + 'd + Send {
/// AND GENERATE SO MUCH NETWORK TRAFFIC. self.partial().then(move |maybe_meta| async move {
/// THIS FUNCTION SHOULD BE REWRITTEN, FOR EXAMPLE WITH let meta = maybe_meta?;
/// SOMETHING LIKE AN ITERATOR
async fn full(&self) -> Result<Vec<QueryResult>> {
let meta_list = self.partial().await?;
meta_list
.into_iter()
.map(|meta| async move {
let content = self
.frozen
.mailbox
.fetch_full(
*meta.uuid(),
&meta
.metadata()
.expect("meta to be PartialResult")
.message_key,
)
.await?;
Ok(meta.into_full(content).expect("meta to be PartialResult")) let content = self
}) .frozen
.collect::<FuturesOrdered<_>>() .mailbox
.collect::<Vec<_>>() .fetch_full(
.await *meta.uuid(),
.into_iter() &meta
.collect::<Result<Vec<_>, _>>() .metadata()
.expect("meta to be PartialResult")
.message_key,
)
.await?;
Ok(meta.into_full(content).expect("meta to be PartialResult"))
})
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub enum QueryResult { pub enum QueryResult {
IndexResult { IndexResult {
uuid: UniqueIdent, uuid: UniqueIdent,

View file

@ -1,7 +1,45 @@
use crate::storage::*;
use aws_sdk_s3::{self as s3, error::SdkError, operation::get_object::GetObjectError}; use aws_sdk_s3::{self as s3, error::SdkError, operation::get_object::GetObjectError};
use aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder;
use aws_smithy_runtime_api::client::http::SharedHttpClient;
use hyper_rustls::HttpsConnector;
use hyper_util::client::legacy::{connect::HttpConnector, Client as HttpClient};
use hyper_util::rt::TokioExecutor;
use serde::Serialize; use serde::Serialize;
use crate::storage::*;
pub struct GarageRoot {
k2v_http: HttpClient<HttpsConnector<HttpConnector>, k2v_client::Body>,
aws_http: SharedHttpClient,
}
impl GarageRoot {
pub fn new() -> anyhow::Result<Self> {
let connector = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()?
.https_or_http()
.enable_http1()
.enable_http2()
.build();
let k2v_http = HttpClient::builder(TokioExecutor::new()).build(connector);
let aws_http = HyperClientBuilder::new().build_https();
Ok(Self { k2v_http, aws_http })
}
pub fn user(&self, conf: GarageConf) -> anyhow::Result<Arc<GarageUser>> {
let mut unicity: Vec<u8> = vec![];
unicity.extend_from_slice(file!().as_bytes());
unicity.append(&mut rmp_serde::to_vec(&conf)?);
Ok(Arc::new(GarageUser {
conf,
aws_http: self.aws_http.clone(),
k2v_http: self.k2v_http.clone(),
unicity,
}))
}
}
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
pub struct GarageConf { pub struct GarageConf {
pub region: String, pub region: String,
@ -12,23 +50,19 @@ pub struct GarageConf {
pub bucket: String, pub bucket: String,
} }
//@FIXME we should get rid of this builder
//and allocate a S3 + K2V client only once per user
//(and using a shared HTTP client)
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct GarageBuilder { pub struct GarageUser {
conf: GarageConf, conf: GarageConf,
aws_http: SharedHttpClient,
k2v_http: HttpClient<HttpsConnector<HttpConnector>, k2v_client::Body>,
unicity: Vec<u8>, unicity: Vec<u8>,
} }
impl GarageBuilder {
pub fn new(conf: GarageConf) -> anyhow::Result<Arc<Self>> {
let mut unicity: Vec<u8> = vec![];
unicity.extend_from_slice(file!().as_bytes());
unicity.append(&mut rmp_serde::to_vec(&conf)?);
Ok(Arc::new(Self { conf, unicity }))
}
}
#[async_trait] #[async_trait]
impl IBuilder for GarageBuilder { impl IBuilder for GarageUser {
async fn build(&self) -> Result<Store, StorageError> { async fn build(&self) -> Result<Store, StorageError> {
let s3_creds = s3::config::Credentials::new( let s3_creds = s3::config::Credentials::new(
self.conf.aws_access_key_id.clone(), self.conf.aws_access_key_id.clone(),
@ -41,6 +75,7 @@ impl IBuilder for GarageBuilder {
let sdk_config = aws_config::from_env() let sdk_config = aws_config::from_env()
.region(aws_config::Region::new(self.conf.region.clone())) .region(aws_config::Region::new(self.conf.region.clone()))
.credentials_provider(s3_creds) .credentials_provider(s3_creds)
.http_client(self.aws_http.clone())
.endpoint_url(self.conf.s3_endpoint.clone()) .endpoint_url(self.conf.s3_endpoint.clone())
.load() .load()
.await; .await;
@ -60,13 +95,14 @@ impl IBuilder for GarageBuilder {
user_agent: None, user_agent: None,
}; };
let k2v_client = match k2v_client::K2vClient::new(k2v_config) { let k2v_client =
Err(e) => { match k2v_client::K2vClient::new_with_client(k2v_config, self.k2v_http.clone()) {
tracing::error!("unable to build k2v client: {}", e); Err(e) => {
return Err(StorageError::Internal); tracing::error!("unable to build k2v client: {}", e);
} return Err(StorageError::Internal);
Ok(v) => v, }
}; Ok(v) => v,
};
Ok(Box::new(GarageStore { Ok(Box::new(GarageStore {
bucket: self.conf.bucket.clone(), bucket: self.conf.bucket.clone(),

View file

@ -225,7 +225,7 @@ fn rfc4551_imapext_condstore() {
FetchKind::Rfc822Size, FetchKind::Rfc822Size,
FetchMod::ChangedSince(2), FetchMod::ChangedSince(2),
)?; )?;
assert!(fetch_res.contains("* 1 FETCH (RFC822.SIZE 84 MODSEQ (3))")); assert!(fetch_res.contains("* 1 FETCH (RFC822.SIZE 81 MODSEQ (3))"));
assert!(!fetch_res.contains("* 2 FETCH")); assert!(!fetch_res.contains("* 2 FETCH"));
assert_eq!(store_res.lines().count(), 2); assert_eq!(store_res.lines().count(), 2);

View file

@ -384,8 +384,7 @@ pub fn append(imap: &mut TcpStream, content: Email) -> Result<String> {
// write our stuff // write our stuff
imap.write(ref_mail)?; imap.write(ref_mail)?;
imap.write(&b"\r\n"[..])?; imap.write(&b"\r\n"[..])?;
let read = read_lines(imap, &mut buffer, None)?; let read = read_lines(imap, &mut buffer, Some(&b"47 OK"[..]))?;
assert_eq!(&read[..5], &b"47 OK"[..]);
let srv_msg = std::str::from_utf8(read)?; let srv_msg = std::str::from_utf8(read)?;
Ok(srv_msg.to_string()) Ok(srv_msg.to_string())

1
tests/emails/.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
*.zstd filter=lfs diff=lfs merge=lfs -text

1
tests/emails/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.mbox

BIN
tests/emails/aero100.mbox.zstd (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,240 @@
Append { mailbox: Other(MailboxOther(Atom(AtomExt("Sent")))), flags: [Seen], date: None, message: REDACTED } }
Append { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), flags: [Seen, Draft], date: None, message: REDACTED } }
Append { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), flags: [Seen], date: None, message: REDACTED } }
Append { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), flags: [Seen], date: Some(2024-02-14T14:12:35+01:00), message: REDACTED } }
Capability
Check
Close
Create { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))) } }
Create { mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))) } }
Create { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))) } }
Enable { capabilities: [CondStore]+ } }
Enable { capabilities: [Utf8(Accept)]+ } }
Examine { mailbox: Inbox, modifiers: [] } }
Expunge { uid_sequence_set: None } }
Fetch { sequence_set: SequenceSet([Range(Asterisk, Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Envelope, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(12), Value(13))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(22)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(24)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(5)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(638435220681800000)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(638435229950250000)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(638435249520030000)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(6)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(7)], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [ChangedSince(8)], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("TO")), Atom(AtomExt("CC")), Atom(AtomExt("MESSAGE-ID")), Atom(AtomExt("REFERENCES")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("CONTENT-DESCRIPTION")), Atom(AtomExt("IN-REPLY-TO")), Atom(AtomExt("REPLY-TO")), Atom(AtomExt("LINES")), Atom(AtomExt("LIST-POST")), Atom(AtomExt("X-LABEL"))]+)), partial: None, peek: true }]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(20))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(20))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(23))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags, Uid]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("TO")), Atom(AtomExt("CC")), Atom(AtomExt("MESSAGE-ID")), Atom(AtomExt("REFERENCES")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("CONTENT-DESCRIPTION")), Atom(AtomExt("IN-REPLY-TO")), Atom(AtomExt("REPLY-TO")), Atom(AtomExt("LINES")), Atom(AtomExt("LIST-POST")), Atom(AtomExt("X-LABEL"))]+)), partial: None, peek: true }]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26))]+), modifiers: [ChangedSince(68)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26))]+), modifiers: [ChangedSince(69)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34))]+), modifiers: [ChangedSince(102)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34))]+), modifiers: [ChangedSince(189)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34))]+), modifiers: [ChangedSince(81)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34)), Range(Value(60), Value(62))]+), modifiers: [ChangedSince(165)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34)), Range(Value(60), Value(62))]+), modifiers: [ChangedSince(166)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(2)), Range(Value(11), Value(13)), Range(Value(18), Value(19)), Range(Value(22), Value(26)), Range(Value(33), Value(34)), Range(Value(60), Value(62))]+), modifiers: [ChangedSince(168)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags, Uid]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [ChangedSince(9)], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(1), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(20), Value(23))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(27), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(2), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(30), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(3), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(3), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(4), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(5), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(5), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
Fetch { sequence_set: SequenceSet([Range(Value(5), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(63), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(6), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(6), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, InternalDate, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(6), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Envelope, BodyStructure, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("REFERENCES")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(6), Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(86), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(8), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Range(Value(9), Asterisk)]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(13))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Flags, Uid]), uid: false } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: false }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("bcc")), Atom(AtomExt("cc")), Atom(AtomExt("date")), Atom(AtomExt("from")), Atom(AtomExt("reply-to")), Atom(AtomExt("sender")), Atom(AtomExt("subject")), Atom(AtomExt("to"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("in-reply-to")), Atom(AtomExt("message-id")), Atom(AtomExt("references"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Rfc822Size, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(1)), Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(22))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Envelope, BodyStructure, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("REFERENCES")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(24))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(24)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(29))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(29))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: false } }
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Envelope, InternalDate, Rfc822Size, Flags, BodyStructure, Uid, BodyExt { section: Some(Header(None)), partial: None, peek: true }, Rfc822Size, InternalDate]), uid: false } }
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(2))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(2)), Single(Value(8))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Flags, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("DATE")), Atom(AtomExt("FROM")), Atom(AtomExt("SUBJECT")), Atom(AtomExt("CONTENT-TYPE")), Atom(AtomExt("X-MS-TNEF-Correlator")), Atom(AtomExt("CONTENT-CLASS")), Atom(AtomExt("IMPORTANCE")), Atom(AtomExt("PRIORITY")), Atom(AtomExt("X-PRIORITY")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("REPLY-TO"))]+)), partial: None, peek: true }, BodyStructure]), uid: false } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2]+))), partial: Some((0, 10240)), peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyStructure, InternalDate, Rfc822Size]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, InternalDate, Envelope, BodyStructure, Rfc822Size, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("REFERENCES")), Atom(AtomExt("THREAD-TOPIC")), Atom(AtomExt("FROM")), Atom(AtomExt("SENDER")), Atom(AtomExt("REPLY-TO")), Atom(AtomExt("AUTO-SUBMITTED")), Atom(AtomExt("BOUNCES-TO")), Atom(AtomExt("LIST-ARCHIVE")), Atom(AtomExt("LIST-HELP")), Atom(AtomExt("LIST-ID")), Atom(AtomExt("LIST-OWNER")), Atom(AtomExt("LIST-POST")), Atom(AtomExt("LIST-SUBSCRIBE")), Atom(AtomExt("LIST-UNSUBSCRIBE")), Atom(AtomExt("PRECEDENCE")), Atom(AtomExt("RESENT-FROM")), Atom(AtomExt("RETURN-PATH"))]+)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(3)), Single(Value(2)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2, 1]+))), partial: None, peek: true }, BodyExt { section: Some(Part(Part([2, 2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(4))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(4)), Single(Value(3)), Single(Value(2)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1, 2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(5))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([1, 2]+))), partial: Some((0, 2227)), peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyStructure, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(60))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(61))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([1, 2]+))), partial: Some((0, 711)), peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyStructure, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(62))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Flags, Envelope, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("References"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(6))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Rfc822Header, BodyExt { section: Some(Part(Part([1, 2]+))), partial: None, peek: true }, BodyExt { section: Some(Part(Part([2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, BodyExt { section: Some(Part(Part([1, 2]+))), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(7))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, BodyExt { section: None, partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(7)), Single(Value(6)), Single(Value(5)), Single(Value(4)), Single(Value(3)), Single(Value(2)), Single(Value(1))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([Uid, Rfc822Size, Flags, BodyExt { section: Some(HeaderFields(None, [Atom(AtomExt("From")), Atom(AtomExt("To")), Atom(AtomExt("Cc")), Atom(AtomExt("Bcc")), Atom(AtomExt("Resent-Message-ID")), Atom(AtomExt("Subject")), Atom(AtomExt("Date")), Atom(AtomExt("Message-ID")), Atom(AtomExt("Priority")), Atom(AtomExt("X-Priority")), Atom(AtomExt("References")), Atom(AtomExt("Newsgroups")), Atom(AtomExt("In-Reply-To")), Atom(AtomExt("Content-Type")), Atom(AtomExt("Reply-To")), Atom(AtomExt("List-Unsubscribe")), Atom(AtomExt("Received")), Atom(AtomExt("Delivery-Date"))]+)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(83))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([BodyExt { section: Some(Header(None)), partial: None, peek: true }, BodyExt { section: Some(Text(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(83))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
Fetch { sequence_set: SequenceSet([Single(Value(85))]+), modifiers: [], macro_or_item_names: MessageDataItemNames([InternalDate, Uid, Rfc822Size, Flags, ModSeq, BodyExt { section: Some(Header(None)), partial: None, peek: true }]), uid: true } }
Idle
List { reference: Other(MailboxOther(String(Quoted(Quoted("Archive."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted("INBOX."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("Dataset"))), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("INBOX"))), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted(""))), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("*"))), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("*"))), return: [Status([Unseen])] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: Token(ListCharString("INBOX")), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: Token(ListCharString("%")), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted("Mailspring."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted("Sent."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
List { reference: Other(MailboxOther(String(Quoted(Quoted("Trash."))))), mailbox_wildcard: Token(ListCharString("*")), return: [] } }
Login { username: Atom(AtomExt("REDACTED")), password: /* REDACTED */ } }
Login { username: String(Quoted(Quoted("REDACTED"))), password: /* REDACTED */ } }
Login { username: String(Quoted(Quoted("REDACTED@saint-ex.deuxfleurs.org"))), password: /* REDACTED */ } }
Logout
Lsub { reference: Other(MailboxOther(String(Quoted(Quoted(""))))), mailbox_wildcard: String(Quoted(Quoted("*"))) } }
Move { sequence_set: SequenceSet([Range(Value(20), Value(21))]+), mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))), uid: true } }
Move { sequence_set: SequenceSet([Single(Value(29))]+), mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))), uid: true } }
Noop
Search { charset: None, criteria: And([Header(Atom(AtomExt("Message-ID")), Atom(AtomExt("<Mailbird-8d3b03f5-7737-479e-b8d7-a3cae013e72f@saint-ex.deuxfleurs.org>"))), Undeleted]+), uid: true } }
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Deleted]+), uid: true } }
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Not(Deleted)]+), uid: true } }
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Since(2023-11-16)]+), uid: true } }
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Asterisk)]+)), Unseen]+), uid: true } }
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Value(1))]+)), Not(Deleted)]+), uid: true } }
Search { charset: None, criteria: And([SequenceSet(SequenceSet([Range(Value(1), Value(4))]+)), Not(Deleted)]+), uid: true } }
Search { charset: None, criteria: And([Since(2024-02-07), All]+), uid: false } }
Search { charset: None, criteria: And([Since(2024-02-08), All]+), uid: false } }
Search { charset: None, criteria: And([Since(2024-02-09), All]+), uid: false } }
Search { charset: None, criteria: And([Undeleted, Since(2023-11-17)]+), uid: false } }
Search { charset: None, criteria: And([Undeleted, Since(2024-02-13)]+), uid: false } }
Search { charset: None, criteria: Before(2024-02-09), uid: true } }
Search { charset: None, criteria: Since(2024-01-31), uid: true } }
Select { mailbox: Inbox, modifiers: [] } }
Select { mailbox: Inbox, modifiers: [Condstore] } }
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Archive")))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Drafts")))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Sent")))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Test.Coucou")))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Test")))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(Atom(AtomExt("Trash")))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Archive"))))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("INBOX.Pourriel"))))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring"))))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), modifiers: [] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), modifiers: [Condstore] } }
Select { mailbox: Other(MailboxOther(String(Quoted(Quoted("Trash"))))), modifiers: [] } }
Status { mailbox: Inbox, item_names: [Messages, Recent, UidNext, UidValidity, Unseen] } }
Status { mailbox: Inbox, item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Archive")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Drafts")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Sent")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(Atom(AtomExt("Trash")))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Archive"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Archive"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), item_names: [Messages] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Drafts"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("INBOX.Pourriel"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("INBOX.Pourriel"))))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))), item_names: [Unseen, Messages, Recent, UidNext, UidValidity, HighestModSeq] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), item_names: [UidNext, Messages, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Sent"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Test.Coucou"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Test"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
Status { mailbox: Other(MailboxOther(String(Quoted(Quoted("Trash"))))), item_names: [UidNext, UidValidity, Unseen, Recent] } }
Store { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), kind: Add, response: Answer, flags: [Deleted], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), kind: Add, response: Answer, flags: [Deleted, Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Range(Value(60), Value(62))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(1))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(24))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(24))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(26))]+), kind: Add, response: Answer, flags: [Answered], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(29))]+), kind: Add, response: Answer, flags: [Keyword(Atom("NonJunk"))], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(29))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(2))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: false } }
Store { sequence_set: SequenceSet([Single(Value(2))]+), kind: Add, response: Silent, flags: [Deleted], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(3))]+), kind: Add, response: Answer, flags: [Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(3))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(5))]+), kind: Add, response: Answer, flags: [Keyword(Atom("NonJunk"))], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(60))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(60))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(61))]+), kind: Add, response: Answer, flags: [Seen, Deleted], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(61))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
Store { sequence_set: SequenceSet([Single(Value(62))]+), kind: Add, response: Silent, flags: [Seen], modifiers: [], uid: true } }
Subscribe { mailbox: Other(MailboxOther(Atom(AtomExt("Mailspring")))) } }
Subscribe { mailbox: Other(MailboxOther(String(Quoted(Quoted("Dataset"))))) } }
Subscribe { mailbox: Other(MailboxOther(String(Quoted(Quoted("Mailspring.Snoozed"))))) } }
Unselect

View file

@ -0,0 +1,45 @@
count,command,aggregation
6,Append,Raw
33,Capability,Raw
96,Check,Raw
6,Close,Raw
3,Create,Raw
9,Enable,Raw
26,Examine,Raw
1,Expunge,Raw
1187,Fetch,Raw
248,Idle,Raw
132,List,Raw
244,Login,Raw
169,Logout,Raw
14,Lsub,Raw
2,Move,Raw
295,Noop,Raw
658,Search,Raw
746,Select,Raw
203,Status,Raw
23,Store,Raw
3,Subscribe,Raw
515,Unselect,Raw
6,Append,Unique
1,Capability,Unique
1,Check,Unique
1,Close,Unique
3,Create,Unique
2,Enable,Unique
1,Examine,Unique
1,Expunge,Unique
128,Fetch,Unique
1,Idle,Unique
12,List,Unique
9,Login,Unique
1,Logout,Unique
1,Lsub,Unique
2,Move,Unique
1,Noop,Unique
14,Search,Unique
18,Select,Unique
22,Status,Unique
19,Store,Unique
3,Subscribe,Unique
1,Unselect,Unique
1 count command aggregation
2 6 Append Raw
3 33 Capability Raw
4 96 Check Raw
5 6 Close Raw
6 3 Create Raw
7 9 Enable Raw
8 26 Examine Raw
9 1 Expunge Raw
10 1187 Fetch Raw
11 248 Idle Raw
12 132 List Raw
13 244 Login Raw
14 169 Logout Raw
15 14 Lsub Raw
16 2 Move Raw
17 295 Noop Raw
18 658 Search Raw
19 746 Select Raw
20 203 Status Raw
21 23 Store Raw
22 3 Subscribe Raw
23 515 Unselect Raw
24 6 Append Unique
25 1 Capability Unique
26 1 Check Unique
27 1 Close Unique
28 3 Create Unique
29 2 Enable Unique
30 1 Examine Unique
31 1 Expunge Unique
32 128 Fetch Unique
33 1 Idle Unique
34 12 List Unique
35 9 Login Unique
36 1 Logout Unique
37 1 Lsub Unique
38 2 Move Unique
39 1 Noop Unique
40 14 Search Unique
41 18 Select Unique
42 22 Status Unique
43 19 Store Unique
44 3 Subscribe Unique
45 1 Unselect Unique

File diff suppressed because it is too large Load diff

14
tests/emails/report.R Normal file
View file

@ -0,0 +1,14 @@
library(tidyverse)
library(lubridate)
read_csv("imap_commands_summary.csv") -> cmd
ggplot(cmd, aes(x=command, y=count)) +
geom_bar(stat = "identity")+
theme_classic() +
facet_wrap(~aggregation, ncol=1, scales = "free")
read_csv("mailbox_email_sizes.csv") -> mbx
ggplot(mbx, aes(x=size, colour=mailbox)) +
stat_ecdf(pad=FALSE,geom = "step") +
scale_x_log10()+ theme_classic()