diff --git a/Cargo.lock b/Cargo.lock index 5beeb2cd4..b9e8298de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15,17 +15,51 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +[[package]] +name = "aead" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf01b9b56e767bb57b94ebf91a58b338002963785cdd7013e21c0d4679471e4" +dependencies = [ + "generic-array 0.12.3", +] + [[package]] name = "aes" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" dependencies = [ - "aes-soft", - "aesni", + "aes-soft 0.3.3", + "aesni 0.6.0", "block-cipher-trait", ] +[[package]] +name = "aes" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7001367fde4c768a19d1029f0a8be5abd9308e1119846d5bd9ad26297b8faf5" +dependencies = [ + "aes-soft 0.4.0", + "aesni 0.7.0", + "block-cipher", +] + +[[package]] +name = "aes-gcm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834a6bda386024dbb7c8fc51322856c10ffe69559f972261c868485f5759c638" +dependencies = [ + "aead", + "aes 0.3.2", + "block-cipher-trait", + "ghash", + "subtle 2.2.3", + "zeroize", +] + [[package]] name = "aes-soft" version = "0.3.3" @@ -37,6 +71,17 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aes-soft" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4925647ee64e5056cf231608957ce7c81e12d6d6e316b9ce1404778cc1d35fa7" +dependencies = [ + "block-cipher", + "byteorder", + "opaque-debug", +] + [[package]] name = "aesni" version = "0.6.0" @@ -47,6 +92,22 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "aesni" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050d39b0b7688b3a3254394c3e30a9d66c41dcf9b05b0e2dbdc623f6505d264" +dependencies = [ + "block-cipher", + "opaque-debug", +] + +[[package]] +name = "ahash" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" + [[package]] name = "aho-corasick" version = "0.7.10" @@ -113,15 +174,15 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" dependencies = [ - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] name = "async-h1" -version = "1.1.2" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "effd501febce09534b924aa471e6a7fd689071fee63659473413f62a1979ae56" +checksum = "7c966ee4e290159619bdc5282ba252749386552be066dbcf5b97adf612798a9a" dependencies = [ "async-std", "byte-pool", @@ -131,7 +192,6 @@ dependencies = [ "lazy_static", "log", "pin-project-lite", - "url", ] [[package]] @@ -180,7 +240,7 @@ dependencies = [ "base64 0.11.0", "bufstream", "fast_chemail", - "hostname", + "hostname 0.1.5", "log", "nom 5.1.1", "pin-project", @@ -193,9 +253,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.6.0" +version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45cee2749d880d7066e328a7e161c7470ced883b2fd000ca4643e9f1dd5083a" +checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d" dependencies = [ "async-attributes", "async-task", @@ -217,24 +277,15 @@ dependencies = [ ] [[package]] -name = "async-stream" -version = "0.2.1" +name = "async-std-resolver" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +checksum = "d113439234775ae3e43d4e7589c5cc64fa3565996ae82dfe26b4ed78c02165be" dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" -dependencies = [ - "proc-macro2", - "quote 1.0.6", - "syn 1.0.23", + "async-std", + "async-trait", + "futures 0.3.5", + "trust-dns-resolver", ] [[package]] @@ -245,13 +296,22 @@ checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3" [[package]] name = "async-trait" -version = "0.1.31" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c4f3195085c36ea8d24d32b2f828d23296a9370a28aa39d111f6f16bef9f3b" +checksum = "89cb5d814ab2a47fd66d3266e9efccb53ca4c740b7451043b8ffcf9a6208f3f8" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", +] + +[[package]] +name = "atoi" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0afb7287b68575f5ca0e5c7e40191cbd4be59d325781f46faa603e176eaef47" +dependencies = [ + "num-traits", ] [[package]] @@ -290,6 +350,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1" + [[package]] name = "base64" version = "0.10.1" @@ -305,6 +371,12 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +[[package]] +name = "base64" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" + [[package]] name = "bit-set" version = "0.5.2" @@ -349,10 +421,31 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" dependencies = [ - "block-padding", + "block-padding 0.1.5", "byte-tools", "byteorder", - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "block-buffer" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbcf92448676f82bb7a334c58bbce8b0d43580fb5362a9d608b18879d12a3d31" +dependencies = [ + "block-padding 0.1.5", + "byte-tools", + "byteorder", + "generic-array 0.14.2", +] + +[[package]] +name = "block-cipher" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa136449e765dc7faa244561ccae839c394048667929af599b5d931ebe7b7f10" +dependencies = [ + "generic-array 0.14.2", ] [[package]] @@ -361,17 +454,17 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" dependencies = [ - "generic-array", + "generic-array 0.12.3", ] [[package]] name = "block-modes" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31aa8410095e39fdb732909fb5730a48d5bd7c2e3cd76bd1b07b3dbea130c529" +checksum = "538b66bc25a51ce985544067a9c3e17d426447f468c495dd6cb00040e5953692" dependencies = [ - "block-cipher-trait", - "block-padding", + "block-cipher", + "block-padding 0.1.5", ] [[package]] @@ -384,12 +477,31 @@ dependencies = [ ] [[package]] -name = "blowfish" -version = "0.4.0" +name = "block-padding" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3" +checksum = "c98bfd7c112b6399fef97cc0614af1cd375b27a112e552ce60f94c1b5f13cb74" + +[[package]] +name = "blocking" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d17efb70ce4421e351d61aafd90c16a20fb5bfe339fcdc32a86816280e62ce0" dependencies = [ - "block-cipher-trait", + "futures-channel", + "futures-util", + "once_cell", + "parking", + "waker-fn", +] + +[[package]] +name = "blowfish" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d01392750dd899a2528948d6b856afe2df508d627fc7c339868c0bd0141b4b" +dependencies = [ + "block-cipher", "byteorder", "opaque-debug", ] @@ -412,9 +524,9 @@ checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" [[package]] name = "bumpalo" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5356f1d23ee24a1f785a56d1d1a5f0fd5b0f6a0c0fb2412ce11da71649ab78f6" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byte-pool" @@ -438,6 +550,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b92204551573580e078dc80017f36a213eb77a0450e4ddd8cfa0f3f2d1f0178f" +[[package]] +name = "bytemuck" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37fa13df2292ecb479ec23aa06f4507928bef07839be9ef15281411076629431" + [[package]] name = "byteorder" version = "1.3.4" @@ -460,6 +578,12 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" +[[package]] +name = "cache-padded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24508e28c677875c380c20f4d28124fab6f8ed4ef929a1397d7b1a31e92f1005" + [[package]] name = "cargo_metadata" version = "0.6.4" @@ -475,11 +599,11 @@ dependencies = [ [[package]] name = "cast5" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce5759b4c52ca74f9a98421817c882f1fd9b0071ae41cd61ab9f9d059c04fd6" +checksum = "923e7bf02710210fc56cbeff9b8f3c0f9c2b4723f08678a0011bd0028b06c6b4" dependencies = [ - "block-cipher-trait", + "block-cipher", "byteorder", "opaque-debug", ] @@ -492,11 +616,11 @@ checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" [[package]] name = "cfb-mode" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190e7b55d3a27cf8879becf61035a141cbc783f3258a41d16d1706719f991345" +checksum = "51fc3a82c422bab680468af21241497d76660ef2e2fc75ec751fb03bf7dd725f" dependencies = [ - "block-cipher-trait", + "block-cipher", "stream-cipher", ] @@ -524,7 +648,7 @@ checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" dependencies = [ "num-integer", "num-traits", - "time", + "time 0.1.43", ] [[package]] @@ -535,9 +659,9 @@ checksum = "b0fc239e0f6cb375d2402d48afb92f76f5404fd1df208a41930ec81eda078bea" [[package]] name = "clear_on_drop" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97276801e127ffb46b66ce23f35cc96bd454fa311294bced4bbace7baa8b1d17" +checksum = "c9cc5db465b294c3fa986d5bbb0f3017cd850bff6dd6c52f9ccff8b4d21b7b08" dependencies = [ "cc", ] @@ -557,6 +681,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" +[[package]] +name = "concurrent-queue" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83c06aff61f2d899eb87c379df3cbf7876f14471dcab474e0b6dc90ab96c080" +dependencies = [ + "cache-padded", +] + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -565,11 +698,18 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "cookie" -version = "0.12.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +checksum = "ca761767cf3fa9068cc893ec8c247a22d0fd0535848e65640c0548bd1f8bbb36" dependencies = [ - "time", + "aes-gcm", + "base64 0.12.1", + "hkdf", + "hmac", + "percent-encoding", + "rand 0.7.3", + "sha2 0.8.2", + "time 0.2.16", ] [[package]] @@ -604,36 +744,20 @@ dependencies = [ ] [[package]] -name = "crossbeam-deque" -version = "0.7.3" +name = "crossbeam-channel" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" dependencies = [ - "crossbeam-epoch", "crossbeam-utils", "maybe-uninit", ] -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg 1.0.0", - "cfg-if", - "crossbeam-utils", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - [[package]] name = "crossbeam-queue" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" +checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" dependencies = [ "cfg-if", "crossbeam-utils", @@ -651,25 +775,35 @@ dependencies = [ ] [[package]] -name = "ctor" -version = "0.1.14" +name = "crypto-mac" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf6b25ee9ac1995c54d7adb2eff8cfffb7260bc774fb63c601ec65467f43cd9d" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ - "quote 1.0.6", - "syn 1.0.27", + "generic-array 0.12.3", + "subtle 1.0.0", +] + +[[package]] +name = "ctor" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39858aa5bac06462d4dd4b9164848eb81ffc4aa5c479746393598fd193afa227" +dependencies = [ + "quote", + "syn", ] [[package]] name = "curve25519-dalek" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26778518a7f6cffa1d25a44b602b62b979bd88adb9e99ffec546998cf3404839" +checksum = "5d85653f070353a16313d0046f173f70d1aadd5b42600a14de626f0dfb3473a5" dependencies = [ "byteorder", - "digest", + "digest 0.8.1", "rand_core 0.5.1", - "subtle", + "subtle 2.2.3", "zeroize", ] @@ -692,9 +826,9 @@ dependencies = [ "fnv", "ident_case", "proc-macro2", - "quote 1.0.6", + "quote", "strsim", - "syn 1.0.27", + "syn", ] [[package]] @@ -704,25 +838,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" dependencies = [ "darling_core", - "quote 1.0.6", - "syn 1.0.27", -] - -[[package]] -name = "debug_stub_derive" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496b7f8a2f853313c3ca370641d7ff3e42c32974fdccda8f0684599ed0a3ff6b" -dependencies = [ - "quote 0.3.15", - "syn 0.11.11", + "quote", + "syn", ] [[package]] name = "deflate" -version = "0.7.20" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" +checksum = "e7e5d2a2273fed52a7f947ee55b092c4057025d7a3e04e5ecdbd25d6c3fb1bd7" dependencies = [ "adler32", "byteorder", @@ -730,7 +854,7 @@ dependencies = [ [[package]] name = "deltachat" -version = "1.34.0" +version = "1.39.0" dependencies = [ "ansi_term 0.12.1", "anyhow", @@ -738,14 +862,14 @@ dependencies = [ "async-native-tls", "async-smtp", "async-std", + "async-std-resolver", "async-trait", "backtrace", - "base64 0.11.0", + "base64 0.12.1", "bitflags", "byteorder", "charset", "chrono", - "debug_stub_derive", "deltachat_derive", "email", "encoded-words", @@ -756,9 +880,11 @@ dependencies = [ "image-meta", "indexmap", "itertools", + "kamadak-exif", "lazy_static", "lettre_email", "libc", + "libsqlite3-sys", "log", "mailparse", "native-tls", @@ -770,16 +896,13 @@ dependencies = [ "pretty_env_logger", "proptest", "quick-xml", - "r2d2", - "r2d2_sqlite", "rand 0.7.3", "regex", - "rusqlite", "rustyline", "sanitize-filename", "serde", "serde_json", - "sha2", + "sha2 0.9.0", "smallvec", "smol", "sqlx", @@ -796,13 +919,13 @@ dependencies = [ name = "deltachat_derive" version = "2.0.0" dependencies = [ - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] name = "deltachat_ffi" -version = "1.34.0" +version = "1.39.0" dependencies = [ "anyhow", "async-std", @@ -823,8 +946,8 @@ dependencies = [ "darling", "derive_builder_core", "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] @@ -835,17 +958,17 @@ checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" dependencies = [ "darling", "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] name = "des" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74ba5f1b5aee9772379c2670ba81306e65a93c0ee3caade7a1d22b188d88a3af" +checksum = "4e6f17f2850a27f147a228d342a95bf3c613934464d77d531fe8d5151ccd9362" dependencies = [ - "block-cipher-trait", + "block-cipher", "byteorder", "opaque-debug", ] @@ -862,7 +985,16 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array", + "generic-array 0.12.3", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.2", ] [[package]] @@ -876,6 +1008,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dotenv" version = "0.15.0" @@ -897,7 +1035,7 @@ dependencies = [ "clear_on_drop", "curve25519-dalek", "rand 0.7.3", - "sha2", + "sha2 0.8.2", ] [[package]] @@ -909,7 +1047,7 @@ checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" [[package]] name = "email" version = "0.0.21" -source = "git+https://github.com/deltachat/rust-email#ace12ee6f8e054dd890589f588d0311604fc25f0" +source = "git+https://github.com/deltachat/rust-email#9161f2d45d29ae8f0119fde0facdb1b0d7c6f077" dependencies = [ "base64 0.11.0", "chrono", @@ -917,7 +1055,7 @@ dependencies = [ "encoding", "lazy_static", "rand 0.7.3", - "time", + "time 0.1.43", "version_check 0.9.2", ] @@ -1015,10 +1153,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" [[package]] -name = "env_logger" -version = "0.6.2" +name = "enum-as-inner" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +checksum = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime", @@ -1046,46 +1196,12 @@ dependencies = [ "entities", ] -[[package]] -name = "failure" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" -dependencies = [ - "backtrace", - "failure_derive", -] - -[[package]] -name = "failure_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" -dependencies = [ - "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", - "synstructure", -] - [[package]] name = "fake-simd" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - -[[package]] -name = "fallible-streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" - [[package]] name = "fast_chemail" version = "0.9.6" @@ -1095,6 +1211,12 @@ dependencies = [ "ascii_utils", ] +[[package]] +name = "fastrand" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a64b0126b293b050395b37b10489951590ed024c03d7df4f249d219c8ded7cbf" + [[package]] name = "flate2" version = "1.0.14" @@ -1196,8 +1318,8 @@ checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39" dependencies = [ "proc-macro-hack", "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] @@ -1256,6 +1378,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "generic-array" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980" +dependencies = [ + "typenum", + "version_check 0.9.2", +] + [[package]] name = "getrandom" version = "0.1.14" @@ -1267,6 +1399,15 @@ dependencies = [ "wasi", ] +[[package]] +name = "ghash" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f0930ed19a7184089ea46d2fedead2f6dc2b674c5db4276b7da336c7cd83252" +dependencies = [ + "polyval", +] + [[package]] name = "gif" version = "0.10.3" @@ -1302,6 +1443,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "hashbrown" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96282e96bfcd3da0d3aa9938bedf1e50df3269b6db08b4876d2da0bb1a0841cf" +dependencies = [ + "ahash", + "autocfg 1.0.0", +] + [[package]] name = "heck" version = "0.3.1" @@ -1326,6 +1477,26 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" +[[package]] +name = "hkdf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa08a006102488bd9cd5b8013aabe84955cf5ae22e304c2caf655b633aefae3" +dependencies = [ + "digest 0.8.1", + "hmac", +] + +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest 0.8.1", +] + [[package]] name = "hostname" version = "0.1.5" @@ -1337,21 +1508,21 @@ dependencies = [ ] [[package]] -name = "http" -version = "0.2.1" +name = "hostname" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ - "bytes 0.5.4", - "fnv", - "itoa", + "libc", + "match_cfg", + "winapi", ] [[package]] name = "http-client" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271fae45413daaca82e8c6bf46e452b897873676ce56196d8939645711f542d5" +checksum = "fdae064c76b75ced4793544e843478c8c9c2bebf16fe6913212df83e30dfdb46" dependencies = [ "async-h1", "async-native-tls", @@ -1367,19 +1538,19 @@ dependencies = [ [[package]] name = "http-types" -version = "1.3.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49d9f44462c2e59d5d5826e7ba74b121ee2fb0ff091ef849692694f1d77aaf50" +checksum = "9a89eaaf43f3700e78c01cb8165d1bd05155065637d26ee2f49800c95e7b62ee" dependencies = [ "anyhow", "async-std", "cookie", - "http", "infer", - "omnom", "pin-project-lite", + "rand 0.7.3", "serde", "serde_json", + "serde_urlencoded", "url", ] @@ -1432,10 +1603,11 @@ dependencies = [ [[package]] name = "image" -version = "0.22.5" +version = "0.23.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08ed2ada878397b045454ac7cfb011d73132c59f31a955d230bd1f1c2e68eb4a" +checksum = "b5b0553fec6407d63fe2975b794dfb099f3f790bdc958823851af37b26404ab4" dependencies = [ + "bytemuck", "byteorder", "gif", "jpeg-decoder", @@ -1467,9 +1639,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ "autocfg 1.0.0", ] @@ -1483,15 +1655,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "inflate" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" -dependencies = [ - "adler32", -] - [[package]] name = "iovec" version = "0.1.4" @@ -1501,6 +1664,18 @@ dependencies = [ "libc", ] +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi", + "winreg", +] + [[package]] name = "itertools" version = "0.8.2" @@ -1534,6 +1709,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kamadak-exif" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5e66d5b5469321038611f7f0e845a48989e4fd54987b6e5bb4c8ae3adbace7" +dependencies = [ + "mutate_once", +] + [[package]] name = "keccak" version = "0.1.0" @@ -1578,7 +1762,7 @@ dependencies = [ "lettre", "mime", "regex", - "time", + "time 0.1.43", "uuid", ] @@ -1674,6 +1858,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matches" version = "0.1.8" @@ -1688,12 +1878,12 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "md-5" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" +checksum = "32e273c0484dae98a721a3d9e8d9780e05e693912dd9fa87c7645e125df7a793" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.8.0", + "digest 0.9.0", "opaque-debug", ] @@ -1703,15 +1893,6 @@ version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" -[[package]] -name = "memoffset" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" -dependencies = [ - "autocfg 1.0.0", -] - [[package]] name = "mime" version = "0.3.16" @@ -1737,6 +1918,12 @@ dependencies = [ "adler32", ] +[[package]] +name = "mutate_once" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b" + [[package]] name = "native-tls" version = "0.2.4" @@ -1795,6 +1982,17 @@ dependencies = [ "version_check 0.9.2", ] +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg 1.0.0", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.6.0" @@ -1821,8 +2019,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8b15b261814f992e33760b1fca9fe8b693d8a65299f20c9901688636cfb746" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] @@ -1848,9 +2046,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.2.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138" dependencies = [ "autocfg 1.0.0", "num-integer", @@ -1882,15 +2080,6 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" -[[package]] -name = "omnom" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b216cee2e0d6e680f73158d15468c80b39e571c11669cd90556f9a644e9fd3" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.4.0" @@ -1934,9 +2123,9 @@ dependencies = [ [[package]] name = "openssl-sys" -version = "0.9.57" +version = "0.9.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7410fef80af8ac071d4f63755c0ab89ac3df0fd1ea91f1d1f37cf5cec4395990" +checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" dependencies = [ "autocfg 1.0.0", "cc", @@ -1973,6 +2162,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "parking" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bcaa58ee64f8e4a3d02f5d8e6ed0340eae28fed6fdabd984ad1776e3b43848a" + [[package]] name = "parking_lot" version = "0.10.2" @@ -1997,6 +2192,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "pem" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b" +dependencies = [ + "base64 0.12.1", + "once_cell", + "regex", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -2005,15 +2211,15 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pgp" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8172973101790c866e66966002bf1028d0df27bf6b3b29be86a6fd440d8a4285" +checksum = "26d3fd4e0b2b7a8195b22e196a947c9ceb0d0f722d060e38e0641f48f56e195b" dependencies = [ - "aes", - "base64 0.11.0", + "aes 0.4.0", + "base64 0.12.1", "bitfield", "block-modes", - "block-padding", + "block-padding 0.2.0", "blowfish", "buf_redux", "byteorder", @@ -2025,10 +2231,10 @@ dependencies = [ "crc24", "derive_builder", "des", - "digest", + "digest 0.9.0", "ed25519-dalek", "flate2", - "generic-array", + "generic-array 0.14.2", "hex", "lazy_static", "log", @@ -2041,7 +2247,7 @@ dependencies = [ "ripemd160", "rsa", "sha-1", - "sha2", + "sha2 0.9.0", "sha3", "smallvec", "thiserror", @@ -2052,30 +2258,74 @@ dependencies = [ ] [[package]] -name = "pin-project" -version = "0.4.17" +name = "phf" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc93aeee735e60ecb40cf740eb319ff23eab1c5748abfdb5c180e4ce49f7791" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared", + "proc-macro-hack", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared", + "rand 0.7.3", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75373ff9037d112bb19bc61333a06a159eaeb217660dcfbea7d88e1db823919" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e58db2081ba5b4c93bd6be09c40fd36cb9193a8336c384f3b40012e531aa7e40" +checksum = "10b4b44893d3c370407a1d6a5cfde7c41ae0478e31c516c85f67eb3adc51be6d" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" [[package]] name = "pin-utils" @@ -2083,18 +2333,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0deb65f46e873ba8aa7c6a8dbe3f23cb1bf59c339a81a1d56361dde4d66ac8" -dependencies = [ - "crossbeam-utils", - "futures-io", - "futures-sink", - "futures-util", -] - [[package]] name = "pkg-config" version = "0.3.17" @@ -2103,14 +2341,24 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "png" -version = "0.15.3" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef859a23054bbfee7811284275ae522f0434a3c8e7f4b74bd4a35ae7e1c4a283" +checksum = "34ccdd66f6fe4b2433b07e4728e9a013e43233120427046e93ceb709c3a439bf" dependencies = [ "bitflags", "crc32fast", "deflate", - "inflate", + "miniz_oxide", +] + +[[package]] +name = "polyval" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ec3341498978de3bfd12d1b22f1af1de22818f5473a11e8a6ef997989e3a212" +dependencies = [ + "cfg-if", + "universal-hash", ] [[package]] @@ -2133,11 +2381,10 @@ dependencies = [ [[package]] name = "pretty_env_logger" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" dependencies = [ - "chrono", "env_logger", "log", ] @@ -2150,24 +2397,24 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694" +checksum = "0afe1bd463b9e9ed51d0e0f0b50b6b146aec855c56fd182bb242388710a9b6de" [[package]] name = "proc-macro2" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ - "unicode-xid 0.2.0", + "unicode-xid", ] [[package]] name = "proptest" -version = "0.9.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01c477819b845fe023d33583ebf10c9f62518c8d79a0960ba5c36d6ac8a55a5b" +checksum = "2520fe6373cf6a3a61e2d200e987c183778ade8d9248ac3e6614ab0edfe4a0c1" dependencies = [ "bit-set", "bitflags", @@ -2175,8 +2422,8 @@ dependencies = [ "lazy_static", "num-traits", "quick-error", - "rand 0.6.5", - "rand_chacha 0.1.1", + "rand 0.7.3", + "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", @@ -2200,24 +2447,18 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quick-xml" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0" +checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82" dependencies = [ "memchr", ] [[package]] name = "quote" -version = "0.3.15" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" - -[[package]] -name = "quote" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] @@ -2228,27 +2469,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" -[[package]] -name = "r2d2" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af" -dependencies = [ - "log", - "parking_lot", - "scheduled-thread-pool", -] - -[[package]] -name = "r2d2_sqlite" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e15ff794e7c8bb8ae20ccac5bac6a93a4a3af708dd801d4094f80da41196f33" -dependencies = [ - "r2d2", - "rusqlite", -] - [[package]] name = "rand" version = "0.4.6" @@ -2262,25 +2482,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rand" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" -dependencies = [ - "autocfg 0.1.7", - "libc", - "rand_chacha 0.1.1", - "rand_core 0.4.2", - "rand_hc 0.1.0", - "rand_isaac", - "rand_jitter", - "rand_os", - "rand_pcg", - "rand_xorshift", - "winapi", -] - [[package]] name = "rand" version = "0.7.3" @@ -2290,19 +2491,10 @@ dependencies = [ "getrandom", "libc", "packed_simd", - "rand_chacha 0.2.2", + "rand_chacha", "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", + "rand_hc", + "rand_pcg", ] [[package]] @@ -2339,15 +2531,6 @@ dependencies = [ "getrandom", ] -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -2357,57 +2540,22 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[package]] -name = "rand_os" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" -dependencies = [ - "cloudabi", - "fuchsia-cprng", - "libc", - "rand_core 0.4.2", - "rdrand", - "winapi", -] - [[package]] name = "rand_pcg" -version = "0.1.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" dependencies = [ - "autocfg 0.1.7", - "rand_core 0.4.2", + "rand_core 0.5.1", ] [[package]] name = "rand_xorshift" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +checksum = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" dependencies = [ - "rand_core 0.3.1", + "rand_core 0.5.1", ] [[package]] @@ -2438,9 +2586,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.3.8" +version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226ddd1197737bcb937489322ec1b9edaac1709d46792886e70f2113923585a6" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" dependencies = [ "aho-corasick", "memchr", @@ -2480,54 +2628,53 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "475e68978dc5b743f2f40d8e0a8fdc83f1c5e78cbf4b8fa5e74e73beebc340de" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", +] + +[[package]] +name = "resolv-conf" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a" +dependencies = [ + "hostname 0.3.1", + "quick-error", ] [[package]] name = "ripemd160" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad5112e0dbbb87577bfbc56c42450235e3012ce336e29c5befd7807bd626da4a" +checksum = "7037e00ff78e861f53edd08ea4c4351fbf9b357145fdb791bc2f9a2236a33b45" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.8.0", + "digest 0.9.0", "opaque-debug", ] [[package]] name = "rsa" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed8692d8e0ea3baae03f0f32ecfc13a6c6f1f85fcd6d9fdefcdf364e70f4df9" +checksum = "3648b669b10afeab18972c105e284a7b953a669b0be3514c27f9b17acab2f9cd" dependencies = [ "byteorder", - "failure", + "digest 0.9.0", "lazy_static", "num-bigint-dig", "num-integer", "num-iter", "num-traits", + "pem", "rand 0.7.3", - "subtle", + "sha2 0.9.0", + "simple_asn1", + "subtle 2.2.3", + "thiserror", "zeroize", ] -[[package]] -name = "rusqlite" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57edf4c4cea4d7e0fab069acb5da9e8e8e5403c78abc81b1f37d83af02148ea5" -dependencies = [ - "bitflags", - "fallible-iterator", - "fallible-streaming-iterator", - "libsqlite3-sys", - "lru-cache", - "memchr", - "time", -] - [[package]] name = "rust-argon2" version = "0.7.0" @@ -2557,9 +2704,9 @@ dependencies = [ [[package]] name = "rusty-fork" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dd93264e10c577503e926bd1430193eeb5d21b059148910082245309b424fae" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", "quick-error", @@ -2586,9 +2733,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "safemem" @@ -2626,19 +2773,10 @@ dependencies = [ ] [[package]] -name = "scheduled-thread-pool" -version = "0.2.4" +name = "scoped-tls" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6" -dependencies = [ - "parking_lot", -] - -[[package]] -name = "scoped-tls-hkt" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" [[package]] name = "scopeguard" @@ -2693,22 +2831,22 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e7b308464d16b56eba9964e4972a3eee817760ab60d88c3f86e1fecb08204c" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.110" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818fbf6bfa9a42d3bfcaca148547aa00c7b915bec71d1757aa2d44ca68771984" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] @@ -2736,41 +2874,76 @@ dependencies = [ [[package]] name = "sha-1" -version = "0.8.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "59520a294fcfdaff2ce8276dc1bdc6170b97abe8043560f70178222e611242fc" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.8.0", + "digest 0.9.0", "fake-simd", "opaque-debug", ] +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" + [[package]] name = "sha2" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72377440080fd008550fe9b441e854e43318db116f90181eef92e9ae9aedab48" +dependencies = [ + "block-buffer 0.8.0", + "digest 0.9.0", "fake-simd", "opaque-debug", ] [[package]] name = "sha3" -version = "0.8.2" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" +checksum = "b859cf80317bb4ab6b29422f3d77de357ef60a0e0b3dedf28469d2b11d098968" dependencies = [ - "block-buffer", + "block-buffer 0.8.0", "byte-tools", - "digest", + "digest 0.9.0", "keccak", "opaque-debug", ] +[[package]] +name = "simple_asn1" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618" +dependencies = [ + "chrono", + "num-bigint", + "num-traits", +] + +[[package]] +name = "siphasher" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" + [[package]] name = "skeptic" version = "0.13.4" @@ -2801,22 +2974,23 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smol" -version = "0.1.10" -source = "git+https://github.com/dignifiedquire/smol-1?branch=isolate-nix#1ba84935e4e48be927c7f8f4a9cb5c05614135c3" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5" dependencies = [ "async-task", - "crossbeam-deque", - "crossbeam-queue", - "crossbeam-utils", + "blocking", + "concurrent-queue", + "fastrand", "futures-io", "futures-util", "libc", "once_cell", - "piper", - "scoped-tls-hkt", + "scoped-tls", "slab", "socket2", - "wepoll-binding", + "wepoll-sys-stjepang", + "winapi", ] [[package]] @@ -2850,9 +3024,8 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8974cacd80085fbe49e778708d660dec6fb351604dc34c3905b26efb2803b038" +version = "0.4.0-pre" +source = "git+https://github.com/launchbadge/sqlx#e1d22a1840c07e0bbdd97791fc4993413e151384" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2860,59 +3033,136 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ac5a436f941c42eac509471a730df5c3c58e1450e68cd39afedbd948206273" +version = "0.4.0-pre" +source = "git+https://github.com/launchbadge/sqlx#e1d22a1840c07e0bbdd97791fc4993413e151384" dependencies = [ - "async-native-tls", - "async-std", - "async-stream", + "atoi", "bitflags", "byteorder", + "bytes 0.5.4", + "crossbeam-channel", "crossbeam-queue", "crossbeam-utils", + "either", "futures-channel", "futures-core", "futures-util", + "hashbrown", "hex", + "itoa", "libc", "libsqlite3-sys", "log", "memchr", + "once_cell", + "parking_lot", "percent-encoding", + "phf", + "smallvec", "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", "url", + "whoami", ] [[package]] name = "sqlx-macros" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de2ae78b783af5922d811b14665a5a3755e531c3087bb805cf24cf71f15e6780" +version = "0.4.0-pre" +source = "git+https://github.com/launchbadge/sqlx#e1d22a1840c07e0bbdd97791fc4993413e151384" dependencies = [ "async-std", "dotenv", "futures 0.3.5", "heck", "proc-macro2", - "quote 1.0.6", + "quote", "sqlx-core", - "syn 1.0.23", + "syn", "url", ] +[[package]] +name = "sqlx-rt" +version = "0.1.0-pre" +source = "git+https://github.com/launchbadge/sqlx#e1d22a1840c07e0bbdd97791fc4993413e151384" +dependencies = [ + "async-native-tls", + "async-std", + "native-tls", +] + [[package]] name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "standback" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0437cfb83762844799a60e1e3b489d5ceb6a650fbacb86437badc1b6d87b246" +dependencies = [ + "version_check 0.9.2", +] + [[package]] name = "static_assertions" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "stop-token" version = "0.1.2" @@ -2925,11 +3175,21 @@ dependencies = [ [[package]] name = "stream-cipher" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" +checksum = "09f8ed9974042b8c3672ff3030a69fcc03b74c47c3d1ecb7755e8a3626011e88" dependencies = [ - "generic-array", + "generic-array 0.14.2", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", ] [[package]] @@ -2940,33 +3200,39 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" [[package]] name = "strum" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22" +checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b" [[package]] name = "strum_macros" -version = "0.16.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81" +checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c" dependencies = [ "heck", "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] name = "subtle" -version = "2.2.2" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c65d530b10ccaeac294f349038a597e435b18fb456aadd0840a623f83b9e941" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + +[[package]] +name = "subtle" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" [[package]] name = "surf" -version = "2.0.0-alpha.2" +version = "2.0.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d72c302d4a24b2c9d412f1fe5edd892946778d33c423bba087c6c10deeb656ff" +checksum = "68bf1412b095b3a54da6ec3a969e2663cb3cf535652cd97c02e99443a400fd7d" dependencies = [ "async-std", "futures 0.3.5", @@ -2984,45 +3250,25 @@ dependencies = [ [[package]] name = "syn" -version = "0.11.11" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -dependencies = [ - "quote 0.3.15", - "synom", - "unicode-xid 0.0.4", -] - -[[package]] -name = "syn" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef781e621ee763a2a40721a8861ec519cb76966aee03bb5d00adb6a31dc1c1de" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" dependencies = [ "proc-macro2", - "quote 1.0.6", - "unicode-xid 0.2.0", -] - -[[package]] -name = "synom" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -dependencies = [ - "unicode-xid 0.0.4", + "quote", + "unicode-xid", ] [[package]] name = "synstructure" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", - "unicode-xid 0.2.0", + "quote", + "syn", + "unicode-xid", ] [[package]] @@ -3074,8 +3320,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", ] [[package]] @@ -3097,6 +3343,44 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a51cadc5b1eec673a685ff7c33192ff7b7603d0b75446fb354939ee615acb15" +dependencies = [ + "cfg-if", + "libc", + "standback", + "stdweb", + "time-macros", + "version_check 0.9.2", + "winapi", +] + +[[package]] +name = "time-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9b6e9f095bc105e183e3cd493d72579be3181ad4004fceb01adbe9eecab2d" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + +[[package]] +name = "time-macros-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c3be1edfad6027c69f5491cf4cb310d1a71ecd6af742788c6ff8bced86b8fa" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + [[package]] name = "tokio-io" version = "0.1.13" @@ -3117,6 +3401,44 @@ dependencies = [ "serde", ] +[[package]] +name = "trust-dns-proto" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28" +dependencies = [ + "async-trait", + "backtrace", + "enum-as-inner", + "futures 0.3.5", + "idna", + "lazy_static", + "log", + "rand 0.7.3", + "smallvec", + "thiserror", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77" +dependencies = [ + "backtrace", + "cfg-if", + "futures 0.3.5", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "trust-dns-proto", +] + [[package]] name = "try_from" version = "0.3.2" @@ -3128,11 +3450,11 @@ dependencies = [ [[package]] name = "twofish" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712d261e83e727c8e2dbb75dacac67c36e35db36a958ee504f2164fc052434e1" +checksum = "5ac7c7f7184cecc73a340d1d2b8bb884385d521650437d96143ef4ccc86aaaf7" dependencies = [ - "block-cipher-trait", + "block-cipher", "byteorder", "opaque-debug", ] @@ -3182,18 +3504,22 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" -[[package]] -name = "unicode-xid" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" - [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +[[package]] +name = "universal-hash" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0c900f2f9b4116803415878ff48b63da9edb268668e08cf9292d7503114a01" +dependencies = [ + "generic-array 0.12.3", + "subtle 2.2.3", +] + [[package]] name = "url" version = "2.1.1" @@ -3222,9 +3548,9 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" +checksum = "55d1e41d56121e07f1e223db0a4def204e45c85425f6a16d462fd07c8d10d74c" [[package]] name = "version_check" @@ -3253,6 +3579,12 @@ dependencies = [ "libc", ] +[[package]] +name = "waker-fn" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7" + [[package]] name = "walkdir" version = "2.3.1" @@ -3290,8 +3622,8 @@ dependencies = [ "lazy_static", "log", "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", "wasm-bindgen-shared", ] @@ -3313,7 +3645,7 @@ version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3" dependencies = [ - "quote 1.0.6", + "quote", "wasm-bindgen-macro-support", ] @@ -3324,8 +3656,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3347,24 +3679,26 @@ dependencies = [ ] [[package]] -name = "wepoll-binding" -version = "2.0.2" +name = "wepoll-sys-stjepang" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7" -dependencies = [ - "bitflags", - "wepoll-sys", -] - -[[package]] -name = "wepoll-sys" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24" +checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694" dependencies = [ "cc", ] +[[package]] +name = "whoami" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08eb844b158ea881e81b94556eede7f7e306e4c7b976aad88f49e6e36dec391" + +[[package]] +name = "widestring" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c" + [[package]] name = "winapi" version = "0.3.8" @@ -3396,6 +3730,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi", +] + [[package]] name = "winutil" version = "0.1.1" @@ -3432,7 +3775,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de251eec69fc7c1bc3923403d18ececb929380e016afe103da75f396704f8ca2" dependencies = [ "proc-macro2", - "quote 1.0.6", - "syn 1.0.27", + "quote", + "syn", "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index 07405ddc2..23d5c8fa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,9 +36,6 @@ indexmap = "1.3.0" kamadak-exif = "0.5" lazy_static = "1.4.0" regex = "1.1.6" -rusqlite = { version = "0.23", features = ["bundled"] } -r2d2_sqlite = "0.16.0" -r2d2 = "0.8.5" strum = "0.18.0" strum_macros = "0.18.0" backtrace = "0.3.33" @@ -60,22 +57,22 @@ anyhow = "1.0.28" async-trait = "0.1.31" url = "2.1.1" async-std-resolver = "0.19.5" -sqlx = { version = "0.3.5", features = ["runtime-async-std", "sqlite"] } +sqlx = { git = "https://github.com/launchbadge/sqlx", branch = "master", features = ["runtime-async-std", "sqlite", "macros"] } +libsqlite3-sys = { version = "0.17.3", features = ["bundled", "min_sqlite_version_3_7_16"] } pretty_env_logger = { version = "0.4.0", optional = true } log = { version = "0.4.8", optional = true } rustyline = { version = "4.1.0", optional = true } ansi_term = { version = "0.12.1", optional = true } - - [dev-dependencies] tempfile = "3.0" pretty_assertions = "0.6.1" pretty_env_logger = "0.4.0" proptest = "0.10" async-std = { version = "1.6.0", features = ["unstable", "attributes"] } -smol = "0.1.10" +smol = "0.1.11" +log = "0.4.8" [workspace] members = [ @@ -100,4 +97,3 @@ internals = [] repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] nightly = ["pgp/nightly"] - diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 261461ce2..d19de4819 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -349,7 +349,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc: | Event::MsgDelivered { chat_id, .. } | Event::MsgFailed { chat_id, .. } | Event::MsgRead { chat_id, .. } - | Event::ChatModified(chat_id) => chat_id.to_u32() as libc::c_int, + | Event::ChatModified(chat_id) => chat_id.to_u32().unwrap_or_default() as libc::c_int, Event::ContactsChanged(id) | Event::LocationChanged(id) => { let id = id.unwrap_or_default(); id as libc::c_int @@ -396,7 +396,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc: | Event::IncomingMsg { msg_id, .. } | Event::MsgDelivered { msg_id, .. } | Event::MsgFailed { msg_id, .. } - | Event::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int, + | Event::MsgRead { msg_id, .. } => msg_id.to_u32().unwrap_or_default() as libc::c_int, Event::SecurejoinInviterProgress { progress, .. } | Event::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int, } @@ -833,7 +833,8 @@ pub unsafe extern "C" fn dc_get_chat_msgs( let arr = dc_array_t::from( chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag) .await - .iter() + .unwrap_or_log_default(ctx, "failed get_chat_msgs") + .into_iter() .map(|msg_id| msg_id.to_u32()) .collect::>(), ); @@ -898,7 +899,7 @@ pub unsafe extern "C" fn dc_get_fresh_msgs( let arr = dc_array_t::from( ctx.get_fresh_msgs() .await - .iter() + .into_iter() .map(|msg_id| msg_id.to_u32()) .collect::>(), ); @@ -975,7 +976,8 @@ pub unsafe extern "C" fn dc_get_chat_media( or_msg_type3, ) .await - .iter() + .unwrap_or_log_default(ctx, "failed get_chat_media") + .into_iter() .map(|msg_id| msg_id.to_u32()) .collect::>(), ); @@ -1019,8 +1021,9 @@ pub unsafe extern "C" fn dc_get_next_media( or_msg_type3, ) .await + .unwrap_or_log_default(ctx, "failed get_next_media") .map(|msg_id| msg_id.to_u32()) - .unwrap_or(0) + .unwrap_or_else(|| 0) }) } @@ -1086,7 +1089,10 @@ pub unsafe extern "C" fn dc_get_chat_contacts( let ctx = &*context; block_on(async move { - let arr = dc_array_t::from(chat::get_chat_contacts(&ctx, ChatId::new(chat_id)).await); + let list = chat::get_chat_contacts(&ctx, ChatId::new(chat_id)) + .await + .unwrap_or_log_default(ctx, "failed get_chat_contacts"); + let arr = dc_array_t::from(list); Box::into_raw(Box::new(arr)) }) } @@ -1107,7 +1113,7 @@ pub unsafe extern "C" fn dc_search_msgs( let arr = dc_array_t::from( ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query)) .await - .iter() + .into_iter() .map(|msg_id| msg_id.to_u32()) .collect::>(), ); @@ -1296,7 +1302,12 @@ pub unsafe extern "C" fn dc_get_msg_info( } let ctx = &*context; - block_on(message::get_msg_info(&ctx, MsgId::new(msg_id))).strdup() + block_on(async move { + message::get_msg_info(ctx, MsgId::new(msg_id)) + .await + .unwrap_or_log_default(ctx, "failed get_msg_info") + }) + .strdup() } #[no_mangle] @@ -1855,7 +1866,11 @@ pub unsafe extern "C" fn dc_set_location( } let ctx = &*context; - block_on(location::set(&ctx, latitude, longitude, accuracy)) as _ + block_on(async move { + location::set(ctx, latitude, longitude, accuracy) + .await + .unwrap_or_log_default(ctx, "failed location::set") + }) as _ } #[no_mangle] @@ -2255,7 +2270,12 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut let ctx = &*ffi_chat.context; block_on(async move { - match ffi_chat.chat.get_profile_image(&ctx).await { + match ffi_chat + .chat + .get_profile_image(&ctx) + .await + .unwrap_or_log_default(ctx, "failed get_profile_image") + { Some(p) => p.to_string_lossy().strdup(), None => ptr::null_mut(), } @@ -2271,7 +2291,13 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 { let ffi_chat = &*chat; let ctx = &*ffi_chat.context; - block_on(ffi_chat.chat.get_color(&ctx)) + block_on(async move { + ffi_chat + .chat + .get_color(&ctx) + .await + .unwrap_or_log_default(ctx, "failed dc_chat_get_color") + }) } #[no_mangle] diff --git a/deltachat_derive/src/lib.rs b/deltachat_derive/src/lib.rs index 664581464..672c43943 100644 --- a/deltachat_derive/src/lib.rs +++ b/deltachat_derive/src/lib.rs @@ -8,36 +8,33 @@ use quote::quote; // data. If this assumption is violated, compiler error will point to // generated code, which is not very user-friendly. -#[proc_macro_derive(ToSql)] -pub fn to_sql_derive(input: TokenStream) -> TokenStream { +#[proc_macro_derive(Sqlx)] +pub fn sqlx_derive(input: TokenStream) -> TokenStream { let ast: syn::DeriveInput = syn::parse(input).unwrap(); let name = &ast.ident; let gen = quote! { - impl rusqlite::types::ToSql for #name { - fn to_sql(&self) -> rusqlite::Result { - let num = *self as i64; - let value = rusqlite::types::Value::Integer(num); - let output = rusqlite::types::ToSqlOutput::Owned(value); - std::result::Result::Ok(output) - } - } - }; - gen.into() -} - -#[proc_macro_derive(FromSql)] -pub fn from_sql_derive(input: TokenStream) -> TokenStream { - let ast: syn::DeriveInput = syn::parse(input).unwrap(); - let name = &ast.ident; - - let gen = quote! { - impl rusqlite::types::FromSql for #name { - fn column_result(col: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { - let inner = rusqlite::types::FromSql::column_result(col)?; - Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default()) + impl<'q> sqlx::encode::Encode<'q, sqlx::sqlite::Sqlite> for #name { + fn encode_by_ref(&self, buf: &mut Vec>) -> sqlx::encode::IsNull{ + num_traits::ToPrimitive::to_i32(self).expect("invalid type").encode(buf) } } + + + impl<'de> sqlx::decode::Decode<'de, sqlx::sqlite::Sqlite> for #name { + fn decode(value: sqlx::sqlite::SqliteValueRef) -> std::result::Result { + let raw: i32 = sqlx::decode::Decode::decode(value)?; + + Ok(num_traits::FromPrimitive::from_i32(raw).unwrap_or_default()) + } + } + + impl sqlx::types::Type for #name { + fn type_info() -> sqlx::sqlite::SqliteTypeInfo { + >::type_info() + } + } + }; gen.into() } diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 82b3f8c4d..03fd17ac0 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -28,7 +28,7 @@ async fn reset_tables(context: &Context, bits: i32) { if 0 != bits & 1 { context .sql() - .execute("DELETE FROM jobs;", paramsv![]) + .execute("DELETE FROM jobs;", paramsx![]) .await .unwrap(); println!("(1) Jobs reset."); @@ -36,7 +36,7 @@ async fn reset_tables(context: &Context, bits: i32) { if 0 != bits & 2 { context .sql() - .execute("DELETE FROM acpeerstates;", paramsv![]) + .execute("DELETE FROM acpeerstates;", paramsx![]) .await .unwrap(); println!("(2) Peerstates reset."); @@ -44,7 +44,7 @@ async fn reset_tables(context: &Context, bits: i32) { if 0 != bits & 4 { context .sql() - .execute("DELETE FROM keypairs;", paramsv![]) + .execute("DELETE FROM keypairs;", paramsx![]) .await .unwrap(); println!("(4) Private keypairs reset."); @@ -52,35 +52,35 @@ async fn reset_tables(context: &Context, bits: i32) { if 0 != bits & 8 { context .sql() - .execute("DELETE FROM contacts WHERE id>9;", paramsv![]) + .execute("DELETE FROM contacts WHERE id>9;", paramsx![]) .await .unwrap(); context .sql() - .execute("DELETE FROM chats WHERE id>9;", paramsv![]) + .execute("DELETE FROM chats WHERE id>9;", paramsx![]) .await .unwrap(); context .sql() - .execute("DELETE FROM chats_contacts;", paramsv![]) + .execute("DELETE FROM chats_contacts;", paramsx![]) .await .unwrap(); context .sql() - .execute("DELETE FROM msgs WHERE id>9;", paramsv![]) + .execute("DELETE FROM msgs WHERE id>9;", paramsx![]) .await .unwrap(); context .sql() .execute( "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';", - paramsv![], + paramsx![], ) .await .unwrap(); context .sql() - .execute("DELETE FROM leftgrps;", paramsv![]) + .execute("DELETE FROM leftgrps;", paramsx![]) .await .unwrap(); println!("(8) Rest but server config reset."); @@ -118,7 +118,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool { .await .unwrap(); } else { - let rs = context.sql().get_raw_config(context, "import_spec").await; + let rs = context.sql().get_raw_config("import_spec").await; if rs.is_none() { error!(context, "Import: No file or folder given."); return false; @@ -276,7 +276,7 @@ async fn log_contactlist(context: &Context, contacts: &[u32]) { } ); let peerstate = Peerstate::from_addr(context, &addr).await; - if peerstate.is_some() && contact_id != 1 as libc::c_uint { + if peerstate.is_ok() && contact_id != 1 as libc::c_uint { line2 = format!( ", prefer-encrypt={}", peerstate.as_ref().unwrap().prefer_encrypt @@ -484,7 +484,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu context.maybe_network().await; } "housekeeping" => { - sql::housekeeping(&context).await; + sql::housekeeping(&context).await?; } "listchats" | "listarchived" | "chats" => { let listflags = if arg0 == "listarchived" { 0x01 } else { 0 }; @@ -573,8 +573,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu ensure!(sel_chat.is_some(), "Failed to select chat"); let sel_chat = sel_chat.as_ref().unwrap(); - let msglist = chat::get_chat_msgs(&context, sel_chat.get_id(), 0x1, None).await; - let members = chat::get_chat_contacts(&context, sel_chat.id).await; + let msglist = chat::get_chat_msgs(&context, sel_chat.get_id(), 0x1, None).await?; + let members = chat::get_chat_contacts(&context, sel_chat.id).await?; let subtitle = if sel_chat.is_device_talk() { "device-talk".to_string() } else if sel_chat.get_type() == Chattype::Single && !members.is_empty() { @@ -594,7 +594,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu } else { "" }, - match sel_chat.get_profile_image(&context).await { + match sel_chat.get_profile_image(&context).await? { Some(icon) => match icon.to_str() { Some(icon) => format!(" Icon: {}", icon), _ => " Icon: Err".to_string(), @@ -691,7 +691,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu ensure!(sel_chat.is_some(), "No chat selected."); let contacts = - chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await; + chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?; println!("Memberlist:"); log_contactlist(&context, &contacts).await; @@ -762,7 +762,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu let latitude = arg1.parse()?; let longitude = arg2.parse()?; - let continue_streaming = location::set(&context, latitude, longitude, 0.).await; + let continue_streaming = location::set(&context, latitude, longitude, 0.).await?; if continue_streaming { println!("Success, streaming should be continued."); } else { @@ -858,7 +858,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu Viewtype::Gif, Viewtype::Video, ) - .await; + .await?; println!("{} images or videos: ", images.len()); for (i, data) in images.iter().enumerate() { if 0 == i { @@ -892,7 +892,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu "msginfo" => { ensure!(!arg1.is_empty(), "Argument missing."); let id = MsgId::new(arg1.parse()?); - let res = message::get_msg_info(&context, id).await; + let res = message::get_msg_info(&context, id).await?; println!("{}", res); } "listfresh" => { diff --git a/src/aheader.rs b/src/aheader.rs index 6dee60eeb..d8b4134d1 100644 --- a/src/aheader.rs +++ b/src/aheader.rs @@ -6,13 +6,15 @@ use std::collections::BTreeMap; use std::str::FromStr; use std::{fmt, str}; +use deltachat_derive::*; + use crate::contact::*; use crate::context::Context; use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::key::{DcKey, SignedPublicKey}; /// Possible values for encryption preference -#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)] +#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive, Sqlx)] #[repr(u8)] pub enum EncryptPreference { NoPreference = 0, diff --git a/src/chat.rs b/src/chat.rs index 988637ee6..ac68a4c92 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -4,9 +4,11 @@ use std::convert::TryFrom; use std::time::{Duration, SystemTime}; use async_std::path::{Path, PathBuf}; +use async_std::prelude::*; use itertools::Itertools; use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; +use sqlx::{Arguments, Row}; use crate::blob::{BlobError, BlobObject}; use crate::chatlist::*; @@ -29,7 +31,20 @@ use crate::stock::StockMessage; /// Some chat IDs are reserved to identify special chat types. This /// type can represent both the special as well as normal chats. #[derive( - Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize, Hash, PartialOrd, Ord, + Debug, + Copy, + Clone, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + Hash, + PartialOrd, + Ord, + FromPrimitive, + ToPrimitive, + deltachat_derive::Sqlx, )] pub struct ChatId(u32); @@ -107,10 +122,14 @@ impl ChatId { context .sql .execute( - "UPDATE contacts - SET selfavatar_sent=? - WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=?);", - paramsv![timestamp, self], + r#" +UPDATE contacts + SET selfavatar_sent=? + WHERE id IN( + SELECT contact_id FROM chats_contacts WHERE chat_id=? + ); +"#, + paramsx![timestamp, self], ) .await?; Ok(()) @@ -125,7 +144,7 @@ impl ChatId { .sql .execute( "UPDATE chats SET blocked=? WHERE id=?;", - paramsv![new_blocked, self], + paramsx![new_blocked, self], ) .await .is_ok() @@ -152,7 +171,7 @@ impl ChatId { .sql .execute( "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", - paramsv![MessageState::InNoticed, self, MessageState::InFresh], + paramsx![MessageState::InNoticed, self, MessageState::InFresh], ) .await?; } @@ -161,7 +180,7 @@ impl ChatId { .sql .execute( "UPDATE chats SET archived=? WHERE id=?;", - paramsv![visibility, self], + paramsx![visibility, self], ) .await?; @@ -180,7 +199,7 @@ impl ChatId { .sql .execute( "UPDATE chats SET archived=0 WHERE id=? and archived=1", - paramsv![self], + paramsx![self], ) .await?; Ok(()) @@ -200,26 +219,26 @@ impl ChatId { .sql .execute( "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", - paramsv![self], + paramsx![self], ) .await?; context .sql - .execute("DELETE FROM msgs WHERE chat_id=?;", paramsv![self]) + .execute("DELETE FROM msgs WHERE chat_id=?;", paramsx![self]) .await?; context .sql .execute( "DELETE FROM chats_contacts WHERE chat_id=?;", - paramsv![self], + paramsx![self], ) .await?; context .sql - .execute("DELETE FROM chats WHERE id=?;", paramsv![self]) + .execute("DELETE FROM chats WHERE id=?;", paramsx![self]) .await?; context.emit_event(Event::MsgsChanged { @@ -267,12 +286,12 @@ impl ChatId { async fn get_draft_msg_id(self, context: &Context) -> Option { context .sql - .query_get_value::( - context, + .query_value( "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - paramsv![self, MessageState::OutDraft], + paramsx![self, MessageState::OutDraft], ) .await + .ok() } pub async fn get_draft(self, context: &Context) -> Result, Error> { @@ -324,17 +343,19 @@ impl ChatId { context .sql .execute( - "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) - VALUES (?,?,?, ?,?,?,?,?);", - paramsv![ + r#" +INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) + VALUES (?,?,?,?,?,?,?,?); +"#, + paramsx![ self, - DC_CONTACT_ID_SELF, + DC_CONTACT_ID_SELF as i32, time(), msg.viewtype, MessageState::OutDraft, - msg.text.as_deref().unwrap_or(""), + msg.text.as_deref().unwrap_or("").to_owned(), msg.param.to_string(), - 1, + 1i32 ], ) .await?; @@ -343,37 +364,34 @@ impl ChatId { /// Returns number of messages in a chat. pub async fn get_msg_cnt(self, context: &Context) -> usize { - context + let v: i32 = context .sql - .query_get_value::( - context, - "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", - paramsv![self], - ) + .query_value("SELECT COUNT(*) FROM msgs WHERE chat_id=?;", paramsx![self]) .await - .unwrap_or_default() as usize + .unwrap_or_default(); + v as usize } pub async fn get_fresh_msg_cnt(self, context: &Context) -> usize { - context + let v: i32 = context .sql - .query_get_value::( - context, + .query_value( "SELECT COUNT(*) FROM msgs WHERE state=10 AND hidden=0 AND chat_id=?;", - paramsv![self], + paramsx![self], ) .await - .unwrap_or_default() as usize + .unwrap_or_default(); + v as usize } pub(crate) async fn get_param(self, context: &Context) -> Result { let res: Option = context .sql - .query_get_value_result("SELECT param FROM chats WHERE id=?", paramsv![self]) + .query_value_optional("SELECT param FROM chats WHERE id=?", paramsx![self]) .await?; Ok(res .map(|s| s.parse().unwrap_or_default()) @@ -390,14 +408,9 @@ impl ChatId { Ok(self.get_param(context).await?.exists(Param::Devicetalk)) } - async fn parent_query( - self, - context: &Context, - fields: &str, - f: F, - ) -> sql::Result> + async fn parent_query(self, context: &Context, fields: &str) -> sql::Result> where - F: FnOnce(&rusqlite::Row) -> rusqlite::Result, + T: for<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> + Unpin + Send, { let sql = &context.sql; let query = format!( @@ -408,22 +421,19 @@ impl ChatId { fields ); sql.query_row_optional( - query, - paramsv![ + &query, + paramsx![ self, MessageState::OutPreparing, MessageState::OutDraft, MessageState::OutPending, MessageState::OutFailed ], - f, ) .await } async fn get_parent_mime_headers(self, context: &Context) -> Option<(String, String, String)> { - let collect = - |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)); let (rfc724_mid, mime_in_reply_to, mime_references, error): ( String, String, @@ -433,12 +443,10 @@ impl ChatId { .parent_query( context, "rfc724_mid, mime_in_reply_to, mime_references, error", - collect, ) .await .ok() .flatten()?; - if !error.is_empty() { // Do not reply to error messages. // @@ -453,11 +461,9 @@ impl ChatId { } async fn parent_is_encrypted(self, context: &Context) -> Result { - let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?)); - let res: Option<(String, String)> = - self.parent_query(context, "param, error", collect).await?; + let packed: Option<(String, String)> = self.parent_query(context, "param, error").await?; - if let Some((ref packed, ref error)) = res { + if let Some((ref packed, ref error)) = packed { let param = packed.parse::()?; Ok(error.is_empty() && param.exists(Param::GuaranteeE2ee)) } else { @@ -495,31 +501,6 @@ impl std::fmt::Display for ChatId { } } -/// Allow converting [ChatId] to an SQLite type. -/// -/// This allows you to directly store [ChatId] into the database as -/// well as query for a [ChatId]. -impl rusqlite::types::ToSql for ChatId { - fn to_sql(&self) -> rusqlite::Result { - let val = rusqlite::types::Value::Integer(self.0 as i64); - let out = rusqlite::types::ToSqlOutput::Owned(val); - Ok(out) - } -} - -/// Allow converting an SQLite integer directly into [ChatId]. -impl rusqlite::types::FromSql for ChatId { - fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { - i64::column_result(value).and_then(|val| { - if 0 <= val && val <= std::u32::MAX as i64 { - Ok(ChatId::new(val as u32)) - } else { - Err(rusqlite::types::FromSqlError::OutOfRange(val)) - } - }) - } -} - /// An object representing a single chat in memory. /// Chat objects are created using eg. `Chat::load_from_db` /// and are not updated on database changes; @@ -537,38 +518,45 @@ pub struct Chat { pub mute_duration: MuteDuration, } +impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Chat { + fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result { + let c = Chat { + id: row.try_get("id").unwrap_or_default(), + typ: row.try_get("type")?, + name: row.try_get::("name")?, + grpid: row.try_get::("grpid")?, + param: row + .try_get::("param")? + .parse() + .unwrap_or_default(), + visibility: row.try_get("archived")?, + blocked: row.try_get::, _>("blocked")?.unwrap_or_default(), + is_sending_locations: row.try_get("locations_send_until")?, + mute_duration: row.try_get("muted_until")?, + }; + + Ok(c) + } +} + impl Chat { /// Loads chat from the database by its ID. pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result { - let res = context + let res: Result = context .sql .query_row( - "SELECT c.type, c.name, c.grpid, c.param, c.archived, - c.blocked, c.locations_send_until, c.muted_until - FROM chats c - WHERE c.id=?;", - paramsv![chat_id], - |row| { - let c = Chat { - id: chat_id, - typ: row.get(0)?, - name: row.get::<_, String>(1)?, - grpid: row.get::<_, String>(2)?, - param: row.get::<_, String>(3)?.parse().unwrap_or_default(), - visibility: row.get(4)?, - blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), - is_sending_locations: row.get(6)?, - mute_duration: row.get(7)?, - }; - Ok(c) - }, + r#" +SELECT c.id, c.type, c.name, c.grpid, c.param, c.archived, + c.blocked, c.locations_send_until, c.muted_until + FROM chats c + WHERE c.id=?; +"#, + paramsx![chat_id], ) .await; match res { - Err(err @ crate::sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { - Err(err.into()) - } + Err(err @ crate::sql::Error::Sqlx(sqlx::Error::RowNotFound)) => Err(err.into()), Err(err) => { error!( context, @@ -587,7 +575,7 @@ impl Chat { chat.name = context.stock_str(StockMessage::StarredMsgs).await.into(); } else { if chat.typ == Chattype::Single { - let contacts = get_chat_contacts(context, chat.id).await; + let contacts = get_chat_contacts(context, chat.id).await?; let mut chat_name = "Err [Name not found]".to_owned(); if let Some(contact_id) = contacts.first() { if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { @@ -626,7 +614,7 @@ impl Chat { .sql .execute( "UPDATE chats SET param=? WHERE id=?", - paramsv![self.param.to_string(), self.id], + paramsx![self.param.to_string(), self.id], ) .await?; Ok(()) @@ -647,32 +635,32 @@ impl Chat { &self.name } - pub async fn get_profile_image(&self, context: &Context) -> Option { + pub async fn get_profile_image(&self, context: &Context) -> Result, Error> { if let Some(image_rel) = self.param.get(Param::ProfileImage) { if !image_rel.is_empty() { - return Some(dc_get_abs_path(context, image_rel)); + return Ok(Some(dc_get_abs_path(context, image_rel))); } } else if self.typ == Chattype::Single { - let contacts = get_chat_contacts(context, self.id).await; + let contacts = get_chat_contacts(context, self.id).await?; if let Some(contact_id) = contacts.first() { if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { - return contact.get_profile_image(context).await; + return Ok(contact.get_profile_image(context).await); } } } - None + Ok(None) } pub async fn get_gossiped_timestamp(&self, context: &Context) -> i64 { get_gossiped_timestamp(context, self.id).await } - pub async fn get_color(&self, context: &Context) -> u32 { + pub async fn get_color(&self, context: &Context) -> Result { let mut color = 0; if self.typ == Chattype::Single { - let contacts = get_chat_contacts(context, self.id).await; + let contacts = get_chat_contacts(context, self.id).await?; if let Some(contact_id) = contacts.first() { if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { color = contact.get_color(); @@ -682,7 +670,7 @@ impl Chat { color = dc_str_to_color(&self.name); } - color + Ok(color) } /// Returns a struct describing the current state of the chat. @@ -702,12 +690,12 @@ impl Chat { param: self.param.to_string(), gossiped_timestamp: self.get_gossiped_timestamp(context).await, is_sending_locations: self.is_sending_locations, - color: self.get_color(context).await, + color: self.get_color(context).await?, profile_image: self .get_profile_image(context) - .await + .await? .map(Into::into) - .unwrap_or_else(std::path::PathBuf::new), + .unwrap_or_default(), draft, is_muted: self.is_muted(), }) @@ -783,12 +771,11 @@ impl Chat { }; if self.typ == Chattype::Single { - if let Some(id) = context + if let Ok(id) = context .sql - .query_get_value( - context, + .query_value( "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", - paramsv![self.id], + paramsx![self.id], ) .await { @@ -818,48 +805,41 @@ impl Chat { // take care that this statement returns NULL rows // if there is no peerstates for a chat member! // for DC_PARAM_SELFTALK this statement does not return any row - let res = context - .sql - .query_map( - "SELECT ps.prefer_encrypted, c.addr \ - FROM chats_contacts cc \ - LEFT JOIN contacts c ON cc.contact_id=c.id \ - LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ - WHERE cc.chat_id=? AND cc.contact_id>9;", - paramsv![self.id], - |row| { - let addr: String = row.get(1)?; + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as( + r#" +SELECT ps.prefer_encrypted, c.addr + FROM chats_contacts cc + LEFT JOIN contacts c ON cc.contact_id=c.id + LEFT JOIN acpeerstates ps ON c.addr=ps.addr + WHERE cc.chat_id=? AND cc.contact_id>9; +"#, + ) + .bind(self.id) + .fetch(&pool); - if let Some(prefer_encrypted) = row.get::<_, Option>(0)? { - // the peerstate exist, so we have either public_key or gossip_key - // and can encrypt potentially - if prefer_encrypted != 1 { - info!( - context, - "[autocrypt] peerstate for {} is {}", - addr, - if prefer_encrypted == 0 { - "NOPREFERENCE" - } else { - "RESET" - }, - ); - all_mutual = false; - } - } else { - info!(context, "[autocrypt] no peerstate for {}", addr,); - can_encrypt = false; - all_mutual = false; - } - Ok(()) - }, - |rows| rows.collect::, _>>().map_err(Into::into), - ) - .await; - match res { - Ok(_) => {} - Err(err) => { - warn!(context, "chat: failed to load peerstates: {:?}", err); + while let Some(row) = rows.next().await { + let (prefer_encrypted, addr): (Option, String) = row?; + if let Some(prefer_encrypted) = prefer_encrypted { + // the peerstate exist, so we have either public_key or gossip_key + // and can encrypt potentially + if prefer_encrypted != 1 { + info!( + context, + "[autocrypt] peerstate for {} is {}", + addr, + if prefer_encrypted == 0 { + "NOPREFERENCE" + } else { + "RESET" + }, + ); + all_mutual = false; + } + } else { + info!(context, "[autocrypt] no peerstate for {}", addr,); + can_encrypt = false; + all_mutual = false; } } @@ -914,12 +894,12 @@ impl Chat { "INSERT INTO locations \ (timestamp,from_id,chat_id, latitude,longitude,independent)\ VALUES (?,?,?, ?,?,1);", // 1=DC_CONTACT_ID_SELF - paramsv![ + paramsx![ timestamp, - DC_CONTACT_ID_SELF, + DC_CONTACT_ID_SELF as i32, self.id, msg.param.get_float(Param::SetLatitude).unwrap_or_default(), - msg.param.get_float(Param::SetLongitude).unwrap_or_default(), + msg.param.get_float(Param::SetLongitude).unwrap_or_default() ], ) .await @@ -928,7 +908,6 @@ impl Chat { location_id = context .sql .get_rowid2( - context, "locations", "timestamp", timestamp, @@ -940,37 +919,48 @@ impl Chat { // add message to the database - if context.sql.execute( - "INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);", - paramsv![ - new_rfc724_mid, - self.id, - DC_CONTACT_ID_SELF, - to_id as i32, - timestamp, - msg.viewtype, - msg.state, - msg.text.as_ref().cloned().unwrap_or_default(), - msg.param.to_string(), - msg.hidden, - new_in_reply_to, - new_references, - location_id as i32, - ] - ).await.is_ok() { - msg_id = context.sql.get_rowid( - context, - "msgs", - "rfc724_mid", - new_rfc724_mid, - ).await?; - } else { - error!( - context, - "Cannot send message, cannot insert to database ({}).", - self.id, - ); - } + if context + .sql + .execute( + r#" +INSERT INTO msgs ( + rfc724_mid, chat_id, from_id, to_id, + timestamp, type, state, txt, + param, hidden, mime_in_reply_to, mime_references, location_id) + VALUES ( + ?,?,?,?, + ?,?,?,?, + ?,?,?,?,?); +"#, + paramsx![ + new_rfc724_mid.clone(), + self.id, + DC_CONTACT_ID_SELF as i32, + to_id as i32, + timestamp, + msg.viewtype, + msg.state, + msg.text.as_ref().cloned(), + msg.param.to_string(), + msg.hidden, + new_in_reply_to, + new_references, + location_id as i32 + ], + ) + .await + .is_ok() + { + msg_id = context + .sql + .get_rowid("msgs", "rfc724_mid", new_rfc724_mid) + .await?; + } else { + error!( + context, + "Cannot send message, cannot insert to database ({}).", self.id, + ); + } } else { error!(context, "Cannot send message, not configured.",); } @@ -979,37 +969,28 @@ impl Chat { } } -#[derive(Debug, Copy, Eq, PartialEq, Clone, Serialize, Deserialize)] +#[derive( + Debug, + Copy, + Eq, + PartialEq, + Clone, + Serialize, + Deserialize, + FromPrimitive, + ToPrimitive, + sqlx::Type, +)] +#[repr(i32)] pub enum ChatVisibility { - Normal, - Archived, - Pinned, + Normal = 0, + Archived = 1, + Pinned = 2, } -impl rusqlite::types::ToSql for ChatVisibility { - fn to_sql(&self) -> rusqlite::Result { - let visibility = match &self { - ChatVisibility::Normal => 0, - ChatVisibility::Archived => 1, - ChatVisibility::Pinned => 2, - }; - let val = rusqlite::types::Value::Integer(visibility); - let out = rusqlite::types::ToSqlOutput::Owned(val); - Ok(out) - } -} - -impl rusqlite::types::FromSql for ChatVisibility { - fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { - i64::column_result(value).map(|val| { - match val { - 2 => ChatVisibility::Pinned, - 1 => ChatVisibility::Archived, - 0 => ChatVisibility::Normal, - // fallback to to Normal for unknown values, may happen eg. on imports created by a newer version. - _ => ChatVisibility::Normal, - } - }) +impl Default for ChatVisibility { + fn default() -> Self { + ChatVisibility::Normal } } @@ -1204,7 +1185,7 @@ async fn update_special_chat_name( .sql .execute( "UPDATE chats SET name=? WHERE id=? AND name!=?;", - paramsv![name, chat_id, name], + paramsx![&name, chat_id, &name], ) .await?; } @@ -1233,35 +1214,32 @@ pub(crate) async fn create_or_lookup_by_contact_id( let contact = Contact::load_from_db(context, contact_id).await?; let chat_name = contact.get_display_name().to_string(); - context - .sql - .with_conn(move |mut conn| { - let conn2 = &mut conn; - let tx = conn2.transaction()?; - tx.execute( - "INSERT INTO chats (type, name, param, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?)", - params![ - Chattype::Single, - chat_name, - match contact_id { - DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk - DC_CONTACT_ID_DEVICE => "D=1".to_string(), // D = Param::Devicetalk - _ => "".to_string(), - }, - create_blocked as u8, - time(), - ], - )?; + let mut tx = context.sql.begin().await?; + sqlx::query_with( + "INSERT INTO chats (type, name, param, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?)", + paramsx![ + Chattype::Single, + chat_name, + match contact_id { + DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk + DC_CONTACT_ID_DEVICE => "D=1".to_string(), // D = Param::Devicetalk + _ => "".to_string(), + }, + create_blocked as i32, + time(), + ], + ) + .execute(&mut tx) + .await?; - tx.execute( - "INSERT INTO chats_contacts (chat_id, contact_id) VALUES((SELECT last_insert_rowid()), ?)", - params![contact_id], - )?; + sqlx::query( + "INSERT INTO chats_contacts (chat_id, contact_id) VALUES ( ( SELECT last_insert_rowid() ), ?)", + ) + .bind(contact_id as i64) + .execute(&mut tx) + .await?; - tx.commit()?; - Ok(()) - }) - .await?; + tx.commit().await?; if contact_id == DC_CONTACT_ID_SELF { update_saved_messages_icon(context).await?; @@ -1288,15 +1266,10 @@ pub(crate) async fn lookup_by_contact_id( WHERE c.type=100 AND c.id>9 AND j.contact_id=?;", - paramsv![contact_id as i32], - |row| { - Ok(( - row.get::<_, ChatId>(0)?, - row.get::<_, Option<_>>(1)?.unwrap_or_default(), - )) - }, + paramsx![contact_id as i32], ) .await + .map(|(id, blocked): (ChatId, Option)| (id, blocked.unwrap_or_default())) .map_err(Into::into) } @@ -1429,7 +1402,7 @@ pub async fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: .sql .exists( "SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id=?;", - paramsv![chat_id, contact_id as i32], + paramsx![chat_id, contact_id as i32], ) .await .unwrap_or_default() @@ -1580,51 +1553,22 @@ pub async fn get_chat_msgs( chat_id: ChatId, flags: u32, marker1before: Option, -) -> Vec { - match delete_device_expired_messages(context).await { - Err(err) => warn!(context, "Failed to delete expired messages: {}", err), - Ok(messages_deleted) => { - if messages_deleted { - context.emit_event(Event::MsgsChanged { - msg_id: MsgId::new(0), - chat_id: ChatId::new(0), - }) - } - } +) -> Result, Error> { + let messages_deleted = delete_device_expired_messages(context).await?; + if messages_deleted { + context.emit_event(Event::MsgsChanged { + msg_id: MsgId::new(0), + chat_id: ChatId::new(0), + }) } - let process_row = - |row: &rusqlite::Row| Ok((row.get::<_, MsgId>("id")?, row.get::<_, i64>("timestamp")?)); - let process_rows = |rows: rusqlite::MappedRows<_>| { - let mut ret = Vec::new(); - let mut last_day = 0; - let cnv_to_local = dc_gm2local_offset(); - for row in rows { - let (curr_id, ts) = row?; - if let Some(marker_id) = marker1before { - if curr_id == marker_id { - ret.push(MsgId::new(DC_MSG_ID_MARKER1)); - } - } - if (flags & DC_GCM_ADDDAYMARKER) != 0 { - let curr_local_timestamp = ts + cnv_to_local; - let curr_day = curr_local_timestamp / 86400; - if curr_day != last_day { - ret.push(MsgId::new(DC_MSG_ID_DAYMARKER)); - last_day = curr_day; - } - } - ret.push(curr_id); - } - Ok(ret) - }; - let success = if chat_id.is_deaddrop() { + let pool = context.sql.get_pool().await?; + let mut rows = if chat_id.is_deaddrop() { let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await) .unwrap_or_default(); - context - .sql - .query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + + sqlx::query_as( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m LEFT JOIN chats ON m.chat_id=chats.id @@ -1637,16 +1581,12 @@ pub async fn get_chat_msgs( AND contacts.blocked=0 AND m.msgrmsg>=? ORDER BY m.timestamp,m.id;", - paramsv![if show_emails == ShowEmails::All { 0 } else { 1 }], - process_row, - process_rows, - ) - .await + ) + .bind(if show_emails == ShowEmails::All { 0 } else { 1 }) + .fetch(&pool) } else if chat_id.is_starred() { - context - .sql - .query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + sqlx::query_as( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id @@ -1654,33 +1594,42 @@ pub async fn get_chat_msgs( AND m.hidden=0 AND ct.blocked=0 ORDER BY m.timestamp,m.id;", - paramsv![], - process_row, - process_rows, - ) - .await + ) + .fetch(&pool) } else { - context - .sql - .query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + sqlx::query_as( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m WHERE m.chat_id=? AND m.hidden=0 ORDER BY m.timestamp, m.id;", - paramsv![chat_id], - process_row, - process_rows, - ) - .await + ) + .bind(chat_id) + .fetch(&pool) }; - match success { - Ok(ret) => ret, - Err(e) => { - error!(context, "Failed to get chat messages: {}", e); - Vec::new() + + let mut ret = Vec::new(); + let mut last_day = 0; + let cnv_to_local = dc_gm2local_offset(); + + while let Some(row) = rows.next().await { + let (curr_id, ts): (MsgId, i64) = row?; + if let Some(marker_id) = marker1before { + if curr_id == marker_id { + ret.push(MsgId::new(DC_MSG_ID_MARKER1)); + } } + if (flags & DC_GCM_ADDDAYMARKER) != 0 { + let curr_local_timestamp = ts + cnv_to_local; + let curr_day = curr_local_timestamp / 86400; + if curr_day != last_day { + ret.push(MsgId::new(DC_MSG_ID_DAYMARKER)); + last_day = curr_day; + } + } + ret.push(curr_id); } + Ok(ret) } pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> { @@ -1688,7 +1637,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), .sql .exists( "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - paramsv![chat_id, MessageState::InFresh], + paramsx![chat_id, MessageState::InFresh], ) .await? { @@ -1698,11 +1647,13 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), context .sql .execute( - "UPDATE msgs - SET state=13 - WHERE chat_id=? - AND state=10;", - paramsv![chat_id], + r#" +UPDATE msgs + SET state=13 + WHERE chat_id=? + AND state=10; +"#, + paramsx![chat_id], ) .await?; @@ -1717,12 +1668,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { if !context .sql - .exists( - "SELECT id - FROM msgs - WHERE state=10;", - paramsv![], - ) + .exists("SELECT id FROM msgs WHERE state=10;", paramsx![]) .await? { return Ok(()); @@ -1730,12 +1676,7 @@ pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { context .sql - .execute( - "UPDATE msgs - SET state=13 - WHERE state=10;", - paramsv![], - ) + .execute("UPDATE msgs SET state=13 WHERE state=10;", paramsx![]) .await?; context.emit_event(Event::MsgsChanged { @@ -1776,10 +1717,10 @@ pub async fn delete_device_expired_messages(context: &Context) -> Result ? \ AND chat_id != ? \ AND chat_id != ?", - paramsv![ - DC_CHAT_ID_TRASH, + paramsx![ + DC_CHAT_ID_TRASH as i32, threshold_timestamp, - DC_CHAT_ID_LAST_SPECIAL, + DC_CHAT_ID_LAST_SPECIAL as i32, self_chat_id, device_chat_id ], @@ -1798,17 +1739,17 @@ pub async fn get_chat_media( msg_type: Viewtype, msg_type2: Viewtype, msg_type3: Viewtype, -) -> Vec { - // TODO This query could/should be converted to `AND type IN (?, ?, ?)`. - context +) -> Result, Error> { + // TODO: This query could/should be converted to `AND type IN (?, ?, ?)`. + let ids = context .sql - .query_map( + .query_values( "SELECT id FROM msgs WHERE chat_id=? AND (type=? OR type=? OR type=?) ORDER BY timestamp, id;", - paramsv![ + paramsx![ chat_id, msg_type, if msg_type2 != Viewtype::Unknown { @@ -1822,19 +1763,10 @@ pub async fn get_chat_media( msg_type }, ], - |row| row.get::<_, MsgId>(0), - |ids| { - let mut ret = Vec::new(); - for id in ids { - if let Ok(msg_id) = id { - ret.push(msg_id) - } - } - Ok(ret) - }, ) - .await - .unwrap_or_default() + .await?; + + Ok(ids) } /// Indicates the direction over which to iterate. @@ -1852,69 +1784,72 @@ pub async fn get_next_media( msg_type: Viewtype, msg_type2: Viewtype, msg_type3: Viewtype, -) -> Option { +) -> Result, Error> { + let msg = Message::load_from_db(context, curr_msg_id).await?; + let list: Vec = get_chat_media( + context, + msg.chat_id, + if msg_type != Viewtype::Unknown { + msg_type + } else { + msg.viewtype + }, + msg_type2, + msg_type3, + ) + .await?; + let mut ret: Option = None; - if let Ok(msg) = Message::load_from_db(context, curr_msg_id).await { - let list: Vec = get_chat_media( - context, - msg.chat_id, - if msg_type != Viewtype::Unknown { - msg_type - } else { - msg.viewtype - }, - msg_type2, - msg_type3, - ) - .await; - for (i, msg_id) in list.iter().enumerate() { - if curr_msg_id == *msg_id { - match direction { - Direction::Forward => { - if i + 1 < list.len() { - ret = list.get(i + 1).copied(); - } - } - Direction::Backward => { - if i >= 1 { - ret = list.get(i - 1).copied(); - } + for (i, msg_id) in list.iter().enumerate() { + if curr_msg_id == *msg_id { + match direction { + Direction::Forward => { + if i + 1 < list.len() { + ret = list.get(i + 1).copied(); + } + } + Direction::Backward => { + if i >= 1 { + ret = list.get(i - 1).copied(); } } - break; } + break; } } - ret + + Ok(ret) } -pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { +pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Result, Error> { /* Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a groupchat but the chats stays visible, moreover, this makes displaying lists easier) */ if chat_id.is_deaddrop() { - return Vec::new(); + return Ok(Vec::new()); } // we could also create a list for all contacts in the deaddrop by searching contacts belonging to chats with // chats.blocked=2, however, currently this is not needed - context + let ids = context .sql - .query_map( + .query_values( "SELECT cc.contact_id FROM chats_contacts cc LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=? ORDER BY c.id=1, LOWER(c.name||c.addr), c.id;", - paramsv![chat_id], - |row| row.get::<_, u32>(0), - |ids| ids.collect::, _>>().map_err(Into::into), + paramsx![chat_id], ) - .await - .unwrap_or_default() + .await? + .into_iter() + .map(|id: i64| id as u32) + .collect(); + + Ok(ids) } pub async fn create_group_chat( @@ -1931,22 +1866,19 @@ pub async fn create_group_chat( context.sql.execute( "INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);", - paramsv![ + paramsx![ if verified != VerifiedStatus::Unverified { Chattype::VerifiedGroup } else { Chattype::Group }, - chat_name.as_ref().to_string(), - grpid, - time(), + chat_name.as_ref(), + &grpid, + time() ], ).await?; - let row_id = context - .sql - .get_rowid(context, "chats", "grpid", grpid) - .await?; + let row_id = context.sql.get_rowid("chats", "grpid", grpid).await?; let chat_id = ChatId::new(row_id); if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await { @@ -1973,7 +1905,7 @@ pub(crate) async fn add_to_chat_contacts_table( .sql .execute( "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", - paramsv![chat_id, contact_id as i32], + paramsx![chat_id, contact_id as i32], ) .await { @@ -1999,7 +1931,7 @@ pub(crate) async fn remove_from_chat_contacts_table( .sql .execute( "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?", - paramsv![chat_id, contact_id as i32], + paramsx![chat_id, contact_id as i32], ) .await { @@ -2127,7 +2059,7 @@ async fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { .sql .exists( "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", - paramsv![chat_id], + paramsx![chat_id], ) .await .unwrap_or_default() @@ -2145,10 +2077,9 @@ pub(crate) async fn reset_gossiped_timestamp( pub async fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { context .sql - .query_get_value::( - context, + .query_value( "SELECT gossiped_timestamp FROM chats WHERE id=?;", - paramsv![chat_id], + paramsx![chat_id], ) .await .unwrap_or_default() @@ -2169,7 +2100,7 @@ pub(crate) async fn set_gossiped_timestamp( .sql .execute( "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", - paramsv![timestamp, chat_id], + paramsx![timestamp, chat_id], ) .await?; @@ -2183,38 +2114,33 @@ pub(crate) async fn shall_attach_selfavatar( // versions before 12/2019 already allowed to set selfavatar, however, it was never sent to others. // to avoid sending out previously set selfavatars unexpectedly we added this additional check. // it can be removed after some time. - if !context - .sql - .get_raw_config_bool(context, "attach_selfavatar") - .await - { + if !context.sql.get_raw_config_bool("attach_selfavatar").await { return Ok(false); } let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60; - let needs_attach = context - .sql - .query_map( - "SELECT c.selfavatar_sent - FROM chats_contacts cc - LEFT JOIN contacts c ON c.id=cc.contact_id - WHERE cc.chat_id=? AND cc.contact_id!=?;", - paramsv![chat_id, DC_CONTACT_ID_SELF], - |row| Ok(row.get::<_, i64>(0)), - |rows| { - let mut needs_attach = false; - for row in rows { - if let Ok(selfavatar_sent) = row { - let selfavatar_sent = selfavatar_sent?; - if selfavatar_sent < timestamp_some_days_ago { - needs_attach = true; - } - } - } - Ok(needs_attach) - }, - ) - .await?; + let pool = context.sql.get_pool().await?; + let mut needs_attach = false; + + let mut rows = sqlx::query_as( + r#" +SELECT c.selfavatar_sent + FROM chats_contacts cc + LEFT JOIN contacts c ON c.id=cc.contact_id + WHERE cc.chat_id=? AND cc.contact_id!=?; +"#, + ) + .bind(chat_id) + .bind(DC_CONTACT_ID_SELF as i32) + .fetch(&pool); + + while let Some(row) = rows.next().await { + let (selfavatar_sent,): (i64,) = row?; + if selfavatar_sent < timestamp_some_days_ago { + needs_attach = true; + } + } + Ok(needs_attach) } @@ -2225,41 +2151,51 @@ pub enum MuteDuration { Until(SystemTime), } -impl rusqlite::types::ToSql for MuteDuration { - fn to_sql(&self) -> rusqlite::Result { - let duration: i64 = match &self { +impl sqlx::encode::Encode<'_, sqlx::sqlite::Sqlite> for MuteDuration { + fn encode_by_ref( + &self, + buf: &mut Vec>, + ) -> sqlx::encode::IsNull { + let duration: i32 = match &self { MuteDuration::NotMuted => 0, MuteDuration::Forever => -1, MuteDuration::Until(when) => { - let duration = when - .duration_since(SystemTime::UNIX_EPOCH) - .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?; - i64::try_from(duration.as_secs()) - .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))? + let duration = when.duration_since(SystemTime::UNIX_EPOCH).unwrap(); + i32::try_from(duration.as_secs()).unwrap() } }; - let val = rusqlite::types::Value::Integer(duration); - let out = rusqlite::types::ToSqlOutput::Owned(val); - Ok(out) + + duration.encode(buf) } } -impl rusqlite::types::FromSql for MuteDuration { - fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { +impl<'de> sqlx::decode::Decode<'de, sqlx::sqlite::Sqlite> for MuteDuration { + fn decode( + value: sqlx::sqlite::SqliteValueRef, + ) -> std::result::Result { // Negative values other than -1 should not be in the // database. If found they'll be NotMuted. - match i64::column_result(value)? { + let raw: i32 = sqlx::decode::Decode::decode(value)?; + match raw { 0 => Ok(MuteDuration::NotMuted), -1 => Ok(MuteDuration::Forever), n if n > 0 => match SystemTime::UNIX_EPOCH.checked_add(Duration::from_secs(n as u64)) { Some(t) => Ok(MuteDuration::Until(t)), - None => Err(rusqlite::types::FromSqlError::OutOfRange(n)), + None => Err(Box::new(sqlx::Error::Decode( + anyhow::anyhow!("mute duration out of range: {}", raw).into(), + ))), }, _ => Ok(MuteDuration::NotMuted), } } } +impl sqlx::types::Type for MuteDuration { + fn type_info() -> sqlx::sqlite::SqliteTypeInfo { + >::type_info() + } +} + pub async fn set_muted( context: &Context, chat_id: ChatId, @@ -2270,7 +2206,7 @@ pub async fn set_muted( .sql .execute( "UPDATE chats SET muted_until=? WHERE id=?;", - paramsv![duration, chat_id], + paramsx![duration, chat_id], ) .await .is_ok() @@ -2373,7 +2309,7 @@ async fn set_group_explicitly_left(context: &Context, grpid: impl AsRef) -> .sql .execute( "INSERT INTO leftgrps (grpid) VALUES(?);", - paramsv![grpid.as_ref().to_string()], + paramsx![grpid.as_ref()], ) .await?; } @@ -2389,7 +2325,7 @@ pub(crate) async fn is_group_explicitly_left( .sql .exists( "SELECT id FROM leftgrps WHERE grpid=?;", - paramsv![grpid.as_ref()], + paramsx![grpid.as_ref()], ) .await .map_err(Into::into) @@ -2423,7 +2359,7 @@ pub async fn set_chat_name( .sql .execute( "UPDATE chats SET name=? WHERE id=?;", - paramsv![new_name.as_ref().to_string(), chat_id], + paramsx![new_name.as_ref(), chat_id], ) .await .is_ok() @@ -2548,21 +2484,21 @@ pub async fn forward_msgs( if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { ensure!(chat.can_send(), "cannot send to {}", chat_id); curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()).await; - let ids = context - .sql - .query_map( - format!( - "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", - msg_ids.iter().map(|_| "?").join(",") - ), - msg_ids.iter().map(|v| v as &dyn crate::ToSql).collect(), - |row| row.get::<_, MsgId>(0), - |ids| ids.collect::, _>>().map_err(Into::into), - ) - .await?; - for id in ids { - let src_msg_id: MsgId = id; + let pool = context.sql.get_pool().await?; + let mut args = sqlx::sqlite::SqliteArguments::default(); + for msg_id in msg_ids { + args.add(msg_id); + } + let query = format!( + "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", + msg_ids.iter().map(|_| "?").join(",") + ); + let mut rows = sqlx::query_with(&query, args).fetch(&pool); + + while let Some(row) = rows.next().await { + let row = row?; + let src_msg_id: MsgId = row.try_get(0)?; let msg = Message::load_from_db(context, src_msg_id).await; if msg.is_err() { break; @@ -2621,29 +2557,29 @@ pub async fn forward_msgs( } pub(crate) async fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize { - context + let v: i32 = context .sql - .query_get_value::( - context, + .query_value( "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;", - paramsv![chat_id], + paramsx![chat_id], ) .await - .unwrap_or_default() as usize + .unwrap_or_default(); + v as usize } pub(crate) async fn get_chat_cnt(context: &Context) -> usize { if context.sql.is_open().await { /* no database, no chats - this is no error (needed eg. for information) */ - context + let v: i32 = context .sql - .query_get_value::( - context, + .query_value( "SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", - paramsv![], + paramsx![], ) .await - .unwrap_or_default() as usize + .unwrap_or_default(); + v as usize } else { 0 } @@ -2653,20 +2589,15 @@ pub(crate) async fn get_chat_id_by_grpid( context: &Context, grpid: impl AsRef, ) -> Result<(ChatId, bool, Blocked), sql::Error> { - context + let (chat_id, blocked, typ): (ChatId, Blocked, Chattype) = context .sql .query_row( "SELECT id, blocked, type FROM chats WHERE grpid=?;", - paramsv![grpid.as_ref()], - |row| { - let chat_id = row.get::<_, ChatId>(0)?; - - let b = row.get::<_, Option>(1)?.unwrap_or_default(); - let v = row.get::<_, Option>(2)?.unwrap_or_default(); - Ok((chat_id, v == Chattype::VerifiedGroup, b)) - }, + paramsx![grpid.as_ref()], ) - .await + .await?; + + Ok((chat_id, typ == Chattype::VerifiedGroup, blocked)) } /// Adds a message to device chat. @@ -2701,25 +2632,33 @@ pub async fn add_device_msg( prepare_msg_blob(context, msg).await?; chat_id.unarchive(context).await?; - context.sql.execute( - "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ - VALUES (?,?,?, ?,?,?, ?,?,?);", - paramsv![ - chat_id, - DC_CONTACT_ID_DEVICE, - DC_CONTACT_ID_SELF, - dc_create_smeared_timestamp(context).await, - msg.viewtype, - MessageState::InFresh, - msg.text.as_ref().cloned().unwrap_or_default(), - msg.param.to_string(), - rfc724_mid, - ], - ).await?; + context + .sql + .execute( + r#" +INSERT INTO msgs ( + chat_id, from_id, to_id, + timestamp, type, state, + txt, param, rfc724_mid) + VALUES (?,?,?,?,?,?,?,?,?); +"#, + paramsx![ + chat_id, + DC_CONTACT_ID_DEVICE as i32, + DC_CONTACT_ID_SELF as i32, + dc_create_smeared_timestamp(context).await, + msg.viewtype, + MessageState::InFresh, + msg.text.as_ref().cloned().unwrap_or_default(), + msg.param.to_string(), + &rfc724_mid + ], + ) + .await?; let row_id = context .sql - .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) + .get_rowid("msgs", "rfc724_mid", &rfc724_mid) .await?; msg_id = MsgId::new(row_id); } @@ -2729,7 +2668,7 @@ pub async fn add_device_msg( .sql .execute( "INSERT INTO devmsglabels (label) VALUES (?);", - paramsv![label.to_string()], + paramsx![label], ) .await?; } @@ -2743,19 +2682,15 @@ pub async fn add_device_msg( pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result { ensure!(!label.is_empty(), "empty label"); - if let Ok(()) = context - .sql - .query_row( - "SELECT label FROM devmsglabels WHERE label=?", - paramsv![label], - |_| Ok(()), - ) - .await - { - return Ok(true); - } - Ok(false) + let exists = context + .sql + .exists( + "SELECT label FROM devmsglabels WHERE label=?", + paramsx![label], + ) + .await?; + Ok(exists) } // needed on device-switches during export/import; @@ -2768,12 +2703,12 @@ pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Resul .sql .execute( "DELETE FROM msgs WHERE from_id=?;", - paramsv![DC_CONTACT_ID_DEVICE], + paramsx![DC_CONTACT_ID_DEVICE as i32], ) .await?; context .sql - .execute("DELETE FROM devmsglabels;", paramsv![]) + .execute("DELETE FROM devmsglabels;", paramsx![]) .await?; Ok(()) } @@ -2786,15 +2721,15 @@ pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl if let Err(e) = context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);", - paramsv![ + paramsx![ chat_id, - DC_CONTACT_ID_INFO, - DC_CONTACT_ID_INFO, + DC_CONTACT_ID_INFO as i32, + DC_CONTACT_ID_INFO as i32, dc_create_smeared_timestamp(context).await, Viewtype::Text, MessageState::InNoticed, - text.as_ref().to_string(), - rfc724_mid, + text.as_ref().to_owned(), + rfc724_mid.clone(), ] ).await { warn!(context, "Could not add info msg: {}", e); @@ -2803,7 +2738,7 @@ pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl let row_id = context .sql - .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) + .get_rowid("msgs", "rfc724_mid", &rfc724_mid) .await .unwrap_or_default(); context.emit_event(Event::MsgsChanged { @@ -2928,7 +2863,7 @@ mod tests { chat.name, t.ctx.stock_str(StockMessage::SavedMessages).await ); - assert!(chat.get_profile_image(&t.ctx).await.is_some()); + assert!(chat.get_profile_image(&t.ctx).await.unwrap().is_some()); } #[async_std::test] @@ -3024,7 +2959,7 @@ mod tests { chat.name, t.ctx.stock_str(StockMessage::DeviceMessages).await ); - assert!(chat.get_profile_image(&t.ctx).await.is_some()); + assert!(chat.get_profile_image(&t.ctx).await.unwrap().is_some()); // delete device message, make sure it is not added again message::delete_msgs(&t.ctx, &[*msg1_id.as_ref().unwrap()]).await; @@ -3161,10 +3096,13 @@ mod tests { let t = TestContext::new().await; let mut msg = Message::new(Viewtype::Text); msg.text = Some("foo".to_string()); - let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); + println!("foo"); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)) + .await + .expect("failed to add device msg"); let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id) .await - .unwrap() + .expect("failed to load message") .chat_id; let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) .await diff --git a/src/chatlist.rs b/src/chatlist.rs index 7a114b2bc..a5f68cb41 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -105,17 +105,6 @@ impl Chatlist { let mut add_archived_link_item = false; - let process_row = |row: &rusqlite::Row| { - let chat_id: ChatId = row.get(0)?; - let msg_id: MsgId = row.get(1).unwrap_or_default(); - Ok((chat_id, msg_id)) - }; - - let process_rows = |rows: rusqlite::MappedRows<_>| { - rows.collect::, _>>() - .map_err(Into::into) - }; - let skip_id = if flag_for_forwarding { chat::lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE) .await @@ -142,7 +131,7 @@ impl Chatlist { // shown at all permanent in the chatlist. let mut ids = if let Some(query_contact_id) = query_contact_id { // show chats shared with a given contact - context.sql.query_map( + context.sql.query_rows( "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m @@ -151,16 +140,14 @@ impl Chatlist { SELECT id FROM msgs WHERE chat_id=c.id - AND (hidden=0 OR state=?1) + AND (hidden=0 OR state=?) ORDER BY timestamp DESC, id DESC LIMIT 1) WHERE c.id>9 AND c.blocked=0 - AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2) + AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) GROUP BY c.id - ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - paramsv![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], - process_row, - process_rows, + ORDER BY c.archived=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", + paramsx![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], ).await? } else if flag_archived_only { // show archived chats @@ -169,7 +156,7 @@ impl Chatlist { // and adapting the number requires larger refactorings and seems not to be worth the effort) context .sql - .query_map( + .query_rows( "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m @@ -185,9 +172,7 @@ impl Chatlist { AND c.archived=1 GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - paramsv![MessageState::OutDraft], - process_row, - process_rows, + paramsx![MessageState::OutDraft], ) .await? } else if let Some(query) = query { @@ -203,7 +188,7 @@ impl Chatlist { let str_like_cmd = format!("%{}%", query); context .sql - .query_map( + .query_rows( "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m @@ -219,9 +204,7 @@ impl Chatlist { AND c.name LIKE ?3 GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - paramsv![MessageState::OutDraft, skip_id, str_like_cmd], - process_row, - process_rows, + paramsx![MessageState::OutDraft, skip_id, str_like_cmd], ) .await? } else { @@ -234,7 +217,7 @@ impl Chatlist { } else { ChatId::new(0) }; - let mut ids = context.sql.query_map( + let mut ids = context.sql.query_rows( "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m @@ -250,10 +233,9 @@ impl Chatlist { AND NOT c.archived=?3 GROUP BY c.id ORDER BY c.id=?4 DESC, c.archived=?5 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - paramsv![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], - process_row, - process_rows, + paramsx![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], ).await?; + if !flag_no_specials { if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await { @@ -387,15 +369,15 @@ impl Chatlist { /// Returns the number of archived chats pub async fn dc_get_archived_cnt(context: &Context) -> u32 { - context + let v: i32 = context .sql - .query_get_value( - context, + .query_value( "SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;", - paramsv![], + paramsx![], ) .await - .unwrap_or_default() + .unwrap_or_default(); + v as u32 } async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option { @@ -403,21 +385,21 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option { // sufficient as there are typically only few fresh messages. context .sql - .query_get_value( - context, - concat!( - "SELECT m.id", - " FROM msgs m", - " LEFT JOIN chats c", - " ON c.id=m.chat_id", - " WHERE m.state=10", - " AND m.hidden=0", - " AND c.blocked=2", - " ORDER BY m.timestamp DESC, m.id DESC;" - ), - paramsv![], + .query_value( + r#" +SELECT m.id + FROM msgs m + LEFT JOIN chats c + ON c.id=m.chat_id + WHERE m.state=10 + AND m.hidden=0 + AND c.blocked=2 + ORDER BY m.timestamp DESC, m.id DESC; +"#, + paramsx![], ) .await + .ok() } #[cfg(test)] diff --git a/src/config.rs b/src/config.rs index 2c59d6435..ece1deac5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -121,20 +121,20 @@ pub enum Config { impl Context { pub async fn config_exists(&self, key: Config) -> bool { - self.sql.get_raw_config(self, key).await.is_some() + self.sql.get_raw_config(key).await.is_some() } /// Get a configuration key. Returns `None` if no value is set, and no default value found. pub async fn get_config(&self, key: Config) -> Option { let value = match key { Config::Selfavatar => { - let rel_path = self.sql.get_raw_config(self, key).await; + let rel_path = self.sql.get_raw_config(key).await; rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned()) } Config::SysVersion => Some((&*DC_VERSION_STR).clone()), Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)), Config::SysConfigKeys => Some(get_config_keys_string()), - _ => self.sql.get_raw_config(self, key).await, + _ => self.sql.get_raw_config(key).await, }; if value.is_some() { @@ -189,7 +189,7 @@ impl Context { match key { Config::Selfavatar => { self.sql - .execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![]) + .execute("UPDATE contacts SET selfavatar_sent=0;", paramsx![]) .await?; self.sql .set_raw_config_bool(self, "attach_selfavatar", true) diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 7b45e5386..33fd3e6c4 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -37,7 +37,7 @@ macro_rules! progress { impl Context { /// Checks if the context is already configured. pub async fn is_configured(&self) -> bool { - self.sql.get_raw_config_bool(self, "configured").await + self.sql.get_raw_config_bool("configured").await } /// Configures this account with the currently set parameters. diff --git a/src/constants.rs b/src/constants.rs index 0e23e0ceb..b688b86fb 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,7 +1,6 @@ //! # Constants #![allow(dead_code)] -use deltachat_derive::*; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -25,12 +24,11 @@ const DC_MVBOX_MOVE_DEFAULT: i32 = 1; Eq, FromPrimitive, ToPrimitive, - FromSql, - ToSql, Serialize, Deserialize, + sqlx::Type, )] -#[repr(u8)] +#[repr(i32)] pub enum Blocked { Not = 0, Manually = 1, @@ -43,8 +41,8 @@ impl Default for Blocked { } } -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] -#[repr(u8)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)] +#[repr(i32)] pub enum ShowEmails { Off = 0, AcceptedContacts = 1, @@ -57,8 +55,8 @@ impl Default for ShowEmails { } } -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] -#[repr(u8)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)] +#[repr(i32)] pub enum MediaQuality { Balanced = 0, Worse = 1, @@ -70,7 +68,7 @@ impl Default for MediaQuality { } } -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)] #[repr(u8)] pub enum KeyGenType { Default = 0, @@ -127,13 +125,12 @@ pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9; Eq, FromPrimitive, ToPrimitive, - FromSql, - ToSql, IntoStaticStr, Serialize, Deserialize, + sqlx::Type, )] -#[repr(u32)] +#[repr(i32)] pub enum Chattype { Undefined = 0, Single = 100, @@ -243,10 +240,9 @@ pub const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3; Eq, FromPrimitive, ToPrimitive, - FromSql, - ToSql, Serialize, Deserialize, + sqlx::Type, )] #[repr(i32)] pub enum Viewtype { diff --git a/src/contact.rs b/src/contact.rs index 48058b9e6..794ac6fd0 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -3,7 +3,7 @@ #![forbid(clippy::indexing_slicing)] use async_std::path::PathBuf; -use deltachat_derive::*; +use async_std::prelude::*; use itertools::Itertools; use lazy_static::lazy_static; use regex::Regex; @@ -72,7 +72,7 @@ pub struct Contact { /// Possible origins of a contact. #[derive( - Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql, + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, sqlx::Type, )] #[repr(i32)] pub enum Origin { @@ -133,6 +133,32 @@ impl Default for Origin { Origin::Unknown } } +impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Contact { + fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result { + use sqlx::Row; + + let contact = Self { + id: row.try_get::("id")? as u32, + name: row + .try_get::, _>("name")? + .unwrap_or_default(), + authname: row + .try_get::, _>("authname")? + .unwrap_or_default(), + addr: row.try_get::("addr")?, + blocked: row + .try_get::, _>("blocked")? + .unwrap_or_default() + != 0, + origin: row.try_get("origin")?, + param: row + .try_get::("param")? + .parse() + .unwrap_or_default(), + }; + Ok(contact) + } +} impl Origin { /// Contacts that are known, i. e. they came in via accepted contacts or @@ -163,27 +189,18 @@ pub enum VerifiedStatus { impl Contact { pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result { - let mut res = context + let mut res: Contact = context .sql .query_row( - "SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param - FROM contacts c - WHERE c.id=?;", - paramsv![contact_id as i32], - |row| { - let contact = Self { - id: contact_id, - name: row.get::<_, String>(0)?, - authname: row.get::<_, String>(4)?, - addr: row.get::<_, String>(1)?, - blocked: row.get::<_, Option>(3)?.unwrap_or_default() != 0, - origin: row.get(2)?, - param: row.get::<_, String>(5)?.parse().unwrap_or_default(), - }; - Ok(contact) - }, + r#" +SELECT id, name, addr, origin, blocked, authname, param + FROM contacts + WHERE id=?; +"#, + paramsx![contact_id as i32], ) .await?; + if contact_id == DC_CONTACT_ID_SELF { res.name = context.stock_str(StockMessage::SelfMsg).await.to_string(); res.addr = context @@ -269,8 +286,11 @@ impl Contact { if context .sql .execute( - "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", - paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh], + r#" +UPDATE msgs SET state=? +WHERE from_id=? AND state=?; +"#, + paramsx![MessageState::InNoticed, id as i32, MessageState::InFresh], ) .await .is_ok() @@ -305,15 +325,15 @@ impl Contact { if addr_cmp(addr_normalized, addr_self) { return DC_CONTACT_ID_SELF; } - context.sql.query_get_value( - context, - "SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;", - paramsv![ + let v: i32 = context.sql.query_value( + "SELECT id FROM contacts WHERE addr=? COLLATE NOCASE AND id>? AND origin>=? AND blocked=0;", + paramsx![ addr_normalized, DC_CONTACT_ID_LAST_SPECIAL as i32, - min_origin as u32, + min_origin as i32 ], - ).await.unwrap_or_default() + ).await.unwrap_or_default(); + v as u32 } /// Lookup a contact and create it if it does not exist yet. @@ -384,39 +404,32 @@ impl Contact { let mut update_authname = false; let mut row_id = 0; - if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row( + let res: Result<(i32, String, String, Origin, String), _> = context.sql.query_row( "SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;", - paramsv![addr.to_string()], - |row| { - let row_id = row.get(0)?; - let row_name: String = row.get(1)?; - let row_addr: String = row.get(2)?; - let row_origin: Origin = row.get(3)?; - let row_authname: String = row.get(4)?; + paramsx![addr.to_string()], + ) + .await; - if !name.as_ref().is_empty() { - if !row_name.is_empty() { - if (origin >= row_origin || row_name == row_authname) - && name.as_ref() != row_name - { - update_name = true; - } - } else { + if let Ok((id, row_name, row_addr, row_origin, row_authname)) = res { + if !name.as_ref().is_empty() { + if !row_name.is_empty() { + if (origin >= row_origin || row_name == row_authname) + && name.as_ref() != row_name + { update_name = true; } - if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname { - update_authname = true; - } - } else if origin == Origin::ManuallyCreated && !row_authname.is_empty() { - // no name given on manual edit, this will update the name to the authname + } else { update_name = true; } + if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname { + update_authname = true; + } + } else if origin == Origin::ManuallyCreated && !row_authname.is_empty() { + // no name given on manual edit, this will update the name to the authname + update_name = true; + } - Ok((row_id, row_name, row_addr, row_origin, row_authname)) - }, - ) - .await { - row_id = id; + row_id = id as u32; if origin as i32 >= row_origin as i32 && addr != row_addr { update_addr = true; } @@ -435,9 +448,13 @@ impl Contact { .sql .execute( "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", - paramsv![ - new_name, - if update_addr { addr.to_string() } else { row_addr }, + paramsx![ + &new_name, + if update_addr { + addr.to_string() + } else { + row_addr + }, if origin > row_origin { origin } else { @@ -448,7 +465,7 @@ impl Contact { } else { row_authname }, - row_id + row_id as i32 ], ) .await @@ -459,7 +476,7 @@ impl Contact { // This is one of the few duplicated data, however, getting the chat list is easier this way. context.sql.execute( "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", - paramsv![new_name, Chattype::Single, row_id] + paramsx![&new_name, Chattype::Single, row_id as i32] ).await.ok(); } sth_modified = Modifier::Modified; @@ -473,20 +490,17 @@ impl Contact { .sql .execute( "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", - paramsv![ - name.as_ref().to_string(), - addr, + paramsx![ + name.as_ref(), + &addr, origin, - if update_authname { name.as_ref().to_string() } else { "".to_string() } + if update_authname { name.as_ref() } else { "" } ], ) .await .is_ok() { - row_id = context - .sql - .get_rowid(context, "contacts", "addr", &addr) - .await?; + row_id = context.sql.get_rowid("contacts", "addr", &addr).await?; sth_modified = Modifier::Created; info!(context, "added contact id={} addr={}", row_id, &addr); } else { @@ -573,35 +587,32 @@ impl Contact { .map(|s| s.as_ref().to_string()) .unwrap_or_default() ); - context - .sql - .query_map( - "SELECT c.id FROM contacts c \ - LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ - WHERE c.addr!=?1 \ - AND c.id>?2 \ - AND c.origin>=?3 \ - AND c.blocked=0 \ - AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ - AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ - ORDER BY LOWER(c.name||c.addr),c.id;", - paramsv![ - self_addr, - DC_CONTACT_ID_LAST_SPECIAL as i32, - Origin::IncomingReplyTo, - s3str_like_cmd, - s3str_like_cmd, - if flag_verified_only { 0i32 } else { 1i32 }, - ], - |row| row.get::<_, i32>(0), - |ids| { - for id in ids { - ret.push(id? as u32); - } - Ok(()) - }, - ) - .await?; + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as( + r#" +SELECT c.id FROM contacts c + LEFT JOIN acpeerstates ps ON c.addr=ps.addr + WHERE c.addr!=? + AND c.id>? + AND c.origin>=? + AND c.blocked=0 + AND (c.name LIKE ? OR c.addr LIKE ?) + AND (1=? OR LENGTH(ps.verified_key_fingerprint)!=0) + ORDER BY LOWER(c.name||c.addr),c.id +"#, + ) + .bind(&self_addr) + .bind(DC_CONTACT_ID_LAST_SPECIAL as i32) + .bind(Origin::IncomingReplyTo) + .bind(&s3str_like_cmd) + .bind(&s3str_like_cmd) + .bind(if flag_verified_only { 0i32 } else { 1i32 }) + .fetch(&pool); + + while let Some(id) = rows.next().await { + let (id,): (i32,) = id?; + ret.push(id as u32); + } let self_name = context .get_config(Config::Displayname) @@ -622,17 +633,15 @@ impl Contact { } else { add_self = true; - context.sql.query_map( - "SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;", - paramsv![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100], - |row| row.get::<_, i32>(0), - |ids| { - for id in ids { - ret.push(id? as u32); - } - Ok(()) - } - ).await?; + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as( + "SELECT id FROM contacts WHERE addr!=? AND id>? AND origin>=? AND blocked=0 ORDER BY LOWER(name || addr), id;" + ).bind(self_addr).bind(DC_CONTACT_ID_LAST_SPECIAL as i32).bind(0x100).fetch(&pool); + + while let Some(id) = rows.next().await { + let (id,): (i32,) = id?; + ret.push(id as u32); + } } if flag_add_self && add_self { @@ -643,32 +652,30 @@ impl Contact { } pub async fn get_blocked_cnt(context: &Context) -> usize { - context + let v: i32 = context .sql - .query_get_value::( - context, + .query_value( "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", - paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], + paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32], ) .await - .unwrap_or_default() as usize + .unwrap_or_default(); + v as usize } /// Get blocked contacts. pub async fn get_all_blocked(context: &Context) -> Vec { context .sql - .query_map( + .query_values( "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;", - paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], - |row| row.get::<_, u32>(0), - |ids| { - ids.collect::, _>>() - .map_err(Into::into) - }, + paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32], ) .await .unwrap_or_default() + .into_iter() + .map(|id: i64| id as u32) + .collect() } /// Returns a textual summary of the encryption state for the contact. @@ -683,9 +690,10 @@ impl Contact { let peerstate = Peerstate::from_addr(context, &contact.addr).await; let loginparam = LoginParam::from_database(context, "configured_").await; - if peerstate.is_some() + if peerstate.is_ok() && peerstate .as_ref() + .ok() .and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified)) .is_some() { @@ -754,10 +762,9 @@ impl Contact { let count_contacts: i32 = context .sql - .query_get_value( - context, + .query_value( "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", - paramsv![contact_id as i32], + paramsx![contact_id as i32], ) .await .unwrap_or_default(); @@ -765,10 +772,9 @@ impl Contact { let count_msgs: i32 = if count_contacts > 0 { context .sql - .query_get_value( - context, + .query_value( "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", - paramsv![contact_id as i32, contact_id as i32], + paramsx![contact_id as i32, contact_id as i32], ) .await .unwrap_or_default() @@ -781,7 +787,7 @@ impl Contact { .sql .execute( "DELETE FROM contacts WHERE id=?;", - paramsv![contact_id as i32], + paramsx![contact_id as i32], ) .await { @@ -819,7 +825,7 @@ impl Contact { .sql .execute( "UPDATE contacts SET param=? WHERE id=?", - paramsv![self.param.to_string(), self.id as i32], + paramsx![self.param.to_string(), self.id as i32], ) .await?; Ok(()) @@ -927,7 +933,7 @@ impl Contact { pub async fn is_verified_ex( &self, context: &Context, - peerstate: Option<&Peerstate<'_>>, + peerstate: Option<&Peerstate>, ) -> VerifiedStatus { // We're always sort of secured-verified as we could verify the key on this device any time with the key // on this device @@ -942,7 +948,7 @@ impl Contact { } let peerstate = Peerstate::from_addr(context, &self.addr).await; - if let Some(ps) = peerstate { + if let Ok(ps) = peerstate { if ps.verified_key.is_some() { return VerifiedStatus::BidirectVerified; } @@ -977,15 +983,15 @@ impl Contact { return 0; } - context + let v: i32 = context .sql - .query_get_value::( - context, + .query_value( "SELECT COUNT(*) FROM contacts WHERE id>?;", - paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], + paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32], ) .await - .unwrap_or_default() as usize + .unwrap_or_default(); + v as usize } pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { @@ -997,7 +1003,7 @@ impl Contact { .sql .exists( "SELECT id FROM contacts WHERE id=?;", - paramsv![contact_id as i32], + paramsx![contact_id as i32], ) .await .unwrap_or_default() @@ -1008,7 +1014,7 @@ impl Contact { .sql .execute( "UPDATE contacts SET origin=? WHERE id=? AND origin BTreeMap<&'static str, String> { let mut res = BTreeMap::new(); res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR)); - res.insert("sqlite_version", rusqlite::version().to_string()); + + let version = + String::from_utf8(libsqlite3_sys::SQLITE_VERSION.to_vec()).expect("invalid version"); + res.insert("sqlite_version", version); + res.insert("arch", (std::mem::size_of::() * 8).to_string()); res.insert("level", "awesome".into()); res @@ -85,8 +89,6 @@ pub fn get_info() -> BTreeMap<&'static str, String> { impl Context { /// Creates new context. pub async fn new(os_name: String, dbfile: PathBuf) -> Result { - // pretty_env_logger::try_init_timed().ok(); - let mut blob_fname = OsString::new(); blob_fname.push(dbfile.file_name().unwrap_or_default()); blob_fname.push("-blobs"); @@ -261,27 +263,29 @@ impl Context { let is_configured = self.get_config_int(Config::Configured).await; let dbversion = self .sql - .get_raw_config_int(self, "dbversion") + .get_raw_config_int("dbversion") .await .unwrap_or_default(); let journal_mode = self .sql - .query_get_value(self, "PRAGMA journal_mode;", paramsv![]) + .query_value("PRAGMA journal_mode;", paramsx![]) .await - .unwrap_or_else(|| "unknown".to_string()); + .unwrap_or_else(|_| "unknown".to_string()); let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await; let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await; let bcc_self = self.get_config_int(Config::BccSelf).await; - let prv_key_cnt: Option = self + let prv_key_cnt: Option = self .sql - .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", paramsv![]) - .await; + .query_value("SELECT COUNT(*) FROM keypairs;", paramsx![]) + .await + .ok(); - let pub_key_cnt: Option = self + let pub_key_cnt: Option = self .sql - .query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![]) - .await; + .query_value("SELECT COUNT(*) FROM acpeerstates;", paramsx![]) + .await + .ok(); let fingerprint_str = match SignedPublicKey::load_self(self).await { Ok(key) => key.fingerprint().hex(), Err(err) => format!("", err), @@ -293,7 +297,7 @@ impl Context { let mvbox_move = self.get_config_int(Config::MvboxMove).await; let folders_configured = self .sql - .get_raw_config_int(self, "folders_configured") + .get_raw_config_int("folders_configured") .await .unwrap_or_default(); @@ -354,30 +358,22 @@ impl Context { pub async fn get_fresh_msgs(&self) -> Vec { let show_deaddrop: i32 = 0; self.sql - .query_map( - concat!( - "SELECT m.id", - " FROM msgs m", - " LEFT JOIN contacts ct", - " ON m.from_id=ct.id", - " LEFT JOIN chats c", - " ON m.chat_id=c.id", - " WHERE m.state=?", - " AND m.hidden=0", - " AND m.chat_id>?", - " AND ct.blocked=0", - " AND (c.blocked=0 OR c.blocked=?)", - " ORDER BY m.timestamp DESC,m.id DESC;" - ), - paramsv![10, 9, if 0 != show_deaddrop { 2 } else { 0 }], - |row| row.get::<_, MsgId>(0), - |rows| { - let mut ret = Vec::new(); - for row in rows { - ret.push(row?); - } - Ok(ret) - }, + .query_values( + r#" +SELECT m.id + FROM msgs m + LEFT JOIN contacts ct + ON m.from_id=ct.id + LEFT JOIN chats c + ON m.chat_id=c.id + WHERE m.state=? + AND m.hidden=0 + AND m.chat_id>? + AND ct.blocked=0 + AND (c.blocked=0 OR c.blocked=?) + ORDER BY m.timestamp DESC,m.id DESC; +"#, + paramsx![10, 9, if 0 != show_deaddrop { 2 } else { 0 }], ) .await .unwrap_or_default() @@ -393,47 +389,36 @@ impl Context { let strLikeBeg = format!("{}%", real_query); let query = if !chat_id.is_unset() { - concat!( - "SELECT m.id AS id, m.timestamp AS timestamp", - " FROM msgs m", - " LEFT JOIN contacts ct", - " ON m.from_id=ct.id", - " WHERE m.chat_id=?", - " AND m.hidden=0", - " AND ct.blocked=0", - " AND (txt LIKE ? OR ct.name LIKE ?)", - " ORDER BY m.timestamp,m.id;" - ) + r#" +SELECT m.id + FROM msgs + LEFT JOIN contacts ct + ON m.from_id=ct.id + WHERE m.chat_id=? + AND m.hidden=0 + AND ct.blocked=0 + AND (txt LIKE ? OR ct.name LIKE ?) + ORDER BY m.timestamp,m.id; +"# } else { - concat!( - "SELECT m.id AS id, m.timestamp AS timestamp", - " FROM msgs m", - " LEFT JOIN contacts ct", - " ON m.from_id=ct.id", - " LEFT JOIN chats c", - " ON m.chat_id=c.id", - " WHERE m.chat_id>9", - " AND m.hidden=0", - " AND (c.blocked=0 OR c.blocked=?)", - " AND ct.blocked=0", - " AND (m.txt LIKE ? OR ct.name LIKE ?)", - " ORDER BY m.timestamp DESC,m.id DESC;" - ) + r#" +SELECT m.id + FROM msgs m + LEFT JOIN contacts ct + ON m.from_id=ct.id + LEFT JOIN chats c + ON m.chat_id=c.id + WHERE m.chat_id>9 + AND m.hidden=0 + AND (c.blocked=0 OR c.blocked=?) + AND ct.blocked=0 + AND (m.txt LIKE ? OR ct.name LIKE ?) + ORDER BY m.timestamp DESC,m.id DESC; +"# }; self.sql - .query_map( - query, - paramsv![chat_id, strLikeInText, strLikeBeg], - |row| row.get::<_, MsgId>("id"), - |rows| { - let mut ret = Vec::new(); - for id in rows { - ret.push(id?); - } - Ok(ret) - }, - ) + .query_values(query, paramsx![chat_id, strLikeInText, strLikeBeg]) .await .unwrap_or_default() } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 761d0406e..eb367b146 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1,9 +1,9 @@ +use async_std::prelude::*; use itertools::join; +use mailparse::SingleInfo; use num_traits::FromPrimitive; use sha2::{Digest, Sha256}; -use mailparse::SingleInfo; - use crate::chat::{self, Chat, ChatId}; use crate::config::Config; use crate::constants::*; @@ -652,9 +652,6 @@ async fn add_parts( let icnt = mime_parser.parts.len(); let subject = mime_parser.get_subject().unwrap_or_default(); - - let mut parts = std::mem::replace(&mut mime_parser.parts, Vec::new()); - let server_folder = server_folder.as_ref().to_string(); let location_kml_is = mime_parser.location_kml.is_some(); let is_system_message = mime_parser.is_system_message; let mime_headers = if save_mime_headers { @@ -663,51 +660,43 @@ async fn add_parts( None }; let sent_timestamp = *sent_timestamp; - let is_hidden = *hidden; let chat_id = *chat_id; let is_mdn = !mime_parser.mdn_reports.is_empty(); - // TODO: can this clone be avoided? - let rfc724_mid = rfc724_mid.to_string(); + for part in &mut mime_parser.parts { + let mut txt_raw = "".to_string(); - let (new_parts, ids, is_hidden) = context - .sql - .with_conn(move |mut conn| { - let mut ids = Vec::with_capacity(parts.len()); - let mut is_hidden = is_hidden; + let is_location_kml = + location_kml_is && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty()); - for part in &mut parts { - let mut txt_raw = "".to_string(); - let mut stmt = conn.prepare_cached( - "INSERT INTO msgs \ - (rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ - timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \ - bytes, hidden, mime_headers, mime_in_reply_to, mime_references, error) \ - VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?, ?);", - )?; + if is_mdn || is_location_kml { + *hidden = true; + if state == MessageState::InFresh { + state = MessageState::InNoticed; + } + } - let is_location_kml = location_kml_is - && icnt == 1 - && (part.msg == "-location-" || part.msg.is_empty()); + if part.typ == Viewtype::Text { + let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default(); + txt_raw = format!("{}\n\n{}", subject, msg_raw); + } + if is_system_message != SystemMessage::Unknown { + part.param.set_int(Param::Cmd, is_system_message as i32); + } - if is_mdn || is_location_kml { - is_hidden = true; - if state == MessageState::InFresh { - state = MessageState::InNoticed; - } - } - - if part.typ == Viewtype::Text { - let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default(); - txt_raw = format!("{}\n\n{}", subject, msg_raw); - } - if is_system_message != SystemMessage::Unknown { - part.param.set_int(Param::Cmd, is_system_message as i32); - } - - stmt.execute(paramsv![ - rfc724_mid, - server_folder, + context + .sql + .execute( + r#" +INSERT INTO msgs ( + rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, + timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, + bytes, hidden, mime_headers, mime_in_reply_to, mime_references) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?); +"#, + paramsx![ + rfc724_mid.to_owned(), + server_folder.as_ref().to_owned(), server_uid as i32, chat_id, from_id as i32, @@ -718,38 +707,30 @@ async fn add_parts( part.typ, state, is_dc_message, - part.msg, + part.msg.clone(), // txt_raw might contain invalid utf8 txt_raw, part.param.to_string(), - part.bytes as isize, - is_hidden, - mime_headers, - mime_in_reply_to, - mime_references, - part.error, - ])?; + part.bytes as i64, + *hidden, + mime_headers.clone(), + mime_in_reply_to.clone(), + mime_references.clone(), + ], + ) + .await?; - drop(stmt); - ids.push(MsgId::new(crate::sql::get_rowid( - &mut conn, - "msgs", - "rfc724_mid", - &rfc724_mid, - )?)); - } - Ok((parts, ids, is_hidden)) - }) - .await?; + let msg_id = MsgId::new( + context + .sql + .get_rowid("msgs", "rfc724_mid", &rfc724_mid) + .await?, + ); - if let Some(id) = ids.iter().last() { - *insert_msg_id = *id; + *insert_msg_id = msg_id; + created_db_entries.push((chat_id, msg_id)); } - *hidden = is_hidden; - created_db_entries.extend(ids.iter().map(|id| (chat_id, *id))); - mime_parser.parts = new_parts; - info!( context, "Message has {} parts and is assigned to chat #{}.", icnt, chat_id, @@ -867,16 +848,15 @@ async fn calc_sort_timestamp( // get newest non fresh message for this chat // update sort_timestamp if less than that if is_fresh_msg { - let last_msg_time: Option = context + let last_msg_time: Result = context .sql - .query_get_value( - context, + .query_value( "SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?", - paramsv![chat_id, MessageState::InFresh], + paramsx![chat_id, MessageState::InFresh], ) .await; - if let Some(last_msg_time) = last_msg_time { + if let Ok(last_msg_time) = last_msg_time { if last_msg_time > sort_timestamp { sort_timestamp = last_msg_time; } @@ -1155,7 +1135,7 @@ async fn create_or_lookup_group( .sql .execute( "UPDATE chats SET name=? WHERE id=?;", - paramsv![grpname.to_string(), chat_id], + paramsx![grpname, chat_id], ) .await .is_ok() @@ -1191,7 +1171,7 @@ async fn create_or_lookup_group( .sql .execute( "DELETE FROM chats_contacts WHERE chat_id=?;", - paramsv![chat_id], + paramsx![chat_id], ) .await .ok(); @@ -1273,10 +1253,10 @@ async fn create_or_lookup_adhoc_group( let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids).await?; if !chat_ids.is_empty() { let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ","); - let res = context + let res: Result<(ChatId, Option), _> = context .sql .query_row( - format!( + &format!( "SELECT c.id, c.blocked FROM chats c @@ -1288,19 +1268,13 @@ async fn create_or_lookup_adhoc_group( LIMIT 1;", chat_ids_str ), - paramsv![], - |row| { - Ok(( - row.get::<_, ChatId>(0)?, - row.get::<_, Option>(1)?.unwrap_or_default(), - )) - }, + paramsx![], ) .await; if let Ok((id, id_blocked)) = res { /* success, chat found */ - return Ok((id, id_blocked)); + return Ok((id, id_blocked.unwrap_or_default())); } } @@ -1332,7 +1306,7 @@ async fn create_or_lookup_adhoc_group( // create a new ad-hoc group // - there is no need to check if this group exists; otherwise we would have caught it above - let grpid = create_adhoc_grp_id(context, &member_ids).await; + let grpid = create_adhoc_grp_id(context, &member_ids).await?; if grpid.is_empty() { warn!( context, @@ -1372,7 +1346,7 @@ async fn create_group_record( ) -> ChatId { if context.sql.execute( "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", - paramsv![ + paramsx![ if VerifiedStatus::Unverified != create_verified { Chattype::VerifiedGroup } else { @@ -1381,7 +1355,7 @@ async fn create_group_record( grpname.as_ref(), grpid.as_ref(), create_blocked, - time(), + time() ], ).await .is_err() @@ -1396,7 +1370,7 @@ async fn create_group_record( } let row_id = context .sql - .get_rowid(context, "chats", "grpid", grpid.as_ref()) + .get_rowid("chats", "grpid", grpid.as_ref()) .await .unwrap_or_default(); @@ -1411,7 +1385,7 @@ async fn create_group_record( chat_id } -async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { +async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> Result { /* algorithm: - sort normalized, lowercased, e-mail addresses alphabetically - put all e-mail addresses into a single string, separate the address by a single comma @@ -1425,30 +1399,20 @@ async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { .unwrap_or_else(|| "no-self".to_string()) .to_lowercase(); - let members = context - .sql - .query_map( - format!( - "SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF - member_ids_str - ), - paramsv![], - |row| row.get::<_, String>(0), - |rows| { - let mut addrs = rows.collect::, _>>()?; - addrs.sort(); - let mut acc = member_cs.clone(); - for addr in &addrs { - acc += ","; - acc += &addr.to_lowercase(); - } - Ok(acc) - }, - ) - .await - .unwrap_or_else(|_| member_cs); + let query = format!( + "SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF + member_ids_str + ); + let mut addrs: Vec = context.sql.query_values(&query, paramsx![]).await?; + addrs.sort(); - hex_hash(&members) + let mut acc = member_cs; + for addr in &addrs { + acc += ","; + acc += &addr.to_lowercase(); + } + + Ok(hex_hash(&acc)) } fn hex_hash(s: impl AsRef) -> String { @@ -1475,8 +1439,7 @@ async fn search_chat_ids_by_contact_ids( if !contact_ids.is_empty() { contact_ids.sort(); let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ","); - context.sql.query_map( - format!( + let query = format!( "SELECT DISTINCT cc.chat_id, cc.contact_id FROM chats_contacts cc LEFT JOIN chats c ON c.id=cc.chat_id @@ -1485,37 +1448,37 @@ async fn search_chat_ids_by_contact_ids( AND cc.contact_id!=1 ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF contact_ids_str - ), - paramsv![], - |row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)), - |rows| { - let mut last_chat_id = ChatId::new(0); - let mut matches = 0; - let mut mismatches = 0; + ); - for row in rows { - let (chat_id, contact_id) = row?; - if chat_id != last_chat_id { - if matches == contact_ids.len() && mismatches == 0 { - chat_ids.push(last_chat_id); - } - last_chat_id = chat_id; - matches = 0; - mismatches = 0; - } - if matches < contact_ids.len() && contact_id == contact_ids[matches] { - matches += 1; - } else { - mismatches += 1; - } - } + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as(&query).fetch(&pool); + let mut last_chat_id = ChatId::new(0); + let mut matches = 0; + let mut mismatches = 0; + + while let Some(row) = rows.next().await { + let (chat_id, contact_id): (ChatId, i64) = row?; + let contact_id = contact_id as u32; + + if chat_id != last_chat_id { if matches == contact_ids.len() && mismatches == 0 { chat_ids.push(last_chat_id); } - Ok(()) + last_chat_id = chat_id; + matches = 0; + mismatches = 0; } - ).await?; + if matches < contact_ids.len() && contact_id == contact_ids[matches] { + matches += 1; + } else { + mismatches += 1; + } + } + + if matches == contact_ids.len() && mismatches == 0 { + chat_ids.push(last_chat_id); + } } } @@ -1539,8 +1502,10 @@ async fn check_verified_properties( if from_id != DC_CONTACT_ID_SELF { let peerstate = Peerstate::from_addr(context, contact.get_addr()).await; - if peerstate.is_none() - || contact.is_verified_ex(context, peerstate.as_ref()).await + if peerstate.is_err() + || contact + .is_verified_ex(context, peerstate.as_ref().ok()) + .await != VerifiedStatus::BidirectVerified { bail!( @@ -1549,7 +1514,7 @@ async fn check_verified_properties( ); } - if let Some(peerstate) = peerstate { + if let Ok(peerstate) = peerstate { ensure!( peerstate.has_verified_key(&mimeparser.signatures), "The message was sent with non-verified encryption." @@ -1566,36 +1531,30 @@ async fn check_verified_properties( } let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ","); - let rows = context - .sql - .query_map( - format!( - "SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \ + let query = format!( + "SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ", - to_ids_str - ), - paramsv![], - |row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))), - |rows| { - rows.collect::, _>>() - .map_err(Into::into) - }, - ) - .await?; + to_ids_str + ); + + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as(&query).fetch(&pool); + + while let Some(row) = rows.next().await { + let (to_addr, is_verified): (String, i32) = row?; - for (to_addr, _is_verified) in rows.into_iter() { info!( context, "check_verified_properties: {:?} self={:?}", to_addr, context.is_self_addr(&to_addr).await ); - let mut is_verified = _is_verified != 0; + let mut is_verified = is_verified != 0; let peerstate = Peerstate::from_addr(context, &to_addr).await; // mark gossiped keys (if any) as verified if mimeparser.gossipped_addr.contains(&to_addr) { - if let Some(mut peerstate) = peerstate { + if let Ok(mut peerstate) = peerstate { // if we're here, we know the gossip key is verified: // - use the gossip-key as verified-key if there is no verified-key // - OR if the verified-key does not match public-key or gossip-key @@ -1685,7 +1644,7 @@ async fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { LEFT JOIN chats c ON m.chat_id=c.id \ WHERE m.rfc724_mid=? \ AND m.chat_id>9 AND c.blocked=0;", - paramsv![rfc724_mid], + paramsx![rfc724_mid], ) .await .unwrap_or_default() @@ -1731,7 +1690,7 @@ async fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool { .sql .exists( "SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;", - paramsv![rfc724_mid], + paramsx![rfc724_mid], ) .await .unwrap_or_default() @@ -2002,14 +1961,32 @@ mod tests { let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Single); assert_eq!(chat.name, "Bob"); - assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 1); - assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 1); + assert_eq!( + chat::get_chat_contacts(&t.ctx, chat_id) + .await + .unwrap() + .len(), + 1 + ); + assert_eq!( + chat::get_chat_msgs(&t.ctx, chat_id, 0, None) + .await + .unwrap() + .len(), + 1 + ); // receive a non-delta-message from Bob, shows up because of the show_emails setting dc_receive_imf(&t.ctx, ONETOONE_NOREPLY_MAIL, "INBOX", 2, false) .await .unwrap(); - assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 2); + assert_eq!( + chat::get_chat_msgs(&t.ctx, chat_id, 0, None) + .await + .unwrap() + .len(), + 2 + ); // let Bob create an adhoc-group by a non-delta-message, shows up because of the show_emails setting dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 3, false) @@ -2023,7 +2000,13 @@ mod tests { let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Group); assert_eq!(chat.name, "group with Alice, Bob and Claire"); - assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3); + assert_eq!( + chat::get_chat_contacts(&t.ctx, chat_id) + .await + .unwrap() + .len(), + 3 + ); } #[async_std::test] @@ -2047,7 +2030,13 @@ mod tests { let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert_eq!(chat.typ, Chattype::Group); assert_eq!(chat.name, "group with Alice, Bob and Claire"); - assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3); + assert_eq!( + chat::get_chat_contacts(&t.ctx, chat_id) + .await + .unwrap() + .len(), + 3 + ); } #[async_std::test] @@ -2073,7 +2062,10 @@ mod tests { .unwrap(); chat::add_contact_to_chat(&t.ctx, group_id, bob_id).await; assert_eq!( - chat::get_chat_msgs(&t.ctx, group_id, 0, None).await.len(), + chat::get_chat_msgs(&t.ctx, group_id, 0, None) + .await + .unwrap() + .len(), 0 ); group_id @@ -2116,7 +2108,9 @@ mod tests { ) .await .unwrap(); - let msgs = chat::get_chat_msgs(&t.ctx, group_id, 0, None).await; + let msgs = chat::get_chat_msgs(&t.ctx, group_id, 0, None) + .await + .unwrap(); assert_eq!(msgs.len(), 1); let msg_id = msgs.first().unwrap(); let msg = message::Message::load_from_db(&t.ctx, msg_id.clone()) @@ -2167,7 +2161,10 @@ mod tests { ) .await.unwrap(); assert_eq!( - chat::get_chat_msgs(&t.ctx, group_id, 0, None).await.len(), + chat::get_chat_msgs(&t.ctx, group_id, 0, None) + .await + .unwrap() + .len(), 1 ); let msg = message::Message::load_from_db(&t.ctx, msg_id.clone()) @@ -2251,7 +2248,7 @@ mod tests { .get_authname(), "Имя, Фамилия", ); - let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await; + let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.unwrap(); assert_eq!(msgs.len(), 1); let msg_id = msgs.first().unwrap(); let msg = message::Message::load_from_db(&t.ctx, msg_id.clone()) @@ -2515,7 +2512,9 @@ mod tests { assert_eq!(msg.state, MessageState::OutFailed); - let msgs = chat::get_chat_msgs(&t.ctx, msg.chat_id, 0, None).await; + let msgs = chat::get_chat_msgs(&t.ctx, msg.chat_id, 0, None) + .await + .unwrap(); let last_msg = Message::load_from_db(&t.ctx, *msgs.last().unwrap()) .await .unwrap(); diff --git a/src/dc_tools.rs b/src/dc_tools.rs index de0c5f7fb..a3822935d 100644 --- a/src/dc_tools.rs +++ b/src/dc_tools.rs @@ -568,14 +568,6 @@ impl FromStr for EmailAddress { } } -impl rusqlite::types::ToSql for EmailAddress { - fn to_sql(&self) -> rusqlite::Result { - let val = rusqlite::types::Value::Text(self.to_string()); - let out = rusqlite::types::ToSqlOutput::Owned(val); - Ok(out) - } -} - /// Utility to check if a in the binary represantion of listflags /// the bit at position bitindex is 1. pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool { diff --git a/src/e2ee.rs b/src/e2ee.rs index cb90a30ef..75ec785e8 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -91,7 +91,7 @@ impl EncryptHelper { context: &Context, min_verified: PeerstateVerifiedStatus, mail_to_encrypt: lettre_email::PartBuilder, - peerstates: Vec<(Option>, &str)>, + peerstates: Vec<(Option, &str)>, ) -> Result { let mut keyring: Keyring = Keyring::new(); @@ -132,7 +132,7 @@ pub async fn try_decrypt( let autocryptheader = Aheader::from_headers(context, &from, &mail.headers); if message_time > 0 { - peerstate = Peerstate::from_addr(context, &from).await; + peerstate = Peerstate::from_addr(context, &from).await.ok(); if let Some(ref mut peerstate) = peerstate { if let Some(ref header) = autocryptheader { @@ -143,7 +143,7 @@ pub async fn try_decrypt( peerstate.save_to_db(&context.sql, false).await?; } } else if let Some(ref header) = autocryptheader { - let p = Peerstate::from_header(context, header, message_time); + let p = Peerstate::from_header(header, message_time); p.save_to_db(&context.sql, true).await?; peerstate = Some(p); } @@ -155,7 +155,7 @@ pub async fn try_decrypt( let mut signatures = HashSet::default(); if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { - peerstate = Peerstate::from_addr(&context, &from).await; + peerstate = Peerstate::from_addr(&context, &from).await.ok(); } if let Some(peerstate) = peerstate { if peerstate.degrade_event.is_some() { diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 22529c5d4..e4ffbcbea 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -473,7 +473,7 @@ impl Imap { folder: S, ) -> (u32, u32) { let key = format!("imap.mailbox.{}", folder.as_ref()); - if let Some(entry) = context.sql.get_raw_config(context, &key).await { + if let Some(entry) = context.sql.get_raw_config(&key).await { // the entry has the format `imap.mailbox.=:` let mut parts = entry.split(':'); ( @@ -1129,10 +1129,7 @@ impl Imap { context: &Context, create_mvbox: bool, ) -> Result<()> { - let folders_configured = context - .sql - .get_raw_config_int(context, "folders_configured") - .await; + let folders_configured = context.sql.get_raw_config_int("folders_configured").await; if folders_configured.unwrap_or_default() >= DC_FOLDERS_CONFIGURED_VERSION { return Ok(()); } @@ -1298,7 +1295,7 @@ impl Imap { .sql .execute( "UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?", - paramsv![folder], + paramsx![folder], ) .await { diff --git a/src/imex.rs b/src/imex.rs index 399db2f47..66d01857c 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -98,7 +98,7 @@ pub async fn has_backup(context: &Context, dir_name: impl AsRef) -> Result let sql = Sql::new(); sql.open(context, &path, true).await?; let curr_backup_time = sql - .get_raw_config_int(context, "backup_time") + .get_raw_config_int("backup_time") .await .unwrap_or_default(); if curr_backup_time > newest_backup_time { @@ -245,7 +245,7 @@ pub fn create_setup_code(_context: &Context) -> String { } async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { - if !context.sql.get_raw_config_bool(context, "bcc_self").await { + if !context.sql.get_raw_config_bool("bcc_self").await { let mut msg = Message::new(Viewtype::Text); // TODO: define this as a stockstring once the wording is settled. msg.text = Some( @@ -396,6 +396,7 @@ async fn imex_inner( Ok(()) } Err(err) => { + error!(context, "IMEX FAILED: {}", err); context.emit_event(Event::ImexProgress(0)); bail!("IMEX FAILED to complete: {}", err); } @@ -435,41 +436,28 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> delete_and_reset_all_device_msgs(&context).await?; - let total_files_cnt = context + let total_files_cnt: i32 = context .sql - .query_get_value::(context, "SELECT COUNT(*) FROM backup_blobs;", paramsv![]) + .query_value("SELECT COUNT(*) FROM backup_blobs;", paramsx![]) .await - .unwrap_or_default() as usize; + .unwrap_or_default(); + let total_files_cnt = total_files_cnt as usize; info!( context, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt, ); - let files = context - .sql - .query_map( - "SELECT file_name, file_content FROM backup_blobs ORDER BY id;", - paramsv![], - |row| { - let name: String = row.get(0)?; - let blob: Vec = row.get(1)?; + let pool = context.sql.get_pool().await?; + let mut files = sqlx::query_as("SELECT file_name, file_content FROM backup_blobs ORDER BY id;") + .fetch(&pool); - Ok((name, blob)) - }, - |files| { - files - .collect::, _>>() - .map_err(Into::into) - }, - ) - .await?; - - let mut all_files_extracted = true; - for (processed_files_cnt, (file_name, file_blob)) in files.into_iter().enumerate() { + let mut processed_files_cnt = 0; + while let Some(files_result) = files.next().await { + let (file_name, file_blob): (String, Vec) = files_result?; if context.shall_stop_ongoing().await { - all_files_extracted = false; break; } + let mut permille = processed_files_cnt * 1000 / total_files_cnt; if permille < 10 { permille = 10 @@ -484,19 +472,22 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> let path_filename = context.get_blobdir().join(file_name); dc_write_file(context, &path_filename, &file_blob).await?; + + processed_files_cnt += 1; } - if all_files_extracted { - // only delete backup_blobs if all files were successfully extracted - context - .sql - .execute("DROP TABLE backup_blobs;", paramsv![]) - .await?; - context.sql.execute("VACUUM;", paramsv![]).await.ok(); - Ok(()) - } else { - bail!("received stop signal"); - } + ensure!( + processed_files_cnt == total_files_cnt, + "received stop signal" + ); + + context + .sql + .execute("DROP TABLE backup_blobs;", paramsx![]) + .await?; + context.sql.execute("VACUUM;", paramsx![]).await?; + + Ok(()) } /******************************************************************************* @@ -512,9 +503,9 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { let dest_path_filename = dc_get_next_backup_path(dir, now).await?; let dest_path_string = dest_path_filename.to_string_lossy().to_string(); - sql::housekeeping(context).await; + sql::housekeeping(context).await?; - context.sql.execute("VACUUM;", paramsv![]).await.ok(); + context.sql.execute("VACUUM;", paramsx![]).await.ok(); // we close the database during the copy of the dbfile context.sql.close().await; @@ -541,11 +532,6 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { dest_sql.open(context, &dest_path_filename, false).await?; let res = match add_files_to_export(context, &dest_sql).await { - Err(err) => { - dc_delete_file(context, &dest_path_filename).await; - error!(context, "backup failed: {}", err); - Err(err) - } Ok(()) => { dest_sql .set_raw_config_int(context, "backup_time", now as i32) @@ -553,6 +539,11 @@ async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { context.emit_event(Event::ImexFileWritten(dest_path_filename)); Ok(()) } + Err(err) => { + dc_delete_file(context, &dest_path_filename).await; + error!(context, "backup failed: {}", err); + Err(err) + } }; dest_sql.close().await; @@ -565,7 +556,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { if !sql.table_exists("backup_blobs").await? { sql.execute( "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", - paramsv![], + paramsx![], ) .await?; } @@ -577,41 +568,38 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); - sql.with_conn_async(|conn| async move { - // scan directory, pass 2: copy files - let mut dir_handle = async_std::fs::read_dir(&dir).await?; + // scan directory, pass 2: copy files + let mut dir_handle = async_std::fs::read_dir(&dir).await?; - let mut processed_files_cnt = 0; - while let Some(entry) = dir_handle.next().await { - let entry = entry?; - if context.shall_stop_ongoing().await { - return Ok(()); - } - processed_files_cnt += 1; - let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); - context.emit_event(Event::ImexProgress(permille)); + let mut processed_files_cnt = 0; + while let Some(entry) = dir_handle.next().await { + let entry = entry?; + if context.shall_stop_ongoing().await { + return Ok(()); + } + processed_files_cnt += 1; + let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); + context.emit_event(Event::ImexProgress(permille)); - let name_f = entry.file_name(); - let name = name_f.to_string_lossy(); - if name.starts_with("delta-chat") && name.ends_with(".bak") { + let name_f = entry.file_name(); + let name = name_f.to_string_lossy(); + if name.starts_with("delta-chat") && name.ends_with(".bak") { + continue; + } + info!(context, "EXPORT: copying filename={}", name); + let curr_path_filename = context.get_blobdir().join(entry.file_name()); + if let Ok(buf) = dc_read_file(context, &curr_path_filename).await { + if buf.is_empty() { continue; } - info!(context, "EXPORT: copying filename={}", name); - let curr_path_filename = context.get_blobdir().join(entry.file_name()); - if let Ok(buf) = dc_read_file(context, &curr_path_filename).await { - if buf.is_empty() { - continue; - } - // bail out if we can't insert - let mut stmt = conn.prepare_cached( - "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);", - )?; - stmt.execute(paramsv![name, buf])?; - } + // bail out if we can't insert + sql.execute( + "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);", + paramsx![name.as_ref(), buf], + ) + .await?; } - Ok(()) - }) - .await?; + } Ok(()) } @@ -674,30 +662,19 @@ async fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<() async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { let mut export_errors = 0; - let keys = context - .sql - .query_map( - "SELECT id, public_key, private_key, is_default FROM keypairs;", - paramsv![], - |row| { - let id = row.get(0)?; - let public_key_blob: Vec = row.get(1)?; - let public_key = SignedPublicKey::from_slice(&public_key_blob); - let private_key_blob: Vec = row.get(2)?; - let private_key = SignedSecretKey::from_slice(&private_key_blob); - let is_default: i32 = row.get(3)?; + let pool = context.sql.get_pool().await?; - Ok((id, public_key, private_key, is_default)) - }, - |keys| { - keys.collect::, _>>() - .map_err(Into::into) - }, - ) - .await?; + let mut keys = sqlx::query_as("SELECT id, public_key, private_key, is_default FROM keypairs;") + .fetch(&pool); + + while let Some(keys_result) = keys.next().await { + let (id, public_key_blob, private_key_blob, is_default): (i64, Vec, Vec, i32) = + keys_result?; + let public_key = SignedPublicKey::from_slice(&public_key_blob); + let private_key = SignedSecretKey::from_slice(&private_key_blob); - for (id, public_key, private_key, is_default) in keys { let id = Some(id).filter(|_| is_default != 0); + if let Ok(key) = public_key { if export_key_to_asc_file(context, &dir, id, &key) .await @@ -721,6 +698,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<() } ensure!(export_errors == 0, "errors while exporting keys"); + Ok(()) } diff --git a/src/job.rs b/src/job.rs index 30b6eccfb..3a07748a5 100644 --- a/src/job.rs +++ b/src/job.rs @@ -6,13 +6,13 @@ use std::fmt; use std::future::Future; -use deltachat_derive::{FromSql, ToSql}; -use itertools::Itertools; -use rand::{thread_rng, Rng}; - use async_smtp::smtp::response::Category; use async_smtp::smtp::response::Code; use async_smtp::smtp::response::Detail; +use async_std::prelude::*; +use deltachat_derive::*; +use itertools::Itertools; +use rand::{thread_rng, Rng}; use crate::blob::BlobObject; use crate::chat::{self, ChatId}; @@ -37,7 +37,7 @@ use crate::{scheduler::InterruptInfo, sql}; const JOB_RETRIES: u32 = 17; /// Thread IDs -#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] +#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)] #[repr(i32)] pub(crate) enum Thread { Unknown = 0, @@ -75,17 +75,7 @@ impl Default for Thread { } #[derive( - Debug, - Display, - Copy, - Clone, - PartialEq, - Eq, - PartialOrd, - FromPrimitive, - ToPrimitive, - FromSql, - ToSql, + Debug, Display, Copy, Clone, PartialEq, Eq, PartialOrd, FromPrimitive, ToPrimitive, Sqlx, )] #[repr(i32)] pub enum Action { @@ -155,6 +145,32 @@ impl fmt::Display for Job { } } +impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Job { + fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result { + use sqlx::Row; + let foreign_id: i64 = row.try_get("foreign_id")?; + if foreign_id < 0 { + return Err(sqlx::Error::Decode( + anyhow::anyhow!("invalid foreign_id").into(), + )); + } + + Ok(Job { + job_id: row.try_get::("id")? as u32, + action: row.try_get("action")?, + foreign_id: foreign_id as u32, + desired_timestamp: row.try_get("desired_timestamp")?, + added_timestamp: row.try_get("added_timestamp")?, + tries: row.try_get::("tries")? as u32, + param: row + .try_get::("param")? + .parse() + .unwrap_or_default(), + pending_error: None, + }) + } +} + impl Job { pub fn new(action: Action, foreign_id: u32, param: Params, delay_seconds: i64) -> Self { let timestamp = time(); @@ -180,7 +196,7 @@ impl Job { if self.job_id != 0 { context .sql - .execute("DELETE FROM jobs WHERE id=?;", paramsv![self.job_id as i32]) + .execute("DELETE FROM jobs WHERE id=?;", paramsx![self.job_id as i32]) .await?; } @@ -200,22 +216,22 @@ impl Job { .sql .execute( "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", - paramsv![ + paramsx![ self.desired_timestamp, self.tries as i64, self.param.to_string(), - self.job_id as i32, + self.job_id as i32 ], ) .await?; } else { context.sql.execute( "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", - paramsv![ + paramsx![ self.added_timestamp, thread, self.action, - self.foreign_id, + self.foreign_id as i32, self.param.to_string(), self.desired_timestamp ] @@ -394,40 +410,32 @@ impl Job { context: &Context, contact_id: u32, ) -> sql::Result<(Vec, Vec)> { - // Extract message IDs from job parameters - let res: Vec<(u32, MsgId)> = context - .sql - .query_map( - "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", - paramsv![contact_id, self.job_id], - |row| { - let job_id: u32 = row.get(0)?; - let params_str: String = row.get(1)?; - let params: Params = params_str.parse().unwrap_or_default(); - Ok((job_id, params)) - }, - |jobs| { - let res = jobs - .filter_map(|row| { - let (job_id, params) = row.ok()?; - let msg_id = params.get_msg_id()?; - Some((job_id, msg_id)) - }) - .collect(); - Ok(res) - }, - ) - .await?; - - // Load corresponding RFC724 message IDs let mut job_ids = Vec::new(); let mut rfc724_mids = Vec::new(); - for (job_id, msg_id) in res { - if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await { - job_ids.push(job_id); - rfc724_mids.push(rfc724_mid); + + let pool = context.sql.get_pool().await?; + + let mut rows = sqlx::query_as("SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?") + .bind(contact_id as i64) + .bind(self.job_id as i64) + .fetch(&pool); + + while let Some(row) = rows.next().await { + let (job_id, params): (i64, String) = row?; + let params: Params = params.parse().unwrap_or_default(); + let msg_id = params.get_msg_id().unwrap_or_default(); + + match Message::load_from_db(context, msg_id).await { + Ok(Message { rfc724_mid, .. }) => { + job_ids.push(job_id as u32); + rfc724_mids.push(rfc724_mid); + } + Err(err) => { + warn!(context, "failed to load mdn job message: {}", err); + } } } + Ok((job_ids, rfc724_mids)) } @@ -664,21 +672,27 @@ impl Job { pub async fn kill_action(context: &Context, action: Action) -> bool { context .sql - .execute("DELETE FROM jobs WHERE action=?;", paramsv![action]) + .execute("DELETE FROM jobs WHERE action=?;", paramsx![action]) .await .is_ok() } /// Remove jobs with specified IDs. async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { + use sqlx::Arguments; + + let mut args = sqlx::sqlite::SqliteArguments::default(); + for job_id in job_ids { + args.add(*job_id as i32); + } context .sql .execute( - format!( + &format!( "DELETE FROM jobs WHERE id IN({})", job_ids.iter().map(|_| "?").join(",") ), - job_ids.iter().map(|i| i as &dyn crate::ToSql).collect(), + args, ) .await?; Ok(()) @@ -687,7 +701,7 @@ async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { pub async fn action_exists(context: &Context, action: Action) -> bool { context .sql - .exists("SELECT id FROM jobs WHERE action=?;", paramsv![action]) + .exists("SELECT id FROM jobs WHERE action=?;", paramsx![action]) .await .unwrap_or_default() } @@ -696,11 +710,7 @@ async fn set_delivered(context: &Context, msg_id: MsgId) { message::update_msg_state(context, msg_id, MessageState::OutDelivered).await; let chat_id: ChatId = context .sql - .query_get_value( - context, - "SELECT chat_id FROM msgs WHERE id=?", - paramsv![msg_id], - ) + .query_value("SELECT chat_id FROM msgs WHERE id=?", paramsx![]) .await .unwrap_or_default(); context.emit_event(Event::MsgDelivered { chat_id, msg_id }); @@ -834,12 +844,12 @@ async fn load_imap_deletion_msgid(context: &Context) -> sql::Result(0), + .query_value_optional( + r#" +SELECT id FROM msgs + WHERE timestamp < ? AND server_uid != 0 +"#, + paramsx![threshold_timestamp], ) .await } else { @@ -975,7 +985,9 @@ async fn perform_job_action( Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await, Action::MoveMsg => job.move_msg(context, connection.inbox()).await, Action::Housekeeping => { - sql::housekeeping(context).await; + if let Err(err) = sql::housekeeping(context).await { + error!(context, "housekeeping failed: {}", err); + } Status::Finished(Ok(())) } }; @@ -1067,9 +1079,8 @@ pub(crate) async fn load_next( info!(context, "loading job for {}-thread", thread); let query; - let params; + let params: Box sqlx::sqlite::SqliteArguments<'static> + 'static + Send>; let t = time(); - let m; let thread_i = thread as i64; if let Some(msg_id) = info.msg_id { @@ -1080,8 +1091,7 @@ WHERE thread=? AND foreign_id=? ORDER BY action DESC, added_timestamp LIMIT 1; "#; - m = msg_id; - params = paramsv![thread_i, m]; + params = Box::new(move || paramsx![thread_i, msg_id]); } else if !info.probe_network { // processing for first-try and after backoff-timeouts: // process jobs in the order they were added. @@ -1092,7 +1102,7 @@ WHERE thread=? AND desired_timestamp<=? ORDER BY action DESC, added_timestamp LIMIT 1; "#; - params = paramsv![thread_i, t]; + params = Box::new(move || paramsx![thread_i, t]); } else { // processing after call to dc_maybe_network(): // process _all_ pending jobs that failed before @@ -1104,27 +1114,12 @@ WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC LIMIT 1; "#; - params = paramsv![thread_i]; + params = Box::new(move || paramsx![thread_i]); }; - let job = loop { - let job_res = context - .sql - .query_row_optional(query, params.clone(), |row| { - let job = Job { - job_id: row.get("id")?, - action: row.get("action")?, - foreign_id: row.get("foreign_id")?, - desired_timestamp: row.get("desired_timestamp")?, - added_timestamp: row.get("added_timestamp")?, - tries: row.get("tries")?, - param: row.get::<_, String>("param")?.parse().unwrap_or_default(), - pending_error: None, - }; - - Ok(job) - }) - .await; + let job: Option = loop { + let p = params(); + let job_res = context.sql.query_row_optional(query, p).await; match job_res { Ok(job) => break job, @@ -1133,15 +1128,13 @@ LIMIT 1; info!(context, "cleaning up job, because of {}", err); // TODO: improve by only doing a single query - match context - .sql - .query_row(query, params.clone(), |row| row.get::<_, i32>(0)) - .await - { + let p = params(); + let id: Result = context.sql.query_value(query, p).await; + match id { Ok(id) => { context .sql - .execute("DELETE FROM jobs WHERE id=?;", paramsv![id]) + .execute("DELETE FROM jobs WHERE id=?;", paramsx![id]) .await .ok(); } @@ -1191,7 +1184,7 @@ mod tests { "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?, ?, ?, ?, ?, ?);", - paramsv![ + paramsx![ now, Thread::from(Action::MoveMsg), Action::MoveMsg, diff --git a/src/key.rs b/src/key.rs index fab783aba..bbb1588ea 100644 --- a/src/key.rs +++ b/src/key.rs @@ -116,22 +116,21 @@ impl DcKey for SignedPublicKey { type KeyType = SignedPublicKey; async fn load_self(context: &Context) -> Result { - match context + let res: std::result::Result, _> = context .sql - .query_row( + .query_value( r#" SELECT public_key FROM keypairs WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr") AND is_default=1; "#, - paramsv![], - |row| row.get::<_, Vec>(0), + paramsx![], ) - .await - { + .await; + match res { Ok(bytes) => Self::from_slice(&bytes), - Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { + Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => { let keypair = generate_keypair(context).await?; Ok(keypair.public) } @@ -161,22 +160,21 @@ impl DcKey for SignedSecretKey { type KeyType = SignedSecretKey; async fn load_self(context: &Context) -> Result { - match context + let res: std::result::Result, _> = context .sql - .query_row( + .query_value( r#" SELECT private_key FROM keypairs WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr") AND is_default=1; "#, - paramsv![], - |row| row.get::<_, Vec>(0), + paramsx![], ) - .await - { + .await; + match res { Ok(bytes) => Self::from_slice(&bytes), - Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { + Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => { let keypair = generate_keypair(context).await?; Ok(keypair.secret) } @@ -227,26 +225,25 @@ async fn generate_keypair(context: &Context) -> Result { let _guard = context.generating_key_mutex.lock().await; // Check if the key appeared while we were waiting on the lock. - match context + let res: std::result::Result<(Vec, Vec), _> = context .sql .query_row( r#" SELECT public_key, private_key FROM keypairs - WHERE addr=?1 + WHERE addr=? AND is_default=1; "#, - paramsv![addr], - |row| Ok((row.get::<_, Vec>(0)?, row.get::<_, Vec>(1)?)), + paramsx![addr.to_string()], ) - .await - { + .await; + match res { Ok((pub_bytes, sec_bytes)) => Ok(KeyPair { addr, public: SignedPublicKey::from_slice(&pub_bytes)?, secret: SignedSecretKey::from_slice(&sec_bytes)?, }), - Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { + Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => { let start = std::time::Instant::now(); let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await) .unwrap_or_default(); @@ -321,14 +318,14 @@ pub async fn store_self_keypair( .sql .execute( "DELETE FROM keypairs WHERE public_key=? OR private_key=?;", - paramsv![public_key, secret_key], + paramsx![&public_key, &secret_key], ) .await .map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?; if default == KeyPairUse::Default { context .sql - .execute("UPDATE keypairs SET is_default=0;", paramsv![]) + .execute("UPDATE keypairs SET is_default=0;", paramsx![]) .await .map_err(|err| SaveKeyError::new("failed to clear default", err))?; } @@ -340,7 +337,7 @@ pub async fn store_self_keypair( let addr = keypair.addr.to_string(); let t = time(); - let params = paramsv![addr, is_default, public_key, secret_key, t]; + let params = paramsx![addr, is_default, public_key, secret_key, t]; context .sql .execute( @@ -616,10 +613,12 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD let ctx1 = ctx.clone(); let nrows = || async { - ctx1.sql - .query_get_value::(&ctx1, "SELECT COUNT(*) FROM keypairs;", paramsv![]) + let val: i32 = ctx1 + .sql + .query_value("SELECT COUNT(*) FROM keypairs;", paramsx![]) .await - .unwrap() + .unwrap(); + val as usize }; assert_eq!(nrows().await, 0); store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default) diff --git a/src/lib.rs b/src/lib.rs index 8ada41560..8566dc002 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,16 +6,10 @@ extern crate num_derive; #[macro_use] extern crate smallvec; -#[macro_use] -extern crate rusqlite; extern crate strum; #[macro_use] extern crate strum_macros; -pub trait ToSql: rusqlite::ToSql + Send + Sync {} - -impl ToSql for T {} - #[macro_use] pub mod log; #[macro_use] diff --git a/src/location.rs b/src/location.rs index dabd54f5d..088668eb5 100644 --- a/src/location.rs +++ b/src/location.rs @@ -1,5 +1,6 @@ //! Location handling +use async_std::prelude::*; use bitflags::bitflags; use quick_xml::events::{BytesEnd, BytesStart, BytesText}; @@ -37,6 +38,34 @@ impl Location { } } +impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Location { + fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result { + use sqlx::Row; + + let msg_id = row.try_get::("msg_id")? as u32; + let txt: String = row.try_get("txt")?; + let marker = if msg_id != 0 && is_marker(&txt) { + Some(txt) + } else { + None + }; + let loc = Location { + location_id: row.try_get::("id")? as u32, + latitude: row.try_get("latitude")?, + longitude: row.try_get("longitude")?, + accuracy: row.try_get("accuracy")?, + timestamp: row.try_get("timestamp")?, + independent: row.try_get::("independent")? as u32, + msg_id, + contact_id: row.try_get::("from_id")? as u32, + chat_id: row.try_get("chat_id")?, + marker, + }; + + Ok(loc) + } +} + #[derive(Debug, Clone, Default)] pub struct Kml { pub addr: Option, @@ -197,14 +226,16 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: if context .sql .execute( - "UPDATE chats \ - SET locations_send_begin=?, \ - locations_send_until=? \ - WHERE id=?", - paramsv![ + r#" +UPDATE chats + SET locations_send_begin=?, + locations_send_until=? + WHERE id=? +"#, + paramsx![ if 0 != seconds { now } else { 0 }, if 0 != seconds { now + seconds } else { 0 }, - chat_id, + chat_id ], ) .await @@ -260,53 +291,60 @@ pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> .sql .exists( "SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", - paramsv![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], + paramsx![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], ) .await .unwrap_or_default() } -pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool { +pub async fn set( + context: &Context, + latitude: f64, + longitude: f64, + accuracy: f64, +) -> Result { if latitude == 0.0 && longitude == 0.0 { - return true; + return Ok(true); } + let mut continue_streaming = false; + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as("SELECT id FROM chats WHERE locations_send_until>?;") + .bind(time()) + .fetch(&pool); - if let Ok(chats) = context - .sql - .query_map( - "SELECT id FROM chats WHERE locations_send_until>?;", - paramsv![time()], - |row| row.get::<_, i32>(0), - |chats| chats.collect::, _>>().map_err(Into::into), - ) - .await - { - for chat_id in chats { - if let Err(err) = context.sql.execute( - "INSERT INTO locations \ - (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);", - paramsv![ - latitude, - longitude, - accuracy, - time(), - chat_id, - DC_CONTACT_ID_SELF, - ] - ).await { - warn!(context, "failed to store location {:?}", err); - } else { - continue_streaming = true; - } + while let Some(row) = rows.next().await { + let (chat_id,): (i64,) = row?; + + if let Err(err) = context + .sql + .execute( + "INSERT INTO locations \ + (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);", + paramsx![ + latitude, + longitude, + accuracy, + time(), + chat_id, + DC_CONTACT_ID_SELF as i32 + ], + ) + .await + { + warn!(context, "failed to store location {:?}", err); + } else { + continue_streaming = true; } - if continue_streaming { - context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); - }; - schedule_maybe_send_locations(context, false).await; } - continue_streaming + if continue_streaming { + context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF))); + }; + + schedule_maybe_send_locations(context, false).await; + + Ok(continue_streaming) } pub async fn get_range( @@ -319,16 +357,21 @@ pub async fn get_range( if timestamp_to == 0 { timestamp_to = time() + 10; } + context .sql - .query_map( - "SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \ - COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt \ - FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \ - AND (? OR l.from_id=?) \ - AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \ - ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;", - paramsv![ + .query_rows( + r#" +SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, + COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt + FROM locations l + LEFT JOIN msgs m ON l.id=m.location_id + WHERE (? OR l.chat_id=?) + AND (? OR l.from_id=?) + AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) + ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC; +"#, + paramsx![ if chat_id.is_unset() { 1 } else { 0 }, chat_id, if contact_id == 0 { 1 } else { 0 }, @@ -336,36 +379,6 @@ pub async fn get_range( timestamp_from, timestamp_to, ], - |row| { - let msg_id = row.get(6)?; - let txt: String = row.get(9)?; - let marker = if msg_id != 0 && is_marker(&txt) { - Some(txt) - } else { - None - }; - let loc = Location { - location_id: row.get(0)?, - latitude: row.get(1)?, - longitude: row.get(2)?, - accuracy: row.get(3)?, - timestamp: row.get(4)?, - independent: row.get(5)?, - msg_id, - contact_id: row.get(7)?, - chat_id: row.get(8)?, - marker, - }; - Ok(loc) - }, - |locations| { - let mut ret = Vec::new(); - - for location in locations { - ret.push(location?); - } - Ok(ret) - }, ) .await .unwrap_or_default() @@ -379,7 +392,7 @@ fn is_marker(txt: &str) -> bool { pub async fn delete_all(context: &Context) -> Result<(), Error> { context .sql - .execute("DELETE FROM locations;", paramsv![]) + .execute("DELETE FROM locations;", paramsx![]) .await?; context.emit_event(Event::LocationChanged(None)); Ok(()) @@ -393,16 +406,10 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32) .await .unwrap_or_default(); - let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row( + let (locations_send_begin, locations_send_until, locations_last_sent): (i64, i64, i64) = context.sql.query_row( "SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;", - paramsv![chat_id], |row| { - let send_begin: i64 = row.get(0)?; - let send_until: i64 = row.get(1)?; - let last_sent: i64 = row.get(2)?; - - Ok((send_begin, send_until, last_sent)) - }) - .await?; + paramsx![chat_id] + ).await?; let now = time(); let mut location_count = 0; @@ -413,40 +420,41 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32) self_addr, ); - context.sql.query_map( - "SELECT id, latitude, longitude, accuracy, timestamp \ - FROM locations WHERE from_id=? \ - AND timestamp>=? \ - AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \ - AND independent=0 \ - GROUP BY timestamp \ - ORDER BY timestamp;", - paramsv![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF], - |row| { - let location_id: i32 = row.get(0)?; - let latitude: f64 = row.get(1)?; - let longitude: f64 = row.get(2)?; - let accuracy: f64 = row.get(3)?; - let timestamp = get_kml_timestamp(row.get(4)?); + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as( + r#" +SELECT id, latitude, longitude, accuracy, timestamp + FROM locations + WHERE from_id=? + AND timestamp>=? + AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) + AND independent=0 + GROUP BY timestamp + ORDER BY timestamp; +"#, + ) + .bind(DC_CONTACT_ID_SELF as i32) + .bind(locations_send_begin) + .bind(locations_last_sent) + .bind(DC_CONTACT_ID_SELF as i32) + .fetch(&pool); + + while let Some(row) = rows.next().await { + let (location_id, latitude, longitude, accuracy, timestamp): (i32, f64, f64, f64, i64) = + row?; + let timestamp = get_kml_timestamp(timestamp); + + ret += &format!( + "{}{},{}\n", + timestamp, + accuracy, + longitude, + latitude + ); + location_count += 1; + last_added_location_id = location_id as u32; + } - Ok((location_id, latitude, longitude, accuracy, timestamp)) - }, - |rows| { - for row in rows { - let (location_id, latitude, longitude, accuracy, timestamp) = row?; - ret += &format!( - "{}{},{}\n", - timestamp, - accuracy, - longitude, - latitude - ); - location_count += 1; - last_added_location_id = location_id as u32; - } - Ok(()) - } - ).await?; ret += "\n"; } @@ -488,7 +496,7 @@ pub async fn set_kml_sent_timestamp( .sql .execute( "UPDATE chats SET locations_last_sent=? WHERE id=?;", - paramsv![timestamp, chat_id], + paramsx![timestamp, chat_id], ) .await?; Ok(()) @@ -503,7 +511,7 @@ pub async fn set_msg_location_id( .sql .execute( "UPDATE msgs SET location_id=? WHERE id=?;", - paramsv![location_id, msg_id], + paramsx![location_id as i32, msg_id], ) .await?; @@ -530,50 +538,51 @@ pub async fn save( accuracy, .. } = location; - let (loc_id, ts) = context + + let exists: Option = context .sql - .with_conn(move |mut conn| { - let mut stmt_test = conn - .prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?; - let mut stmt_insert = conn.prepare_cached( - "INSERT INTO locations\ - (timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \ - VALUES (?,?,?,?,?,?,?);", - )?; + .query_value_optional( + "SELECT id FROM locations WHERE timestamp=? AND from_id=?", + paramsx![timestamp, contact_id as i32], + ) + .await?; - let exists = stmt_test.exists(paramsv![timestamp, contact_id as i32])?; - - if independent || !exists { - stmt_insert.execute(paramsv![ + if independent || exists.is_none() { + context + .sql + .execute( + r#" +INSERT INTO locations + (timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) + VALUES (?,?,?,?,?,?,?); +"#, + paramsx![ timestamp, contact_id as i32, chat_id, latitude, longitude, accuracy, - independent, - ])?; + independent + ], + ) + .await?; - if timestamp > newest_timestamp { - // okay to drop, as we use cached prepared statements - drop(stmt_test); - drop(stmt_insert); - newest_timestamp = timestamp; - newest_location_id = crate::sql::get_rowid2( - &mut conn, - "locations", - "timestamp", - timestamp, - "from_id", - contact_id as i32, - )?; - } - } - Ok((newest_location_id, newest_timestamp)) - }) - .await?; - newest_timestamp = ts; - newest_location_id = loc_id; + if timestamp > newest_timestamp { + // okay to drop, as we use cached prepared statements + newest_timestamp = timestamp; + newest_location_id = context + .sql + .get_rowid2( + "locations", + "timestamp", + timestamp, + "from_id", + contact_id as i32, + ) + .await?; + } + } } Ok(newest_location_id) @@ -587,85 +596,75 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j " ----------------- MAYBE_SEND_LOCATIONS -------------- ", ); - let rows = context - .sql - .query_map( - "SELECT id, locations_send_begin, locations_last_sent \ - FROM chats \ - WHERE locations_send_until>?;", - paramsv![now], - |row| { - let chat_id: ChatId = row.get(0)?; - let locations_send_begin: i64 = row.get(1)?; - let locations_last_sent: i64 = row.get(2)?; - continue_streaming = true; + let pool = match context.sql.get_pool().await { + Ok(pool) => pool, + Err(err) => { + return job::Status::Finished(Err(err.into())); + } + }; - // be a bit tolerant as the timer may not align exactly with time(NULL) - if now - locations_last_sent < (60 - 3) { - Ok(None) - } else { - Ok(Some((chat_id, locations_send_begin, locations_last_sent))) - } - }, - |rows| { - rows.filter_map(|v| v.transpose()) - .collect::, _>>() - .map_err(Into::into) - }, - ) - .await; + let mut rows = sqlx::query_as( + r#" +SELECT id, locations_send_begin, locations_last_sent + FROM chats + WHERE locations_send_until>?; +"#, + ) + .bind(now) + .fetch(&pool); - if rows.is_ok() { - let msgs = context + while let Some(row) = rows.next().await { + let (chat_id, locations_send_begin, locations_last_sent): (ChatId, i64, i64) = match row { + Ok(res) => res, + Err(err) => { + warn!(context, "invalid row: {}", err); + continue; + } + }; + continue_streaming = true; + + // be a bit tolerant as the timer may not align exactly with time(NULL) + if now - locations_last_sent < (60 - 3) { + continue; + } + let exists = context .sql - .with_conn(move |conn| { - let rows = rows.unwrap(); - - let mut stmt_locations = conn.prepare_cached( - "SELECT id \ - FROM locations \ - WHERE from_id=? \ - AND timestamp>=? \ - AND timestamp>? \ - AND independent=0 \ - ORDER BY timestamp;", - )?; - - let mut msgs = Vec::new(); - for (chat_id, locations_send_begin, locations_last_sent) in &rows { - if !stmt_locations - .exists(paramsv![ - DC_CONTACT_ID_SELF, - *locations_send_begin, - *locations_last_sent, - ]) - .unwrap_or_default() - { - // if there is no new location, there's nothing to send. - // however, maybe we want to bypass this test eg. 15 minutes - } else { - // pending locations are attached automatically to every message, - // so also to this empty text message. - // DC_CMD_LOCATION is only needed to create a nicer subject. - // - // for optimisation and to avoid flooding the sending queue, - // we could sending these messages only if we're really online. - // the easiest way to determine this, is to check for an empty message queue. - // (might not be 100%, however, as positions are sent combined later - // and dc_set_location() is typically called periodically, this is ok) - let mut msg = Message::new(Viewtype::Text); - msg.hidden = true; - msg.param.set_cmd(SystemMessage::LocationOnly); - msgs.push((*chat_id, msg)); - } - } - - Ok(msgs) - }) + .exists( + r#" +SELECT id + FROM locations + WHERE from_id=? + AND timestamp>=? + AND timestamp>? + AND independent=0 + ORDER BY timestamp;", +"#, + paramsx![ + DC_CONTACT_ID_SELF as i32, + locations_send_begin, + locations_last_sent, + ], + ) .await .unwrap_or_default(); - for (chat_id, mut msg) in msgs.into_iter() { + if !exists { + // if there is no new location, there's nothing to send. + // however, maybe we want to bypass this test eg. 15 minutes + } else { + // pending locations are attached automatically to every message, + // so also to this empty text message. + // DC_CMD_LOCATION is only needed to create a nicer subject. + // + // for optimisation and to avoid flooding the sending queue, + // we could sending these messages only if we're really online. + // the easiest way to determine this, is to check for an empty message queue. + // (might not be 100%, however, as positions are sent combined later + // and dc_set_location() is typically called periodically, this is ok) + let mut msg = Message::new(Viewtype::Text); + msg.hidden = true; + msg.param.set_cmd(SystemMessage::LocationOnly); + // TODO: better error handling chat::send_msg(context, chat_id, &mut msg) .await @@ -676,6 +675,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j if continue_streaming { schedule_maybe_send_locations(context, true).await; } + job::Status::Finished(Ok(())) } @@ -689,13 +689,12 @@ pub(crate) async fn job_maybe_send_locations_ended( let chat_id = ChatId::new(job.foreign_id); - let (send_begin, send_until) = job_try!( + let (send_begin, send_until): (i64, i64) = job_try!( context .sql .query_row( "SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", - paramsv![chat_id], - |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), + paramsx![chat_id], ) .await ); @@ -707,8 +706,8 @@ pub(crate) async fn job_maybe_send_locations_ended( if !(send_begin == 0 && send_until == 0) { // not streaming, device-message already sent job_try!(context.sql.execute( - "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", - paramsv![chat_id], + "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", + paramsx![chat_id], ).await); let stock_str = context diff --git a/src/login_param.rs b/src/login_param.rs index f5693eb5a..3bcb2fe5e 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -56,63 +56,54 @@ impl LoginParam { let key = format!("{}addr", prefix); let addr = sql - .get_raw_config(context, key) + .get_raw_config(key) .await .unwrap_or_default() .trim() .to_string(); let key = format!("{}mail_server", prefix); - let mail_server = sql.get_raw_config(context, key).await.unwrap_or_default(); + let mail_server = sql.get_raw_config(key).await.unwrap_or_default(); let key = format!("{}mail_port", prefix); - let mail_port = sql - .get_raw_config_int(context, key) - .await - .unwrap_or_default(); + let mail_port = sql.get_raw_config_int(key).await.unwrap_or_default(); let key = format!("{}mail_user", prefix); - let mail_user = sql.get_raw_config(context, key).await.unwrap_or_default(); + let mail_user = sql.get_raw_config(key).await.unwrap_or_default(); let key = format!("{}mail_pw", prefix); - let mail_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); + let mail_pw = sql.get_raw_config(key).await.unwrap_or_default(); let key = format!("{}imap_certificate_checks", prefix); let imap_certificate_checks = - if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { + if let Some(certificate_checks) = sql.get_raw_config_int(key).await { num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() } else { Default::default() }; let key = format!("{}send_server", prefix); - let send_server = sql.get_raw_config(context, key).await.unwrap_or_default(); + let send_server = sql.get_raw_config(key).await.unwrap_or_default(); let key = format!("{}send_port", prefix); - let send_port = sql - .get_raw_config_int(context, key) - .await - .unwrap_or_default(); + let send_port = sql.get_raw_config_int(key).await.unwrap_or_default(); let key = format!("{}send_user", prefix); - let send_user = sql.get_raw_config(context, key).await.unwrap_or_default(); + let send_user = sql.get_raw_config(key).await.unwrap_or_default(); let key = format!("{}send_pw", prefix); - let send_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); + let send_pw = sql.get_raw_config(key).await.unwrap_or_default(); let key = format!("{}smtp_certificate_checks", prefix); let smtp_certificate_checks = - if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { + if let Some(certificate_checks) = sql.get_raw_config_int(key).await { num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() } else { Default::default() }; let key = format!("{}server_flags", prefix); - let server_flags = sql - .get_raw_config_int(context, key) - .await - .unwrap_or_default(); + let server_flags = sql.get_raw_config_int(key).await.unwrap_or_default(); LoginParam { addr, diff --git a/src/lot.rs b/src/lot.rs index 1fa426bac..99283e298 100644 --- a/src/lot.rs +++ b/src/lot.rs @@ -1,4 +1,4 @@ -use deltachat_derive::{FromSql, ToSql}; +use deltachat_derive::*; use crate::key::Fingerprint; @@ -22,7 +22,7 @@ pub struct Lot { } #[repr(u8)] -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)] pub enum Meaning { None = 0, Text1Draft = 1, @@ -67,7 +67,7 @@ impl Lot { } #[repr(i32)] -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)] pub enum LotState { // Default Undefined = 0, diff --git a/src/message.rs b/src/message.rs index 2936ceaf8..6096b8208 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,7 +1,8 @@ //! # Messages and their identifiers use async_std::path::{Path, PathBuf}; -use deltachat_derive::{FromSql, ToSql}; +use async_std::prelude::*; +use deltachat_derive::*; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -33,7 +34,20 @@ const SUMMARY_CHARACTERS: usize = 160; /// This type can represent both the special as well as normal /// messages. #[derive( - Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, + Debug, + Copy, + Clone, + Default, + PartialEq, + Eq, + Hash, + PartialOrd, + Ord, + Serialize, + Deserialize, + ToPrimitive, + FromPrimitive, + Sqlx, )] pub struct MsgId(u32); @@ -92,7 +106,7 @@ impl MsgId { .sql .execute( "UPDATE msgs SET chat_id=?, txt='', txt_raw='' WHERE id=?", - paramsv![chat_id, self], + paramsx![chat_id, self], ) .await?; @@ -105,11 +119,11 @@ impl MsgId { // sure they are not left while the message is deleted. context .sql - .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![self]) + .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsx![self]) .await?; context .sql - .execute("DELETE FROM msgs WHERE id=?;", paramsv![self]) + .execute("DELETE FROM msgs WHERE id=?;", paramsx![self]) .await?; Ok(()) } @@ -123,10 +137,12 @@ impl MsgId { context .sql .execute( - "UPDATE msgs \ - SET server_folder='', server_uid=0 \ - WHERE id=?", - paramsv![self], + r#" +UPDATE msgs + SET server_folder='', server_uid=0 + WHERE id=? +"#, + paramsx![self], ) .await?; Ok(()) @@ -156,41 +172,6 @@ impl std::fmt::Display for MsgId { } } -/// Allow converting [MsgId] to an SQLite type. -/// -/// This allows you to directly store [MsgId] into the database. -/// -/// # Errors -/// -/// This **does** ensure that no special message IDs are written into -/// the database and the conversion will fail if this is not the case. -impl rusqlite::types::ToSql for MsgId { - fn to_sql(&self) -> rusqlite::Result { - if self.0 <= DC_MSG_ID_LAST_SPECIAL { - return Err(rusqlite::Error::ToSqlConversionFailure(Box::new( - InvalidMsgId, - ))); - } - let val = rusqlite::types::Value::Integer(self.0 as i64); - let out = rusqlite::types::ToSqlOutput::Owned(val); - Ok(out) - } -} - -/// Allow converting an SQLite integer directly into [MsgId]. -impl rusqlite::types::FromSql for MsgId { - fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult { - // Would be nice if we could use match here, but alas. - i64::column_result(value).and_then(|val| { - if 0 <= val && val <= std::u32::MAX as i64 { - Ok(MsgId::new(val as u32)) - } else { - Err(rusqlite::types::FromSqlError::OutOfRange(val)) - } - }) - } -} - /// Message ID was invalid. /// /// This usually occurs when trying to use a message ID of @@ -201,16 +182,7 @@ impl rusqlite::types::FromSql for MsgId { pub struct InvalidMsgId; #[derive( - Debug, - Copy, - Clone, - PartialEq, - FromPrimitive, - ToPrimitive, - FromSql, - ToSql, - Serialize, - Deserialize, + Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive, Serialize, Deserialize, Sqlx, )] #[repr(u8)] pub(crate) enum MessengerMessage { @@ -259,6 +231,58 @@ pub struct Message { pub(crate) param: Params, } +impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Message { + fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result { + use sqlx::Row; + + let id = row.try_get("id")?; + + let text; + if let Some(buf) = row.try_get::, _>("txt")? { + if let Ok(t) = String::from_utf8(buf.to_vec()) { + text = t; + } else { + eprintln!( + "dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}", + id + ); + text = String::from_utf8_lossy(buf).into_owned(); + } + } else { + text = "".to_string(); + } + + Ok(Message { + id, + rfc724_mid: row.try_get::("rfc724mid")?, + in_reply_to: row.try_get::, _>("mime_in_reply_to")?, + server_folder: row.try_get::, _>("server_folder")?, + server_uid: row.try_get::("server_uid")? as u32, + chat_id: row.try_get("chat_id")?, + from_id: row.try_get::("from_id")? as u32, + to_id: row.try_get::("to_id")? as u32, + timestamp_sort: row.try_get("timestamp")?, + timestamp_sent: row.try_get("timestamp_sent")?, + timestamp_rcvd: row.try_get("timestamp_rcvd")?, + viewtype: row.try_get("type")?, + state: row.try_get("state")?, + is_dc_message: row.try_get("msgrmsg")?, + text: Some(text), + param: row + .try_get::("param")? + .parse() + .unwrap_or_default(), + starred: row.try_get("starred")?, + hidden: row.try_get("hidden")?, + location_id: row.try_get::("location")? as u32, + chat_blocked: row + .try_get::, _>("blocked")? + .unwrap_or_default(), + error: row.try_get("error")?, + }) + } +} + impl Message { pub fn new(viewtype: Viewtype) -> Self { let mut msg = Message::default(); @@ -272,7 +296,7 @@ impl Message { !id.is_special(), "Can not load special message IDs from DB." ); - let msg = context + let msg: Message = context .sql .query_row( concat!( @@ -301,56 +325,7 @@ impl Message { " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", " WHERE m.id=?;" ), - paramsv![id], - |row| { - let mut msg = Message::default(); - // msg.id = row.get::<_, AnyMsgId>("id")?; - msg.id = row.get("id")?; - msg.rfc724_mid = row.get::<_, String>("rfc724mid")?; - msg.in_reply_to = row.get::<_, Option>("mime_in_reply_to")?; - msg.server_folder = row.get::<_, Option>("server_folder")?; - msg.server_uid = row.get("server_uid")?; - msg.chat_id = row.get("chat_id")?; - msg.from_id = row.get("from_id")?; - msg.to_id = row.get("to_id")?; - msg.timestamp_sort = row.get("timestamp")?; - msg.timestamp_sent = row.get("timestamp_sent")?; - msg.timestamp_rcvd = row.get("timestamp_rcvd")?; - msg.viewtype = row.get("type")?; - msg.state = row.get("state")?; - msg.error = row.get("error")?; - msg.is_dc_message = row.get("msgrmsg")?; - - let text; - if let rusqlite::types::ValueRef::Text(buf) = row.get_raw("txt") { - if let Ok(t) = String::from_utf8(buf.to_vec()) { - text = t; - } else { - warn!( - context, - concat!( - "dc_msg_load_from_db: could not get ", - "text column as non-lossy utf8 id {}" - ), - id - ); - text = String::from_utf8_lossy(buf).into_owned(); - } - } else { - text = "".to_string(); - } - msg.text = Some(text); - - msg.param = row.get::<_, String>("param")?.parse().unwrap_or_default(); - msg.starred = row.get("starred")?; - msg.hidden = row.get("hidden")?; - msg.location_id = row.get("location")?; - msg.chat_blocked = row - .get::<_, Option>("blocked")? - .unwrap_or_default(); - - Ok(msg) - }, + paramsx![id], ) .await?; @@ -654,7 +629,7 @@ impl Message { .sql .execute( "UPDATE msgs SET param=? WHERE id=?;", - paramsv![self.param.to_string(), self.id], + paramsx![self.param.to_string(), self.id], ) .await .is_ok() @@ -669,10 +644,9 @@ impl Message { Eq, FromPrimitive, ToPrimitive, - ToSql, - FromSql, Serialize, Deserialize, + sqlx::Type, )] #[repr(i32)] pub enum MessageState { @@ -844,29 +818,16 @@ impl Lot { } } -pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { +pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result { let mut ret = String::new(); - let msg = Message::load_from_db(context, msg_id).await; - if msg.is_err() { - return ret; - } - - let msg = msg.unwrap_or_default(); + let msg = Message::load_from_db(context, msg_id).await?; let rawtxt: Option = context .sql - .query_get_value( - context, - "SELECT txt_raw FROM msgs WHERE id=?;", - paramsv![msg_id], - ) - .await; + .query_value("SELECT txt_raw FROM msgs WHERE id=?;", paramsx![msg_id]) + .await?; - if rawtxt.is_none() { - ret += &format!("Cannot load message {}.", msg_id); - return ret; - } let rawtxt = rawtxt.unwrap_or_default(); let rawtxt = dc_truncate(rawtxt.trim(), 100_000); @@ -875,8 +836,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { let name = Contact::load_from_db(context, msg.from_id) .await - .map(|contact| contact.get_name_n_addr()) - .unwrap_or_default(); + .map(|contact| contact.get_name_n_addr())?; ret += &format!(" by {}", name); ret += "\n"; @@ -893,35 +853,28 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO { // device-internal message, no further details needed - return ret; + return Ok(ret); } - if let Ok(rows) = context - .sql - .query_map( - "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;", - paramsv![msg_id], - |row| { - let contact_id: i32 = row.get(0)?; - let ts: i64 = row.get(1)?; - Ok((contact_id, ts)) - }, - |rows| rows.collect::, _>>().map_err(Into::into), - ) - .await - { - for (contact_id, ts) in rows { - let fts = dc_timestamp_to_str(ts); - ret += &format!("Read: {}", fts); + let pool = context.sql.get_pool().await?; - let name = Contact::load_from_db(context, contact_id as u32) - .await - .map(|contact| contact.get_name_n_addr()) - .unwrap_or_default(); + let mut rows = + sqlx::query_as("SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;") + .bind(msg_id) + .fetch(&pool); - ret += &format!(" by {}", name); - ret += "\n"; - } + while let Some(row) = rows.next().await { + let (contact_id, ts): (i32, i64) = row?; + + let fts = dc_timestamp_to_str(ts); + ret += &format!("Read: {}", fts); + + let name = Contact::load_from_db(context, contact_id as u32) + .await + .map(|contact| contact.get_name_n_addr())?; + + ret += &format!(" by {}", name); + ret += "\n"; } ret += &format!("State: {}", msg.state); @@ -978,7 +931,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { } } - ret + Ok(ret) } pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> { @@ -1006,12 +959,12 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> { pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option { context .sql - .query_get_value( - context, + .query_value( "SELECT mime_headers FROM msgs WHERE id=?;", - paramsv![msg_id], + paramsx![msg_id], ) .await + .ok() } pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { @@ -1050,7 +1003,7 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool { .sql .execute( "DELETE FROM locations WHERE independent = 1 AND id=?;", - paramsv![location_id as i32], + paramsx![location_id as i32], ) .await .is_ok() @@ -1061,56 +1014,40 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec) -> bool { return false; } - let msgs = context - .sql - .with_conn(move |conn| { - let mut stmt = conn.prepare_cached(concat!( - "SELECT", - " m.state AS state,", - " c.blocked AS blocked", - " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", - " WHERE m.id=? AND m.chat_id>9" - ))?; - - let mut msgs = Vec::with_capacity(msg_ids.len()); - for id in msg_ids.into_iter() { - let query_res = stmt.query_row(paramsv![id], |row| { - Ok(( - row.get::<_, MessageState>("state")?, - row.get::<_, Option>("blocked")? - .unwrap_or_default(), - )) - }); - if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res { - continue; - } - let (state, blocked) = query_res.map_err(Into::::into)?; - msgs.push((id, state, blocked)); - } - - Ok(msgs) - }) - .await - .unwrap_or_default(); - let mut send_event = false; + for id in msg_ids.into_iter() { + let query_res: Result)>, _> = context + .sql + .query_row_optional( + r#" +SELECT + m.state + c.blocked + FROM msgs m LEFT JOIN chats c ON c.id = m.chat_id + WHERE m.id = ? AND m.chat_id > 9 +"#, + paramsx![id], + ) + .await; - for (id, curr_state, curr_blocked) in msgs.into_iter() { - if curr_blocked == Blocked::Not { - if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed { - update_msg_state(context, id, MessageState::InSeen).await; - info!(context, "Seen message {}.", id); + if let Ok(Some((state, blocked))) = query_res { + let blocked = blocked.unwrap_or_default(); + if blocked == Blocked::Not { + if state == MessageState::InFresh || state == MessageState::InNoticed { + update_msg_state(context, id, MessageState::InSeen).await; + info!(context, "Seen message {}.", id); - job::add( - context, - job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0), - ) - .await; + job::add( + context, + job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0), + ) + .await; + send_event = true; + } + } else if state == MessageState::InFresh { + update_msg_state(context, id, MessageState::InNoticed).await; send_event = true; } - } else if curr_state == MessageState::InFresh { - update_msg_state(context, id, MessageState::InNoticed).await; - send_event = true; } } @@ -1129,7 +1066,7 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt .sql .execute( "UPDATE msgs SET state=? WHERE id=?;", - paramsv![state, msg_id], + paramsx![state, msg_id], ) .await .is_ok() @@ -1139,17 +1076,22 @@ pub async fn star_msgs(context: &Context, msg_ids: Vec, star: bool) -> bo if msg_ids.is_empty() { return false; } - context - .sql - .with_conn(move |conn| { - let mut stmt = conn.prepare("UPDATE msgs SET starred=? WHERE id=?;")?; - for msg_id in msg_ids.into_iter() { - stmt.execute(paramsv![star as i32, msg_id])?; - } - Ok(()) - }) - .await - .is_ok() + + for msg_id in msg_ids.into_iter() { + if context + .sql + .execute( + "UPDATE msgs SET starred=? WHERE id=?;", + paramsx![star as i32, msg_id], + ) + .await + .is_err() + { + return false; + } + } + + true } /// Returns a summary test. @@ -1240,12 +1182,9 @@ pub async fn exists(context: &Context, msg_id: MsgId) -> bool { let chat_id: Option = context .sql - .query_get_value( - context, - "SELECT chat_id FROM msgs WHERE id=?;", - paramsv![msg_id], - ) - .await; + .query_value("SELECT chat_id FROM msgs WHERE id=?;", paramsx![msg_id]) + .await + .ok(); if let Some(chat_id) = chat_id { !chat_id.is_trash() @@ -1271,7 +1210,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option = context .sql .query_row( - concat!( - "SELECT", - " m.id AS msg_id,", - " c.id AS chat_id,", - " c.type AS type,", - " m.state AS state", - " FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id", - " WHERE rfc724_mid=? AND from_id=1", - " ORDER BY m.id;" - ), - paramsv![rfc724_mid], - |row| { - Ok(( - row.get::<_, MsgId>("msg_id")?, - row.get::<_, ChatId>("chat_id")?, - row.get::<_, Chattype>("type")?, - row.get::<_, MessageState>("state")?, - )) - }, + r#" +SELECT + m.id AS msg_id, + c.id AS chat_id, + c.type AS type, + m.state AS state + FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id + WHERE rfc724_mid=? AND from_id=1 + ORDER BY m.id;"#, + paramsx![rfc724_mid], ) .await; if let Err(ref err) = res { @@ -1336,7 +1266,7 @@ pub async fn handle_mdn( .sql .exists( "SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;", - paramsv![msg_id, from_id as i32,], + paramsx![msg_id, from_id as i32,], ) .await .unwrap_or_default(); @@ -1344,7 +1274,7 @@ pub async fn handle_mdn( if !mdn_already_in_table { context.sql.execute( "INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);", - paramsv![msg_id, from_id as i32, timestamp_sent], + paramsx![msg_id, from_id as i32, timestamp_sent], ) .await .unwrap_or_default(); // TODO: better error handling @@ -1356,15 +1286,16 @@ pub async fn handle_mdn( read_by_all = true; } else { // send event about new state - let ist_cnt = context + let ist_cnt: i32 = context .sql - .query_get_value::( - context, + .query_value( "SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;", - paramsv![msg_id], + paramsx![msg_id], ) .await - .unwrap_or_default() as usize; + .unwrap_or_default(); + let ist_cnt = ist_cnt as usize; + /* Groupsize: Min. MDNs @@ -1405,25 +1336,18 @@ pub(crate) async fn handle_ndn( return; } - let res = context + let res: Result<(MsgId, ChatId, Chattype), _> = context .sql .query_row( - concat!( - "SELECT", - " m.id AS msg_id,", - " c.id AS chat_id,", - " c.type AS type", - " FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id", - " WHERE rfc724_mid=? AND from_id=1", - ), - paramsv![failed.rfc724_mid], - |row| { - Ok(( - row.get::<_, MsgId>("msg_id")?, - row.get::<_, ChatId>("chat_id")?, - row.get::<_, Chattype>("type")?, - )) - }, + r#" +SELECT + m.id AS msg_id, + c.id AS chat_id, + c.type AS type + FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id + WHERE rfc724_mid=? AND from_id=1 +"#, + paramsx![&failed.rfc724_mid], ) .await; if let Err(ref err) = res { @@ -1461,12 +1385,13 @@ pub(crate) async fn handle_ndn( pub async fn get_real_msg_cnt(context: &Context) -> i32 { match context .sql - .query_row( - "SELECT COUNT(*) \ - FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ - WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;", - paramsv![], - |row| row.get(0), + .query_value( + r#" +SELECT COUNT(*) + FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id + WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0; +"#, + paramsx![], ) .await { @@ -1479,17 +1404,17 @@ pub async fn get_real_msg_cnt(context: &Context) -> i32 { } pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize { - match context + let res: Result = context .sql - .query_row( - "SELECT COUNT(*) \ - FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ - WHERE c.blocked=2;", - paramsv![], - |row| row.get::<_, isize>(0), + .query_value( + r#" +SELECT COUNT(*) + FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id + WHERE c.blocked=2;"#, + paramsx![], ) - .await - { + .await; + match res { Ok(res) => res as usize, Err(err) => { error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err); @@ -1509,37 +1434,39 @@ pub async fn estimate_deletion_cnt( .0; let threshold_timestamp = time() - seconds; - let cnt: isize = if from_server { + let cnt: i32 = if from_server { context .sql - .query_row( - "SELECT COUNT(*) + .query_value( + r#"SELECT COUNT(*) FROM msgs m WHERE m.id > ? AND timestamp < ? AND chat_id != ? - AND server_uid != 0;", - paramsv![DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id], - |row| row.get(0), + AND server_uid != 0;"#, + paramsx![ + DC_MSG_ID_LAST_SPECIAL as i32, + threshold_timestamp, + self_chat_id + ], ) .await? } else { context .sql - .query_row( - "SELECT COUNT(*) + .query_value( + r#"SELECT COUNT(*) FROM msgs m WHERE m.id > ? AND timestamp < ? AND chat_id != ? - AND chat_id != ? AND hidden = 0;", - paramsv![ - DC_MSG_ID_LAST_SPECIAL, + AND chat_id != ? AND hidden = 0;"#, + paramsx![ + DC_MSG_ID_LAST_SPECIAL as i32, threshold_timestamp, self_chat_id, ChatId::new(DC_CHAT_ID_TRASH) ], - |row| row.get(0), ) .await? }; @@ -1554,10 +1481,9 @@ pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 { // check the number of messages with the same rfc724_mid match context .sql - .query_row( + .query_value( "SELECT COUNT(*) FROM msgs WHERE rfc724_mid=? AND NOT server_uid = 0", - paramsv![rfc724_mid], - |row| row.get(0), + paramsx![rfc724_mid], ) .await { @@ -1578,22 +1504,15 @@ pub(crate) async fn rfc724_mid_exists( return Ok(None); } - let res = context + let res: Option<(Option, i32, MsgId)> = context .sql .query_row_optional( "SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?", - paramsv![rfc724_mid], - |row| { - let server_folder = row.get::<_, Option>(0)?.unwrap_or_default(); - let server_uid = row.get(1)?; - let msg_id: MsgId = row.get(2)?; - - Ok((server_folder, server_uid, msg_id)) - }, + paramsx![rfc724_mid], ) .await?; - Ok(res) + Ok(res.map(|(a, b, c)| (a.unwrap_or_default(), b as u32, c))) } pub async fn update_server_uid( @@ -1605,9 +1524,8 @@ pub async fn update_server_uid( match context .sql .execute( - "UPDATE msgs SET server_folder=?, server_uid=? \ - WHERE rfc724_mid=?", - paramsv![server_folder.as_ref(), server_uid, rfc724_mid], + "UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?", + paramsx![server_folder.as_ref(), server_uid as i32, rfc724_mid], ) .await { diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 1e674e560..1f6b28355 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -1,3 +1,4 @@ +use async_std::prelude::*; use chrono::TimeZone; use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder}; @@ -87,30 +88,26 @@ impl<'a, 'b> MimeFactory<'a, 'b> { if chat.is_self_talk() { recipients.push((from_displayname.to_string(), from_addr.to_string())); } else { - context - .sql - .query_map( - "SELECT c.authname, c.addr \ - FROM chats_contacts cc \ - LEFT JOIN contacts c ON cc.contact_id=c.id \ - WHERE cc.chat_id=? AND cc.contact_id>9;", - paramsv![msg.chat_id], - |row| { - let authname: String = row.get(0)?; - let addr: String = row.get(1)?; - Ok((authname, addr)) - }, - |rows| { - for row in rows { - let (authname, addr) = row?; - if !recipients_contain_addr(&recipients, &addr) { - recipients.push((authname, addr)); - } - } - Ok(()) - }, - ) - .await?; + let pool = context.sql.get_pool().await?; + + let mut rows = sqlx::query_as( + r#" +SELECT c.authname, c.addr + FROM chats_contacts cc + LEFT JOIN contacts c ON cc.contact_id=c.id + WHERE cc.chat_id=? AND cc.contact_id>9; +"#, + ) + .bind(msg.chat_id) + .fetch(&pool); + + while let Some(row) = rows.next().await { + let (authname, addr): (String, String) = row?; + + if !recipients_contain_addr(&recipients, &addr) { + recipients.push((authname, addr)); + } + } let command = msg.param.get_cmd(); @@ -125,18 +122,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .sql .query_row( "SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?", - paramsv![msg.id], - |row| { - let in_reply_to: String = row.get(0)?; - let references: String = row.get(1)?; - - Ok(( - render_rfc724_mid_list(&in_reply_to), - render_rfc724_mid_list(&references), - )) - }, + paramsx![msg.id], ) - .await?; + .await + .map(|(in_reply_to, references): (String, String)| { + ( + render_rfc724_mid_list(&in_reply_to), + render_rfc724_mid_list(&references), + ) + })?; let default_str = context .stock_str(StockMessage::StatusLine) @@ -211,7 +205,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { Ok(res) } - async fn peerstates_for_recipients(&self) -> Result>, &str)>, Error> { + async fn peerstates_for_recipients(&self) -> Result, &str)>, Error> { let self_addr = self .context .get_config(Config::ConfiguredAddr) @@ -225,7 +219,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .filter(|(_, addr)| addr != &self_addr) { res.push(( - Peerstate::from_addr(self.context, addr).await, + Peerstate::from_addr(self.context, addr).await.ok(), addr.as_str(), )); } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 189956316..aedce795e 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet}; use std::future::Future; use std::pin::Pin; -use deltachat_derive::{FromSql, ToSql}; +use deltachat_derive::*; use lazy_static::lazy_static; use lettre_email::mime::{self, Mime}; use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo}; @@ -64,7 +64,7 @@ pub(crate) enum AvatarAction { Change(String), } -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)] #[repr(i32)] pub enum SystemMessage { Unknown = 0, @@ -989,12 +989,12 @@ async fn update_gossip_peerstates( .iter() .any(|info| info.addr == header.addr.to_lowercase()) { - let mut peerstate = Peerstate::from_addr(context, &header.addr).await; + let mut peerstate = Peerstate::from_addr(context, &header.addr).await.ok(); if let Some(ref mut peerstate) = peerstate { peerstate.apply_gossip(header, message_time); peerstate.save_to_db(&context.sql, false).await?; } else { - let p = Peerstate::from_gossip(context, header, message_time); + let p = Peerstate::from_gossip(header, message_time); p.save_to_db(&context.sql, true).await?; peerstate = Some(p); } diff --git a/src/oauth2.rs b/src/oauth2.rs index 4f59f55d3..77262c434 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -95,10 +95,7 @@ pub async fn dc_get_oauth2_access_token( // read generated token if !regenerate && !is_expired(context).await { - let access_token = context - .sql - .get_raw_config(context, "oauth2_access_token") - .await; + let access_token = context.sql.get_raw_config("oauth2_access_token").await; if access_token.is_some() { // success return access_token; @@ -106,13 +103,10 @@ pub async fn dc_get_oauth2_access_token( } // generate new token: build & call auth url - let refresh_token = context - .sql - .get_raw_config(context, "oauth2_refresh_token") - .await; + let refresh_token = context.sql.get_raw_config("oauth2_refresh_token").await; let refresh_token_for = context .sql - .get_raw_config(context, "oauth2_refresh_token_for") + .get_raw_config("oauth2_refresh_token_for") .await .unwrap_or_else(|| "unset".into()); @@ -122,7 +116,7 @@ pub async fn dc_get_oauth2_access_token( ( context .sql - .get_raw_config(context, "oauth2_pending_redirect_uri") + .get_raw_config("oauth2_pending_redirect_uri") .await .unwrap_or_else(|| "unset".into()), oauth2.init_token, @@ -136,7 +130,7 @@ pub async fn dc_get_oauth2_access_token( ( context .sql - .get_raw_config(context, "oauth2_redirect_uri") + .get_raw_config("oauth2_redirect_uri") .await .unwrap_or_else(|| "unset".into()), oauth2.refresh_token, @@ -360,7 +354,7 @@ impl Oauth2 { async fn is_expired(context: &Context) -> bool { let expire_timestamp = context .sql - .get_raw_config_int64(context, "oauth2_timestamp_expires") + .get_raw_config_int64("oauth2_timestamp_expires") .await .unwrap_or_default(); diff --git a/src/peerstate.rs b/src/peerstate.rs index 8df0c7b4f..923ff2464 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -1,8 +1,8 @@ //! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module -use std::collections::HashSet; -use std::fmt; -use num_traits::FromPrimitive; +use std::collections::HashSet; + +use anyhow::Result; use crate::aheader::*; use crate::context::Context; @@ -24,8 +24,8 @@ pub enum PeerstateVerifiedStatus { } /// Peerstate represents the state of an Autocrypt peer. -pub struct Peerstate<'a> { - pub context: &'a Context, +#[derive(Debug, PartialEq, Eq)] +pub struct Peerstate { pub addr: String, pub last_seen: i64, pub last_seen_autocrypt: i64, @@ -41,43 +41,49 @@ pub struct Peerstate<'a> { pub degrade_event: Option, } -impl<'a> PartialEq for Peerstate<'a> { - fn eq(&self, other: &Peerstate) -> bool { - self.addr == other.addr - && self.last_seen == other.last_seen - && self.last_seen_autocrypt == other.last_seen_autocrypt - && self.prefer_encrypt == other.prefer_encrypt - && self.public_key == other.public_key - && self.public_key_fingerprint == other.public_key_fingerprint - && self.gossip_key == other.gossip_key - && self.gossip_timestamp == other.gossip_timestamp - && self.gossip_key_fingerprint == other.gossip_key_fingerprint - && self.verified_key == other.verified_key - && self.verified_key_fingerprint == other.verified_key_fingerprint - && self.to_save == other.to_save - && self.degrade_event == other.degrade_event - } -} +impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Peerstate { + fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result { + use sqlx::Row; -impl<'a> Eq for Peerstate<'a> {} + let mut res = Self::new(row.try_get("addr")?); -impl<'a> fmt::Debug for Peerstate<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Peerstate") - .field("addr", &self.addr) - .field("last_seen", &self.last_seen) - .field("last_seen_autocrypt", &self.last_seen_autocrypt) - .field("prefer_encrypt", &self.prefer_encrypt) - .field("public_key", &self.public_key) - .field("public_key_fingerprint", &self.public_key_fingerprint) - .field("gossip_key", &self.gossip_key) - .field("gossip_timestamp", &self.gossip_timestamp) - .field("gossip_key_fingerprint", &self.gossip_key_fingerprint) - .field("verified_key", &self.verified_key) - .field("verified_key_fingerprint", &self.verified_key_fingerprint) - .field("to_save", &self.to_save) - .field("degrade_event", &self.degrade_event) - .finish() + res.last_seen = row.try_get("last_seen")?; + res.last_seen_autocrypt = row.try_get("last_seen_autocrypt")?; + res.prefer_encrypt = row.try_get("prefer_encrypted")?; + res.gossip_timestamp = row.try_get("gossip_timestamp")?; + + res.public_key_fingerprint = row + .try_get::, _>("public_key_fingerprint")? + .map(|fp| fp.parse::()) + .transpose() + .map_err(|err| sqlx::Error::Decode(Box::new(err)))?; + res.gossip_key_fingerprint = row + .try_get::, _>("gossip_key_fingerprint")? + .map(|fp| fp.parse::()) + .transpose() + .map_err(|err| sqlx::Error::Decode(Box::new(err)))?; + res.verified_key_fingerprint = row + .try_get::, _>("verified_key_fingerprint")? + .map(|fp| fp.parse::()) + .transpose() + .map_err(|err| sqlx::Error::Decode(Box::new(err)))?; + res.public_key = row + .try_get::, _>("public_key")? + .map(|blob| SignedPublicKey::from_slice(blob)) + .transpose() + .map_err(|err| sqlx::Error::Decode(Box::new(err)))?; + res.gossip_key = row + .try_get::, _>("gossip_key")? + .map(|blob| SignedPublicKey::from_slice(blob)) + .transpose() + .map_err(|err| sqlx::Error::Decode(Box::new(err)))?; + res.verified_key = row + .try_get::, _>("verified_key")? + .map(|blob| SignedPublicKey::from_slice(blob)) + .transpose() + .map_err(|err| sqlx::Error::Decode(Box::new(err)))?; + + Ok(res) } } @@ -98,10 +104,9 @@ pub enum DegradeEvent { FingerprintChanged = 0x02, } -impl<'a> Peerstate<'a> { - pub fn new(context: &'a Context, addr: String) -> Self { +impl Peerstate { + pub fn new(addr: String) -> Self { Peerstate { - context, addr, last_seen: 0, last_seen_autocrypt: 0, @@ -118,8 +123,8 @@ impl<'a> Peerstate<'a> { } } - pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self { - let mut res = Self::new(context, header.addr.clone()); + pub fn from_header(header: &Aheader, message_time: i64) -> Self { + let mut res = Self::new(header.addr.clone()); res.last_seen = message_time; res.last_seen_autocrypt = message_time; @@ -131,8 +136,8 @@ impl<'a> Peerstate<'a> { res } - pub fn from_gossip(context: &'a Context, gossip_header: &Aheader, message_time: i64) -> Self { - let mut res = Self::new(context, gossip_header.addr.clone()); + pub fn from_gossip(gossip_header: &Aheader, message_time: i64) -> Self { + let mut res = Self::new(gossip_header.addr.clone()); res.gossip_timestamp = message_time; res.to_save = Some(ToSave::All); @@ -142,76 +147,53 @@ impl<'a> Peerstate<'a> { res } - pub async fn from_addr(context: &'a Context, addr: &str) -> Option> { - let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;"; - Self::from_stmt(context, query, paramsv![addr]).await + pub async fn from_addr(context: &Context, addr: &str) -> Result { + let query = r#" +SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, + gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, + verified_key, verified_key_fingerprint + FROM acpeerstates + WHERE addr=? COLLATE NOCASE; +"#; + Self::from_stmt(context, query, paramsx![addr]).await } pub async fn from_fingerprint( - context: &'a Context, - _sql: &Sql, + context: &Context, fingerprint: &Fingerprint, - ) -> Option> { - let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \ - gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \ - verified_key, verified_key_fingerprint \ - FROM acpeerstates \ - WHERE public_key_fingerprint=? COLLATE NOCASE \ - OR gossip_key_fingerprint=? COLLATE NOCASE \ - ORDER BY public_key_fingerprint=? DESC;"; - let fp = fingerprint.hex(); - Self::from_stmt(context, query, paramsv![fp, fp, fp]).await + ) -> Result { + let query = r#" +SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, + gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, + verified_key, verified_key_fingerprint + FROM acpeerstates + WHERE public_key_fingerprint=? COLLATE NOCASE + OR gossip_key_fingerprint=? COLLATE NOCASE + ORDER BY public_key_fingerprint=? DESC; +"#; + + let fingerprint = fingerprint.hex(); + Self::from_stmt( + context, + query, + paramsx![&fingerprint, &fingerprint, &fingerprint], + ) + .await } - async fn from_stmt( - context: &'a Context, - query: &str, - params: Vec<&dyn crate::ToSql>, - ) -> Option> { - context - .sql - .query_row(query, params, |row| { - /* all the above queries start with this: SELECT - addr, last_seen, last_seen_autocrypt, prefer_encrypted, - public_key, gossip_timestamp, gossip_key, public_key_fingerprint, - gossip_key_fingerprint, verified_key, verified_key_fingerprint - */ - let mut res = Self::new(context, row.get(0)?); + async fn from_stmt<'a, P: sqlx::IntoArguments<'a, sqlx::sqlite::Sqlite> + 'a>( + context: &Context, + query: &'a str, + params: P, + ) -> Result { + /* all the above queries start with this: SELECT + addr, last_seen, last_seen_autocrypt, prefer_encrypted, + public_key, gossip_timestamp, gossip_key, public_key_fingerprint, + gossip_key_fingerprint, verified_key, verified_key_fingerprint + */ + let peerstate = context.sql.query_row(query, params).await?; - res.last_seen = row.get(1)?; - res.last_seen_autocrypt = row.get(2)?; - res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default(); - res.gossip_timestamp = row.get(5)?; - - res.public_key_fingerprint = row - .get::<_, Option>(7)? - .map(|s| s.parse::()) - .transpose()?; - res.gossip_key_fingerprint = row - .get::<_, Option>(8)? - .map(|s| s.parse::()) - .transpose()?; - res.verified_key_fingerprint = row - .get::<_, Option>(10)? - .map(|s| s.parse::()) - .transpose()?; - res.public_key = row - .get(4) - .ok() - .and_then(|blob: Vec| SignedPublicKey::from_slice(&blob).ok()); - res.gossip_key = row - .get(6) - .ok() - .and_then(|blob: Vec| SignedPublicKey::from_slice(&blob).ok()); - res.verified_key = row - .get(9) - .ok() - .and_then(|blob: Vec| SignedPublicKey::from_slice(&blob).ok()); - - Ok(res) - }) - .await - .ok() + Ok(peerstate) } pub fn recalc_fingerprint(&mut self) { @@ -403,19 +385,21 @@ impl<'a> Peerstate<'a> { if create { sql.execute( "INSERT INTO acpeerstates (addr) VALUES(?);", - paramsv![self.addr], + paramsx![&self.addr], ) .await?; } if self.to_save == Some(ToSave::All) || create { sql.execute( - "UPDATE acpeerstates \ - SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \ - public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \ - verified_key=?, verified_key_fingerprint=? \ - WHERE addr=?;", - paramsv![ + r#" +UPDATE acpeerstates + SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, + public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, + verified_key=?, verified_key_fingerprint=? + WHERE addr=?; +"#, + paramsx![ self.last_seen, self.last_seen_autocrypt, self.prefer_encrypt as i64, @@ -426,18 +410,17 @@ impl<'a> Peerstate<'a> { self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()), self.verified_key.as_ref().map(|k| k.to_bytes()), self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()), - self.addr, + &self.addr ], ).await?; } else if self.to_save == Some(ToSave::Timestamps) { sql.execute( - "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \ - WHERE addr=?;", - paramsv![ + "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? WHERE addr=?;", + paramsx![ self.last_seen, self.last_seen_autocrypt, self.gossip_timestamp, - self.addr + &self.addr ], ) .await?; @@ -455,18 +438,11 @@ impl<'a> Peerstate<'a> { } } -impl From for rusqlite::Error { - fn from(_source: crate::key::FingerprintError) -> Self { - Self::InvalidColumnType(0, "Invalid fingerprint".into(), rusqlite::types::Type::Text) - } -} - #[cfg(test)] mod tests { use super::*; use crate::test_utils::*; use pretty_assertions::assert_eq; - use tempfile::TempDir; #[async_std::test] async fn test_peerstate_save_to_db() { @@ -476,7 +452,6 @@ mod tests { let pub_key = alice_keypair().public; let mut peerstate = Peerstate { - context: &ctx.ctx, addr: addr.into(), last_seen: 10, last_seen_autocrypt: 11, @@ -504,10 +479,9 @@ mod tests { // clear to_save, as that is not persissted peerstate.to_save = None; assert_eq!(peerstate, peerstate_new); - let peerstate_new2 = - Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint()) - .await - .expect("failed to load peerstate from db"); + let peerstate_new2 = Peerstate::from_fingerprint(&ctx.ctx, &pub_key.fingerprint()) + .await + .expect("failed to load peerstate from db"); assert_eq!(peerstate, peerstate_new2); } @@ -518,7 +492,6 @@ mod tests { let pub_key = alice_keypair().public; let peerstate = Peerstate { - context: &ctx.ctx, addr: addr.into(), last_seen: 10, last_seen_autocrypt: 11, @@ -552,7 +525,6 @@ mod tests { let pub_key = alice_keypair().public; let mut peerstate = Peerstate { - context: &ctx.ctx, addr: addr.into(), last_seen: 10, last_seen_autocrypt: 11, @@ -581,11 +553,4 @@ mod tests { peerstate.to_save = None; assert_eq!(peerstate, peerstate_new); } - - // TODO: don't copy this from stress.rs - #[allow(dead_code)] - struct TestContext { - ctx: Context, - dir: TempDir, - } } diff --git a/src/qr.rs b/src/qr.rs index 8509e9f16..2b2b642ab 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -138,10 +138,10 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot { let mut lot = Lot::new(); // retrieve known state for this fingerprint - let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint).await; + let peerstate = Peerstate::from_fingerprint(context, &fingerprint).await; if invitenumber.is_none() || auth.is_none() { - if let Some(peerstate) = peerstate { + if let Ok(peerstate) = peerstate { lot.state = LotState::QrFprOk; lot.id = Contact::add_or_lookup( diff --git a/src/securejoin.rs b/src/securejoin.rs index 2dd97559a..3591cf7c3 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -152,81 +152,69 @@ async fn get_self_fingerprint(context: &Context) -> Option { } } -async fn cleanup( - context: &Context, - contact_chat_id: ChatId, - ongoing_allocated: bool, - join_vg: bool, -) -> ChatId { - let mut bob = context.bob.write().await; - bob.expects = 0; - let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { - if join_vg { - chat::get_chat_id_by_grpid( - context, - bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), - ) - .await - .unwrap_or((ChatId::new(0), false, Blocked::Not)) - .0 - } else { - contact_chat_id - } - } else { - ChatId::new(0) - }; - bob.qr_scan = None; - - if ongoing_allocated { - context.free_ongoing().await; - } - ret_chat_id -} - /// Take a scanned QR-code and do the setup-contact/join-group handshake. /// See the ffi-documentation for more details. pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { - if context.alloc_ongoing().await.is_err() { - return cleanup(&context, ChatId::new(0), false, false).await; + if let Err(err) = context.alloc_ongoing().await { + error!(context, "SecureJoin Error: {}", err); + return ChatId::new(0); } - securejoin(context, qr).await + match securejoin(context, qr).await { + Ok(id) => id, + Err(err) => { + error!(context, "SecureJoin Error: {}", err); + ChatId::new(0) + } + } } -async fn securejoin(context: &Context, qr: &str) -> ChatId { - /*======================================================== - ==== Bob - the joiner's side ===== - ==== Step 2 in "Setup verified contact" protocol ===== - ========================================================*/ +/// Bob - the joiner's side +/// Step 2 in "Setup verified contact" protocol +async fn securejoin(context: &Context, qr: &str) -> Result { + struct DropGuard<'a> { + context: &'a Context, + } - let mut contact_chat_id = ChatId::new(0); - let mut join_vg: bool = false; + impl Drop for DropGuard<'_> { + fn drop(&mut self) { + async_std::task::block_on(async { + let mut bob = self.context.bob.write().await; + bob.expects = 0; + bob.qr_scan = None; + self.context.free_ongoing().await; + }); + } + } - info!(context, "Requesting secure-join ...",); + let _guard = DropGuard { context: &context }; + + info!(context, "Requesting secure-join ..."); ensure_secret_key_exists(context).await.ok(); let qr_scan = check_qr(context, &qr).await; if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup { - error!(context, "Unknown QR code.",); - return cleanup(&context, contact_chat_id, true, join_vg).await; + bail!("Unknown QR code."); } - contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await { + let contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await { Ok(chat_id) => chat_id, Err(_) => { - error!(context, "Unknown contact."); - return cleanup(&context, contact_chat_id, true, join_vg).await; + bail!("Unknown contact."); } }; + if context.shall_stop_ongoing().await { - return cleanup(&context, contact_chat_id, true, join_vg).await; + bail!("Interrupted"); } - join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup; + + let join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup; { let mut bob = context.bob.write().await; bob.status = 0; bob.qr_scan = Some(qr_scan); } - if fingerprint_equals_sender( + + let fp_equals_sender = fingerprint_equals_sender( context, context .bob @@ -240,21 +228,22 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { .unwrap(), contact_chat_id, ) - .await - { + .await?; + + if fp_equals_sender { // the scanned fingerprint matches Alice's key, // we can proceed to step 4b) directly and save two mails info!(context, "Taking protocol shortcut."); context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM; joiner_progress!( context, - chat_id_2_contact_id(context, contact_chat_id).await, + chat_id_2_contact_id(context, contact_chat_id).await?, 400 ); let own_fingerprint = get_self_fingerprint(context).await; // Bob -> Alice - if let Err(err) = send_handshake_msg( + send_handshake_msg( context, contact_chat_id, if join_vg { @@ -270,16 +259,12 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { "".to_string() }, ) - .await - { - error!(context, "failed to send handshake message: {}", err); - return cleanup(&context, contact_chat_id, true, join_vg).await; - } + .await?; } else { context.bob.write().await.expects = DC_VC_AUTH_REQUIRED; // Bob -> Alice - if let Err(err) = send_handshake_msg( + send_handshake_msg( context, contact_chat_id, if join_vg { "vg-request" } else { "vc-request" }, @@ -287,11 +272,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { None, "", ) - .await - { - error!(context, "failed to send handshake message: {}", err); - return cleanup(&context, contact_chat_id, true, join_vg).await; - } + .await?; } if join_vg { @@ -299,12 +280,23 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId { while !context.shall_stop_ongoing().await { async_std::task::sleep(Duration::from_millis(50)).await; } - cleanup(&context, contact_chat_id, true, join_vg).await + let bob = context.bob.read().await; + if bob.status == DC_BOB_SUCCESS { + let id = chat::get_chat_id_by_grpid( + context, + bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), + ) + .await? + .0; + Ok(id) + } else { + bail!("Failed to join"); + } } else { // for a one-to-one-chat, the chat is already known, return the chat-id, // the verification runs in background context.free_ongoing().await; - contact_chat_id + Ok(contact_chat_id) } } @@ -351,12 +343,12 @@ async fn send_handshake_msg( Ok(()) } -async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { - let contacts = chat::get_chat_contacts(context, contact_chat_id).await; +async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> Result { + let contacts = chat::get_chat_contacts(context, contact_chat_id).await?; if contacts.len() == 1 { - contacts[0] + Ok(contacts[0]) } else { - 0 + Ok(0) } } @@ -364,22 +356,24 @@ async fn fingerprint_equals_sender( context: &Context, fingerprint: &Fingerprint, contact_chat_id: ChatId, -) -> bool { - let contacts = chat::get_chat_contacts(context, contact_chat_id).await; +) -> Result { + let contacts = chat::get_chat_contacts(context, contact_chat_id).await?; if contacts.len() == 1 { if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await { - if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await { + if let Ok(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await { if peerstate.public_key_fingerprint.is_some() && fingerprint == peerstate.public_key_fingerprint.as_ref().unwrap() { - return true; + return Ok(true); } } } } - false + + Ok(false) } + #[derive(Debug, thiserror::Error)] pub(crate) enum HandshakeError { #[error("Can not be called with special contact ID")] @@ -400,6 +394,8 @@ pub(crate) enum HandshakeError { MsgSendFailed(#[source] Error), #[error("Failed to parse fingerprint")] BadFingerprint(#[from] crate::key::FingerprintError), + #[error("{0}")] + Other(#[from] Error), } /// What to do with a Secure-Join handshake message after it was handled. @@ -533,20 +529,20 @@ pub(crate) async fn handle_securejoin_handshake( "Not encrypted." }, ) - .await; + .await?; context.bob.write().await.status = 0; // secure-join failed context.stop_ongoing().await; return Ok(HandshakeMessage::Ignore); } if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) - .await + .await? { could_not_establish_secure_connection( context, contact_chat_id, "Fingerprint mismatch on joiner-side.", ) - .await; + .await?; context.bob.write().await.status = 0; // secure-join failed context.stop_ongoing().await; return Ok(HandshakeMessage::Ignore); @@ -589,7 +585,7 @@ pub(crate) async fn handle_securejoin_handshake( contact_chat_id, "Fingerprint not provided.", ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } }; @@ -599,16 +595,16 @@ pub(crate) async fn handle_securejoin_handshake( contact_chat_id, "Auth not encrypted.", ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } - if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await { + if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await? { could_not_establish_secure_connection( context, contact_chat_id, "Fingerprint mismatch on inviter-side.", ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } info!(context, "Fingerprint verified.",); @@ -621,13 +617,13 @@ pub(crate) async fn handle_securejoin_handshake( contact_chat_id, "Auth not provided.", ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } }; if !token::exists(context, token::Namespace::Auth, &auth_0).await { could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.") - .await; + .await?; return Ok(HandshakeMessage::Ignore); } if mark_peer_as_verified(context, &fingerprint).await.is_err() { @@ -636,12 +632,12 @@ pub(crate) async fn handle_securejoin_handshake( contact_chat_id, "Fingerprint mismatch on inviter-side.", ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await; info!(context, "Auth verified.",); - secure_connection_established(context, contact_chat_id).await; + secure_connection_established(context, contact_chat_id).await?; emit_event!(context, Event::ContactsChanged(Some(contact_id))); inviter_progress!(context, contact_id, 600); if join_vg { @@ -744,7 +740,7 @@ pub(crate) async fn handle_securejoin_handshake( contact_chat_id, "Contact confirm message not encrypted.", ) - .await; + .await?; context.bob.write().await.status = 0; return Ok(abort_retval); } @@ -758,7 +754,7 @@ pub(crate) async fn handle_securejoin_handshake( contact_chat_id, "Fingerprint mismatch on joiner-side.", ) - .await; + .await?; return Ok(abort_retval); } Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined).await; @@ -776,7 +772,7 @@ pub(crate) async fn handle_securejoin_handshake( info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); return Ok(abort_retval); } - secure_connection_established(context, contact_chat_id).await; + secure_connection_established(context, contact_chat_id).await?; context.bob.write().await.expects = 0; // Bob -> Alice @@ -901,7 +897,7 @@ pub(crate) async fn observe_securejoin_on_other_device( contact_chat_id, "Message not encrypted correctly.", ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint) @@ -913,7 +909,7 @@ pub(crate) async fn observe_securejoin_on_other_device( contact_chat_id, "Fingerprint not provided, please update Delta Chat on all your devices.", ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } }; @@ -923,7 +919,7 @@ pub(crate) async fn observe_securejoin_on_other_device( contact_chat_id, format!("Fingerprint mismatch on observing {}.", step).as_ref(), ) - .await; + .await?; return Ok(HandshakeMessage::Ignore); } Ok(if step.as_str() == "vg-member-added" { @@ -936,8 +932,11 @@ pub(crate) async fn observe_securejoin_on_other_device( } } -async fn secure_connection_established(context: &Context, contact_chat_id: ChatId) { - let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await; +async fn secure_connection_established( + context: &Context, + contact_chat_id: ChatId, +) -> Result<(), HandshakeError> { + let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await?; let contact = Contact::get_by_id(context, contact_id).await; let addr = if let Ok(ref contact) = contact { @@ -950,14 +949,16 @@ async fn secure_connection_established(context: &Context, contact_chat_id: ChatI .await; chat::add_info_msg(context, contact_chat_id, msg).await; emit_event!(context, Event::ChatModified(contact_chat_id)); + + Ok(()) } async fn could_not_establish_secure_connection( context: &Context, contact_chat_id: ChatId, details: &str, -) { - let contact_id = chat_id_2_contact_id(context, contact_chat_id).await; +) -> Result<(), HandshakeError> { + let contact_id = chat_id_2_contact_id(context, contact_chat_id).await?; let contact = Contact::get_by_id(context, contact_id).await; let msg = context .stock_string_repl_str( @@ -972,12 +973,11 @@ async fn could_not_establish_secure_connection( chat::add_info_msg(context, contact_chat_id, &msg).await; error!(context, "{} ({})", &msg, details); + Ok(()) } async fn mark_peer_as_verified(context: &Context, fingerprint: &Fingerprint) -> Result<(), Error> { - if let Some(ref mut peerstate) = - Peerstate::from_fingerprint(context, &context.sql, fingerprint).await - { + if let Ok(ref mut peerstate) = Peerstate::from_fingerprint(context, fingerprint).await { if peerstate.set_verified( PeerstateKeyType::PublicKey, fingerprint, @@ -1031,31 +1031,21 @@ fn encrypted_and_signed( } } -pub async fn handle_degrade_event( - context: &Context, - peerstate: &Peerstate<'_>, -) -> Result<(), Error> { +pub async fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> { // - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal // - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes // together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother // with things they cannot fix, so the user is just kicked from the verified group // (and he will know this and can fix this) if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event { - let contact_id: i32 = match context + let contact_id: i32 = context .sql - .query_get_value( - context, + .query_value( "SELECT id FROM contacts WHERE addr=?;", - paramsv![peerstate.addr], + paramsx![&peerstate.addr], ) - .await - { - None => bail!( - "contact with peerstate.addr {:?} not found", - &peerstate.addr - ), - Some(contact_id) => contact_id, - }; + .await?; + if contact_id > 0 { let (contact_chat_id, _) = chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop) diff --git a/src/sql/macros.rs b/src/sql/macros.rs new file mode 100644 index 000000000..4090c959e --- /dev/null +++ b/src/sql/macros.rs @@ -0,0 +1,13 @@ +#[macro_export] +macro_rules! paramsx { + () => { + sqlx::sqlite::SqliteArguments::default() + }; + ($($param:expr),+ $(,)?) => {{ + use sqlx::Arguments; + + let mut args = sqlx::sqlite::SqliteArguments::default(); + $(args.add($param);)+ + args + }}; +} diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index 35b7c3052..a846ed59a 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -13,7 +13,7 @@ pub async fn run( if dbversion < version { info!(context, "[migration] v{}", version); - sql.xexecute_batch(stmt).await?; + sql.execute_batch(stmt).await?; sql.set_raw_config_int(context, "dbversion", version) .await?; } @@ -329,10 +329,9 @@ CREATE INDEX devmsglabels_index1 ON devmsglabels (label); // records in the devmsglabels are kept when the message is deleted. // so, msg_id may or may not exist. - if dbversion < 59 { - if exists_before_update && sql.get_raw_config_int(context, "bcc_self").await.is_none() { - sql.set_raw_config_int(context, "bcc_self", 1).await?; - } + if dbversion < 59 && exists_before_update && sql.get_raw_config_int("bcc_self").await.is_none() + { + sql.set_raw_config_int(context, "bcc_self", 1).await?; } migrate( @@ -369,7 +368,7 @@ UPDATE chats SET grpid='' WHERE type=100; migrate( 64, - r" + r#" ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT ''; "#, ).await?; diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 2f72eb6f7..3b69ece1f 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,13 +1,11 @@ //! # SQLite wrapper -use async_std::prelude::*; -use async_std::sync::RwLock; - use std::collections::HashSet; use std::path::Path; -use std::time::Duration; -use rusqlite::{Connection, Error as SqlError, OpenFlags}; +use async_std::prelude::*; +use async_std::sync::RwLock; +use sqlx::sqlite::*; use crate::chat::{update_device_icon, update_saved_messages_icon}; use crate::constants::DC_CHAT_ID_TRASH; @@ -16,24 +14,14 @@ use crate::dc_tools::*; use crate::param::*; use crate::peerstate::*; -#[macro_export] -macro_rules! paramsv { - () => { - Vec::new() - }; - ($($param:expr),+ $(,)?) => { - vec![$(&$param as &dyn $crate::ToSql),+] - }; -} - +#[macro_use] +mod macros; mod migrations; +pub use macros::*; + #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("Sqlite Error: {0:?}")] - Sql(#[from] rusqlite::Error), - #[error("Sqlite Connection Pool Error: {0:?}")] - ConnectionPool(#[from] r2d2::Error), #[error("Sqlite: Connection closed")] SqlNoConnection, #[error("Sqlite: Already open")] @@ -48,6 +36,8 @@ pub enum Error { Other(#[from] crate::error::Error), #[error("{0}")] Sqlx(#[from] sqlx::Error), + #[error("{0}: {1}")] + SqlxWithContext(String, #[source] sqlx::Error), } pub type Result = std::result::Result; @@ -55,14 +45,12 @@ pub type Result = std::result::Result; /// A wrapper around the underlying Sqlite3 object. #[derive(Debug)] pub struct Sql { - pool: RwLock>>, xpool: RwLock>, } impl Default for Sql { fn default() -> Self { Self { - pool: RwLock::new(None), xpool: RwLock::new(None), } } @@ -73,14 +61,17 @@ impl Sql { Self::default() } + /// Returns `true` if there is a working sqlite connection, `false` otherwise. pub async fn is_open(&self) -> bool { - self.pool.read().await.is_some() && self.xpool.read().await.is_some() + let pool = self.xpool.read().await; + pool.is_some() && !pool.as_ref().unwrap().is_closed() } + /// Shuts down all sqlite connections. pub async fn close(&self) { - let _ = self.pool.write().await.take(); - let _ = self.xpool.write().await.take(); - // drop closes the connection + if let Some(pool) = self.xpool.write().await.take() { + pool.close().await; + } } pub async fn open>( @@ -102,37 +93,21 @@ impl Sql { Ok(()) } - pub async fn execute>( - &self, - sql: S, - params: Vec<&dyn crate::ToSql>, - ) -> Result { - let res = { - let conn = self.get_conn().await?; - conn.execute(sql.as_ref(), params) - }; - - res.map_err(Into::into) - } - - pub async fn xexecute>( - &self, - statement: S, - params: sqlx::sqlite::SqliteArguments, - ) -> Result<()> { + /// Execute a single query. + pub async fn execute<'a, P>(&self, statement: &'a str, params: P) -> Result + where + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + { let lock = self.xpool.read().await; let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; - sqlx::query(statement.as_ref()) - .bind_all(params) - .execute(xpool) - .await?; + let count = sqlx::query_with(statement, params).execute(xpool).await?; - Ok(()) + Ok(count as usize) } /// Execute a list of statements, without any bindings - pub async fn xexecute_batch>(&self, statement: S) -> Result<()> { + pub async fn execute_batch(&self, statement: &str) -> Result<()> { let lock = self.xpool.read().await; let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; @@ -141,173 +116,151 @@ impl Sql { Ok(()) } - pub async fn execute_batch>(&self, sql: S) -> Result<()> { - let res = { - let conn = self.get_conn().await?; - conn.execute_batch(sql.as_ref()) - }; - - res.map_err(Into::into) + pub async fn get_pool(&self) -> Result { + let lock = self.xpool.read().await; + lock.as_ref().cloned().ok_or_else(|| Error::SqlNoConnection) } - /// Prepares and executes the statement and maps a function over the resulting rows. - /// Then executes the second function over the returned iterator and returns the - /// result of that function. - pub async fn query_map( + + /// Starts a new transaction. + pub async fn begin( &self, - sql: impl AsRef, - params: Vec<&dyn crate::ToSql>, - f: F, - mut g: G, - ) -> Result - where - F: FnMut(&rusqlite::Row) -> rusqlite::Result, - G: FnMut(rusqlite::MappedRows) -> Result, - { - let sql = sql.as_ref(); - - let conn = self.get_conn().await?; - let mut stmt = conn.prepare(sql)?; - let res = stmt.query_map(¶ms, f)?; - g(res) - } - - pub async fn get_conn( - &self, - ) -> Result> { - let lock = self.pool.read().await; - let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; - let conn = pool.get()?; - - Ok(conn) - } - - pub async fn with_conn(&self, g: G) -> Result - where - H: Send + 'static, - G: Send - + 'static - + FnOnce(r2d2::PooledConnection) -> Result, - { - let lock = self.pool.read().await; - let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; - let conn = pool.get()?; - - g(conn) - } - - pub async fn with_conn_async(&self, mut g: G) -> Result - where - G: FnMut(r2d2::PooledConnection) -> Fut, - Fut: Future> + Send, - { - let lock = self.pool.read().await; + ) -> Result>> { + let lock = self.xpool.read().await; let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; - let conn = pool.get()?; - g(conn).await + let tx = pool.begin().await?; + Ok(tx) + } + + /// Execute a query which is expected to return zero or more rows. + pub async fn query_rows<'a, T, P>(&self, statement: &'a str, params: P) -> Result> + where + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send, + { + let lock = self.xpool.read().await; + let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + let rows = sqlx::query_with(statement.as_ref(), params) + .try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row)) + .fetch_all(xpool) + .await?; + + Ok(rows) + } + + /// Execute a query which is expected to return zero or more rows. + pub async fn query_values<'a, T, P>(&self, statement: &'a str, params: P) -> Result> + where + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + T: for<'b> sqlx::decode::Decode<'b, Sqlite>, + T: sqlx::Type, + T: 'static + Unpin + Send, + { + let lock = self.xpool.read().await; + let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + let rows = sqlx::query_with(statement.as_ref(), params) + .try_map(|row: SqliteRow| { + let (val,): (T,) = sqlx::FromRow::from_row(&row)?; + Ok(val) + }) + .fetch_all(xpool) + .await?; + + Ok(rows) } /// Return `true` if a query in the SQL statement it executes returns one or more /// rows and false if the SQL returns an empty set. - pub async fn exists(&self, sql: &str, params: Vec<&dyn crate::ToSql>) -> Result { - let res = { - let conn = self.get_conn().await?; - let mut stmt = conn.prepare(sql)?; - stmt.exists(¶ms) - }; + pub async fn exists<'a, P>(&self, statement: &'a str, params: P) -> Result + where + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + { + let lock = self.xpool.read().await; + let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; - res.map_err(Into::into) + let mut rows = sqlx::query_with(statement, params).fetch(xpool); + + match rows.next().await { + Some(Ok(_)) => Ok(true), + None => Ok(false), + Some(Err(sqlx::Error::RowNotFound)) => Ok(false), + Some(Err(err)) => Err(Error::SqlxWithContext( + format!("exists: '{}'", statement), + err, + )), + } } /// Execute a query which is expected to return one row. - pub async fn query_row( - &self, - sql: impl AsRef, - params: Vec<&dyn crate::ToSql>, - f: F, - ) -> Result + pub async fn query_row<'a, T, P>(&self, statement: &'a str, params: P) -> Result where - F: FnOnce(&rusqlite::Row) -> rusqlite::Result, + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send, { - let sql = sql.as_ref(); - let res = { - let conn = self.get_conn().await?; - conn.query_row(sql, params, f) - }; + let lock = self.xpool.read().await; + let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + let row = sqlx::query_with(statement.as_ref(), params) + .try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row)) + .fetch_one(xpool) + .await?; - res.map_err(Into::into) - } - - pub async fn table_exists(&self, name: impl AsRef) -> Result { - let name = name.as_ref().to_string(); - self.with_conn(move |conn| { - let mut exists = false; - conn.pragma(None, "table_info", &name, |_row| { - // will only be executed if the info was found - exists = true; - Ok(()) - })?; - - Ok(exists) - }) - .await + Ok(row) } /// Execute a query which is expected to return zero or one row. - pub async fn query_row_optional( + pub async fn query_row_optional<'a, T, P>( &self, - sql: impl AsRef, - params: Vec<&dyn crate::ToSql>, - f: F, + statement: &'a str, + params: P, ) -> Result> where - F: FnOnce(&rusqlite::Row) -> rusqlite::Result, + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send, { - match self.query_row(sql, params, f).await { - Ok(res) => Ok(Some(res)), - Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Ok(None), - Err(Error::Sql(rusqlite::Error::InvalidColumnType( - _, - _, - rusqlite::types::Type::Null, - ))) => Ok(None), - Err(err) => Err(err), + let lock = self.xpool.read().await; + let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + let row = sqlx::query_with(statement.as_ref(), params) + .try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row)) + .fetch_optional(xpool) + .await?; + + Ok(row) + } + + pub async fn query_value_optional<'a, T, P>( + &self, + statement: &'a str, + params: P, + ) -> Result> + where + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + T: for<'b> sqlx::decode::Decode<'b, Sqlite>, + T: sqlx::Type, + T: 'static + Unpin + Send, + { + match self.query_row_optional(statement, params).await? { + Some((val,)) => Ok(Some(val)), + None => Ok(None), } } - /// Executes a query which is expected to return one row and one - /// column. If the query does not return a value or returns SQL - /// `NULL`, returns `Ok(None)`. - pub async fn query_get_value_result( - &self, - query: &str, - params: Vec<&dyn crate::ToSql>, - ) -> Result> + pub async fn query_value<'a, T, P>(&self, statement: &'a str, params: P) -> Result where - T: rusqlite::types::FromSql, + P: sqlx::IntoArguments<'a, Sqlite> + 'a, + T: for<'b> sqlx::decode::Decode<'b, Sqlite>, + T: sqlx::Type, + T: 'static + Unpin + Send, { - self.query_row_optional(query, params, |row| row.get::<_, T>(0)) - .await + let (val,): (T,) = self.query_row(statement, params).await?; + Ok(val) } - /// Not resultified version of `query_get_value_result`. Returns - /// `None` on error. - pub async fn query_get_value( - &self, - context: &Context, - query: &str, - params: Vec<&dyn crate::ToSql>, - ) -> Option - where - T: rusqlite::types::FromSql, - { - match self.query_get_value_result(query, params).await { - Ok(res) => res, - Err(err) => { - warn!(context, "sql: Failed query_row: {}", err); - None - } - } + pub async fn table_exists(&self, name: impl AsRef) -> Result { + self.exists( + "SELECT name FROM sqlite_master WHERE type = 'table' AND name=?", + paramsx![name.as_ref()], + ) + .await } /// Set private configuration options. @@ -316,58 +269,49 @@ impl Sql { /// will already have been logged. pub async fn set_raw_config( &self, - context: &Context, + _context: &Context, key: impl AsRef, value: Option<&str>, ) -> Result<()> { - if !self.is_open().await { - error!(context, "set_raw_config(): Database not ready."); - return Err(Error::SqlNoConnection); - } - let key = key.as_ref(); - let res = if let Some(ref value) = value { + + if let Some(ref value) = value { let exists = self - .exists("SELECT value FROM config WHERE keyname=?;", paramsv![key]) + .exists("SELECT value FROM config WHERE keyname=?;", paramsx![key]) .await?; if exists { self.execute( "UPDATE config SET value=? WHERE keyname=?;", - paramsv![(*value).to_string(), key.to_string()], + paramsx![value, key], ) - .await + .await?; } else { self.execute( "INSERT INTO config (keyname, value) VALUES (?, ?);", - paramsv![key.to_string(), (*value).to_string()], + paramsx![key, value], ) - .await + .await?; } } else { - self.execute("DELETE FROM config WHERE keyname=?;", paramsv![key]) - .await - }; - - match res { - Ok(_) => Ok(()), - Err(err) => { - error!(context, "set_raw_config(): Cannot change value. {:?}", &err); - Err(err) - } + self.execute("DELETE FROM config WHERE keyname=?;", paramsx![key]) + .await?; } + + Ok(()) } /// Get configuration options from the database. - pub async fn get_raw_config(&self, context: &Context, key: impl AsRef) -> Option { + pub async fn get_raw_config(&self, key: impl AsRef) -> Option { if !self.is_open().await || key.as_ref().is_empty() { return None; } - self.query_get_value( - context, + self.query_row( "SELECT value FROM config WHERE keyname=?;", - paramsv![key.as_ref().to_string()], + paramsx![key.as_ref().to_string()], ) .await + .ok() + .map(|(res,)| res) } pub async fn set_raw_config_int( @@ -380,16 +324,14 @@ impl Sql { .await } - pub async fn get_raw_config_int(&self, context: &Context, key: impl AsRef) -> Option { - self.get_raw_config(context, key) - .await - .and_then(|s| s.parse().ok()) + pub async fn get_raw_config_int(&self, key: impl AsRef) -> Option { + self.get_raw_config(key).await.and_then(|s| s.parse().ok()) } - pub async fn get_raw_config_bool(&self, context: &Context, key: impl AsRef) -> bool { + pub async fn get_raw_config_bool(&self, key: impl AsRef) -> bool { // Not the most obvious way to encode bool as string, but it is matter // of backward compatibility. - let res = self.get_raw_config_int(context, key).await; + let res = self.get_raw_config_int(key).await; res.unwrap_or_default() > 0 } @@ -411,14 +353,8 @@ impl Sql { .await } - pub async fn get_raw_config_int64( - &self, - context: &Context, - key: impl AsRef, - ) -> Option { - self.get_raw_config(context, key) - .await - .and_then(|r| r.parse().ok()) + pub async fn get_raw_config_int64(&self, key: impl AsRef) -> Option { + self.get_raw_config(key).await.and_then(|r| r.parse().ok()) } /// Alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. @@ -426,128 +362,88 @@ impl Sql { /// eg. if a Message-ID is split into different messages. pub async fn get_rowid( &self, - _context: &Context, table: impl AsRef, field: impl AsRef, value: impl AsRef, ) -> Result { - let res = { - let mut conn = self.get_conn().await?; - get_rowid(&mut conn, table, field, value) - }; + // alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. + // the ORDER BY ensures, this function always returns the most recent id, + // eg. if a Message-ID is split into different messages. + let query = format!( + "SELECT id FROM {} WHERE {}=? ORDER BY id DESC", + table.as_ref(), + field.as_ref(), + ); - res.map_err(Into::into) + let res: i64 = self.query_value(&query, paramsx![value.as_ref()]).await?; + + Ok(res as u32) } pub async fn get_rowid2( &self, - _context: &Context, table: impl AsRef, field: impl AsRef, value: i64, field2: impl AsRef, value2: i32, ) -> Result { - let res = { - let mut conn = self.get_conn().await?; - get_rowid2(&mut conn, table, field, value, field2, value2) - }; + let query = format!( + "SELECT id FROM {} WHERE {}=? AND {}=? ORDER BY id DESC", + table.as_ref(), + field.as_ref(), + field2.as_ref(), + ); - res.map_err(Into::into) + let res: i64 = self + .query_value(query.as_ref(), paramsx![value, value2]) + .await?; + + Ok(res as u32) } } -pub fn get_rowid( - conn: &mut Connection, - table: impl AsRef, - field: impl AsRef, - value: impl AsRef, -) -> std::result::Result { - // alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. - // the ORDER BY ensures, this function always returns the most recent id, - // eg. if a Message-ID is split into different messages. - let query = format!( - "SELECT id FROM {} WHERE {}=? ORDER BY id DESC", - table.as_ref(), - field.as_ref(), - ); - - conn.query_row(&query, params![value.as_ref()], |row| row.get::<_, u32>(0)) -} - -pub fn get_rowid2( - conn: &mut Connection, - table: impl AsRef, - field: impl AsRef, - value: i64, - field2: impl AsRef, - value2: i32, -) -> std::result::Result { - conn.query_row( - &format!( - "SELECT id FROM {} WHERE {}={} AND {}={} ORDER BY id DESC", - table.as_ref(), - field.as_ref(), - value, - field2.as_ref(), - value2, - ), - params![], - |row| row.get::<_, u32>(0), - ) -} - -pub async fn housekeeping(context: &Context) { +pub async fn housekeeping(context: &Context) -> Result<()> { let mut files_in_use = HashSet::new(); - let mut unreferenced_count = 0; + let mut unreferenced_count = 0usize; info!(context, "Start housekeeping..."); maybe_add_from_param( context, &mut files_in_use, - "SELECT param FROM msgs WHERE chat_id!=3 AND type!=10;", + "SELECT param FROM msgs WHERE chat_id!=3 AND type!=10;", Param::File, ) - .await; + .await?; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM jobs;", Param::File, ) - .await; + .await?; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM chats;", Param::ProfileImage, ) - .await; + .await?; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM contacts;", Param::ProfileImage, ) - .await; + .await?; - context - .sql - .query_map( - "SELECT value FROM config;", - paramsv![], - |row| row.get::<_, String>(0), - |rows| { - for row in rows { - maybe_add_file(&mut files_in_use, row?); - } - Ok(()) - }, - ) - .await - .unwrap_or_else(|err| { - warn!(context, "sql: failed query: {}", err); - }); + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as("SELECT value FROM config;").fetch(&pool); + + while let Some(row) = rows.next().await { + let (row,): (String,) = row?; + maybe_add_file(&mut files_in_use, row); + } info!(context, "{} files in use.", files_in_use.len(),); /* go through directory and delete unused files */ @@ -622,6 +518,8 @@ pub async fn housekeeping(context: &Context) { } info!(context, "Housekeeping done.",); + + Ok(()) } fn is_file_in_use(files_in_use: &HashSet, namespc_opt: Option<&str>, name: &str) -> bool { @@ -651,27 +549,23 @@ async fn maybe_add_from_param( files_in_use: &mut HashSet, query: &str, param_id: Param, -) { - context - .sql - .query_map( - query, - paramsv![], - |row| row.get::<_, String>(0), - |rows| { - for row in rows { - let param: Params = row?.parse().unwrap_or_default(); - if let Some(file) = param.get(param_id) { - maybe_add_file(files_in_use, file); - } - } - Ok(()) - }, - ) - .await - .unwrap_or_else(|err| { - warn!(context, "sql: failed to add_from_param: {}", err); - }); +) -> Result<()> { + info!(context, "maybe_add_from_param: {}", query); + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as(query).fetch(&pool); + + while let Some(row) = rows.next().await { + let (row,): (String,) = row?; + info!(context, "param: {}", &row); + let param: Params = row.parse().unwrap_or_default(); + + if let Some(file) = param.get(param_id) { + info!(context, "got file: {:?} {}", param_id, file); + maybe_add_file(files_in_use, file); + } + } + + Ok(()) } #[allow(clippy::cognitive_complexity)] @@ -690,40 +584,13 @@ async fn open( return Err(Error::SqlAlreadyOpen.into()); } - let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX; if readonly { - open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY); - } else { - open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE); - open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE); - } - - // this actually creates min_idle database handles just now. - // therefore, with_init() must not try to modify the database as otherwise - // we easily get busy-errors (eg. table-creation, journal_mode etc. should be done on only one handle) - let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref()) - .with_flags(open_flags) - .with_init(|c| { - c.execute_batch(&format!( - "PRAGMA secure_delete=on; PRAGMA busy_timeout = {};", - Duration::from_secs(10).as_millis() - ))?; - Ok(()) - }); - let pool = r2d2::Pool::builder() - .min_idle(Some(2)) - .max_size(10) - .connection_timeout(Duration::from_secs(60)) - .build(mgr) - .map_err(Error::ConnectionPool)?; - - { - *sql.pool.write().await = Some(pool); + // TODO: readonly mode } let xpool = sqlx::SqlitePool::builder() .min_size(1) - .max_size(1) + .max_size(4) .build(&format!("sqlite://{}", dbfile.as_ref().to_string_lossy())) .await?; @@ -732,24 +599,14 @@ async fn open( } if !readonly { - // journal_mode is persisted, it is sufficient to change it only for one handle. - // (nb: execute() always returns errors for this PRAGMA call, just discard it. - // but even if execute() would handle errors more gracefully, we should continue on errors - - // systems might not be able to handle WAL, in which case the standard-journal is used. - // that may be not optimal, but better than not working at all :) - sql.execute("PRAGMA journal_mode=WAL;", paramsv![]) - .await - .ok(); - let mut exists_before_update = false; let mut dbversion_before_update: i32 = -1; if sql.table_exists("config").await? { exists_before_update = true; - dbversion_before_update = sql - .get_raw_config_int(context, "dbversion") - .await - .unwrap_or_default(); + if let Some(version) = sql.get_raw_config_int("dbversion").await { + dbversion_before_update = version; + } } // (1) update low-level database structure. @@ -776,25 +633,19 @@ async fn open( if recalc_fingerprints { info!(context, "[migration] recalc fingerprints"); - let addrs = sql - .query_map( - "SELECT addr FROM acpeerstates;", - paramsv![], - |row| row.get::<_, String>(0), - |addrs| { - addrs - .collect::, _>>() - .map_err(Into::into) - }, - ) - .await?; - for addr in &addrs { - if let Some(ref mut peerstate) = Peerstate::from_addr(context, addr).await { + let pool = context.sql.get_pool().await?; + let mut rows = sqlx::query_as("SELECT addr FROM acpeerstates;").fetch(&pool); + + while let Some(addr) = rows.next().await { + let (addr,): (String,) = addr?; + + if let Ok(ref mut peerstate) = Peerstate::from_addr(context, &addr).await { peerstate.recalc_fingerprint(); peerstate.save_to_db(sql, false).await?; } } } + if update_icons { update_saved_messages_icon(context).await?; update_device_icon(context).await?; @@ -812,10 +663,12 @@ async fn prune_tombstones(context: &Context) -> Result<()> { context .sql .execute( - "DELETE FROM msgs \ - WHERE (chat_id = ? OR hidden) \ - AND server_uid = 0", - paramsv![DC_CHAT_ID_TRASH], + r#" +DELETE FROM msgs + WHERE (chat_id = ? OR hidden) + AND server_uid = 0 +"#, + paramsx![DC_CHAT_ID_TRASH as i32], ) .await?; Ok(()) diff --git a/src/stock.rs b/src/stock.rs index 00b34fd0b..d35277f26 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -360,7 +360,7 @@ impl Context { // create saved-messages chat; // we do this only once, if the user has deleted the chat, he can recreate it manually. - if !self.sql.get_raw_config_bool(&self, "self-chat-added").await { + if !self.sql.get_raw_config_bool("self-chat-added").await { self.sql .set_raw_config_bool(&self, "self-chat-added", true) .await?; diff --git a/src/test_utils.rs b/src/test_utils.rs index ec3f6069d..b9588474b 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -27,10 +27,20 @@ impl TestContext { /// /// [Context]: crate::context::Context pub async fn new() -> Self { + pretty_env_logger::try_init_timed().ok(); + let dir = tempdir().unwrap(); let dbfile = dir.path().join("db.sqlite"); let ctx = Context::new("FakeOS".into(), dbfile.into()).await.unwrap(); - Self { ctx, dir } + let events = ctx.get_event_emitter(); + + async_std::task::spawn(async move { + while let Some(event) = events.recv().await { + log::info!("{:?}", event); + } + }); + + TestContext { ctx, dir } } /// Create a new configured [TestContext]. diff --git a/src/token.rs b/src/token.rs index b3a89d5b4..86c3f31d9 100644 --- a/src/token.rs +++ b/src/token.rs @@ -11,7 +11,7 @@ use crate::context::Context; use crate::dc_tools::*; /// Token namespace -#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] +#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)] #[repr(i32)] pub enum Namespace { Unknown = 0, @@ -34,7 +34,7 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) - .sql .execute( "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", - paramsv![namespace, foreign_id, token, time()], + paramsx![namespace, foreign_id, &token, time()], ) .await .ok(); @@ -44,12 +44,12 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) - pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option { context .sql - .query_get_value::( - context, + .query_value( "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", - paramsv![namespace, foreign_id], + paramsx![namespace, foreign_id], ) .await + .ok() } pub async fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { @@ -65,7 +65,7 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> boo .sql .exists( "SELECT id FROM tokens WHERE namespc=? AND token=?;", - paramsv![namespace, token], + paramsx![namespace, token], ) .await .unwrap_or_default()