mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 09:26:29 +03:00
sql: switch from sqlx to rusqlite
This commit is contained in:
287
Cargo.lock
generated
287
Cargo.lock
generated
@@ -113,17 +113,6 @@ version = "0.4.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ahash"
|
|
||||||
version = "0.7.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7f200cbb1e856866d9eade941cf3aa0c5d7dd36f74311c4273b494f4ef036957"
|
|
||||||
dependencies = [
|
|
||||||
"getrandom 0.2.2",
|
|
||||||
"once_cell",
|
|
||||||
"version_check 0.9.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.15"
|
version = "0.7.15"
|
||||||
@@ -425,15 +414,6 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atoi"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5"
|
|
||||||
dependencies = [
|
|
||||||
"num-traits",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic-waker"
|
name = "atomic-waker"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@@ -537,18 +517,6 @@ version = "1.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitvec"
|
|
||||||
version = "0.19.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321"
|
|
||||||
dependencies = [
|
|
||||||
"funty",
|
|
||||||
"radium",
|
|
||||||
"tap",
|
|
||||||
"wyz",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "blake2b_simd"
|
name = "blake2b_simd"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
@@ -648,12 +616,6 @@ version = "0.1.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
|
checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "build_const"
|
|
||||||
version = "0.2.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.6.1"
|
version = "3.6.1"
|
||||||
@@ -682,12 +644,6 @@ version = "1.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bytes"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cache-padded"
|
name = "cache-padded"
|
||||||
version = "1.1.1"
|
version = "1.1.1"
|
||||||
@@ -882,15 +838,6 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
|
checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "crc"
|
|
||||||
version = "1.8.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb"
|
|
||||||
dependencies = [
|
|
||||||
"build_const",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc24"
|
name = "crc24"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
@@ -1178,6 +1125,7 @@ dependencies = [
|
|||||||
"charset",
|
"charset",
|
||||||
"chrono",
|
"chrono",
|
||||||
"criterion",
|
"criterion",
|
||||||
|
"deltachat_derive",
|
||||||
"dirs 3.0.1",
|
"dirs 3.0.1",
|
||||||
"email",
|
"email",
|
||||||
"encoded-words",
|
"encoded-words",
|
||||||
@@ -1191,7 +1139,6 @@ dependencies = [
|
|||||||
"kamadak-exif",
|
"kamadak-exif",
|
||||||
"lettre_email",
|
"lettre_email",
|
||||||
"libc",
|
"libc",
|
||||||
"libsqlite3-sys",
|
|
||||||
"log",
|
"log",
|
||||||
"mailparse",
|
"mailparse",
|
||||||
"native-tls",
|
"native-tls",
|
||||||
@@ -1205,8 +1152,11 @@ dependencies = [
|
|||||||
"pretty_env_logger",
|
"pretty_env_logger",
|
||||||
"proptest",
|
"proptest",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
|
"r2d2",
|
||||||
|
"r2d2_sqlite",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rusqlite",
|
||||||
"rust-hsluv",
|
"rust-hsluv",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"sanitize-filename",
|
"sanitize-filename",
|
||||||
@@ -1215,7 +1165,6 @@ dependencies = [
|
|||||||
"sha-1",
|
"sha-1",
|
||||||
"sha2",
|
"sha2",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlx",
|
|
||||||
"stop-token",
|
"stop-token",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
@@ -1227,6 +1176,14 @@ dependencies = [
|
|||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deltachat_derive"
|
||||||
|
version = "2.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deltachat_ffi"
|
name = "deltachat_ffi"
|
||||||
version = "1.53.0"
|
version = "1.53.0"
|
||||||
@@ -1330,12 +1287,6 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dotenv"
|
|
||||||
version = "0.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -1513,6 +1464,18 @@ version = "2.5.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
|
checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59"
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "fast_chemail"
|
name = "fast_chemail"
|
||||||
version = "0.9.6"
|
version = "0.9.6"
|
||||||
@@ -1586,12 +1549,6 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "funty"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.13"
|
version = "0.3.13"
|
||||||
@@ -1782,7 +1739,7 @@ version = "0.9.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.4.7",
|
"ahash",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2132,9 +2089,9 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsqlite3-sys"
|
name = "libsqlite3-sys"
|
||||||
version = "0.22.0"
|
version = "0.20.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f6332d94daa84478d55a6aa9dbb3b305ed6500fb0cb9400cb9e1525d0e0e188"
|
checksum = "64d31059f22935e6c31830db5249ba2b7ecd54fd73a9909286f0a67aa55c2fbd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
@@ -2186,12 +2143,6 @@ dependencies = [
|
|||||||
"quoted_printable",
|
"quoted_printable",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "maplit"
|
|
||||||
version = "1.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "match_cfg"
|
name = "match_cfg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -2339,19 +2290,6 @@ dependencies = [
|
|||||||
"version_check 0.9.3",
|
"version_check 0.9.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nom"
|
|
||||||
version = "6.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2"
|
|
||||||
dependencies = [
|
|
||||||
"bitvec",
|
|
||||||
"funty",
|
|
||||||
"lexical-core",
|
|
||||||
"memchr",
|
|
||||||
"version_check 0.9.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint"
|
name = "num-bigint"
|
||||||
version = "0.2.6"
|
version = "0.2.6"
|
||||||
@@ -2863,10 +2801,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
|
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "radium"
|
name = "r2d2"
|
||||||
version = "0.5.3"
|
version = "0.8.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
|
checksum = "545c5bc2b880973c9c10e4067418407a0ccaa3091781d1671d46eb35107cb26f"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"scheduled-thread-pool",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r2d2_sqlite"
|
||||||
|
version = "0.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "227ab35ff4cbb01fa76da8f062590fe677b93c8d9e8415eb5fa981f2c1dba9d8"
|
||||||
|
dependencies = [
|
||||||
|
"r2d2",
|
||||||
|
"rusqlite",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
@@ -3109,6 +3062,21 @@ dependencies = [
|
|||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rusqlite"
|
||||||
|
version = "0.24.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d5f38ee71cbab2c827ec0ac24e76f82eca723cee92c509a65f67dee393c25112"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"fallible-iterator",
|
||||||
|
"fallible-streaming-iterator",
|
||||||
|
"hashlink",
|
||||||
|
"libsqlite3-sys",
|
||||||
|
"memchr",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-argon2"
|
name = "rust-argon2"
|
||||||
version = "0.8.3"
|
version = "0.8.3"
|
||||||
@@ -3212,6 +3180,15 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scheduled-thread-pool"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc6f74fd1204073fa02d5d5d68bec8021be4c38690b61264b2fdb48083d0e7d7"
|
||||||
|
dependencies = [
|
||||||
|
"parking_lot",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -3449,94 +3426,6 @@ version = "0.5.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlformat"
|
|
||||||
version = "0.1.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d86e3c77ff882a828346ba401a7ef4b8e440df804491c6064fe8295765de71c"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"maplit",
|
|
||||||
"nom 6.1.2",
|
|
||||||
"regex",
|
|
||||||
"unicode_categories",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlx"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "git+https://github.com/deltachat/sqlx?branch=master#36ebc7a5f7d34efc539766a8a3693513b85bef29"
|
|
||||||
dependencies = [
|
|
||||||
"sqlx-core",
|
|
||||||
"sqlx-macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlx-core"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "git+https://github.com/deltachat/sqlx?branch=master#36ebc7a5f7d34efc539766a8a3693513b85bef29"
|
|
||||||
dependencies = [
|
|
||||||
"ahash 0.7.2",
|
|
||||||
"atoi",
|
|
||||||
"bitflags",
|
|
||||||
"byteorder",
|
|
||||||
"bytes",
|
|
||||||
"crc",
|
|
||||||
"crossbeam-channel",
|
|
||||||
"crossbeam-queue 0.3.1",
|
|
||||||
"crossbeam-utils 0.8.3",
|
|
||||||
"either",
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-util",
|
|
||||||
"hashlink",
|
|
||||||
"hex",
|
|
||||||
"itoa",
|
|
||||||
"libc",
|
|
||||||
"libsqlite3-sys",
|
|
||||||
"log",
|
|
||||||
"memchr",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot",
|
|
||||||
"percent-encoding",
|
|
||||||
"sha2",
|
|
||||||
"smallvec",
|
|
||||||
"sqlformat",
|
|
||||||
"sqlx-rt",
|
|
||||||
"stringprep",
|
|
||||||
"thiserror",
|
|
||||||
"url",
|
|
||||||
"whoami",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlx-macros"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "git+https://github.com/deltachat/sqlx?branch=master#36ebc7a5f7d34efc539766a8a3693513b85bef29"
|
|
||||||
dependencies = [
|
|
||||||
"dotenv",
|
|
||||||
"either",
|
|
||||||
"futures",
|
|
||||||
"heck",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"sha2",
|
|
||||||
"sqlx-core",
|
|
||||||
"sqlx-rt",
|
|
||||||
"syn",
|
|
||||||
"url",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlx-rt"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "git+https://github.com/deltachat/sqlx?branch=master#36ebc7a5f7d34efc539766a8a3693513b85bef29"
|
|
||||||
dependencies = [
|
|
||||||
"async-native-tls",
|
|
||||||
"async-std",
|
|
||||||
"native-tls",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -3627,16 +3516,6 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
@@ -3709,12 +3588,6 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tap"
|
|
||||||
version = "1.0.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
@@ -3969,12 +3842,6 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "unicode_categories"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "universal-hash"
|
name = "universal-hash"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -4182,16 +4049,6 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "whoami"
|
|
||||||
version = "1.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e296f550993cba2c5c3eba5da0fb335562b2fa3d97b7a8ac9dc91f40a3abc70"
|
|
||||||
dependencies = [
|
|
||||||
"wasm-bindgen",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -4247,12 +4104,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wyz"
|
|
||||||
version = "0.2.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x25519-dalek"
|
name = "x25519-dalek"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ debug = 0
|
|||||||
lto = true
|
lto = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
deltachat_derive = { path = "./deltachat_derive" }
|
||||||
|
|
||||||
ansi_term = { version = "0.12.1", optional = true }
|
ansi_term = { version = "0.12.1", optional = true }
|
||||||
anyhow = "1.0.28"
|
anyhow = "1.0.28"
|
||||||
async-imap = "0.4.0"
|
async-imap = "0.4.0"
|
||||||
@@ -50,8 +52,11 @@ percent-encoding = "2.0"
|
|||||||
pgp = { version = "0.7.0", default-features = false }
|
pgp = { version = "0.7.0", default-features = false }
|
||||||
pretty_env_logger = { version = "0.4.0", optional = true }
|
pretty_env_logger = { version = "0.4.0", optional = true }
|
||||||
quick-xml = "0.18.1"
|
quick-xml = "0.18.1"
|
||||||
|
r2d2 = "0.8.5"
|
||||||
|
r2d2_sqlite = "0.17.0"
|
||||||
rand = "0.7.0"
|
rand = "0.7.0"
|
||||||
regex = "1.1.6"
|
regex = "1.1.6"
|
||||||
|
rusqlite = { version = "0.24", features = ["bundled"] }
|
||||||
rust-hsluv = "0.1.4"
|
rust-hsluv = "0.1.4"
|
||||||
rustyline = { version = "4.1.0", optional = true }
|
rustyline = { version = "4.1.0", optional = true }
|
||||||
sanitize-filename = "0.3.0"
|
sanitize-filename = "0.3.0"
|
||||||
@@ -60,9 +65,6 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
sha-1 = "0.9.3"
|
sha-1 = "0.9.3"
|
||||||
sha2 = "0.9.0"
|
sha2 = "0.9.0"
|
||||||
smallvec = "1.0.0"
|
smallvec = "1.0.0"
|
||||||
sqlx = { git = "https://github.com/deltachat/sqlx", branch = "master", features = ["runtime-async-std-native-tls", "sqlite"] }
|
|
||||||
# keep in sync with sqlx
|
|
||||||
libsqlite3-sys = { version = "0.22.0", default-features = false, features = [ "pkg-config", "vcpkg", "bundled" ] }
|
|
||||||
stop-token = { version = "0.1.1", features = ["unstable"] }
|
stop-token = { version = "0.1.1", features = ["unstable"] }
|
||||||
strum = "0.20.0"
|
strum = "0.20.0"
|
||||||
strum_macros = "0.20.1"
|
strum_macros = "0.20.1"
|
||||||
@@ -86,6 +88,7 @@ tempfile = "3.0"
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"deltachat-ffi",
|
"deltachat-ffi",
|
||||||
|
"deltachat_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
|
|||||||
13
deltachat_derive/Cargo.toml
Normal file
13
deltachat_derive/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "deltachat_derive"
|
||||||
|
version = "2.0.0"
|
||||||
|
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
syn = "1.0.13"
|
||||||
|
quote = "1.0.2"
|
||||||
47
deltachat_derive/src/lib.rs
Normal file
47
deltachat_derive/src/lib.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#![recursion_limit = "128"]
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use crate::proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
// For now, assume (not check) that these macroses are applied to enum without
|
||||||
|
// 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 {
|
||||||
|
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<rusqlite::types::ToSqlOutput> {
|
||||||
|
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<Self> {
|
||||||
|
let inner = rusqlite::types::FromSql::column_result(col)?;
|
||||||
|
if let Some(value) = num_traits::FromPrimitive::from_i64(inner) {
|
||||||
|
Ok(value)
|
||||||
|
} else {
|
||||||
|
Err(rusqlite::types::FromSqlError::OutOfRange(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
gen.into()
|
||||||
|
}
|
||||||
@@ -34,7 +34,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
|||||||
if 0 != bits & 1 {
|
if 0 != bits & 1 {
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM jobs;"))
|
.execute("DELETE FROM jobs;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("(1) Jobs reset.");
|
println!("(1) Jobs reset.");
|
||||||
@@ -42,7 +42,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
|||||||
if 0 != bits & 2 {
|
if 0 != bits & 2 {
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM acpeerstates;"))
|
.execute("DELETE FROM acpeerstates;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("(2) Peerstates reset.");
|
println!("(2) Peerstates reset.");
|
||||||
@@ -50,7 +50,7 @@ async fn reset_tables(context: &Context, bits: i32) {
|
|||||||
if 0 != bits & 4 {
|
if 0 != bits & 4 {
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM keypairs;"))
|
.execute("DELETE FROM keypairs;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("(4) Private keypairs reset.");
|
println!("(4) Private keypairs reset.");
|
||||||
@@ -58,34 +58,35 @@ async fn reset_tables(context: &Context, bits: i32) {
|
|||||||
if 0 != bits & 8 {
|
if 0 != bits & 8 {
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM contacts WHERE id>9;"))
|
.execute("DELETE FROM contacts WHERE id>9;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM chats WHERE id>9;"))
|
.execute("DELETE FROM chats WHERE id>9;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM chats_contacts;"))
|
.execute("DELETE FROM chats_contacts;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM msgs WHERE id>9;"))
|
.execute("DELETE FROM msgs WHERE id>9;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query(
|
.execute(
|
||||||
"DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';",
|
"DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';",
|
||||||
))
|
paramsv![],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
context
|
context
|
||||||
.sql()
|
.sql()
|
||||||
.execute(sqlx::query("DELETE FROM leftgrps;"))
|
.execute("DELETE FROM leftgrps;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
println!("(8) Rest but server config reset.");
|
println!("(8) Rest but server config reset.");
|
||||||
@@ -602,7 +603,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
ensure!(sel_chat.is_some(), "Failed to select chat");
|
ensure!(sel_chat.is_some(), "Failed to select chat");
|
||||||
let sel_chat = sel_chat.as_ref().unwrap();
|
let sel_chat = sel_chat.as_ref().unwrap();
|
||||||
|
|
||||||
let msglist = chat::get_chat_msgs(&context, sel_chat.get_id(), 0x1, None).await?;
|
let msglist =
|
||||||
|
chat::get_chat_msgs(&context, sel_chat.get_id(), DC_GCM_ADDDAYMARKER, None).await?;
|
||||||
let msglist: Vec<MsgId> = msglist
|
let msglist: Vec<MsgId> = msglist
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| match x {
|
.map(|x| match x {
|
||||||
|
|||||||
857
src/chat.rs
857
src/chat.rs
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
|||||||
//! # Chat list module
|
//! # Chat list module
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Result};
|
use anyhow::{bail, ensure, Result};
|
||||||
use async_std::prelude::*;
|
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::chat;
|
use crate::chat;
|
||||||
use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility};
|
use crate::chat::{update_special_chat_names, Chat, ChatId, ChatVisibility};
|
||||||
@@ -121,13 +119,17 @@ impl Chatlist {
|
|||||||
ChatId::new(0)
|
ChatId::new(0)
|
||||||
};
|
};
|
||||||
|
|
||||||
let process_row = |row: sqlx::Result<sqlx::sqlite::SqliteRow>| {
|
let process_row = |row: &rusqlite::Row| {
|
||||||
let row = row?;
|
let chat_id: ChatId = row.get(0)?;
|
||||||
let chat_id: ChatId = row.try_get(0)?;
|
let msg_id: MsgId = row.get(1).unwrap_or_default();
|
||||||
let msg_id: MsgId = row.try_get(1).unwrap_or_default();
|
|
||||||
Ok((chat_id, msg_id))
|
Ok((chat_id, msg_id))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let process_rows = |rows: rusqlite::MappedRows<_>| {
|
||||||
|
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
};
|
||||||
|
|
||||||
// select with left join and minimum:
|
// select with left join and minimum:
|
||||||
//
|
//
|
||||||
// - the inner select must use `hidden` and _not_ `m.hidden`
|
// - the inner select must use `hidden` and _not_ `m.hidden`
|
||||||
@@ -143,10 +145,10 @@ impl Chatlist {
|
|||||||
// tg do the same) for the deaddrop, however, they should
|
// tg do the same) for the deaddrop, however, they should
|
||||||
// really be hidden, however, _currently_ the deaddrop is not
|
// really be hidden, however, _currently_ the deaddrop is not
|
||||||
// shown at all permanent in the chatlist.
|
// shown at all permanent in the chatlist.
|
||||||
let mut ids: Vec<_> = if let Some(query_contact_id) = query_contact_id {
|
let mut ids = if let Some(query_contact_id) = query_contact_id {
|
||||||
// show chats shared with a given contact
|
// show chats shared with a given contact
|
||||||
context.sql.fetch(
|
context.sql.query_map(
|
||||||
sqlx::query("SELECT c.id, m.id
|
"SELECT c.id, m.id
|
||||||
FROM chats c
|
FROM chats c
|
||||||
LEFT JOIN msgs m
|
LEFT JOIN msgs m
|
||||||
ON c.id=m.chat_id
|
ON c.id=m.chat_id
|
||||||
@@ -160,9 +162,11 @@ impl Chatlist {
|
|||||||
AND c.blocked=0
|
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=?2)
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;"
|
ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||||
).bind(MessageState::OutDraft).bind(query_contact_id).bind(ChatVisibility::Pinned)
|
paramsv![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
|
||||||
).await?.map(process_row).collect::<sqlx::Result<_>>().await?
|
process_row,
|
||||||
|
process_rows,
|
||||||
|
).await?
|
||||||
} else if flag_archived_only {
|
} else if flag_archived_only {
|
||||||
// show archived chats
|
// show archived chats
|
||||||
// (this includes the archived device-chat; we could skip it,
|
// (this includes the archived device-chat; we could skip it,
|
||||||
@@ -170,8 +174,7 @@ impl Chatlist {
|
|||||||
// and adapting the number requires larger refactorings and seems not to be worth the effort)
|
// and adapting the number requires larger refactorings and seems not to be worth the effort)
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT c.id, m.id
|
"SELECT c.id, m.id
|
||||||
FROM chats c
|
FROM chats c
|
||||||
LEFT JOIN msgs m
|
LEFT JOIN msgs m
|
||||||
@@ -187,12 +190,10 @@ impl Chatlist {
|
|||||||
AND c.archived=1
|
AND c.archived=1
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||||
|
paramsv![MessageState::OutDraft],
|
||||||
|
process_row,
|
||||||
|
process_rows,
|
||||||
)
|
)
|
||||||
.bind(MessageState::OutDraft),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.map(process_row)
|
|
||||||
.collect::<sqlx::Result<_>>()
|
|
||||||
.await?
|
.await?
|
||||||
} else if let Some(query) = query {
|
} else if let Some(query) = query {
|
||||||
let query = query.trim().to_string();
|
let query = query.trim().to_string();
|
||||||
@@ -207,8 +208,7 @@ impl Chatlist {
|
|||||||
let str_like_cmd = format!("%{}%", query);
|
let str_like_cmd = format!("%{}%", query);
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT c.id, m.id
|
"SELECT c.id, m.id
|
||||||
FROM chats c
|
FROM chats c
|
||||||
LEFT JOIN msgs m
|
LEFT JOIN msgs m
|
||||||
@@ -224,14 +224,10 @@ impl Chatlist {
|
|||||||
AND c.name LIKE ?3
|
AND c.name LIKE ?3
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||||
|
paramsv![MessageState::OutDraft, skip_id, str_like_cmd],
|
||||||
|
process_row,
|
||||||
|
process_rows,
|
||||||
)
|
)
|
||||||
.bind(MessageState::OutDraft)
|
|
||||||
.bind(skip_id)
|
|
||||||
.bind(str_like_cmd),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.map(process_row)
|
|
||||||
.collect::<sqlx::Result<_>>()
|
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
// show normal chatlist
|
// show normal chatlist
|
||||||
@@ -243,8 +239,7 @@ impl Chatlist {
|
|||||||
} else {
|
} else {
|
||||||
ChatId::new(0)
|
ChatId::new(0)
|
||||||
};
|
};
|
||||||
|
let mut ids = context.sql.query_map(
|
||||||
let mut ids: Vec<_> = context.sql.fetch(sqlx::query(
|
|
||||||
"SELECT c.id, m.id
|
"SELECT c.id, m.id
|
||||||
FROM chats c
|
FROM chats c
|
||||||
LEFT JOIN msgs m
|
LEFT JOIN msgs m
|
||||||
@@ -259,15 +254,11 @@ impl Chatlist {
|
|||||||
AND c.blocked=0
|
AND c.blocked=0
|
||||||
AND NOT c.archived=?3
|
AND NOT c.archived=?3
|
||||||
GROUP BY c.id
|
GROUP BY c.id
|
||||||
ORDER BY c.id=?4 DESC, c.archived=?5 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;"
|
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],
|
||||||
.bind(MessageState::OutDraft)
|
process_row,
|
||||||
.bind(skip_id)
|
process_rows,
|
||||||
.bind(ChatVisibility::Archived)
|
).await?;
|
||||||
.bind(sort_id_up)
|
|
||||||
.bind(ChatVisibility::Pinned)
|
|
||||||
).await?.map(process_row).collect::<sqlx::Result<_>>().await?;
|
|
||||||
|
|
||||||
if !flag_no_specials {
|
if !flag_no_specials {
|
||||||
if let Some(last_deaddrop_fresh_msg_id) =
|
if let Some(last_deaddrop_fresh_msg_id) =
|
||||||
get_last_deaddrop_fresh_msg(context).await?
|
get_last_deaddrop_fresh_msg(context).await?
|
||||||
@@ -410,9 +401,10 @@ impl Chatlist {
|
|||||||
pub async fn dc_get_archived_cnt(context: &Context) -> Result<usize> {
|
pub async fn dc_get_archived_cnt(context: &Context) -> Result<usize> {
|
||||||
let count = context
|
let count = context
|
||||||
.sql
|
.sql
|
||||||
.count(sqlx::query(
|
.count(
|
||||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
||||||
))
|
paramsv![],
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
@@ -422,7 +414,8 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Result<Option<MsgId>>
|
|||||||
// sufficient as there are typically only few fresh messages.
|
// sufficient as there are typically only few fresh messages.
|
||||||
let id = context
|
let id = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query(concat!(
|
.query_get_value(
|
||||||
|
concat!(
|
||||||
"SELECT m.id",
|
"SELECT m.id",
|
||||||
" FROM msgs m",
|
" FROM msgs m",
|
||||||
" LEFT JOIN chats c",
|
" LEFT JOIN chats c",
|
||||||
@@ -431,7 +424,9 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Result<Option<MsgId>>
|
|||||||
" AND m.hidden=0",
|
" AND m.hidden=0",
|
||||||
" AND c.blocked=2",
|
" AND c.blocked=2",
|
||||||
" ORDER BY m.timestamp DESC, m.id DESC;"
|
" ORDER BY m.timestamp DESC, m.id DESC;"
|
||||||
)))
|
),
|
||||||
|
paramsv![],
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ impl Context {
|
|||||||
match key {
|
match key {
|
||||||
Config::Selfavatar => {
|
Config::Selfavatar => {
|
||||||
self.sql
|
self.sql
|
||||||
.execute(sqlx::query("UPDATE contacts SET selfavatar_sent=0;"))
|
.execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![])
|
||||||
.await?;
|
.await?;
|
||||||
self.sql
|
self.sql
|
||||||
.set_raw_config_bool("attach_selfavatar", true)
|
.set_raw_config_bool("attach_selfavatar", true)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
//! # Constants
|
//! # Constants
|
||||||
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -15,9 +16,10 @@ pub static DC_VERSION_STR: Lazy<String> = Lazy::new(|| env!("CARGO_PKG_VERSION")
|
|||||||
Eq,
|
Eq,
|
||||||
FromPrimitive,
|
FromPrimitive,
|
||||||
ToPrimitive,
|
ToPrimitive,
|
||||||
|
FromSql,
|
||||||
|
ToSql,
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
sqlx::Type,
|
|
||||||
)]
|
)]
|
||||||
#[repr(i8)]
|
#[repr(i8)]
|
||||||
pub enum Blocked {
|
pub enum Blocked {
|
||||||
@@ -32,7 +34,9 @@ impl Default for Blocked {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||||
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum ShowEmails {
|
pub enum ShowEmails {
|
||||||
Off = 0,
|
Off = 0,
|
||||||
@@ -46,7 +50,9 @@ impl Default for ShowEmails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||||
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum MediaQuality {
|
pub enum MediaQuality {
|
||||||
Balanced = 0,
|
Balanced = 0,
|
||||||
@@ -59,7 +65,9 @@ impl Default for MediaQuality {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||||
|
)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum KeyGenType {
|
pub enum KeyGenType {
|
||||||
Default = 0,
|
Default = 0,
|
||||||
@@ -73,7 +81,9 @@ impl Default for KeyGenType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||||
|
)]
|
||||||
#[repr(i8)]
|
#[repr(i8)]
|
||||||
pub enum VideochatType {
|
pub enum VideochatType {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
@@ -133,10 +143,11 @@ pub const DC_CHAT_ID_LAST_SPECIAL: ChatId = ChatId::new(9);
|
|||||||
Eq,
|
Eq,
|
||||||
FromPrimitive,
|
FromPrimitive,
|
||||||
ToPrimitive,
|
ToPrimitive,
|
||||||
|
FromSql,
|
||||||
|
ToSql,
|
||||||
IntoStaticStr,
|
IntoStaticStr,
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
sqlx::Type,
|
|
||||||
)]
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Chattype {
|
pub enum Chattype {
|
||||||
@@ -247,9 +258,10 @@ pub const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
|
|||||||
Eq,
|
Eq,
|
||||||
FromPrimitive,
|
FromPrimitive,
|
||||||
ToPrimitive,
|
ToPrimitive,
|
||||||
|
FromSql,
|
||||||
|
ToSql,
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
sqlx::Type,
|
|
||||||
)]
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Viewtype {
|
pub enum Viewtype {
|
||||||
|
|||||||
326
src/contact.rs
326
src/contact.rs
@@ -1,13 +1,13 @@
|
|||||||
//! Contacts module
|
//! Contacts module
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
use anyhow::{bail, ensure, format_err, Result};
|
use anyhow::{bail, ensure, format_err, Result};
|
||||||
use async_std::path::PathBuf;
|
use async_std::path::PathBuf;
|
||||||
use async_std::prelude::*;
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::aheader::EncryptPreference;
|
use crate::aheader::EncryptPreference;
|
||||||
use crate::chat::ChatId;
|
use crate::chat::ChatId;
|
||||||
@@ -79,7 +79,7 @@ pub struct Contact {
|
|||||||
|
|
||||||
/// Possible origins of a contact.
|
/// Possible origins of a contact.
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, sqlx::Type,
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||||
)]
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Origin {
|
pub enum Origin {
|
||||||
@@ -176,29 +176,35 @@ pub enum VerifiedStatus {
|
|||||||
|
|
||||||
impl Contact {
|
impl Contact {
|
||||||
pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
|
pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result<Self> {
|
||||||
let row = context
|
let mut contact = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query(
|
|
||||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param, c.status
|
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param, c.status
|
||||||
FROM contacts c
|
FROM contacts c
|
||||||
WHERE c.id=?;",
|
WHERE c.id=?;",
|
||||||
)
|
paramsv![contact_id as i32],
|
||||||
.bind(contact_id),
|
|row| {
|
||||||
|
let name: String = row.get(0)?;
|
||||||
|
let addr: String = row.get(1)?;
|
||||||
|
let origin: Origin = row.get(2)?;
|
||||||
|
let blocked: Option<bool> = row.get(3)?;
|
||||||
|
let authname: String = row.get(4)?;
|
||||||
|
let param: String = row.get(5)?;
|
||||||
|
let status: Option<String> = row.get(6)?;
|
||||||
|
let contact = Self {
|
||||||
|
id: contact_id,
|
||||||
|
name,
|
||||||
|
authname,
|
||||||
|
addr,
|
||||||
|
blocked: blocked.unwrap_or_default(),
|
||||||
|
origin,
|
||||||
|
param: param.parse().unwrap_or_default(),
|
||||||
|
status: status.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
Ok(contact)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut contact = Contact {
|
|
||||||
id: contact_id,
|
|
||||||
name: row.try_get(0)?,
|
|
||||||
authname: row.try_get(4)?,
|
|
||||||
addr: row.try_get(1)?,
|
|
||||||
blocked: row.try_get::<Option<i32>, _>(3)?.unwrap_or_default() != 0,
|
|
||||||
origin: row.try_get(2)?,
|
|
||||||
param: row.try_get::<String, _>(5)?.parse().unwrap_or_default(),
|
|
||||||
status: row.try_get::<Option<String>, _>(6)?.unwrap_or_default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if contact_id == DC_CONTACT_ID_SELF {
|
if contact_id == DC_CONTACT_ID_SELF {
|
||||||
contact.name = stock_str::self_msg(context).await;
|
contact.name = stock_str::self_msg(context).await;
|
||||||
contact.addr = context
|
contact.addr = context
|
||||||
@@ -213,7 +219,6 @@ impl Contact {
|
|||||||
contact.name = stock_str::device_messages(context).await;
|
contact.name = stock_str::device_messages(context).await;
|
||||||
contact.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
|
contact.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(contact)
|
Ok(contact)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,10 +290,8 @@ impl Contact {
|
|||||||
if context
|
if context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE msgs SET state=? WHERE from_id=? AND state=?;")
|
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
|
||||||
.bind(MessageState::InNoticed)
|
paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh],
|
||||||
.bind(id as i32)
|
|
||||||
.bind(MessageState::InFresh),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -322,18 +325,16 @@ impl Contact {
|
|||||||
let id = context
|
let id = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value(
|
||||||
sqlx::query(
|
|
||||||
"SELECT id FROM contacts \
|
"SELECT id FROM contacts \
|
||||||
WHERE addr=?1 COLLATE NOCASE \
|
WHERE addr=?1 COLLATE NOCASE \
|
||||||
AND id>?2 AND origin>=?3 AND blocked=0;",
|
AND id>?2 AND origin>=?3 AND blocked=0;",
|
||||||
|
paramsv![
|
||||||
|
addr_normalized,
|
||||||
|
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||||
|
min_origin as u32,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.bind(addr_normalized)
|
.await?;
|
||||||
.bind(DC_CONTACT_ID_LAST_SPECIAL)
|
|
||||||
.bind(min_origin),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,23 +434,21 @@ impl Contact {
|
|||||||
|
|
||||||
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context
|
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query(
|
|
||||||
"SELECT id, name, addr, origin, authname \
|
"SELECT id, name, addr, origin, authname \
|
||||||
FROM contacts WHERE addr=? COLLATE NOCASE;",
|
FROM contacts WHERE addr=? COLLATE NOCASE;",
|
||||||
)
|
paramsv![addr.to_string()],
|
||||||
.bind(addr.to_string()),
|
|row| {
|
||||||
)
|
let row_id: isize = row.get(0)?;
|
||||||
.await
|
let row_name: String = row.get(1)?;
|
||||||
.and_then(|row| {
|
let row_addr: String = row.get(2)?;
|
||||||
let row_id = row.try_get(0)?;
|
let row_origin: Origin = row.get(3)?;
|
||||||
let row_name: String = row.try_get(1)?;
|
let row_authname: String = row.get(4)?;
|
||||||
let row_addr: String = row.try_get(2)?;
|
|
||||||
let row_origin: Origin = row.try_get(3)?;
|
|
||||||
let row_authname: String = row.try_get(4)?;
|
|
||||||
|
|
||||||
Ok((row_id, row_name, row_addr, row_origin, row_authname))
|
Ok((row_id, row_name, row_addr, row_origin, row_authname))
|
||||||
})
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
let update_name = manual && name != row_name;
|
let update_name = manual && name != row_name;
|
||||||
let update_authname = !manual
|
let update_authname = !manual
|
||||||
@@ -458,7 +457,8 @@ impl Contact {
|
|||||||
&& (origin >= row_origin
|
&& (origin >= row_origin
|
||||||
|| origin == Origin::IncomingUnknownFrom
|
|| origin == Origin::IncomingUnknownFrom
|
||||||
|| row_authname.is_empty());
|
|| row_authname.is_empty());
|
||||||
row_id = id;
|
|
||||||
|
row_id = u32::try_from(id)?;
|
||||||
if origin as i32 >= row_origin as i32 && addr != row_addr {
|
if origin as i32 >= row_origin as i32 && addr != row_addr {
|
||||||
update_addr = true;
|
update_addr = true;
|
||||||
}
|
}
|
||||||
@@ -469,36 +469,39 @@ impl Contact {
|
|||||||
row_name
|
row_name
|
||||||
};
|
};
|
||||||
|
|
||||||
let query = sqlx::query(
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
|
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
|
||||||
)
|
paramsv![
|
||||||
.bind(&new_name)
|
new_name,
|
||||||
.bind(if update_addr {
|
if update_addr {
|
||||||
addr.to_string()
|
addr.to_string()
|
||||||
} else {
|
} else {
|
||||||
row_addr
|
row_addr
|
||||||
})
|
},
|
||||||
.bind(if origin > row_origin {
|
if origin > row_origin {
|
||||||
origin
|
origin
|
||||||
} else {
|
} else {
|
||||||
row_origin
|
row_origin
|
||||||
})
|
},
|
||||||
.bind(if update_authname {
|
if update_authname {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
} else {
|
} else {
|
||||||
row_authname
|
row_authname
|
||||||
})
|
},
|
||||||
.bind(row_id);
|
row_id
|
||||||
|
],
|
||||||
context.sql.execute(query).await.ok();
|
)
|
||||||
|
.await
|
||||||
|
.ok();
|
||||||
|
|
||||||
if update_name {
|
if update_name {
|
||||||
// Update the contact name also if it is used as a group name.
|
// Update the contact name also if it is used as a group name.
|
||||||
// This is one of the few duplicated data, however, getting the chat list is easier this way.
|
// This is one of the few duplicated data, however, getting the chat list is easier this way.
|
||||||
let chat_id = context.sql.query_get_value::<_, u32>(
|
let chat_id = context.sql.query_get_value::<i32>(
|
||||||
sqlx::query(
|
"SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
|
||||||
"SELECT id FROM chats WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)"
|
paramsv![Chattype::Single, isize::try_from(row_id)?]
|
||||||
).bind(Chattype::Single).bind(row_id)
|
|
||||||
).await?;
|
).await?;
|
||||||
if let Some(chat_id) = chat_id {
|
if let Some(chat_id) = chat_id {
|
||||||
let contact = Contact::get_by_id(context, row_id as u32).await?;
|
let contact = Contact::get_by_id(context, row_id as u32).await?;
|
||||||
@@ -506,10 +509,8 @@ impl Contact {
|
|||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE chats SET name=?1 WHERE id=?2 AND name!=?3")
|
"UPDATE chats SET name=?1 WHERE id=?2 AND name!=?3",
|
||||||
.bind(&chat_name)
|
paramsv![chat_name, chat_id, chat_name],
|
||||||
.bind(chat_id)
|
|
||||||
.bind(&chat_name),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -517,8 +518,9 @@ impl Contact {
|
|||||||
Ok(count) => {
|
Ok(count) => {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
// Chat name updated
|
// Chat name updated
|
||||||
context
|
context.emit_event(EventType::ChatModified(ChatId::new(
|
||||||
.emit_event(EventType::ChatModified(ChatId::new(chat_id)));
|
chat_id.try_into()?,
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,25 +535,25 @@ impl Contact {
|
|||||||
if let Ok(new_row_id) = context
|
if let Ok(new_row_id) = context
|
||||||
.sql
|
.sql
|
||||||
.insert(
|
.insert(
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
|
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
|
||||||
)
|
paramsv![
|
||||||
.bind(if update_name {
|
if update_name {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
})
|
},
|
||||||
.bind(&addr)
|
addr,
|
||||||
.bind(origin)
|
origin,
|
||||||
.bind(if update_authname {
|
if update_authname {
|
||||||
name.to_string()
|
name.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
}),
|
}
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
row_id = new_row_id;
|
row_id = u32::try_from(new_row_id)?;
|
||||||
sth_modified = Modifier::Created;
|
sth_modified = Modifier::Created;
|
||||||
info!(context, "added contact id={} addr={}", row_id, &addr);
|
info!(context, "added contact id={} addr={}", row_id, &addr);
|
||||||
} else {
|
} else {
|
||||||
@@ -559,7 +561,7 @@ impl Contact {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((u32::try_from(row_id)?, sth_modified))
|
Ok((row_id, sth_modified))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a number of contacts.
|
/// Add a number of contacts.
|
||||||
@@ -638,11 +640,9 @@ impl Contact {
|
|||||||
.map(|s| s.as_ref().to_string())
|
.map(|s| s.as_ref().to_string())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
);
|
);
|
||||||
|
context
|
||||||
let mut rows = context
|
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT c.id FROM contacts c \
|
"SELECT c.id FROM contacts c \
|
||||||
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
|
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
|
||||||
WHERE c.addr!=?1 \
|
WHERE c.addr!=?1 \
|
||||||
@@ -652,19 +652,23 @@ impl Contact {
|
|||||||
AND (iif(c.name='',c.authname,c.name) LIKE ?4 OR c.addr LIKE ?5) \
|
AND (iif(c.name='',c.authname,c.name) LIKE ?4 OR c.addr LIKE ?5) \
|
||||||
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
|
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
|
||||||
ORDER BY LOWER(iif(c.name='',c.authname,c.name)||c.addr),c.id;",
|
ORDER BY LOWER(iif(c.name='',c.authname,c.name)||c.addr),c.id;",
|
||||||
)
|
paramsv![
|
||||||
.bind(&self_addr)
|
self_addr,
|
||||||
.bind(DC_CONTACT_ID_LAST_SPECIAL)
|
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||||
.bind(Origin::IncomingReplyTo)
|
Origin::IncomingReplyTo,
|
||||||
.bind(&s3str_like_cmd)
|
s3str_like_cmd,
|
||||||
.bind(&s3str_like_cmd)
|
s3str_like_cmd,
|
||||||
.bind(if flag_verified_only { 0i32 } else { 1i32 }),
|
if flag_verified_only { 0i32 } else { 1i32 },
|
||||||
)
|
],
|
||||||
.await?
|
|row| row.get::<_, i32>(0),
|
||||||
.map(|row| row?.try_get(0));
|
|ids| {
|
||||||
while let Some(id) = rows.next().await {
|
for id in ids {
|
||||||
ret.push(id?);
|
ret.push(id? as u32);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let self_name = context
|
let self_name = context
|
||||||
.get_config(Config::Displayname)
|
.get_config(Config::Displayname)
|
||||||
@@ -685,27 +689,29 @@ impl Contact {
|
|||||||
} else {
|
} else {
|
||||||
add_self = true;
|
add_self = true;
|
||||||
|
|
||||||
let mut rows = context
|
context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT id FROM contacts
|
"SELECT id FROM contacts
|
||||||
WHERE addr!=?1
|
WHERE addr!=?1
|
||||||
AND id>?2
|
AND id>?2
|
||||||
AND origin>=?3
|
AND origin>=?3
|
||||||
AND blocked=0
|
AND blocked=0
|
||||||
ORDER BY LOWER(iif(name='',authname,name)||addr),id;",
|
ORDER BY LOWER(iif(name='',authname,name)||addr),id;",
|
||||||
)
|
paramsv![
|
||||||
.bind(self_addr)
|
self_addr,
|
||||||
.bind(DC_CONTACT_ID_LAST_SPECIAL)
|
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||||
.bind(Origin::IncomingReplyTo),
|
Origin::IncomingReplyTo
|
||||||
)
|
],
|
||||||
.await?
|
|row| row.get::<_, i32>(0),
|
||||||
.map(|row| row?.try_get(0));
|
|ids| {
|
||||||
|
for id in ids {
|
||||||
while let Some(id) = rows.next().await {
|
ret.push(id? as u32);
|
||||||
ret.push(id?);
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if flag_add_self && add_self {
|
if flag_add_self && add_self {
|
||||||
@@ -721,38 +727,38 @@ impl Contact {
|
|||||||
// from the users perspective,
|
// from the users perspective,
|
||||||
// there is not much difference in an email- and a mailinglist-address)
|
// there is not much difference in an email- and a mailinglist-address)
|
||||||
async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
|
async fn update_blocked_mailinglist_contacts(context: &Context) -> Result<()> {
|
||||||
let mut rows = context
|
let blocked_mailinglists = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query("SELECT name, grpid FROM chats WHERE type=? AND blocked=?;")
|
"SELECT name, grpid FROM chats WHERE type=? AND blocked=?;",
|
||||||
.bind(Chattype::Mailinglist)
|
paramsv![Chattype::Mailinglist, Blocked::Manually],
|
||||||
.bind(Blocked::Manually),
|
|row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?)),
|
||||||
|
|rows| {
|
||||||
|
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
for (name, grpid) in blocked_mailinglists {
|
||||||
while let Some(row) = rows.next().await {
|
|
||||||
let row = row?;
|
|
||||||
let name = row.try_get::<String, _>(0)?;
|
|
||||||
let grpid = row.try_get::<String, _>(1)?;
|
|
||||||
|
|
||||||
if !context
|
if !context
|
||||||
.sql
|
.sql
|
||||||
.exists(sqlx::query("SELECT COUNT(id) FROM contacts WHERE addr=?;").bind(&grpid))
|
.exists(
|
||||||
|
"SELECT COUNT(id) FROM contacts WHERE addr=?;",
|
||||||
|
paramsv![grpid],
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("INSERT INTO contacts (addr) VALUES (?);").bind(&grpid))
|
.execute("INSERT INTO contacts (addr) VALUES (?);", paramsv![grpid])
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
// always do an update in case the blocking is reset or name is changed
|
// always do an update in case the blocking is reset or name is changed
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE contacts SET name=?, origin=?, blocked=1 WHERE addr=?;")
|
"UPDATE contacts SET name=?, origin=?, blocked=1 WHERE addr=?;",
|
||||||
.bind(name)
|
paramsv![name, Origin::MailinglistAddress, grpid],
|
||||||
.bind(Origin::MailinglistAddress)
|
|
||||||
.bind(&grpid),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -763,8 +769,8 @@ impl Contact {
|
|||||||
let count = context
|
let count = context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query("SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0")
|
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
|
||||||
.bind(DC_CONTACT_ID_LAST_SPECIAL),
|
paramsv![DC_CONTACT_ID_LAST_SPECIAL],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(count as usize)
|
Ok(count as usize)
|
||||||
@@ -781,16 +787,16 @@ impl Contact {
|
|||||||
|
|
||||||
let list = context
|
let list = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(iif(name='',authname,name)||addr),id;",
|
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(iif(name='',authname,name)||addr),id;",
|
||||||
).bind(DC_CONTACT_ID_LAST_SPECIAL)
|
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||||
|
|row| row.get::<_, u32>(0),
|
||||||
|
|ids| {
|
||||||
|
ids.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?
|
|
||||||
.map(|row| row?.try_get::<u32, _>(0))
|
|
||||||
.collect::<sqlx::Result<Vec<_>>>()
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -877,8 +883,8 @@ impl Contact {
|
|||||||
let count_contacts = context
|
let count_contacts = context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query("SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;")
|
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
|
||||||
.bind(contact_id),
|
paramsv![contact_id as i32],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -886,9 +892,8 @@ impl Contact {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query("SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;")
|
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
|
||||||
.bind(contact_id)
|
paramsv![contact_id as i32, contact_id as i32],
|
||||||
.bind(contact_id),
|
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
@@ -898,7 +903,10 @@ impl Contact {
|
|||||||
if count_msgs == 0 {
|
if count_msgs == 0 {
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM contacts WHERE id=?;").bind(contact_id as i32))
|
.execute(
|
||||||
|
"DELETE FROM contacts WHERE id=?;",
|
||||||
|
paramsv![contact_id as i32],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
@@ -935,9 +943,8 @@ impl Contact {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE contacts SET param=? WHERE id=?")
|
"UPDATE contacts SET param=? WHERE id=?",
|
||||||
.bind(self.param.to_string())
|
paramsv![self.param.to_string(), self.id as i32],
|
||||||
.bind(self.id as i32),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -948,9 +955,8 @@ impl Contact {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE contacts SET status=? WHERE id=?")
|
"UPDATE contacts SET status=? WHERE id=?",
|
||||||
.bind(&self.status)
|
paramsv![self.status, self.id as i32],
|
||||||
.bind(self.id as i32),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1121,8 +1127,8 @@ impl Contact {
|
|||||||
let count = context
|
let count = context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query("SELECT COUNT(*) FROM contacts WHERE id>?;")
|
"SELECT COUNT(*) FROM contacts WHERE id>?;",
|
||||||
.bind(DC_CONTACT_ID_LAST_SPECIAL),
|
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(count)
|
Ok(count)
|
||||||
@@ -1135,7 +1141,10 @@ impl Contact {
|
|||||||
|
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.exists(sqlx::query("SELECT COUNT(*) FROM contacts WHERE id=?;").bind(contact_id))
|
.exists(
|
||||||
|
"SELECT COUNT(*) FROM contacts WHERE id=?;",
|
||||||
|
paramsv![contact_id as i32],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
@@ -1144,10 +1153,8 @@ impl Contact {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE contacts SET origin=? WHERE id=? AND origin<?;")
|
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
|
||||||
.bind(origin)
|
paramsv![origin, contact_id as i32, origin],
|
||||||
.bind(contact_id)
|
|
||||||
.bind(origin),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -1201,9 +1208,8 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
|
|||||||
&& context
|
&& context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE contacts SET blocked=? WHERE id=?;")
|
"UPDATE contacts SET blocked=? WHERE id=?;",
|
||||||
.bind(new_blocking as i32)
|
paramsv![new_blocking as i32, contact_id as i32],
|
||||||
.bind(contact_id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -1216,7 +1222,6 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
|
|||||||
if context
|
if context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE chats
|
UPDATE chats
|
||||||
SET blocked=?
|
SET blocked=?
|
||||||
@@ -1224,10 +1229,7 @@ WHERE type=? AND id IN (
|
|||||||
SELECT chat_id FROM chats_contacts WHERE contact_id=?
|
SELECT chat_id FROM chats_contacts WHERE contact_id=?
|
||||||
);
|
);
|
||||||
"#,
|
"#,
|
||||||
)
|
paramsv![new_blocking, Chattype::Single, contact_id],
|
||||||
.bind(new_blocking)
|
|
||||||
.bind(Chattype::Single)
|
|
||||||
.bind(contact_id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
|
|||||||
@@ -6,14 +6,12 @@ use std::ops::Deref;
|
|||||||
use std::time::{Instant, SystemTime};
|
use std::time::{Instant, SystemTime};
|
||||||
|
|
||||||
use anyhow::{bail, ensure, Result};
|
use anyhow::{bail, ensure, Result};
|
||||||
use async_std::prelude::*;
|
|
||||||
use async_std::{
|
use async_std::{
|
||||||
channel::{self, Receiver, Sender},
|
channel::{self, Receiver, Sender},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
task,
|
task,
|
||||||
};
|
};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::chat::{get_chat_cnt, ChatId};
|
use crate::chat::{get_chat_cnt, ChatId};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -91,7 +89,7 @@ pub struct RunningState {
|
|||||||
pub fn get_info() -> BTreeMap<&'static str, String> {
|
pub fn get_info() -> BTreeMap<&'static str, String> {
|
||||||
let mut res = BTreeMap::new();
|
let mut res = BTreeMap::new();
|
||||||
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
|
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
|
||||||
res.insert("sqlite_version", crate::sql::version().to_string());
|
res.insert("sqlite_version", rusqlite::version().to_string());
|
||||||
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
|
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
|
||||||
res.insert("num_cpus", num_cpus::get().to_string());
|
res.insert("num_cpus", num_cpus::get().to_string());
|
||||||
res.insert("level", "awesome".into());
|
res.insert("level", "awesome".into());
|
||||||
@@ -290,7 +288,7 @@ impl Context {
|
|||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let journal_mode = self
|
let journal_mode = self
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("PRAGMA journal_mode;"))
|
.query_get_value("PRAGMA journal_mode;", paramsv![])
|
||||||
.await?
|
.await?
|
||||||
.unwrap_or_else(|| "unknown".to_string());
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
|
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
|
||||||
@@ -299,12 +297,12 @@ impl Context {
|
|||||||
|
|
||||||
let prv_key_cnt = self
|
let prv_key_cnt = self
|
||||||
.sql
|
.sql
|
||||||
.count(sqlx::query("SELECT COUNT(*) FROM keypairs;"))
|
.count("SELECT COUNT(*) FROM keypairs;", paramsv![])
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let pub_key_cnt = self
|
let pub_key_cnt = self
|
||||||
.sql
|
.sql
|
||||||
.count(sqlx::query("SELECT COUNT(*) FROM acpeerstates;"))
|
.count("SELECT COUNT(*) FROM acpeerstates;", paramsv![])
|
||||||
.await?;
|
.await?;
|
||||||
let fingerprint_str = match SignedPublicKey::load_self(self).await {
|
let fingerprint_str = match SignedPublicKey::load_self(self).await {
|
||||||
Ok(key) => key.fingerprint().hex(),
|
Ok(key) => key.fingerprint().hex(),
|
||||||
@@ -431,8 +429,8 @@ impl Context {
|
|||||||
pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
|
pub async fn get_fresh_msgs(&self) -> Result<Vec<MsgId>> {
|
||||||
let list = self
|
let list = self
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(concat!(
|
concat!(
|
||||||
"SELECT m.id",
|
"SELECT m.id",
|
||||||
" FROM msgs m",
|
" FROM msgs m",
|
||||||
" LEFT JOIN contacts ct",
|
" LEFT JOIN contacts ct",
|
||||||
@@ -446,13 +444,17 @@ impl Context {
|
|||||||
" AND c.blocked=0",
|
" AND c.blocked=0",
|
||||||
" AND NOT(c.muted_until=-1 OR c.muted_until>?)",
|
" AND NOT(c.muted_until=-1 OR c.muted_until>?)",
|
||||||
" ORDER BY m.timestamp DESC,m.id DESC;"
|
" ORDER BY m.timestamp DESC,m.id DESC;"
|
||||||
))
|
),
|
||||||
.bind(MessageState::InFresh)
|
paramsv![MessageState::InFresh, time()],
|
||||||
.bind(time()),
|
|row| row.get::<_, MsgId>(0),
|
||||||
|
|rows| {
|
||||||
|
let mut list = Vec::new();
|
||||||
|
for row in rows {
|
||||||
|
list.push(row?);
|
||||||
|
}
|
||||||
|
Ok(list)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?
|
|
||||||
.map(|row| row?.try_get("id"))
|
|
||||||
.collect::<sqlx::Result<_>>()
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
@@ -472,10 +474,23 @@ impl Context {
|
|||||||
}
|
}
|
||||||
let str_like_in_text = format!("%{}%", real_query);
|
let str_like_in_text = format!("%{}%", real_query);
|
||||||
|
|
||||||
|
let do_query = |query, params| {
|
||||||
|
self.sql.query_map(
|
||||||
|
query,
|
||||||
|
params,
|
||||||
|
|row| row.get::<_, MsgId>("id"),
|
||||||
|
|rows| {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
for id in rows {
|
||||||
|
ret.push(id?);
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
let list = if let Some(chat_id) = chat_id {
|
let list = if let Some(chat_id) = chat_id {
|
||||||
self.sql
|
do_query(
|
||||||
.fetch(
|
|
||||||
sqlx::query(
|
|
||||||
"SELECT m.id AS id, m.timestamp AS timestamp
|
"SELECT m.id AS id, m.timestamp AS timestamp
|
||||||
FROM msgs m
|
FROM msgs m
|
||||||
LEFT JOIN contacts ct
|
LEFT JOIN contacts ct
|
||||||
@@ -485,17 +500,8 @@ impl Context {
|
|||||||
AND ct.blocked=0
|
AND ct.blocked=0
|
||||||
AND txt LIKE ?
|
AND txt LIKE ?
|
||||||
ORDER BY m.timestamp,m.id;",
|
ORDER BY m.timestamp,m.id;",
|
||||||
|
paramsv![chat_id, str_like_in_text],
|
||||||
)
|
)
|
||||||
.bind(chat_id)
|
|
||||||
.bind(str_like_in_text),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.map(|row| {
|
|
||||||
let row = row?;
|
|
||||||
let id = row.try_get::<MsgId, _>("id")?;
|
|
||||||
Ok(id)
|
|
||||||
})
|
|
||||||
.collect::<sqlx::Result<Vec<MsgId>>>()
|
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
// For performance reasons results are sorted only by `id`, that is in the order of
|
// For performance reasons results are sorted only by `id`, that is in the order of
|
||||||
@@ -508,9 +514,7 @@ impl Context {
|
|||||||
// of unwanted results that are discarded moments later, we added `LIMIT 1000`.
|
// of unwanted results that are discarded moments later, we added `LIMIT 1000`.
|
||||||
// According to some tests, this limit speeds up eg. 2 character searches by factor 10.
|
// According to some tests, this limit speeds up eg. 2 character searches by factor 10.
|
||||||
// The limit is documented and UI may add a hint when getting 1000 results.
|
// The limit is documented and UI may add a hint when getting 1000 results.
|
||||||
self.sql
|
do_query(
|
||||||
.fetch(
|
|
||||||
sqlx::query(
|
|
||||||
"SELECT m.id AS id, m.timestamp AS timestamp
|
"SELECT m.id AS id, m.timestamp AS timestamp
|
||||||
FROM msgs m
|
FROM msgs m
|
||||||
LEFT JOIN contacts ct
|
LEFT JOIN contacts ct
|
||||||
@@ -523,16 +527,8 @@ impl Context {
|
|||||||
AND ct.blocked=0
|
AND ct.blocked=0
|
||||||
AND m.txt LIKE ?
|
AND m.txt LIKE ?
|
||||||
ORDER BY m.id DESC LIMIT 1000",
|
ORDER BY m.id DESC LIMIT 1000",
|
||||||
|
paramsv![str_like_in_text],
|
||||||
)
|
)
|
||||||
.bind(str_like_in_text),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.map(|row| {
|
|
||||||
let row = row?;
|
|
||||||
let id = row.try_get::<MsgId, _>("id")?;
|
|
||||||
Ok(id)
|
|
||||||
})
|
|
||||||
.collect::<sqlx::Result<Vec<MsgId>>>()
|
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -747,9 +743,8 @@ mod tests {
|
|||||||
// we need to modify the database directly
|
// we need to modify the database directly
|
||||||
t.sql
|
t.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE chats SET muted_until=? WHERE id=?;")
|
"UPDATE chats SET muted_until=? WHERE id=?;",
|
||||||
.bind(time() - 3600)
|
paramsv![time() - 3600, bob.id],
|
||||||
.bind(bob.id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -766,7 +761,10 @@ mod tests {
|
|||||||
// to test get_fresh_msgs() with invalid mute_until (everything < -1),
|
// to test get_fresh_msgs() with invalid mute_until (everything < -1),
|
||||||
// that results in "muted forever" by definition.
|
// that results in "muted forever" by definition.
|
||||||
t.sql
|
t.sql
|
||||||
.execute(sqlx::query("UPDATE chats SET muted_until=-2 WHERE id=?;").bind(bob.id))
|
.execute(
|
||||||
|
"UPDATE chats SET muted_until=-2 WHERE id=?;",
|
||||||
|
paramsv![bob.id],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let bob = Chat::load_from_db(&t, bob.id).await.unwrap();
|
let bob = Chat::load_from_db(&t, bob.id).await.unwrap();
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use anyhow::{bail, ensure, format_err, Result};
|
use anyhow::{bail, ensure, format_err, Result};
|
||||||
use async_std::prelude::*;
|
|
||||||
use itertools::join;
|
use itertools::join;
|
||||||
use mailparse::SingleInfo;
|
use mailparse::SingleInfo;
|
||||||
use num_traits::FromPrimitive;
|
use num_traits::FromPrimitive;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::chat::{self, Chat, ChatId, ProtectionStatus};
|
use crate::chat::{self, Chat, ChatId, ProtectionStatus};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -915,7 +913,8 @@ async fn add_parts(
|
|||||||
|
|
||||||
let subject = mime_parser.get_subject().unwrap_or_default();
|
let subject = mime_parser.get_subject().unwrap_or_default();
|
||||||
|
|
||||||
let server_folder = server_folder.as_ref();
|
let mut parts = std::mem::replace(&mut mime_parser.parts, Vec::new());
|
||||||
|
let server_folder = server_folder.as_ref().to_string();
|
||||||
let is_system_message = mime_parser.is_system_message;
|
let is_system_message = mime_parser.is_system_message;
|
||||||
|
|
||||||
// if indicated by the parser,
|
// if indicated by the parser,
|
||||||
@@ -927,25 +926,61 @@ async fn add_parts(
|
|||||||
|
|
||||||
let mime_headers = if save_mime_headers || save_mime_modified {
|
let mime_headers = if save_mime_headers || save_mime_modified {
|
||||||
if mime_parser.was_encrypted() && !mime_parser.decoded_data.is_empty() {
|
if mime_parser.was_encrypted() && !mime_parser.decoded_data.is_empty() {
|
||||||
String::from_utf8_lossy(&mime_parser.decoded_data)
|
String::from_utf8_lossy(&mime_parser.decoded_data).to_string()
|
||||||
} else {
|
} else {
|
||||||
String::from_utf8_lossy(imf_raw)
|
String::from_utf8_lossy(imf_raw).to_string()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
"".into()
|
"".into()
|
||||||
};
|
};
|
||||||
|
|
||||||
for part in &mut mime_parser.parts {
|
let sent_timestamp = *sent_timestamp;
|
||||||
let mut txt_raw = "".to_string();
|
let is_hidden = *hidden;
|
||||||
|
let chat_id = *chat_id;
|
||||||
|
|
||||||
let is_location_kml =
|
// TODO: can this clone be avoided?
|
||||||
location_kml_is && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty());
|
let rfc724_mid = rfc724_mid.to_string();
|
||||||
|
|
||||||
|
let (new_parts, ids, is_hidden) = context
|
||||||
|
.sql
|
||||||
|
.with_conn(move |conn| {
|
||||||
|
let mut ids = Vec::with_capacity(parts.len());
|
||||||
|
let mut is_hidden = is_hidden;
|
||||||
|
|
||||||
|
for part in &mut parts {
|
||||||
|
let mut txt_raw = "".to_string();
|
||||||
|
let mut stmt = conn.prepare_cached(
|
||||||
|
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, subject, txt_raw, param,
|
||||||
|
bytes, hidden, mime_headers, mime_in_reply_to,
|
||||||
|
mime_references, mime_modified, error, ephemeral_timer,
|
||||||
|
ephemeral_timestamp
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?, ?, ?, ?,
|
||||||
|
?
|
||||||
|
);
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let is_location_kml = location_kml_is
|
||||||
|
&& icnt == 1
|
||||||
|
&& (part.msg == "-location-" || part.msg.is_empty());
|
||||||
|
|
||||||
if is_mdn || is_location_kml {
|
if is_mdn || is_location_kml {
|
||||||
*hidden = true;
|
is_hidden = true;
|
||||||
if incoming {
|
if incoming {
|
||||||
// Set the state to InSeen so that precheck_imf() adds a markseen job after we moved the message
|
state = MessageState::InSeen; // Set the state to InSeen so that precheck_imf() adds a markseen job after we moved the message
|
||||||
state = MessageState::InSeen;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -968,7 +1003,9 @@ async fn add_parts(
|
|||||||
} else {
|
} else {
|
||||||
match ephemeral_timer {
|
match ephemeral_timer {
|
||||||
EphemeralTimer::Disabled => 0,
|
EphemeralTimer::Disabled => 0,
|
||||||
EphemeralTimer::Enabled { duration } => rcvd_timestamp + i64::from(duration),
|
EphemeralTimer::Enabled { duration } => {
|
||||||
|
rcvd_timestamp + i64::from(duration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -976,78 +1013,63 @@ async fn add_parts(
|
|||||||
// also change `MsgId::trash()` and `delete_expired_messages()`
|
// also change `MsgId::trash()` and `delete_expired_messages()`
|
||||||
let trash = chat_id.is_trash();
|
let trash = chat_id.is_trash();
|
||||||
|
|
||||||
let row_id = context
|
stmt.execute(paramsv![
|
||||||
.sql
|
rfc724_mid,
|
||||||
.insert(
|
server_folder,
|
||||||
sqlx::query(
|
server_uid as i32,
|
||||||
r#"
|
chat_id,
|
||||||
INSERT INTO msgs
|
if trash { 0 } else { from_id as i32 },
|
||||||
(
|
if trash { 0 } else { to_id as i32 },
|
||||||
rfc724_mid, server_folder, server_uid, chat_id,
|
sort_timestamp,
|
||||||
from_id, to_id, timestamp, timestamp_sent,
|
sent_timestamp,
|
||||||
timestamp_rcvd, type, state, msgrmsg,
|
rcvd_timestamp,
|
||||||
txt, subject, txt_raw, param,
|
part.typ,
|
||||||
bytes, hidden, mime_headers, mime_in_reply_to,
|
state,
|
||||||
mime_references, mime_modified, error, ephemeral_timer,
|
is_dc_message,
|
||||||
ephemeral_timestamp
|
if trash { "" } else { &part.msg },
|
||||||
)
|
if trash { "" } else { &subject },
|
||||||
VALUES (
|
|
||||||
?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?,
|
|
||||||
?, ?, ?, ?,
|
|
||||||
?
|
|
||||||
);
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(rfc724_mid)
|
|
||||||
.bind(server_folder)
|
|
||||||
.bind(server_uid as i32)
|
|
||||||
.bind(*chat_id)
|
|
||||||
.bind(if trash { 0 } else { from_id as i32 })
|
|
||||||
.bind(if trash { 0 } else { to_id as i32 })
|
|
||||||
.bind(sort_timestamp)
|
|
||||||
.bind(*sent_timestamp)
|
|
||||||
.bind(rcvd_timestamp)
|
|
||||||
.bind(part.typ)
|
|
||||||
.bind(state)
|
|
||||||
.bind(is_dc_message)
|
|
||||||
.bind(if trash { "" } else { &part.msg })
|
|
||||||
.bind(if trash { "" } else { &subject })
|
|
||||||
// txt_raw might contain invalid utf8
|
// txt_raw might contain invalid utf8
|
||||||
.bind(if trash { "" } else { &txt_raw })
|
if trash { "" } else { &txt_raw },
|
||||||
.bind(if trash {
|
if trash {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
} else {
|
} else {
|
||||||
part.param.to_string()
|
part.param.to_string()
|
||||||
})
|
},
|
||||||
.bind(part.bytes as i64)
|
part.bytes as isize,
|
||||||
.bind(*hidden)
|
is_hidden,
|
||||||
.bind(if (save_mime_headers || mime_modified) && !trash {
|
if (save_mime_headers || mime_modified) && !trash {
|
||||||
mime_headers.to_string()
|
mime_headers.to_string()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
})
|
},
|
||||||
.bind(&mime_in_reply_to)
|
mime_in_reply_to,
|
||||||
.bind(&mime_references)
|
mime_references,
|
||||||
.bind(&mime_modified)
|
mime_modified,
|
||||||
.bind(part.error.take().unwrap_or_default())
|
part.error.take().unwrap_or_default(),
|
||||||
.bind(ephemeral_timer)
|
ephemeral_timer,
|
||||||
.bind(ephemeral_timestamp),
|
ephemeral_timestamp
|
||||||
)
|
])?;
|
||||||
.await?;
|
let row_id = conn.last_insert_rowid();
|
||||||
let msg_id = MsgId::new(u32::try_from(row_id)?);
|
|
||||||
|
|
||||||
created_db_entries.push((*chat_id, msg_id));
|
drop(stmt);
|
||||||
*insert_msg_id = msg_id;
|
ids.push(MsgId::new(u32::try_from(row_id)?));
|
||||||
|
}
|
||||||
|
Ok((parts, ids, is_hidden))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Some(id) = ids.iter().last() {
|
||||||
|
*insert_msg_id = *id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !*hidden {
|
if !is_hidden {
|
||||||
chat_id.unarchive(context).await?;
|
chat_id.unarchive(context).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*hidden = is_hidden;
|
||||||
|
created_db_entries.extend(ids.iter().map(|id| (chat_id, *id)));
|
||||||
|
mime_parser.parts = new_parts;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
"Message has {} parts and is assigned to chat #{}.", icnt, chat_id,
|
"Message has {} parts and is assigned to chat #{}.", icnt, chat_id,
|
||||||
@@ -1055,7 +1077,7 @@ INSERT INTO msgs
|
|||||||
|
|
||||||
// new outgoing message from another device marks the chat as noticed.
|
// new outgoing message from another device marks the chat as noticed.
|
||||||
if !incoming && !*hidden && !chat_id.is_special() {
|
if !incoming && !*hidden && !chat_id.is_special() {
|
||||||
chat::marknoticed_chat_if_older_than(context, *chat_id, sort_timestamp).await?;
|
chat::marknoticed_chat_if_older_than(context, chat_id, sort_timestamp).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check event to send
|
// check event to send
|
||||||
@@ -1085,7 +1107,7 @@ INSERT INTO msgs
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
if !is_mdn {
|
if !is_mdn {
|
||||||
update_last_subject(context, *chat_id, mime_parser)
|
update_last_subject(context, chat_id, mime_parser)
|
||||||
.await
|
.await
|
||||||
.ok_or_log_msg(context, "Could not update LastSubject of chat");
|
.ok_or_log_msg(context, "Could not update LastSubject of chat");
|
||||||
}
|
}
|
||||||
@@ -1167,9 +1189,8 @@ async fn calc_sort_timestamp(
|
|||||||
let last_msg_time: Option<i64> = context
|
let last_msg_time: Option<i64> = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value(
|
||||||
sqlx::query("SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?")
|
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?",
|
||||||
.bind(chat_id)
|
paramsv![chat_id, MessageState::InFresh],
|
||||||
.bind(MessageState::InFresh),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -1481,9 +1502,8 @@ async fn create_or_lookup_group(
|
|||||||
if context
|
if context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE chats SET name=? WHERE id=?;")
|
"UPDATE chats SET name=? WHERE id=?;",
|
||||||
.bind(grpname.to_string())
|
paramsv![grpname.to_string(), chat_id],
|
||||||
.bind(chat_id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -1520,7 +1540,10 @@ async fn create_or_lookup_group(
|
|||||||
// start from scratch.
|
// start from scratch.
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM chats_contacts WHERE chat_id=?;").bind(chat_id))
|
.execute(
|
||||||
|
"DELETE FROM chats_contacts WHERE chat_id=?;",
|
||||||
|
paramsv![chat_id],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
@@ -1764,14 +1787,15 @@ async fn create_multiuser_record(
|
|||||||
) -> Result<ChatId> {
|
) -> Result<ChatId> {
|
||||||
let row_id =
|
let row_id =
|
||||||
context.sql.insert(
|
context.sql.insert(
|
||||||
sqlx::query(
|
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected) VALUES(?, ?, ?, ?, ?, ?);",
|
||||||
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected) VALUES(?, ?, ?, ?, ?, ?);")
|
paramsv![
|
||||||
.bind(chattype)
|
chattype,
|
||||||
.bind(grpname.as_ref())
|
grpname.as_ref(),
|
||||||
.bind(grpid.as_ref())
|
grpid.as_ref(),
|
||||||
.bind(create_blocked)
|
create_blocked,
|
||||||
.bind(time())
|
time(),
|
||||||
.bind(create_protected)
|
create_protected,
|
||||||
|
],
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
let chat_id = ChatId::new(u32::try_from(row_id)?);
|
||||||
@@ -1805,24 +1829,27 @@ async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> Result<St
|
|||||||
.unwrap_or_else(|| "no-self".to_string())
|
.unwrap_or_else(|| "no-self".to_string())
|
||||||
.to_lowercase();
|
.to_lowercase();
|
||||||
|
|
||||||
let q = format!(
|
let members = context
|
||||||
|
.sql
|
||||||
|
.query_map(
|
||||||
|
format!(
|
||||||
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
|
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
|
||||||
member_ids_str
|
member_ids_str
|
||||||
);
|
),
|
||||||
|
paramsv![],
|
||||||
let mut members = member_cs;
|
|row| row.get::<_, String>(0),
|
||||||
|
|rows| {
|
||||||
if let Ok(rows) = context.sql.fetch(sqlx::query(&q)).await {
|
let mut addrs = rows.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||||
let mut addrs = rows
|
|
||||||
.map(|row| row?.try_get::<String, _>(0))
|
|
||||||
.collect::<sqlx::Result<Vec<_>>>()
|
|
||||||
.await?;
|
|
||||||
addrs.sort();
|
addrs.sort();
|
||||||
|
let mut acc = member_cs.clone();
|
||||||
for addr in &addrs {
|
for addr in &addrs {
|
||||||
members += ",";
|
acc += ",";
|
||||||
members += &addr.to_lowercase();
|
acc += &addr.to_lowercase();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Ok(acc)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(hex_hash(&members))
|
Ok(hex_hash(&members))
|
||||||
}
|
}
|
||||||
@@ -1889,26 +1916,34 @@ async fn check_verified_properties(
|
|||||||
}
|
}
|
||||||
let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ",");
|
let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ",");
|
||||||
|
|
||||||
let q = format!(
|
let rows = context
|
||||||
|
.sql
|
||||||
|
.query_map(
|
||||||
|
format!(
|
||||||
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
|
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
|
||||||
LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
|
LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
|
||||||
to_ids_str
|
to_ids_str
|
||||||
);
|
),
|
||||||
|
paramsv![],
|
||||||
let mut rows = context.sql.fetch(sqlx::query(&q)).await?;
|
|row| {
|
||||||
|
let to_addr: String = row.get(0)?;
|
||||||
while let Some(row) = rows.next().await {
|
let is_verified: i32 = row.get(1)?;
|
||||||
let row = row?;
|
Ok((to_addr, is_verified != 0))
|
||||||
let to_addr: String = row.try_get(0)?;
|
},
|
||||||
let mut is_verified = row.try_get::<i32, _>(1)? != 0;
|
|rows| {
|
||||||
|
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
for (to_addr, mut is_verified) in rows.into_iter() {
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
"check_verified_properties: {:?} self={:?}",
|
"check_verified_properties: {:?} self={:?}",
|
||||||
to_addr,
|
to_addr,
|
||||||
context.is_self_addr(&to_addr).await
|
context.is_self_addr(&to_addr).await
|
||||||
);
|
);
|
||||||
|
|
||||||
let peerstate = Peerstate::from_addr(context, &to_addr).await?;
|
let peerstate = Peerstate::from_addr(context, &to_addr).await?;
|
||||||
|
|
||||||
// mark gossiped keys (if any) as verified
|
// mark gossiped keys (if any) as verified
|
||||||
|
|||||||
@@ -632,6 +632,14 @@ impl FromStr for EmailAddress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl rusqlite::types::ToSql for EmailAddress {
|
||||||
|
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||||
|
let val = rusqlite::types::Value::Text(self.to_string());
|
||||||
|
let out = rusqlite::types::ToSqlOutput::Owned(val);
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Makes sure that a user input that is not supposed to contain newlines does not contain newlines.
|
/// Makes sure that a user input that is not supposed to contain newlines does not contain newlines.
|
||||||
pub(crate) fn improve_single_line_input(input: impl AsRef<str>) -> String {
|
pub(crate) fn improve_single_line_input(input: impl AsRef<str>) -> String {
|
||||||
input
|
input
|
||||||
|
|||||||
152
src/ephemeral.rs
152
src/ephemeral.rs
@@ -64,7 +64,6 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|||||||
use anyhow::{ensure, Context as _, Error};
|
use anyhow::{ensure, Context as _, Error};
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
Viewtype, DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_SELF,
|
Viewtype, DC_CHAT_ID_LAST_SPECIAL, DC_CHAT_ID_TRASH, DC_CONTACT_ID_DEVICE, DC_CONTACT_ID_SELF,
|
||||||
@@ -124,41 +123,28 @@ impl FromStr for Timer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl sqlx::Type<sqlx::Sqlite> for Timer {
|
impl rusqlite::types::ToSql for Timer {
|
||||||
fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
|
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> {
|
||||||
<i64 as sqlx::Type<_>>::type_info()
|
let val = rusqlite::types::Value::Integer(match self {
|
||||||
}
|
Self::Disabled => 0,
|
||||||
|
Self::Enabled { duration } => i64::from(*duration),
|
||||||
fn compatible(ty: &sqlx::sqlite::SqliteTypeInfo) -> bool {
|
});
|
||||||
<i64 as sqlx::Type<_>>::compatible(ty)
|
let out = rusqlite::types::ToSqlOutput::Owned(val);
|
||||||
|
Ok(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'q> sqlx::Encode<'q, sqlx::Sqlite> for Timer {
|
impl rusqlite::types::FromSql for Timer {
|
||||||
fn encode_by_ref(
|
fn column_result(value: rusqlite::types::ValueRef) -> rusqlite::types::FromSqlResult<Self> {
|
||||||
&self,
|
i64::column_result(value).and_then(|value| {
|
||||||
args: &mut Vec<sqlx::sqlite::SqliteArgumentValue<'q>>,
|
|
||||||
) -> sqlx::encode::IsNull {
|
|
||||||
args.push(sqlx::sqlite::SqliteArgumentValue::Int64(
|
|
||||||
self.to_u32() as i64
|
|
||||||
));
|
|
||||||
|
|
||||||
sqlx::encode::IsNull::No
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'r> sqlx::Decode<'r, sqlx::Sqlite> for Timer {
|
|
||||||
fn decode(value: sqlx::sqlite::SqliteValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
|
|
||||||
let value: i64 = sqlx::Decode::decode(value)?;
|
|
||||||
if value == 0 {
|
if value == 0 {
|
||||||
Ok(Self::Disabled)
|
Ok(Self::Disabled)
|
||||||
} else if let Ok(duration) = u32::try_from(value) {
|
} else if let Ok(duration) = u32::try_from(value) {
|
||||||
Ok(Self::Enabled { duration })
|
Ok(Self::Enabled { duration })
|
||||||
} else {
|
} else {
|
||||||
Err(Box::new(sqlx::Error::Decode(Box::new(
|
Err(rusqlite::types::FromSqlError::OutOfRange(value))
|
||||||
crate::error::OutOfRangeError,
|
|
||||||
))))
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +154,8 @@ impl ChatId {
|
|||||||
let timer = context
|
let timer = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value(
|
||||||
sqlx::query("SELECT ephemeral_timer FROM chats WHERE id=?;").bind(self),
|
"SELECT ephemeral_timer FROM chats WHERE id=?;",
|
||||||
|
paramsv![self],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(timer.unwrap_or_default())
|
Ok(timer.unwrap_or_default())
|
||||||
@@ -188,13 +175,10 @@ impl ChatId {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE chats
|
"UPDATE chats
|
||||||
SET ephemeral_timer=?
|
SET ephemeral_timer=?
|
||||||
WHERE id=?;",
|
WHERE id=?;",
|
||||||
)
|
paramsv![timer, self],
|
||||||
.bind(timer)
|
|
||||||
.bind(self),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -233,45 +217,44 @@ pub(crate) async fn stock_ephemeral_timer_changed(
|
|||||||
from_id: u32,
|
from_id: u32,
|
||||||
) -> String {
|
) -> String {
|
||||||
match timer {
|
match timer {
|
||||||
Timer::Disabled => stock_str::msg_ephemeral_timer_disabled(context, from_id as u32).await,
|
Timer::Disabled => stock_str::msg_ephemeral_timer_disabled(context, from_id).await,
|
||||||
Timer::Enabled { duration } => match duration {
|
Timer::Enabled { duration } => match duration {
|
||||||
0..=59 => {
|
0..=59 => {
|
||||||
stock_str::msg_ephemeral_timer_enabled(context, timer.to_string(), from_id as u32)
|
stock_str::msg_ephemeral_timer_enabled(context, timer.to_string(), from_id).await
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
60 => stock_str::msg_ephemeral_timer_minute(context, from_id as u32).await,
|
60 => stock_str::msg_ephemeral_timer_minute(context, from_id).await,
|
||||||
61..=3599 => {
|
61..=3599 => {
|
||||||
stock_str::msg_ephemeral_timer_minutes(
|
stock_str::msg_ephemeral_timer_minutes(
|
||||||
context,
|
context,
|
||||||
format!("{}", (f64::from(duration) / 6.0).round() / 10.0),
|
format!("{}", (f64::from(duration) / 6.0).round() / 10.0),
|
||||||
from_id as u32,
|
from_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
3600 => stock_str::msg_ephemeral_timer_hour(context, from_id as u32).await,
|
3600 => stock_str::msg_ephemeral_timer_hour(context, from_id).await,
|
||||||
3601..=86399 => {
|
3601..=86399 => {
|
||||||
stock_str::msg_ephemeral_timer_hours(
|
stock_str::msg_ephemeral_timer_hours(
|
||||||
context,
|
context,
|
||||||
format!("{}", (f64::from(duration) / 360.0).round() / 10.0),
|
format!("{}", (f64::from(duration) / 360.0).round() / 10.0),
|
||||||
from_id as u32,
|
from_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
86400 => stock_str::msg_ephemeral_timer_day(context, from_id as u32).await,
|
86400 => stock_str::msg_ephemeral_timer_day(context, from_id).await,
|
||||||
86401..=604_799 => {
|
86401..=604_799 => {
|
||||||
stock_str::msg_ephemeral_timer_days(
|
stock_str::msg_ephemeral_timer_days(
|
||||||
context,
|
context,
|
||||||
format!("{}", (f64::from(duration) / 8640.0).round() / 10.0),
|
format!("{}", (f64::from(duration) / 8640.0).round() / 10.0),
|
||||||
from_id as u32,
|
from_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
604_800 => stock_str::msg_ephemeral_timer_week(context, from_id as u32).await,
|
604_800 => stock_str::msg_ephemeral_timer_week(context, from_id).await,
|
||||||
_ => {
|
_ => {
|
||||||
stock_str::msg_ephemeral_timer_weeks(
|
stock_str::msg_ephemeral_timer_weeks(
|
||||||
context,
|
context,
|
||||||
format!("{}", (f64::from(duration) / 60480.0).round() / 10.0),
|
format!("{}", (f64::from(duration) / 60480.0).round() / 10.0),
|
||||||
from_id as u32,
|
from_id,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -284,15 +267,14 @@ impl MsgId {
|
|||||||
pub(crate) async fn ephemeral_timer(self, context: &Context) -> anyhow::Result<Timer> {
|
pub(crate) async fn ephemeral_timer(self, context: &Context) -> anyhow::Result<Timer> {
|
||||||
let res = match context
|
let res = match context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value::<_, i64>(
|
.query_get_value(
|
||||||
sqlx::query("SELECT ephemeral_timer FROM msgs WHERE id=?").bind(self),
|
"SELECT ephemeral_timer FROM msgs WHERE id=?",
|
||||||
|
paramsv![self],
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
None | Some(0) => Timer::Disabled,
|
None | Some(0) => Timer::Disabled,
|
||||||
Some(duration) => Timer::Enabled {
|
Some(duration) => Timer::Enabled { duration },
|
||||||
duration: u32::try_from(duration)?,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
@@ -305,14 +287,10 @@ impl MsgId {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE msgs SET ephemeral_timestamp = ? \
|
"UPDATE msgs SET ephemeral_timestamp = ? \
|
||||||
WHERE (ephemeral_timestamp == 0 OR ephemeral_timestamp > ?) \
|
WHERE (ephemeral_timestamp == 0 OR ephemeral_timestamp > ?) \
|
||||||
AND id = ?",
|
AND id = ?",
|
||||||
)
|
paramsv![ephemeral_timestamp, ephemeral_timestamp, self],
|
||||||
.bind(ephemeral_timestamp)
|
|
||||||
.bind(ephemeral_timestamp)
|
|
||||||
.bind(self),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
schedule_ephemeral_task(context).await;
|
schedule_ephemeral_task(context).await;
|
||||||
@@ -333,7 +311,6 @@ pub(crate) async fn delete_expired_messages(context: &Context) -> Result<bool, E
|
|||||||
let mut updated = context
|
let mut updated = context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
// If you change which information is removed here, also change MsgId::trash() and
|
// If you change which information is removed here, also change MsgId::trash() and
|
||||||
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
||||||
r#"
|
r#"
|
||||||
@@ -346,10 +323,7 @@ WHERE
|
|||||||
AND ephemeral_timestamp <= ?
|
AND ephemeral_timestamp <= ?
|
||||||
AND chat_id != ?
|
AND chat_id != ?
|
||||||
"#,
|
"#,
|
||||||
)
|
paramsv![DC_CHAT_ID_TRASH, time(), DC_CHAT_ID_TRASH],
|
||||||
.bind(DC_CHAT_ID_TRASH)
|
|
||||||
.bind(time())
|
|
||||||
.bind(DC_CHAT_ID_TRASH),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("update failed")?
|
.context("update failed")?
|
||||||
@@ -374,19 +348,19 @@ WHERE
|
|||||||
let rows_modified = context
|
let rows_modified = context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE msgs \
|
"UPDATE msgs \
|
||||||
SET txt = 'DELETED', chat_id = ? \
|
SET txt = 'DELETED', chat_id = ? \
|
||||||
WHERE timestamp < ? \
|
WHERE timestamp < ? \
|
||||||
AND chat_id > ? \
|
AND chat_id > ? \
|
||||||
AND chat_id != ? \
|
AND chat_id != ? \
|
||||||
AND chat_id != ?",
|
AND chat_id != ?",
|
||||||
)
|
paramsv![
|
||||||
.bind(DC_CHAT_ID_TRASH)
|
DC_CHAT_ID_TRASH,
|
||||||
.bind(threshold_timestamp)
|
threshold_timestamp,
|
||||||
.bind(DC_CHAT_ID_LAST_SPECIAL)
|
DC_CHAT_ID_LAST_SPECIAL,
|
||||||
.bind(self_chat_id)
|
self_chat_id,
|
||||||
.bind(device_chat_id),
|
device_chat_id
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context("deleted update failed")?;
|
.context("deleted update failed")?;
|
||||||
@@ -412,7 +386,6 @@ pub async fn schedule_ephemeral_task(context: &Context) {
|
|||||||
let ephemeral_timestamp: Option<i64> = match context
|
let ephemeral_timestamp: Option<i64> = match context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value(
|
||||||
sqlx::query(
|
|
||||||
r#"
|
r#"
|
||||||
SELECT ephemeral_timestamp
|
SELECT ephemeral_timestamp
|
||||||
FROM msgs
|
FROM msgs
|
||||||
@@ -421,8 +394,7 @@ pub async fn schedule_ephemeral_task(context: &Context) {
|
|||||||
ORDER BY ephemeral_timestamp ASC
|
ORDER BY ephemeral_timestamp ASC
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
"#,
|
"#,
|
||||||
)
|
paramsv![DC_CHAT_ID_TRASH], // Trash contains already deleted messages, skip them
|
||||||
.bind(DC_CHAT_ID_TRASH), // Trash contains already deleted messages, skip them
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -475,7 +447,7 @@ pub async fn schedule_ephemeral_task(context: &Context) {
|
|||||||
///
|
///
|
||||||
/// It looks up the trash chat too, to find messages that are already
|
/// It looks up the trash chat too, to find messages that are already
|
||||||
/// deleted locally, but not deleted on the server.
|
/// deleted locally, but not deleted on the server.
|
||||||
pub(crate) async fn load_imap_deletion_msgid(context: &Context) -> sql::Result<Option<MsgId>> {
|
pub(crate) async fn load_imap_deletion_msgid(context: &Context) -> anyhow::Result<Option<MsgId>> {
|
||||||
let now = time();
|
let now = time();
|
||||||
|
|
||||||
let threshold_timestamp = match context.get_config_delete_server_after().await? {
|
let threshold_timestamp = match context.get_config_delete_server_after().await? {
|
||||||
@@ -483,10 +455,9 @@ pub(crate) async fn load_imap_deletion_msgid(context: &Context) -> sql::Result<O
|
|||||||
Some(delete_server_after) => now - delete_server_after,
|
Some(delete_server_after) => now - delete_server_after,
|
||||||
};
|
};
|
||||||
|
|
||||||
let row = context
|
context
|
||||||
.sql
|
.sql
|
||||||
.fetch_optional(
|
.query_row_optional(
|
||||||
sqlx::query(
|
|
||||||
"SELECT id FROM msgs \
|
"SELECT id FROM msgs \
|
||||||
WHERE ( \
|
WHERE ( \
|
||||||
timestamp < ? \
|
timestamp < ? \
|
||||||
@@ -495,19 +466,13 @@ pub(crate) async fn load_imap_deletion_msgid(context: &Context) -> sql::Result<O
|
|||||||
AND server_uid != 0 \
|
AND server_uid != 0 \
|
||||||
AND NOT id IN (SELECT foreign_id FROM jobs WHERE action = ?)
|
AND NOT id IN (SELECT foreign_id FROM jobs WHERE action = ?)
|
||||||
LIMIT 1",
|
LIMIT 1",
|
||||||
|
paramsv![threshold_timestamp, now, job::Action::DeleteMsgOnImap],
|
||||||
|
|row| {
|
||||||
|
let msg_id: MsgId = row.get(0)?;
|
||||||
|
Ok(msg_id)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.bind(threshold_timestamp)
|
.await
|
||||||
.bind(now)
|
|
||||||
.bind(job::Action::DeleteMsgOnImap),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(row) = row {
|
|
||||||
let msg_id = row.try_get(0)?;
|
|
||||||
Ok(Some(msg_id))
|
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start ephemeral timers for seen messages if they are not started
|
/// Start ephemeral timers for seen messages if they are not started
|
||||||
@@ -523,17 +488,17 @@ pub(crate) async fn start_ephemeral_timers(context: &Context) -> sql::Result<()>
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE msgs \
|
"UPDATE msgs \
|
||||||
SET ephemeral_timestamp = ? + ephemeral_timer \
|
SET ephemeral_timestamp = ? + ephemeral_timer \
|
||||||
WHERE ephemeral_timer > 0 \
|
WHERE ephemeral_timer > 0 \
|
||||||
AND ephemeral_timestamp = 0 \
|
AND ephemeral_timestamp = 0 \
|
||||||
AND state NOT IN (?, ?, ?)",
|
AND state NOT IN (?, ?, ?)",
|
||||||
)
|
paramsv![
|
||||||
.bind(time())
|
time(),
|
||||||
.bind(MessageState::InFresh)
|
MessageState::InFresh,
|
||||||
.bind(MessageState::InNoticed)
|
MessageState::InNoticed,
|
||||||
.bind(MessageState::OutDraft),
|
MessageState::OutDraft
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -770,7 +735,10 @@ mod tests {
|
|||||||
// Check that the msg will be deleted on the server
|
// Check that the msg will be deleted on the server
|
||||||
// First of all, set a server_uid so that DC thinks that it's actually possible to delete
|
// First of all, set a server_uid so that DC thinks that it's actually possible to delete
|
||||||
t.sql
|
t.sql
|
||||||
.execute(sqlx::query("UPDATE msgs SET server_uid=1 WHERE id=?").bind(msg.sender_msg_id))
|
.execute(
|
||||||
|
"UPDATE msgs SET server_uid=1 WHERE id=?",
|
||||||
|
paramsv![msg.sender_msg_id],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let job = job::load_imap_deletion_job(&t).await.unwrap();
|
let job = job::load_imap_deletion_job(&t).await.unwrap();
|
||||||
@@ -808,7 +776,7 @@ mod tests {
|
|||||||
assert!(msg.text.is_none_or_empty(), "{:?}", msg.text);
|
assert!(msg.text.is_none_or_empty(), "{:?}", msg.text);
|
||||||
let rawtxt: Option<String> = t
|
let rawtxt: Option<String> = t
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("SELECT txt_raw FROM msgs WHERE id=?;").bind(msg_id))
|
.query_get_value("SELECT txt_raw FROM msgs WHERE id=?;", paramsv![msg_id])
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(rawtxt.is_none_or_empty(), "{:?}", rawtxt);
|
assert!(rawtxt.is_none_or_empty(), "{:?}", rawtxt);
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
//! # Error handling
|
//! # Error handling
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[error("Out of Range")]
|
|
||||||
pub struct OutOfRangeError;
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ensure_eq {
|
macro_rules! ensure_eq {
|
||||||
($left:expr, $right:expr) => ({
|
($left:expr, $right:expr) => ({
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ test some special html-characters as < > and & but also " and &#x
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_get_html_empty() {
|
async fn test_get_html_empty() {
|
||||||
let t = TestContext::new().await;
|
let t = TestContext::new().await;
|
||||||
let msg_id = MsgId::new_unset();
|
let msg_id = MsgId::new(100);
|
||||||
assert!(msg_id.get_html(&t).await.unwrap().is_none())
|
assert!(msg_id.get_html(&t).await.unwrap().is_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
48
src/imap.rs
48
src/imap.rs
@@ -521,30 +521,22 @@ impl Imap {
|
|||||||
// Write collected UIDs to SQLite database.
|
// Write collected UIDs to SQLite database.
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.transaction(|conn| {
|
.transaction(move |transaction| {
|
||||||
Box::pin(async move {
|
transaction.execute(
|
||||||
sqlx::query("UPDATE msgs SET server_uid=0 WHERE server_folder=?")
|
"UPDATE msgs SET server_uid=0 WHERE server_folder=?",
|
||||||
.bind(&folder)
|
params![folder],
|
||||||
.execute(&mut *conn)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
for (uid, rfc724_mid) in &msg_ids {
|
for (uid, rfc724_mid) in &msg_ids {
|
||||||
// This may detect previously undetected moved
|
// This may detect previously undetected moved
|
||||||
// messages, so we update server_folder too.
|
// messages, so we update server_folder too.
|
||||||
sqlx::query(
|
transaction.execute(
|
||||||
"UPDATE msgs \
|
"UPDATE msgs \
|
||||||
SET server_folder=?,server_uid=? WHERE rfc724_mid=?",
|
SET server_folder=?,server_uid=? WHERE rfc724_mid=?",
|
||||||
)
|
params![folder, uid, rfc724_mid],
|
||||||
.bind(&folder)
|
)?;
|
||||||
.bind(uid)
|
|
||||||
.bind(rfc724_mid)
|
|
||||||
.execute(&mut *conn)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1732,15 +1724,9 @@ pub(crate) async fn set_uid_next(context: &Context, folder: &str, uid_next: u32)
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO imap_sync (folder, uidvalidity, uid_next) VALUES (?,?,?)
|
"INSERT INTO imap_sync (folder, uidvalidity, uid_next) VALUES (?,?,?)
|
||||||
ON CONFLICT(folder) DO UPDATE SET uid_next=? WHERE folder=?;",
|
ON CONFLICT(folder) DO UPDATE SET uid_next=? WHERE folder=?;",
|
||||||
)
|
paramsv![folder, 0u32, uid_next, uid_next, folder],
|
||||||
.bind(folder)
|
|
||||||
.bind(0i32)
|
|
||||||
.bind(uid_next as i64)
|
|
||||||
.bind(uid_next as i64)
|
|
||||||
.bind(folder),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1754,7 +1740,10 @@ pub(crate) async fn set_uid_next(context: &Context, folder: &str, uid_next: u32)
|
|||||||
async fn get_uid_next(context: &Context, folder: &str) -> Result<u32> {
|
async fn get_uid_next(context: &Context, folder: &str) -> Result<u32> {
|
||||||
Ok(context
|
Ok(context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("SELECT uid_next FROM imap_sync WHERE folder=?;").bind(folder))
|
.query_get_value(
|
||||||
|
"SELECT uid_next FROM imap_sync WHERE folder=?;",
|
||||||
|
paramsv![folder],
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
.unwrap_or(0))
|
.unwrap_or(0))
|
||||||
}
|
}
|
||||||
@@ -1767,15 +1756,9 @@ pub(crate) async fn set_uidvalidity(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO imap_sync (folder, uidvalidity, uid_next) VALUES (?,?,?)
|
"INSERT INTO imap_sync (folder, uidvalidity, uid_next) VALUES (?,?,?)
|
||||||
ON CONFLICT(folder) DO UPDATE SET uidvalidity=? WHERE folder=?;",
|
ON CONFLICT(folder) DO UPDATE SET uidvalidity=? WHERE folder=?;",
|
||||||
)
|
paramsv![folder, uidvalidity, 0u32, uidvalidity, folder],
|
||||||
.bind(folder)
|
|
||||||
.bind(uidvalidity as i32)
|
|
||||||
.bind(0i32)
|
|
||||||
.bind(uidvalidity as i32)
|
|
||||||
.bind(folder),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1785,7 +1768,8 @@ async fn get_uidvalidity(context: &Context, folder: &str) -> Result<u32> {
|
|||||||
Ok(context
|
Ok(context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value(
|
||||||
sqlx::query("SELECT uidvalidity FROM imap_sync WHERE folder=?;").bind(folder),
|
"SELECT uidvalidity FROM imap_sync WHERE folder=?;",
|
||||||
|
paramsv![folder],
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.unwrap_or(0))
|
.unwrap_or(0))
|
||||||
|
|||||||
73
src/imex.rs
73
src/imex.rs
@@ -10,7 +10,6 @@ use async_std::{
|
|||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::chat;
|
use crate::chat;
|
||||||
use crate::chat::delete_and_reset_all_device_msgs;
|
use crate::chat::delete_and_reset_all_device_msgs;
|
||||||
@@ -595,9 +594,8 @@ async fn import_backup_old(context: &Context, backup_to_import: impl AsRef<Path>
|
|||||||
|
|
||||||
let total_files_cnt = context
|
let total_files_cnt = context
|
||||||
.sql
|
.sql
|
||||||
.count(sqlx::query("SELECT COUNT(*) FROM backup_blobs;"))
|
.count("SELECT COUNT(*) FROM backup_blobs;", paramsv![])
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
context,
|
context,
|
||||||
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
|
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
|
||||||
@@ -607,25 +605,33 @@ async fn import_backup_old(context: &Context, backup_to_import: impl AsRef<Path>
|
|||||||
// consuming too much memory.
|
// consuming too much memory.
|
||||||
let file_ids = context
|
let file_ids = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(sqlx::query("SELECT id FROM backup_blobs ORDER BY id"))
|
.query_map(
|
||||||
.await?
|
"SELECT id FROM backup_blobs ORDER BY id",
|
||||||
.map(|row| row?.try_get(0))
|
paramsv![],
|
||||||
.collect::<sqlx::Result<Vec<i64>>>()
|
|row| row.get(0),
|
||||||
|
|ids| {
|
||||||
|
ids.collect::<std::result::Result<Vec<i64>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut all_files_extracted = true;
|
let mut all_files_extracted = true;
|
||||||
for (processed_files_cnt, file_id) in file_ids.into_iter().enumerate() {
|
for (processed_files_cnt, file_id) in file_ids.into_iter().enumerate() {
|
||||||
// Load a single blob into memory
|
// Load a single blob into memory
|
||||||
let row = context
|
let (file_name, file_blob) = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query("SELECT file_name, file_content FROM backup_blobs WHERE id = ?")
|
"SELECT file_name, file_content FROM backup_blobs WHERE id = ?",
|
||||||
.bind(file_id),
|
paramsv![file_id],
|
||||||
|
|row| {
|
||||||
|
let file_name: String = row.get(0)?;
|
||||||
|
let file_blob: Vec<u8> = row.get(1)?;
|
||||||
|
Ok((file_name, file_blob))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let file_name: String = row.try_get(0)?;
|
|
||||||
let file_blob: &[u8] = row.try_get(1)?;
|
|
||||||
if context.shall_stop_ongoing().await {
|
if context.shall_stop_ongoing().await {
|
||||||
all_files_extracted = false;
|
all_files_extracted = false;
|
||||||
break;
|
break;
|
||||||
@@ -643,16 +649,16 @@ async fn import_backup_old(context: &Context, backup_to_import: impl AsRef<Path>
|
|||||||
}
|
}
|
||||||
|
|
||||||
let path_filename = context.get_blobdir().join(file_name);
|
let path_filename = context.get_blobdir().join(file_name);
|
||||||
dc_write_file(context, &path_filename, file_blob).await?;
|
dc_write_file(context, &path_filename, &file_blob).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if all_files_extracted {
|
if all_files_extracted {
|
||||||
// only delete backup_blobs if all files were successfully extracted
|
// only delete backup_blobs if all files were successfully extracted
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DROP TABLE backup_blobs;"))
|
.execute("DROP TABLE backup_blobs;", paramsv![])
|
||||||
.await?;
|
.await?;
|
||||||
context.sql.execute(sqlx::query("VACUUM;")).await.ok();
|
context.sql.execute("VACUUM;", paramsv![]).await.ok();
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
bail!("received stop signal");
|
bail!("received stop signal");
|
||||||
@@ -677,7 +683,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
|||||||
|
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("VACUUM;"))
|
.execute("VACUUM;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.map_err(|e| warn!(context, "Vacuum failed, exporting anyway {}", e));
|
.map_err(|e| warn!(context, "Vacuum failed, exporting anyway {}", e));
|
||||||
|
|
||||||
@@ -830,26 +836,29 @@ async fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
|||||||
async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||||
let mut export_errors = 0;
|
let mut export_errors = 0;
|
||||||
|
|
||||||
let mut keys = context
|
let keys = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(sqlx::query(
|
.query_map(
|
||||||
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||||
))
|
paramsv![],
|
||||||
.await?
|
|row| {
|
||||||
.map(|row| -> sqlx::Result<_> {
|
let id = row.get(0)?;
|
||||||
let row = row?;
|
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||||
let id = row.try_get(0)?;
|
let public_key = SignedPublicKey::from_slice(&public_key_blob);
|
||||||
let public_key_blob: &[u8] = row.try_get(1)?;
|
let private_key_blob: Vec<u8> = row.get(2)?;
|
||||||
let public_key = SignedPublicKey::from_slice(public_key_blob);
|
let private_key = SignedSecretKey::from_slice(&private_key_blob);
|
||||||
let private_key_blob: &[u8] = row.try_get(2)?;
|
let is_default: i32 = row.get(3)?;
|
||||||
let private_key = SignedSecretKey::from_slice(private_key_blob);
|
|
||||||
let is_default: i32 = row.try_get(3)?;
|
|
||||||
|
|
||||||
Ok((id, public_key, private_key, is_default))
|
Ok((id, public_key, private_key, is_default))
|
||||||
});
|
},
|
||||||
|
|keys| {
|
||||||
|
keys.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
while let Some(parts) = keys.next().await {
|
for (id, public_key, private_key, is_default) in keys {
|
||||||
let (id, public_key, private_key, is_default) = parts?;
|
|
||||||
let id = Some(id).filter(|_| is_default != 0);
|
let id = Some(id).filter(|_| is_default != 0);
|
||||||
if let Ok(key) = public_key {
|
if let Ok(key) = public_key {
|
||||||
if export_key_to_asc_file(context, &dir, id, &key)
|
if export_key_to_asc_file(context, &dir, id, &key)
|
||||||
|
|||||||
193
src/job.rs
193
src/job.rs
@@ -7,11 +7,10 @@ use std::{fmt, time::Duration};
|
|||||||
|
|
||||||
use anyhow::{bail, ensure, format_err, Context as _, Error, Result};
|
use anyhow::{bail, ensure, format_err, Context as _, Error, Result};
|
||||||
use async_smtp::smtp::response::{Category, Code, Detail};
|
use async_smtp::smtp::response::{Category, Code, Detail};
|
||||||
use async_std::prelude::*;
|
|
||||||
use async_std::task::sleep;
|
use async_std::task::sleep;
|
||||||
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::dc_tools::{dc_delete_file, dc_read_file, time};
|
use crate::dc_tools::{dc_delete_file, dc_read_file, time};
|
||||||
use crate::ephemeral::load_imap_deletion_msgid;
|
use crate::ephemeral::load_imap_deletion_msgid;
|
||||||
@@ -37,7 +36,9 @@ use crate::{scheduler::InterruptInfo, sql};
|
|||||||
const JOB_RETRIES: u32 = 17;
|
const JOB_RETRIES: u32 = 17;
|
||||||
|
|
||||||
/// Thread IDs
|
/// Thread IDs
|
||||||
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
|
#[derive(
|
||||||
|
Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql,
|
||||||
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub(crate) enum Thread {
|
pub(crate) enum Thread {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
@@ -75,7 +76,17 @@ impl Default for Thread {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Display, Copy, Clone, PartialEq, Eq, PartialOrd, FromPrimitive, ToPrimitive, sqlx::Type,
|
Debug,
|
||||||
|
Display,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
PartialOrd,
|
||||||
|
FromPrimitive,
|
||||||
|
ToPrimitive,
|
||||||
|
FromSql,
|
||||||
|
ToSql,
|
||||||
)]
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
@@ -173,7 +184,7 @@ impl Job {
|
|||||||
if self.job_id != 0 {
|
if self.job_id != 0 {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM jobs WHERE id=?;").bind(self.job_id as i32))
|
.execute("DELETE FROM jobs WHERE id=?;", paramsv![self.job_id as i32])
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,24 +203,26 @@ impl Job {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
|
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
|
||||||
)
|
paramsv![
|
||||||
.bind(self.desired_timestamp)
|
self.desired_timestamp,
|
||||||
.bind(self.tries as i64)
|
self.tries as i64,
|
||||||
.bind(self.param.to_string())
|
self.param.to_string(),
|
||||||
.bind(self.job_id as i32),
|
self.job_id as i32,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
context.sql.execute(
|
context.sql.execute(
|
||||||
sqlx::query("INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);")
|
"INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);",
|
||||||
.bind(self.added_timestamp)
|
paramsv![
|
||||||
.bind(thread)
|
self.added_timestamp,
|
||||||
.bind(self.action)
|
thread,
|
||||||
.bind(self.foreign_id)
|
self.action,
|
||||||
.bind(self.param.to_string())
|
self.foreign_id,
|
||||||
.bind(self.desired_timestamp)
|
self.param.to_string(),
|
||||||
|
self.desired_timestamp
|
||||||
|
]
|
||||||
).await?;
|
).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -419,32 +432,39 @@ impl Job {
|
|||||||
contact_id: u32,
|
contact_id: u32,
|
||||||
) -> sql::Result<(Vec<u32>, Vec<String>)> {
|
) -> sql::Result<(Vec<u32>, Vec<String>)> {
|
||||||
// Extract message IDs from job parameters
|
// Extract message IDs from job parameters
|
||||||
let mut rows = context
|
let res: Vec<(u32, MsgId)> = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query("SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?")
|
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
|
||||||
.bind(contact_id)
|
paramsv![contact_id, self.job_id],
|
||||||
.bind(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?;
|
.await?;
|
||||||
|
|
||||||
// Load corresponding RFC724 message IDs
|
// Load corresponding RFC724 message IDs
|
||||||
let mut job_ids = Vec::new();
|
let mut job_ids = Vec::new();
|
||||||
let mut rfc724_mids = Vec::new();
|
let mut rfc724_mids = Vec::new();
|
||||||
|
for (job_id, msg_id) in res {
|
||||||
while let Some(row) = rows.next().await {
|
if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await {
|
||||||
let row = row?;
|
|
||||||
let job_id: u32 = row.try_get(0)?;
|
|
||||||
let params_str: String = row.try_get(1)?;
|
|
||||||
let params: Params = params_str.parse().unwrap_or_default();
|
|
||||||
if let Some(msg_id) = params.get_msg_id() {
|
|
||||||
if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await
|
|
||||||
{
|
|
||||||
job_ids.push(job_id);
|
job_ids.push(job_id);
|
||||||
rfc724_mids.push(rfc724_mid);
|
rfc724_mids.push(rfc724_mid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok((job_ids, rfc724_mids))
|
Ok((job_ids, rfc724_mids))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -820,7 +840,7 @@ impl Job {
|
|||||||
pub async fn kill_action(context: &Context, action: Action) -> bool {
|
pub async fn kill_action(context: &Context, action: Action) -> bool {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM jobs WHERE action=?;").bind(action))
|
.execute("DELETE FROM jobs WHERE action=?;", paramsv![action])
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
}
|
}
|
||||||
@@ -831,18 +851,20 @@ async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
|
|||||||
"DELETE FROM jobs WHERE id IN({})",
|
"DELETE FROM jobs WHERE id IN({})",
|
||||||
job_ids.iter().map(|_| "?").join(",")
|
job_ids.iter().map(|_| "?").join(",")
|
||||||
);
|
);
|
||||||
let mut query = sqlx::query(&q);
|
context
|
||||||
for id in job_ids {
|
.sql
|
||||||
query = query.bind(*id);
|
.execute(q, job_ids.iter().map(|i| i as &dyn crate::ToSql).collect())
|
||||||
}
|
.await?;
|
||||||
context.sql.execute(query).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn action_exists(context: &Context, action: Action) -> bool {
|
pub async fn action_exists(context: &Context, action: Action) -> bool {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.exists(sqlx::query("SELECT COUNT(*) FROM jobs WHERE action=?;").bind(action))
|
.exists(
|
||||||
|
"SELECT COUNT(*) FROM jobs WHERE action=?;",
|
||||||
|
paramsv![action],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
@@ -851,7 +873,7 @@ async fn set_delivered(context: &Context, msg_id: MsgId) -> Result<()> {
|
|||||||
message::update_msg_state(context, msg_id, MessageState::OutDelivered).await;
|
message::update_msg_state(context, msg_id, MessageState::OutDelivered).await;
|
||||||
let chat_id: ChatId = context
|
let chat_id: ChatId = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("SELECT chat_id FROM msgs WHERE id=?").bind(msg_id))
|
.query_get_value("SELECT chat_id FROM msgs WHERE id=?", paramsv![msg_id])
|
||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
context.emit_event(EventType::MsgDelivered { chat_id, msg_id });
|
context.emit_event(EventType::MsgDelivered { chat_id, msg_id });
|
||||||
@@ -1282,77 +1304,65 @@ pub(crate) async fn load_next(
|
|||||||
sleep(Duration::from_millis(500)).await;
|
sleep(Duration::from_millis(500)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let query;
|
||||||
|
let params;
|
||||||
let t = time();
|
let t = time();
|
||||||
|
let m;
|
||||||
let thread_i = thread as i64;
|
let thread_i = thread as i64;
|
||||||
|
|
||||||
let get_query = || {
|
|
||||||
if let Some(msg_id) = info.msg_id {
|
if let Some(msg_id) = info.msg_id {
|
||||||
sqlx::query(
|
query = r#"
|
||||||
r#"
|
|
||||||
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
|
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
|
||||||
FROM jobs
|
FROM jobs
|
||||||
WHERE thread=? AND foreign_id=?
|
WHERE thread=? AND foreign_id=?
|
||||||
ORDER BY action DESC, added_timestamp
|
ORDER BY action DESC, added_timestamp
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
"#,
|
"#;
|
||||||
)
|
m = msg_id;
|
||||||
.bind(thread_i)
|
params = paramsv![thread_i, m];
|
||||||
.bind(msg_id)
|
|
||||||
} else if !info.probe_network {
|
} else if !info.probe_network {
|
||||||
// processing for first-try and after backoff-timeouts:
|
// processing for first-try and after backoff-timeouts:
|
||||||
// process jobs in the order they were added.
|
// process jobs in the order they were added.
|
||||||
sqlx::query(
|
query = r#"
|
||||||
r#"
|
|
||||||
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
|
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
|
||||||
FROM jobs
|
FROM jobs
|
||||||
WHERE thread=? AND desired_timestamp<=?
|
WHERE thread=? AND desired_timestamp<=?
|
||||||
ORDER BY action DESC, added_timestamp
|
ORDER BY action DESC, added_timestamp
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
"#,
|
"#;
|
||||||
)
|
params = paramsv![thread_i, t];
|
||||||
.bind(thread_i)
|
|
||||||
.bind(t)
|
|
||||||
} else {
|
} else {
|
||||||
// processing after call to dc_maybe_network():
|
// processing after call to dc_maybe_network():
|
||||||
// process _all_ pending jobs that failed before
|
// process _all_ pending jobs that failed before
|
||||||
// in the order of their backoff-times.
|
// in the order of their backoff-times.
|
||||||
sqlx::query(
|
query = r#"
|
||||||
r#"
|
|
||||||
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
|
SELECT id, action, foreign_id, param, added_timestamp, desired_timestamp, tries
|
||||||
FROM jobs
|
FROM jobs
|
||||||
WHERE thread=? AND tries>0
|
WHERE thread=? AND tries>0
|
||||||
ORDER BY desired_timestamp, action DESC
|
ORDER BY desired_timestamp, action DESC
|
||||||
LIMIT 1;
|
LIMIT 1;
|
||||||
"#,
|
"#;
|
||||||
)
|
params = paramsv![thread_i];
|
||||||
.bind(thread_i)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let job = loop {
|
let job = loop {
|
||||||
let job_res = context
|
let job_res = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_optional(get_query())
|
.query_row_optional(query, params.clone(), |row| {
|
||||||
.await
|
let job = Job {
|
||||||
.and_then(|row| {
|
job_id: row.get("id")?,
|
||||||
if let Some(row) = row {
|
action: row.get("action")?,
|
||||||
Ok(Some(Job {
|
foreign_id: row.get("foreign_id")?,
|
||||||
job_id: row.try_get("id")?,
|
desired_timestamp: row.get("desired_timestamp")?,
|
||||||
action: row.try_get("action")?,
|
added_timestamp: row.get("added_timestamp")?,
|
||||||
foreign_id: row.try_get("foreign_id")?,
|
tries: row.get("tries")?,
|
||||||
desired_timestamp: row.try_get("desired_timestamp")?,
|
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
|
||||||
added_timestamp: row.try_get("added_timestamp")?,
|
|
||||||
tries: row.try_get::<i64, _>("tries")? as u32,
|
|
||||||
param: row
|
|
||||||
.try_get::<String, _>("param")?
|
|
||||||
.parse()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
pending_error: None,
|
pending_error: None,
|
||||||
}))
|
};
|
||||||
} else {
|
|
||||||
Ok(None)
|
Ok(job)
|
||||||
}
|
})
|
||||||
});
|
.await;
|
||||||
|
|
||||||
match job_res {
|
match job_res {
|
||||||
Ok(job) => break job,
|
Ok(job) => break job,
|
||||||
@@ -1363,14 +1373,13 @@ LIMIT 1;
|
|||||||
// TODO: improve by only doing a single query
|
// TODO: improve by only doing a single query
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(get_query())
|
.query_row(query, params.clone(), |row| row.get::<_, i32>(0))
|
||||||
.await
|
.await
|
||||||
.and_then(|row| row.try_get::<i32, _>(0).map_err(Into::into))
|
|
||||||
{
|
{
|
||||||
Ok(id) => {
|
Ok(id) => {
|
||||||
if let Err(err) = context
|
if let Err(err) = context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM jobs WHERE id=?;").bind(id))
|
.execute("DELETE FROM jobs WHERE id=?;", paramsv![id])
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
warn!(context, "failed to delete job {}: {:?}", id, err);
|
warn!(context, "failed to delete job {}: {:?}", id, err);
|
||||||
@@ -1421,17 +1430,17 @@ mod tests {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO jobs
|
"INSERT INTO jobs
|
||||||
(added_timestamp, thread, action, foreign_id, param, desired_timestamp)
|
(added_timestamp, thread, action, foreign_id, param, desired_timestamp)
|
||||||
VALUES (?, ?, ?, ?, ?, ?);",
|
VALUES (?, ?, ?, ?, ?, ?);",
|
||||||
)
|
paramsv![
|
||||||
.bind(now)
|
now,
|
||||||
.bind(Thread::from(Action::MoveMsg))
|
Thread::from(Action::MoveMsg),
|
||||||
.bind(if valid { Action::MoveMsg as i32 } else { -1 })
|
if valid { Action::MoveMsg as i32 } else { -1 },
|
||||||
.bind(foreign_id)
|
foreign_id,
|
||||||
.bind(Params::new().to_string())
|
Params::new().to_string(),
|
||||||
.bind(now),
|
now
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -1451,7 +1460,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
// The housekeeping job should be loaded as we didn't run housekeeping in the last day:
|
// The housekeeping job should be loaded as we didn't run housekeeping in the last day:
|
||||||
assert!(jobs.unwrap().action == Action::Housekeeping);
|
assert_eq!(jobs.unwrap().action, Action::Housekeeping);
|
||||||
|
|
||||||
insert_job(&t, 1, true).await;
|
insert_job(&t, 1, true).await;
|
||||||
let jobs = load_next(
|
let jobs = load_next(
|
||||||
|
|||||||
59
src/key.rs
59
src/key.rs
@@ -9,7 +9,6 @@ use num_traits::FromPrimitive;
|
|||||||
use pgp::composed::Deserializable;
|
use pgp::composed::Deserializable;
|
||||||
use pgp::ser::Serialize;
|
use pgp::ser::Serialize;
|
||||||
use pgp::types::{KeyTrait, SecretKeyTrait};
|
use pgp::types::{KeyTrait, SecretKeyTrait};
|
||||||
use sqlx::Row;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -42,8 +41,6 @@ pub enum Error {
|
|||||||
InvalidConfiguredAddr(#[from] InvalidEmailError),
|
InvalidConfiguredAddr(#[from] InvalidEmailError),
|
||||||
#[error("no data provided")]
|
#[error("no data provided")]
|
||||||
Empty,
|
Empty,
|
||||||
#[error("db: {}", _0)]
|
|
||||||
Sql(#[from] sqlx::Error),
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(#[from] anyhow::Error),
|
Other(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
@@ -123,17 +120,22 @@ impl DcKey for SignedPublicKey {
|
|||||||
async fn load_self(context: &Context) -> Result<Self::KeyType> {
|
async fn load_self(context: &Context) -> Result<Self::KeyType> {
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.fetch_optional(sqlx::query(
|
.query_row_optional(
|
||||||
r#"
|
r#"
|
||||||
SELECT public_key
|
SELECT public_key
|
||||||
FROM keypairs
|
FROM keypairs
|
||||||
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
||||||
AND is_default=1;
|
AND is_default=1;
|
||||||
"#,
|
"#,
|
||||||
))
|
paramsv![],
|
||||||
|
|row| {
|
||||||
|
let bytes: Vec<u8> = row.get(0)?;
|
||||||
|
Ok(bytes)
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
Some(row) => Self::from_slice(row.try_get(0)?),
|
Some(bytes) => Self::from_slice(&bytes),
|
||||||
None => {
|
None => {
|
||||||
let keypair = generate_keypair(context).await?;
|
let keypair = generate_keypair(context).await?;
|
||||||
Ok(keypair.public)
|
Ok(keypair.public)
|
||||||
@@ -165,17 +167,22 @@ impl DcKey for SignedSecretKey {
|
|||||||
async fn load_self(context: &Context) -> Result<Self::KeyType> {
|
async fn load_self(context: &Context) -> Result<Self::KeyType> {
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.fetch_optional(sqlx::query(
|
.query_row_optional(
|
||||||
r#"
|
r#"
|
||||||
SELECT private_key
|
SELECT private_key
|
||||||
FROM keypairs
|
FROM keypairs
|
||||||
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
WHERE addr=(SELECT value FROM config WHERE keyname="configured_addr")
|
||||||
AND is_default=1;
|
AND is_default=1;
|
||||||
"#,
|
"#,
|
||||||
))
|
paramsv![],
|
||||||
|
|row| {
|
||||||
|
let bytes: Vec<u8> = row.get(0)?;
|
||||||
|
Ok(bytes)
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
Some(row) => Self::from_slice(row.try_get(0)?),
|
Some(bytes) => Self::from_slice(&bytes),
|
||||||
None => {
|
None => {
|
||||||
let keypair = generate_keypair(context).await?;
|
let keypair = generate_keypair(context).await?;
|
||||||
Ok(keypair.secret)
|
Ok(keypair.secret)
|
||||||
@@ -228,23 +235,26 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
|
|||||||
// Check if the key appeared while we were waiting on the lock.
|
// Check if the key appeared while we were waiting on the lock.
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.fetch_optional(
|
.query_row_optional(
|
||||||
sqlx::query(
|
|
||||||
r#"
|
r#"
|
||||||
SELECT public_key, private_key
|
SELECT public_key, private_key
|
||||||
FROM keypairs
|
FROM keypairs
|
||||||
WHERE addr=?1
|
WHERE addr=?1
|
||||||
AND is_default=1;
|
AND is_default=1;
|
||||||
"#,
|
"#,
|
||||||
)
|
paramsv![addr],
|
||||||
.bind(addr.to_string()),
|
|row| {
|
||||||
|
let pub_bytes: Vec<u8> = row.get(0)?;
|
||||||
|
let sec_bytes: Vec<u8> = row.get(1)?;
|
||||||
|
Ok((pub_bytes, sec_bytes))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
Some(row) => Ok(KeyPair {
|
Some((pub_bytes, sec_bytes)) => Ok(KeyPair {
|
||||||
addr,
|
addr,
|
||||||
public: SignedPublicKey::from_slice(row.try_get(0)?)?,
|
public: SignedPublicKey::from_slice(&pub_bytes)?,
|
||||||
secret: SignedSecretKey::from_slice(row.try_get(1)?)?,
|
secret: SignedSecretKey::from_slice(&sec_bytes)?,
|
||||||
}),
|
}),
|
||||||
None => {
|
None => {
|
||||||
let start = std::time::SystemTime::now();
|
let start = std::time::SystemTime::now();
|
||||||
@@ -319,16 +329,15 @@ pub async fn store_self_keypair(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("DELETE FROM keypairs WHERE public_key=? OR private_key=?;")
|
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
|
||||||
.bind(&public_key)
|
paramsv![public_key, secret_key],
|
||||||
.bind(&secret_key),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?;
|
.map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?;
|
||||||
if default == KeyPairUse::Default {
|
if default == KeyPairUse::Default {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("UPDATE keypairs SET is_default=0;"))
|
.execute("UPDATE keypairs SET is_default=0;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.map_err(|err| SaveKeyError::new("failed to clear default", err))?;
|
.map_err(|err| SaveKeyError::new("failed to clear default", err))?;
|
||||||
}
|
}
|
||||||
@@ -343,15 +352,9 @@ pub async fn store_self_keypair(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created)
|
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created)
|
||||||
VALUES (?,?,?,?,?);",
|
VALUES (?,?,?,?,?);",
|
||||||
)
|
paramsv![addr, is_default, public_key, secret_key, t],
|
||||||
.bind(addr)
|
|
||||||
.bind(is_default)
|
|
||||||
.bind(&public_key)
|
|
||||||
.bind(&secret_key)
|
|
||||||
.bind(t),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|err| SaveKeyError::new("failed to insert keypair", err))?;
|
.map_err(|err| SaveKeyError::new("failed to insert keypair", err))?;
|
||||||
@@ -625,7 +628,7 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
|||||||
|
|
||||||
let nrows = || async {
|
let nrows = || async {
|
||||||
ctx.sql
|
ctx.sql
|
||||||
.count(sqlx::query("SELECT COUNT(*) FROM keypairs;"))
|
.count("SELECT COUNT(*) FROM keypairs;", paramsv![])
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|||||||
10
src/lib.rs
10
src/lib.rs
@@ -1,11 +1,11 @@
|
|||||||
|
#![forbid(unsafe_code)]
|
||||||
#![deny(
|
#![deny(
|
||||||
clippy::correctness,
|
clippy::correctness,
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
clippy::all,
|
clippy::all,
|
||||||
clippy::indexing_slicing,
|
clippy::indexing_slicing,
|
||||||
clippy::wildcard_imports,
|
clippy::wildcard_imports,
|
||||||
clippy::needless_borrow,
|
clippy::needless_borrow
|
||||||
unsafe_code
|
|
||||||
)]
|
)]
|
||||||
#![allow(clippy::match_bool, clippy::eval_order_dependence)]
|
#![allow(clippy::match_bool, clippy::eval_order_dependence)]
|
||||||
|
|
||||||
@@ -13,10 +13,16 @@
|
|||||||
extern crate num_derive;
|
extern crate num_derive;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate smallvec;
|
extern crate smallvec;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rusqlite;
|
||||||
extern crate strum;
|
extern crate strum;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate strum_macros;
|
extern crate strum_macros;
|
||||||
|
|
||||||
|
pub trait ToSql: rusqlite::ToSql + Send + Sync {}
|
||||||
|
|
||||||
|
impl<T: rusqlite::ToSql + Send + Sync> ToSql for T {}
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod log;
|
pub mod log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
|||||||
350
src/location.rs
350
src/location.rs
@@ -2,10 +2,8 @@
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use anyhow::{ensure, Error};
|
use anyhow::{ensure, Error};
|
||||||
use async_std::prelude::*;
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::chat::{self, ChatId};
|
use crate::chat::{self, ChatId};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -201,15 +199,15 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
|
|||||||
if context
|
if context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE chats \
|
"UPDATE chats \
|
||||||
SET locations_send_begin=?, \
|
SET locations_send_begin=?, \
|
||||||
locations_send_until=? \
|
locations_send_until=? \
|
||||||
WHERE id=?",
|
WHERE id=?",
|
||||||
)
|
paramsv![
|
||||||
.bind(if 0 != seconds { now } else { 0 })
|
if 0 != seconds { now } else { 0 },
|
||||||
.bind(if 0 != seconds { now + seconds } else { 0 })
|
if 0 != seconds { now + seconds } else { 0 },
|
||||||
.bind(chat_id),
|
chat_id,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -262,17 +260,16 @@ pub async fn is_sending_locations_to_chat(context: &Context, chat_id: Option<Cha
|
|||||||
Some(chat_id) => context
|
Some(chat_id) => context
|
||||||
.sql
|
.sql
|
||||||
.exists(
|
.exists(
|
||||||
sqlx::query("SELECT COUNT(id) FROM chats WHERE id=? AND locations_send_until>?;")
|
"SELECT COUNT(id) FROM chats WHERE id=? AND locations_send_until>?;",
|
||||||
.bind(chat_id)
|
paramsv![chat_id, time()],
|
||||||
.bind(time()),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
None => context
|
None => context
|
||||||
.sql
|
.sql
|
||||||
.exists(
|
.exists(
|
||||||
sqlx::query("SELECT COUNT(id) FROM chats WHERE locations_send_until>?;")
|
"SELECT COUNT(id) FROM chats WHERE locations_send_until>?;",
|
||||||
.bind(time()),
|
paramsv![time()],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@@ -285,29 +282,28 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
|||||||
}
|
}
|
||||||
let mut continue_streaming = false;
|
let mut continue_streaming = false;
|
||||||
|
|
||||||
if let Ok(mut chats) = context
|
if let Ok(chats) = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(sqlx::query("SELECT id FROM chats WHERE locations_send_until>?;").bind(time()))
|
.query_map(
|
||||||
.await
|
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||||
.map(|rows| rows.map(|row| row?.try_get::<i32, _>(0)))
|
paramsv![time()],
|
||||||
{
|
|row| row.get::<_, i32>(0),
|
||||||
while let Some(chat_id) = chats.next().await {
|
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||||
let chat_id = match chat_id {
|
|
||||||
Ok(id) => id,
|
|
||||||
Err(_) => break,
|
|
||||||
};
|
|
||||||
if let Err(err) = context.sql.execute(
|
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO locations \
|
|
||||||
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);"
|
|
||||||
)
|
)
|
||||||
.bind(latitude)
|
.await
|
||||||
.bind(longitude)
|
{
|
||||||
.bind(accuracy)
|
for chat_id in chats {
|
||||||
.bind(time())
|
if let Err(err) = context.sql.execute(
|
||||||
.bind(chat_id)
|
"INSERT INTO locations \
|
||||||
.bind(DC_CONTACT_ID_SELF)
|
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
||||||
|
paramsv![
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
accuracy,
|
||||||
|
time(),
|
||||||
|
chat_id,
|
||||||
|
DC_CONTACT_ID_SELF,
|
||||||
|
]
|
||||||
).await {
|
).await {
|
||||||
warn!(context, "failed to store location {:?}", err);
|
warn!(context, "failed to store location {:?}", err);
|
||||||
} else {
|
} else {
|
||||||
@@ -342,50 +338,54 @@ pub async fn get_range(
|
|||||||
Some(contact_id) => (0, contact_id),
|
Some(contact_id) => (0, contact_id),
|
||||||
None => (1, 0), // this contact_id is unused
|
None => (1, 0), // this contact_id is unused
|
||||||
};
|
};
|
||||||
|
|
||||||
let list = context
|
let list = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \
|
"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 \
|
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=?) \
|
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \
|
||||||
AND (? OR l.from_id=?) \
|
AND (? OR l.from_id=?) \
|
||||||
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||||
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
|
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
|
||||||
)
|
paramsv![
|
||||||
.bind(disable_chat_id)
|
disable_chat_id,
|
||||||
.bind(chat_id)
|
chat_id,
|
||||||
.bind(disable_contact_id)
|
disable_contact_id,
|
||||||
.bind(contact_id as i64)
|
contact_id as i32,
|
||||||
.bind(timestamp_from)
|
timestamp_from,
|
||||||
.bind(timestamp_to),
|
timestamp_to,
|
||||||
)
|
],
|
||||||
.await?
|
|row| {
|
||||||
.map(|row| {
|
let msg_id = row.get(6)?;
|
||||||
let row = row?;
|
let txt: String = row.get(9)?;
|
||||||
let msg_id = row.try_get(6)?;
|
|
||||||
let txt: String = row.try_get(9)?;
|
|
||||||
let marker = if msg_id != 0 && is_marker(&txt) {
|
let marker = if msg_id != 0 && is_marker(&txt) {
|
||||||
Some(txt)
|
Some(txt)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
let loc = Location {
|
let loc = Location {
|
||||||
location_id: row.try_get(0)?,
|
location_id: row.get(0)?,
|
||||||
latitude: row.try_get(1)?,
|
latitude: row.get(1)?,
|
||||||
longitude: row.try_get(2)?,
|
longitude: row.get(2)?,
|
||||||
accuracy: row.try_get(3)?,
|
accuracy: row.get(3)?,
|
||||||
timestamp: row.try_get(4)?,
|
timestamp: row.get(4)?,
|
||||||
independent: row.try_get(5)?,
|
independent: row.get(5)?,
|
||||||
msg_id,
|
msg_id,
|
||||||
contact_id: row.try_get(7)?,
|
contact_id: row.get(7)?,
|
||||||
chat_id: row.try_get(8)?,
|
chat_id: row.get(8)?,
|
||||||
marker,
|
marker,
|
||||||
};
|
};
|
||||||
Ok(loc)
|
Ok(loc)
|
||||||
})
|
},
|
||||||
.collect::<sqlx::Result<_>>()
|
|locations| {
|
||||||
|
let mut ret = Vec::new();
|
||||||
|
|
||||||
|
for location in locations {
|
||||||
|
ret.push(location?);
|
||||||
|
}
|
||||||
|
Ok(ret)
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(list)
|
Ok(list)
|
||||||
}
|
}
|
||||||
@@ -403,7 +403,7 @@ fn is_marker(txt: &str) -> bool {
|
|||||||
pub async fn delete_all(context: &Context) -> Result<(), Error> {
|
pub async fn delete_all(context: &Context) -> Result<(), Error> {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM locations;"))
|
.execute("DELETE FROM locations;", paramsv![])
|
||||||
.await?;
|
.await?;
|
||||||
context.emit_event(EventType::LocationChanged(None));
|
context.emit_event(EventType::LocationChanged(None));
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -417,65 +417,70 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)
|
|||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let (locations_send_begin, locations_send_until, locations_last_sent) = {
|
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||||
let row = context.sql.fetch_one(
|
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||||
sqlx::query(
|
paramsv![chat_id], |row| {
|
||||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;"
|
let send_begin: i64 = row.get(0)?;
|
||||||
)
|
let send_until: i64 = row.get(1)?;
|
||||||
.bind(chat_id)
|
let last_sent: i64 = row.get(2)?;
|
||||||
).await?;
|
|
||||||
|
|
||||||
let send_begin: i64 = row.try_get(0)?;
|
Ok((send_begin, send_until, last_sent))
|
||||||
let send_until: i64 = row.try_get(1)?;
|
})
|
||||||
let last_sent: i64 = row.try_get(2)?;
|
.await?;
|
||||||
|
|
||||||
(send_begin, send_until, last_sent)
|
|
||||||
};
|
|
||||||
|
|
||||||
let now = time();
|
let now = time();
|
||||||
let mut location_count = 0;
|
let mut location_count = 0;
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
if locations_send_begin != 0 && now <= locations_send_until {
|
if locations_send_begin != 0 && now <= locations_send_until {
|
||||||
ret += &format!(
|
ret += &format!(
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
|
||||||
|
<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"{}\">\n",
|
||||||
self_addr,
|
self_addr,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut rows = context.sql.fetch(
|
context
|
||||||
sqlx::query(
|
.sql
|
||||||
|
.query_map(
|
||||||
"SELECT id, latitude, longitude, accuracy, timestamp \
|
"SELECT id, latitude, longitude, accuracy, timestamp \
|
||||||
FROM locations WHERE from_id=? \
|
FROM locations WHERE from_id=? \
|
||||||
AND timestamp>=? \
|
AND timestamp>=? \
|
||||||
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
AND (timestamp>=? OR \
|
||||||
|
timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \
|
||||||
AND independent=0 \
|
AND independent=0 \
|
||||||
GROUP BY timestamp \
|
GROUP BY timestamp \
|
||||||
ORDER BY timestamp;"
|
ORDER BY timestamp;",
|
||||||
)
|
paramsv![
|
||||||
.bind(DC_CONTACT_ID_SELF)
|
DC_CONTACT_ID_SELF,
|
||||||
.bind(locations_send_begin)
|
locations_send_begin,
|
||||||
.bind(locations_last_sent)
|
locations_last_sent,
|
||||||
.bind(DC_CONTACT_ID_SELF)
|
DC_CONTACT_ID_SELF
|
||||||
).await?;
|
],
|
||||||
|
|row| {
|
||||||
while let Some(row) = rows.next().await {
|
let location_id: i32 = row.get(0)?;
|
||||||
let row = row?;
|
let latitude: f64 = row.get(1)?;
|
||||||
let location_id: u32 = row.try_get(0)?;
|
let longitude: f64 = row.get(2)?;
|
||||||
let latitude: f64 = row.try_get(1)?;
|
let accuracy: f64 = row.get(3)?;
|
||||||
let longitude: f64 = row.try_get(2)?;
|
let timestamp = get_kml_timestamp(row.get(4)?);
|
||||||
let accuracy: f64 = row.try_get(3)?;
|
|
||||||
let timestamp = get_kml_timestamp(row.try_get(4)?);
|
|
||||||
|
|
||||||
|
Ok((location_id, latitude, longitude, accuracy, timestamp))
|
||||||
|
},
|
||||||
|
|rows| {
|
||||||
|
for row in rows {
|
||||||
|
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
|
||||||
ret += &format!(
|
ret += &format!(
|
||||||
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n",
|
"<Placemark>\
|
||||||
timestamp,
|
<Timestamp><when>{}</when></Timestamp>\
|
||||||
accuracy,
|
<Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point>\
|
||||||
longitude,
|
</Placemark>\n",
|
||||||
latitude
|
timestamp, accuracy, longitude, latitude
|
||||||
);
|
);
|
||||||
location_count += 1;
|
location_count += 1;
|
||||||
last_added_location_id = location_id;
|
last_added_location_id = location_id as u32;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
ret += "</Document>\n</kml>";
|
ret += "</Document>\n</kml>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,9 +521,8 @@ pub async fn set_kml_sent_timestamp(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE chats SET locations_last_sent=? WHERE id=?;")
|
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||||
.bind(timestamp)
|
paramsv![timestamp, chat_id],
|
||||||
.bind(chat_id),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -532,9 +536,8 @@ pub async fn set_msg_location_id(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE msgs SET location_id=? WHERE id=?;")
|
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||||
.bind(location_id)
|
paramsv![location_id, msg_id],
|
||||||
.bind(msg_id),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -553,7 +556,6 @@ pub async fn save(
|
|||||||
let mut newest_timestamp = 0;
|
let mut newest_timestamp = 0;
|
||||||
let mut newest_location_id = 0;
|
let mut newest_location_id = 0;
|
||||||
|
|
||||||
let stmt_test = "SELECT COUNT(*) FROM locations WHERE timestamp=? AND from_id=?";
|
|
||||||
let stmt_insert = "INSERT INTO locations\
|
let stmt_insert = "INSERT INTO locations\
|
||||||
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
|
||||||
VALUES (?,?,?,?,?,?,?);";
|
VALUES (?,?,?,?,?,?,?);";
|
||||||
@@ -566,30 +568,39 @@ pub async fn save(
|
|||||||
accuracy,
|
accuracy,
|
||||||
..
|
..
|
||||||
} = location;
|
} = location;
|
||||||
let exists = context
|
let (loc_id, ts) = context
|
||||||
.sql
|
.sql
|
||||||
.exists(sqlx::query(stmt_test).bind(timestamp).bind(contact_id))
|
.with_conn(move |conn| {
|
||||||
.await?;
|
let mut stmt_test = conn
|
||||||
|
.prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?;
|
||||||
|
let mut stmt_insert = conn.prepare_cached(stmt_insert)?;
|
||||||
|
|
||||||
|
let exists = stmt_test.exists(paramsv![timestamp, contact_id as i32])?;
|
||||||
|
|
||||||
if independent || !exists {
|
if independent || !exists {
|
||||||
let row_id = context
|
stmt_insert.execute(paramsv![
|
||||||
.sql
|
timestamp,
|
||||||
.insert(
|
contact_id as i32,
|
||||||
sqlx::query(stmt_insert)
|
chat_id,
|
||||||
.bind(timestamp)
|
latitude,
|
||||||
.bind(contact_id)
|
longitude,
|
||||||
.bind(chat_id)
|
accuracy,
|
||||||
.bind(latitude)
|
independent,
|
||||||
.bind(longitude)
|
])?;
|
||||||
.bind(accuracy)
|
|
||||||
.bind(independent),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if timestamp > newest_timestamp {
|
if timestamp > newest_timestamp {
|
||||||
|
// okay to drop, as we use cached prepared statements
|
||||||
|
drop(stmt_test);
|
||||||
|
drop(stmt_insert);
|
||||||
newest_timestamp = timestamp;
|
newest_timestamp = timestamp;
|
||||||
newest_location_id = row_id;
|
newest_location_id = conn.last_insert_rowid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok((newest_location_id, newest_timestamp))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
newest_timestamp = ts;
|
||||||
|
newest_location_id = loc_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(u32::try_from(newest_location_id)?)
|
Ok(u32::try_from(newest_location_id)?)
|
||||||
@@ -605,21 +616,15 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
|||||||
|
|
||||||
let rows = context
|
let rows = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT id, locations_send_begin, locations_last_sent \
|
"SELECT id, locations_send_begin, locations_last_sent \
|
||||||
FROM chats \
|
FROM chats \
|
||||||
WHERE locations_send_until>?;",
|
WHERE locations_send_until>?;",
|
||||||
)
|
paramsv![now],
|
||||||
.bind(now),
|
|row| {
|
||||||
)
|
let chat_id: ChatId = row.get(0)?;
|
||||||
.await
|
let locations_send_begin: i64 = row.get(1)?;
|
||||||
.map(|rows| {
|
let locations_last_sent: i64 = row.get(2)?;
|
||||||
rows.map(|row| -> sqlx::Result<Option<_>> {
|
|
||||||
let row = row?;
|
|
||||||
let chat_id: ChatId = row.try_get(0)?;
|
|
||||||
let locations_send_begin: i64 = row.try_get(1)?;
|
|
||||||
let locations_last_sent: i64 = row.try_get(2)?;
|
|
||||||
continue_streaming = true;
|
continue_streaming = true;
|
||||||
|
|
||||||
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
// be a bit tolerant as the timer may not align exactly with time(NULL)
|
||||||
@@ -628,37 +633,41 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
|||||||
} else {
|
} else {
|
||||||
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
Ok(Some((chat_id, locations_send_begin, locations_last_sent)))
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
.filter_map(|v| v.transpose())
|
|rows| {
|
||||||
});
|
rows.filter_map(|v| v.transpose())
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let stmt = "SELECT COUNT(*) \
|
if rows.is_ok() {
|
||||||
|
let msgs = context
|
||||||
|
.sql
|
||||||
|
.with_conn(move |conn| {
|
||||||
|
let rows = rows.unwrap();
|
||||||
|
|
||||||
|
let mut stmt_locations = conn.prepare_cached(
|
||||||
|
"SELECT id \
|
||||||
FROM locations \
|
FROM locations \
|
||||||
WHERE from_id=? \
|
WHERE from_id=? \
|
||||||
AND timestamp>=? \
|
AND timestamp>=? \
|
||||||
AND timestamp>? \
|
AND timestamp>? \
|
||||||
AND independent=0 \
|
AND independent=0 \
|
||||||
ORDER BY timestamp;";
|
ORDER BY timestamp;",
|
||||||
|
)?;
|
||||||
|
|
||||||
if let Ok(mut rows) = rows {
|
|
||||||
let mut msgs = Vec::new();
|
let mut msgs = Vec::new();
|
||||||
while let Some(row) = rows.next().await {
|
for (chat_id, locations_send_begin, locations_last_sent) in &rows {
|
||||||
let (chat_id, locations_send_begin, locations_last_sent) = match row {
|
if !stmt_locations
|
||||||
Ok(row) => row,
|
.exists(paramsv![
|
||||||
Err(_) => break,
|
DC_CONTACT_ID_SELF,
|
||||||
};
|
*locations_send_begin,
|
||||||
let exists = context
|
*locations_last_sent,
|
||||||
.sql
|
])
|
||||||
.exists(
|
.unwrap_or_default()
|
||||||
sqlx::query(stmt)
|
{
|
||||||
.bind(DC_CONTACT_ID_SELF)
|
|
||||||
.bind(locations_send_begin)
|
|
||||||
.bind(locations_last_sent),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default(); // TODO: better error handling
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
// if there is no new location, there's nothing to send.
|
// if there is no new location, there's nothing to send.
|
||||||
// however, maybe we want to bypass this test eg. 15 minutes
|
// however, maybe we want to bypass this test eg. 15 minutes
|
||||||
} else {
|
} else {
|
||||||
@@ -674,10 +683,15 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
|||||||
let mut msg = Message::new(Viewtype::Text);
|
let mut msg = Message::new(Viewtype::Text);
|
||||||
msg.hidden = true;
|
msg.hidden = true;
|
||||||
msg.param.set_cmd(SystemMessage::LocationOnly);
|
msg.param.set_cmd(SystemMessage::LocationOnly);
|
||||||
msgs.push((chat_id, msg));
|
msgs.push((*chat_id, msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(msgs)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap_or_default(); // TODO: better error handling
|
||||||
|
|
||||||
for (chat_id, mut msg) in msgs.into_iter() {
|
for (chat_id, mut msg) in msgs.into_iter() {
|
||||||
// TODO: better error handling
|
// TODO: better error handling
|
||||||
chat::send_msg(context, chat_id, &mut msg)
|
chat::send_msg(context, chat_id, &mut msg)
|
||||||
@@ -702,16 +716,16 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
|||||||
|
|
||||||
let chat_id = ChatId::new(job.foreign_id);
|
let chat_id = ChatId::new(job.foreign_id);
|
||||||
|
|
||||||
let (send_begin, send_until) = job_try!(context
|
let (send_begin, send_until) = job_try!(
|
||||||
|
context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query(
|
|
||||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||||
)
|
paramsv![chat_id],
|
||||||
.bind(chat_id)
|
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.and_then(|row| { Ok((row.try_get::<i64, _>(0)?, row.try_get::<i64, _>(1)?)) }));
|
);
|
||||||
|
|
||||||
if !(send_begin != 0 && time() <= send_until) {
|
if !(send_begin != 0 && time() <= send_until) {
|
||||||
// still streaming -
|
// still streaming -
|
||||||
@@ -723,12 +737,10 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE chats \
|
"UPDATE chats \
|
||||||
SET locations_send_begin=0, locations_send_until=0 \
|
SET locations_send_begin=0, locations_send_until=0 \
|
||||||
WHERE id=?"
|
WHERE id=?",
|
||||||
)
|
paramsv![chat_id],
|
||||||
.bind(chat_id)
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
);
|
);
|
||||||
|
|||||||
10
src/lot.rs
10
src/lot.rs
@@ -1,3 +1,5 @@
|
|||||||
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
|
|
||||||
use crate::key::Fingerprint;
|
use crate::key::Fingerprint;
|
||||||
|
|
||||||
/// An object containing a set of values.
|
/// An object containing a set of values.
|
||||||
@@ -20,7 +22,9 @@ pub struct Lot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql,
|
||||||
|
)]
|
||||||
pub enum Meaning {
|
pub enum Meaning {
|
||||||
None = 0,
|
None = 0,
|
||||||
Text1Draft = 1,
|
Text1Draft = 1,
|
||||||
@@ -64,8 +68,10 @@ impl Lot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql,
|
||||||
|
)]
|
||||||
pub enum LotState {
|
pub enum LotState {
|
||||||
// Default
|
// Default
|
||||||
Undefined = 0,
|
Undefined = 0,
|
||||||
|
|||||||
415
src/message.rs
415
src/message.rs
@@ -1,11 +1,13 @@
|
|||||||
//! # Messages and their identifiers
|
//! # Messages and their identifiers
|
||||||
|
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use anyhow::{ensure, Error};
|
use anyhow::{ensure, Error};
|
||||||
use async_std::path::{Path, PathBuf};
|
use async_std::path::{Path, PathBuf};
|
||||||
use async_std::prelude::*;
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::chat::{self, Chat, ChatId};
|
use crate::chat::{self, Chat, ChatId};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -29,7 +31,6 @@ use crate::mimeparser::{FailureReport, SystemMessage};
|
|||||||
use crate::param::{Param, Params};
|
use crate::param::{Param, Params};
|
||||||
use crate::pgp::split_armored_data;
|
use crate::pgp::split_armored_data;
|
||||||
use crate::stock_str;
|
use crate::stock_str;
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
// In practice, the user additionally cuts the string themselves
|
// In practice, the user additionally cuts the string themselves
|
||||||
// pixel-accurate.
|
// pixel-accurate.
|
||||||
@@ -41,20 +42,8 @@ const SUMMARY_CHARACTERS: usize = 160;
|
|||||||
/// This type can represent both the special as well as normal
|
/// This type can represent both the special as well as normal
|
||||||
/// messages.
|
/// messages.
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
|
||||||
Copy,
|
|
||||||
Clone,
|
|
||||||
Default,
|
|
||||||
PartialEq,
|
|
||||||
Eq,
|
|
||||||
Hash,
|
|
||||||
PartialOrd,
|
|
||||||
Ord,
|
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
sqlx::Type,
|
|
||||||
)]
|
)]
|
||||||
#[sqlx(transparent)]
|
|
||||||
pub struct MsgId(u32);
|
pub struct MsgId(u32);
|
||||||
|
|
||||||
impl MsgId {
|
impl MsgId {
|
||||||
@@ -92,7 +81,7 @@ impl MsgId {
|
|||||||
pub async fn get_state(self, context: &Context) -> crate::sql::Result<MessageState> {
|
pub async fn get_state(self, context: &Context) -> crate::sql::Result<MessageState> {
|
||||||
let result = context
|
let result = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("SELECT state FROM msgs WHERE id=?").bind(self))
|
.query_get_value("SELECT state FROM msgs WHERE id=?", paramsv![self])
|
||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@@ -172,7 +161,6 @@ impl MsgId {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
// If you change which information is removed here, also change delete_expired_messages() and
|
// If you change which information is removed here, also change delete_expired_messages() and
|
||||||
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
// which information dc_receive_imf::add_parts() still adds to the db if the chat_id is TRASH
|
||||||
r#"
|
r#"
|
||||||
@@ -185,9 +173,7 @@ SET
|
|||||||
param=''
|
param=''
|
||||||
WHERE id=?;
|
WHERE id=?;
|
||||||
"#,
|
"#,
|
||||||
)
|
paramsv![chat_id, self],
|
||||||
.bind(chat_id)
|
|
||||||
.bind(self),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -200,11 +186,11 @@ WHERE id=?;
|
|||||||
// sure they are not left while the message is deleted.
|
// sure they are not left while the message is deleted.
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM msgs_mdns WHERE msg_id=?;").bind(self))
|
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![self])
|
||||||
.await?;
|
.await?;
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM msgs WHERE id=?;").bind(self))
|
.execute("DELETE FROM msgs WHERE id=?;", paramsv![self])
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -218,12 +204,10 @@ WHERE id=?;
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE msgs \
|
"UPDATE msgs \
|
||||||
SET server_folder='', server_uid=0 \
|
SET server_folder='', server_uid=0 \
|
||||||
WHERE id=?",
|
WHERE id=?",
|
||||||
)
|
paramsv![self],
|
||||||
.bind(self),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -244,6 +228,41 @@ 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<rusqlite::types::ToSqlOutput> {
|
||||||
|
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<Self> {
|
||||||
|
// 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.
|
/// Message ID was invalid.
|
||||||
///
|
///
|
||||||
/// This usually occurs when trying to use a message ID of
|
/// This usually occurs when trying to use a message ID of
|
||||||
@@ -254,9 +273,18 @@ impl std::fmt::Display for MsgId {
|
|||||||
pub struct InvalidMsgId;
|
pub struct InvalidMsgId;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive, Serialize, Deserialize, sqlx::Type,
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
FromPrimitive,
|
||||||
|
ToPrimitive,
|
||||||
|
FromSql,
|
||||||
|
ToSql,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
)]
|
)]
|
||||||
#[repr(i8)]
|
#[repr(u8)]
|
||||||
pub(crate) enum MessengerMessage {
|
pub(crate) enum MessengerMessage {
|
||||||
No = 0,
|
No = 0,
|
||||||
Yes = 1,
|
Yes = 1,
|
||||||
@@ -320,10 +348,10 @@ impl Message {
|
|||||||
"Can not load special message ID {} from DB.",
|
"Can not load special message ID {} from DB.",
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
let row = context
|
let msg = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query(concat!(
|
concat!(
|
||||||
"SELECT",
|
"SELECT",
|
||||||
" m.id AS id,",
|
" m.id AS id,",
|
||||||
" rfc724_mid AS rfc724mid,",
|
" rfc724_mid AS rfc724mid,",
|
||||||
@@ -351,16 +379,14 @@ impl Message {
|
|||||||
" c.blocked AS blocked",
|
" c.blocked AS blocked",
|
||||||
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|
||||||
" WHERE m.id=?;"
|
" WHERE m.id=?;"
|
||||||
))
|
),
|
||||||
.bind(id),
|
paramsv![id],
|
||||||
)
|
|row| {
|
||||||
.await?;
|
let text = match row.get_raw("txt") {
|
||||||
|
rusqlite::types::ValueRef::Text(buf) => {
|
||||||
let text;
|
match String::from_utf8(buf.to_vec()) {
|
||||||
if let Ok(Some(buf)) = row.try_get::<Option<&[u8]>, _>("txt") {
|
Ok(t) => t,
|
||||||
if let Ok(t) = String::from_utf8(buf.to_vec()) {
|
Err(_) => {
|
||||||
text = t;
|
|
||||||
} else {
|
|
||||||
warn!(
|
warn!(
|
||||||
context,
|
context,
|
||||||
concat!(
|
concat!(
|
||||||
@@ -369,45 +395,46 @@ impl Message {
|
|||||||
),
|
),
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
text = String::from_utf8_lossy(buf).into_owned();
|
String::from_utf8_lossy(buf).into_owned()
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
text = "".to_string();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let msg = Message {
|
_ => String::new(),
|
||||||
id: row.try_get("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")?,
|
|
||||||
chat_id: row.try_get("chat_id")?,
|
|
||||||
from_id: row.try_get("from_id")?,
|
|
||||||
to_id: row.try_get("to_id")?,
|
|
||||||
timestamp_sort: row.try_get("timestamp")?,
|
|
||||||
timestamp_sent: row.try_get("timestamp_sent")?,
|
|
||||||
timestamp_rcvd: row.try_get("timestamp_rcvd")?,
|
|
||||||
ephemeral_timer: row.try_get("ephemeral_timer")?,
|
|
||||||
ephemeral_timestamp: row.try_get("ephemeral_timestamp")?,
|
|
||||||
viewtype: row.try_get("type")?,
|
|
||||||
state: row.try_get("state")?,
|
|
||||||
error: row
|
|
||||||
.try_get::<Option<String>, _>("error")?
|
|
||||||
.filter(|e| !e.is_empty()),
|
|
||||||
is_dc_message: row.try_get("msgrmsg")?,
|
|
||||||
mime_modified: row.try_get("mime_modified")?,
|
|
||||||
subject: row.try_get("subject")?,
|
|
||||||
param: row
|
|
||||||
.try_get::<String, _>("param")?
|
|
||||||
.parse()
|
|
||||||
.unwrap_or_default(),
|
|
||||||
hidden: row.try_get("hidden")?,
|
|
||||||
location_id: row.try_get("location")?,
|
|
||||||
chat_blocked: row
|
|
||||||
.try_get::<Option<Blocked>, _>("blocked")?
|
|
||||||
.unwrap_or_default(),
|
|
||||||
text: Some(text),
|
|
||||||
};
|
};
|
||||||
|
let msg = Message {
|
||||||
|
id: row.get("id")?,
|
||||||
|
rfc724_mid: row.get::<_, String>("rfc724mid")?,
|
||||||
|
in_reply_to: row.get::<_, Option<String>>("mime_in_reply_to")?,
|
||||||
|
server_folder: row.get::<_, Option<String>>("server_folder")?,
|
||||||
|
server_uid: row.get("server_uid")?,
|
||||||
|
chat_id: row.get("chat_id")?,
|
||||||
|
from_id: row.get("from_id")?,
|
||||||
|
to_id: row.get("to_id")?,
|
||||||
|
timestamp_sort: row.get("timestamp")?,
|
||||||
|
timestamp_sent: row.get("timestamp_sent")?,
|
||||||
|
timestamp_rcvd: row.get("timestamp_rcvd")?,
|
||||||
|
ephemeral_timer: row.get("ephemeral_timer")?,
|
||||||
|
ephemeral_timestamp: row.get("ephemeral_timestamp")?,
|
||||||
|
viewtype: row.get("type")?,
|
||||||
|
state: row.get("state")?,
|
||||||
|
error: Some(row.get::<_, String>("error")?)
|
||||||
|
.filter(|error| !error.is_empty()),
|
||||||
|
is_dc_message: row.get("msgrmsg")?,
|
||||||
|
mime_modified: row.get("mime_modified")?,
|
||||||
|
text: Some(text),
|
||||||
|
subject: row.get("subject")?,
|
||||||
|
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
|
||||||
|
hidden: row.get("hidden")?,
|
||||||
|
location_id: row.get("location")?,
|
||||||
|
chat_blocked: row
|
||||||
|
.get::<_, Option<Blocked>>("blocked")?
|
||||||
|
.unwrap_or_default(),
|
||||||
|
};
|
||||||
|
Ok(msg)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -901,9 +928,8 @@ impl Message {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE msgs SET param=? WHERE id=?;")
|
"UPDATE msgs SET param=? WHERE id=?;",
|
||||||
.bind(self.param.to_string())
|
paramsv![self.param.to_string(), self.id],
|
||||||
.bind(self.id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok_or_log(context);
|
.ok_or_log(context);
|
||||||
@@ -913,9 +939,8 @@ impl Message {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE msgs SET subject=? WHERE id=?;")
|
"UPDATE msgs SET subject=? WHERE id=?;",
|
||||||
.bind(&self.subject)
|
paramsv![self.subject, self.id],
|
||||||
.bind(&self.id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok_or_log(context);
|
.ok_or_log(context);
|
||||||
@@ -953,9 +978,10 @@ pub enum ContactRequestDecision {
|
|||||||
Eq,
|
Eq,
|
||||||
FromPrimitive,
|
FromPrimitive,
|
||||||
ToPrimitive,
|
ToPrimitive,
|
||||||
|
ToSql,
|
||||||
|
FromSql,
|
||||||
Serialize,
|
Serialize,
|
||||||
Deserialize,
|
Deserialize,
|
||||||
sqlx::Type,
|
|
||||||
)]
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum MessageState {
|
pub enum MessageState {
|
||||||
@@ -1198,7 +1224,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String, Er
|
|||||||
let msg = Message::load_from_db(context, msg_id).await?;
|
let msg = Message::load_from_db(context, msg_id).await?;
|
||||||
let rawtxt: Option<String> = context
|
let rawtxt: Option<String> = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("SELECT txt_raw FROM msgs WHERE id=?;").bind(msg_id))
|
.query_get_value("SELECT txt_raw FROM msgs WHERE id=?;", paramsv![msg_id])
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut ret = String::new();
|
let mut ret = String::new();
|
||||||
@@ -1247,29 +1273,25 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String, Er
|
|||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(mut rows) = context
|
if let Ok(rows) = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query("SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;")
|
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
|
||||||
.bind(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::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map(|rows| {
|
|
||||||
rows.map(|row| -> sqlx::Result<_> {
|
|
||||||
let row = row?;
|
|
||||||
let contact_id = row.try_get(0)?;
|
|
||||||
let ts = row.try_get(1)?;
|
|
||||||
Ok((contact_id, ts))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
while let Some(row) = rows.next().await {
|
for (contact_id, ts) in rows {
|
||||||
let (contact_id, ts) = row?;
|
|
||||||
|
|
||||||
let fts = dc_timestamp_to_str(ts);
|
let fts = dc_timestamp_to_str(ts);
|
||||||
ret += &format!("Read: {}", fts);
|
ret += &format!("Read: {}", fts);
|
||||||
|
|
||||||
let name = Contact::load_from_db(context, contact_id)
|
let name = Contact::load_from_db(context, contact_id.try_into()?)
|
||||||
.await
|
.await
|
||||||
.map(|contact| contact.get_name_n_addr())
|
.map(|contact| contact.get_name_n_addr())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
@@ -1422,7 +1444,10 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
|
|||||||
pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<String, Error> {
|
pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Result<String, Error> {
|
||||||
let headers = context
|
let headers = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("SELECT mime_headers FROM msgs WHERE id=?;").bind(msg_id))
|
.query_get_value(
|
||||||
|
"SELECT mime_headers FROM msgs WHERE id=?;",
|
||||||
|
paramsv![msg_id],
|
||||||
|
)
|
||||||
.await?
|
.await?
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Ok(headers)
|
Ok(headers)
|
||||||
@@ -1463,7 +1488,8 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("DELETE FROM locations WHERE independent = 1 AND id=?;").bind(location_id),
|
"DELETE FROM locations WHERE independent = 1 AND id=?;",
|
||||||
|
paramsv![location_id as i32],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -1473,40 +1499,41 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> bool {
|
|||||||
if msg_ids.is_empty() {
|
if msg_ids.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let stmt = concat!(
|
|
||||||
|
let msgs = context
|
||||||
|
.sql
|
||||||
|
.with_conn(move |conn| {
|
||||||
|
let mut stmt = conn.prepare_cached(concat!(
|
||||||
"SELECT",
|
"SELECT",
|
||||||
" m.chat_id AS chat_id,",
|
" m.chat_id AS chat_id,",
|
||||||
" m.state AS state,",
|
" m.state AS state,",
|
||||||
" c.blocked AS blocked",
|
" c.blocked AS blocked",
|
||||||
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|
||||||
" WHERE m.id=? AND m.chat_id>9"
|
" WHERE m.id=? AND m.chat_id>9"
|
||||||
);
|
))?;
|
||||||
|
|
||||||
let mut msgs = Vec::with_capacity(msg_ids.len());
|
let mut msgs = Vec::with_capacity(msg_ids.len());
|
||||||
for id in msg_ids.into_iter() {
|
for id in msg_ids.into_iter() {
|
||||||
match context
|
let query_res = stmt.query_row(paramsv![id], |row| {
|
||||||
.sql
|
Ok((
|
||||||
.fetch_optional(sqlx::query(stmt).bind(id))
|
row.get::<_, ChatId>("chat_id")?,
|
||||||
.await
|
row.get::<_, MessageState>("state")?,
|
||||||
.and_then(|row| {
|
row.get::<_, Option<Blocked>>("blocked")?
|
||||||
if let Some(row) = row {
|
|
||||||
Ok(Some((
|
|
||||||
row.try_get::<ChatId, _>("chat_id")?,
|
|
||||||
row.try_get::<MessageState, _>("state")?,
|
|
||||||
row.try_get::<Option<Blocked>, _>("blocked")?
|
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
)))
|
))
|
||||||
} else {
|
});
|
||||||
Ok(None)
|
if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res {
|
||||||
}
|
continue;
|
||||||
}) {
|
|
||||||
Ok(Some((chat_id, state, blocked))) => msgs.push((id, chat_id, state, blocked)),
|
|
||||||
Ok(None) => {}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(context, "failed to markseen msgs: {:?}", err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
let (chat_id, state, blocked) = query_res.map_err(Into::<anyhow::Error>::into)?;
|
||||||
|
msgs.push((id, chat_id, state, blocked));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(msgs)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let mut updated_chat_ids = BTreeMap::new();
|
let mut updated_chat_ids = BTreeMap::new();
|
||||||
|
|
||||||
for (id, curr_chat_id, curr_state, curr_blocked) in msgs.into_iter() {
|
for (id, curr_chat_id, curr_state, curr_blocked) in msgs.into_iter() {
|
||||||
@@ -1547,9 +1574,8 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE msgs SET state=? WHERE id=?;")
|
"UPDATE msgs SET state=? WHERE id=?;",
|
||||||
.bind(state)
|
paramsv![state, msg_id],
|
||||||
.bind(msg_id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.is_ok()
|
.is_ok()
|
||||||
@@ -1639,7 +1665,7 @@ pub async fn exists(context: &Context, msg_id: MsgId) -> anyhow::Result<bool> {
|
|||||||
|
|
||||||
let chat_id: Option<ChatId> = context
|
let chat_id: Option<ChatId> = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(sqlx::query("SELECT chat_id FROM msgs WHERE id=?;").bind(msg_id))
|
.query_get_value("SELECT chat_id FROM msgs WHERE id=?;", paramsv![msg_id])
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(chat_id) = chat_id {
|
if let Some(chat_id) = chat_id {
|
||||||
@@ -1665,10 +1691,8 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl
|
|||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("UPDATE msgs SET state=?, error=? WHERE id=?;")
|
"UPDATE msgs SET state=?, error=? WHERE id=?;",
|
||||||
.bind(msg.state)
|
paramsv![msg.state, error, msg_id],
|
||||||
.bind(error)
|
|
||||||
.bind(msg_id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -1696,8 +1720,8 @@ pub async fn handle_mdn(
|
|||||||
|
|
||||||
let res = context
|
let res = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query(concat!(
|
concat!(
|
||||||
"SELECT",
|
"SELECT",
|
||||||
" m.id AS msg_id,",
|
" m.id AS msg_id,",
|
||||||
" c.id AS chat_id,",
|
" c.id AS chat_id,",
|
||||||
@@ -1706,19 +1730,18 @@ pub async fn handle_mdn(
|
|||||||
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
|
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
|
||||||
" WHERE rfc724_mid=? AND from_id=1",
|
" WHERE rfc724_mid=? AND from_id=1",
|
||||||
" ORDER BY m.id;"
|
" ORDER BY m.id;"
|
||||||
))
|
),
|
||||||
.bind(rfc724_mid),
|
paramsv![rfc724_mid],
|
||||||
)
|
|row| {
|
||||||
.await
|
|
||||||
.and_then(|row| {
|
|
||||||
Ok((
|
Ok((
|
||||||
row.try_get::<MsgId, _>("msg_id")?,
|
row.get::<_, MsgId>("msg_id")?,
|
||||||
row.try_get::<ChatId, _>("chat_id")?,
|
row.get::<_, ChatId>("chat_id")?,
|
||||||
row.try_get::<Chattype, _>("type")?,
|
row.get::<_, Chattype>("type")?,
|
||||||
row.try_get::<MessageState, _>("state")?,
|
row.get::<_, MessageState>("state")?,
|
||||||
))
|
))
|
||||||
});
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
if let Err(ref err) = res {
|
if let Err(ref err) = res {
|
||||||
info!(context, "Failed to select MDN {:?}", err);
|
info!(context, "Failed to select MDN {:?}", err);
|
||||||
}
|
}
|
||||||
@@ -1733,19 +1756,16 @@ pub async fn handle_mdn(
|
|||||||
let mdn_already_in_table = context
|
let mdn_already_in_table = context
|
||||||
.sql
|
.sql
|
||||||
.exists(
|
.exists(
|
||||||
sqlx::query("SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=? AND contact_id=?;")
|
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=? AND contact_id=?;",
|
||||||
.bind(msg_id)
|
paramsv![msg_id, from_id as i32,],
|
||||||
.bind(from_id),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
if !mdn_already_in_table {
|
if !mdn_already_in_table {
|
||||||
context.sql.execute(
|
context.sql.execute(
|
||||||
sqlx::query("INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);")
|
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
|
||||||
.bind(msg_id)
|
paramsv![msg_id, from_id as i32, timestamp_sent],
|
||||||
.bind(from_id)
|
|
||||||
.bind(timestamp_sent)
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default(); // TODO: better error handling
|
.unwrap_or_default(); // TODO: better error handling
|
||||||
@@ -1760,7 +1780,8 @@ pub async fn handle_mdn(
|
|||||||
let ist_cnt = context
|
let ist_cnt = context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query("SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;").bind(msg_id),
|
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
|
||||||
|
paramsv![msg_id],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@@ -1804,28 +1825,32 @@ pub(crate) async fn handle_ndn(
|
|||||||
|
|
||||||
// The NDN might be for a message-id that had attachments and was sent from a non-Delta Chat client.
|
// The NDN might be for a message-id that had attachments and was sent from a non-Delta Chat client.
|
||||||
// In this case we need to mark multiple "msgids" as failed that all refer to the same message-id.
|
// In this case we need to mark multiple "msgids" as failed that all refer to the same message-id.
|
||||||
let mut rows = context
|
let msgs: Vec<_> = context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(concat!(
|
concat!(
|
||||||
"SELECT",
|
"SELECT",
|
||||||
" m.id AS msg_id,",
|
" m.id AS msg_id,",
|
||||||
" c.id AS chat_id,",
|
" c.id AS chat_id,",
|
||||||
" c.type AS type",
|
" c.type AS type",
|
||||||
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
|
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
|
||||||
" WHERE rfc724_mid=? AND from_id=1",
|
" 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")?,
|
||||||
))
|
))
|
||||||
.bind(&failed.rfc724_mid),
|
},
|
||||||
|
|rows| Ok(rows.collect::<Vec<_>>()),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
while let Some(row) = rows.next().await {
|
for msg in msgs.into_iter() {
|
||||||
let row = row?;
|
let (msg_id, chat_id, chat_type) = msg?;
|
||||||
let msg_id = row.try_get::<MsgId, _>("msg_id")?;
|
|
||||||
let chat_id = row.try_get::<ChatId, _>("chat_id")?;
|
|
||||||
let chat_type = row.try_get::<Chattype, _>("type")?;
|
|
||||||
|
|
||||||
set_msg_failed(context, msg_id, error.as_ref()).await;
|
set_msg_failed(context, msg_id, error.as_ref()).await;
|
||||||
if first {
|
if first {
|
||||||
// Add only one info msg for all failed messages
|
// Add only one info msg for all failed messages
|
||||||
@@ -1874,11 +1899,12 @@ async fn ndn_maybe_add_info_msg(
|
|||||||
pub async fn get_real_msg_cnt(context: &Context) -> usize {
|
pub async fn get_real_msg_cnt(context: &Context) -> usize {
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.count(sqlx::query(
|
.count(
|
||||||
"SELECT COUNT(*) \
|
"SELECT COUNT(*) \
|
||||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
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;",
|
WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
|
||||||
))
|
paramsv![],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
@@ -1892,11 +1918,12 @@ pub async fn get_real_msg_cnt(context: &Context) -> usize {
|
|||||||
pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize {
|
pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize {
|
||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.count(sqlx::query(
|
.count(
|
||||||
"SELECT COUNT(*) \
|
"SELECT COUNT(*) \
|
||||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||||
WHERE c.blocked=2;",
|
WHERE c.blocked=2;",
|
||||||
))
|
paramsv![],
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
@@ -1922,35 +1949,31 @@ pub async fn estimate_deletion_cnt(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query(
|
|
||||||
"SELECT COUNT(*)
|
"SELECT COUNT(*)
|
||||||
FROM msgs m
|
FROM msgs m
|
||||||
WHERE m.id > ?
|
WHERE m.id > ?
|
||||||
AND timestamp < ?
|
AND timestamp < ?
|
||||||
AND chat_id != ?
|
AND chat_id != ?
|
||||||
AND server_uid != 0;",
|
AND server_uid != 0;",
|
||||||
)
|
paramsv![DC_MSG_ID_LAST_SPECIAL, threshold_timestamp, self_chat_id],
|
||||||
.bind(DC_MSG_ID_LAST_SPECIAL)
|
|
||||||
.bind(threshold_timestamp)
|
|
||||||
.bind(self_chat_id),
|
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query(
|
|
||||||
"SELECT COUNT(*)
|
"SELECT COUNT(*)
|
||||||
FROM msgs m
|
FROM msgs m
|
||||||
WHERE m.id > ?
|
WHERE m.id > ?
|
||||||
AND timestamp < ?
|
AND timestamp < ?
|
||||||
AND chat_id != ?
|
AND chat_id != ?
|
||||||
AND chat_id != ? AND hidden = 0;",
|
AND chat_id != ? AND hidden = 0;",
|
||||||
)
|
paramsv![
|
||||||
.bind(DC_MSG_ID_LAST_SPECIAL)
|
DC_MSG_ID_LAST_SPECIAL,
|
||||||
.bind(threshold_timestamp)
|
threshold_timestamp,
|
||||||
.bind(self_chat_id)
|
self_chat_id,
|
||||||
.bind(DC_CHAT_ID_TRASH),
|
DC_CHAT_ID_TRASH
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
@@ -1966,8 +1989,8 @@ pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> usize {
|
|||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.count(
|
.count(
|
||||||
sqlx::query("SELECT COUNT(*) FROM msgs WHERE rfc724_mid=? AND NOT server_uid = 0")
|
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=? AND NOT server_uid = 0",
|
||||||
.bind(rfc724_mid),
|
paramsv![rfc724_mid],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
@@ -1989,22 +2012,22 @@ pub(crate) async fn rfc724_mid_exists(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let row = context
|
let res = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_optional(
|
.query_row_optional(
|
||||||
sqlx::query("SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?")
|
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
|
||||||
.bind(rfc724_mid),
|
paramsv![rfc724_mid],
|
||||||
|
|row| {
|
||||||
|
let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default();
|
||||||
|
let server_uid = row.get(1)?;
|
||||||
|
let msg_id: MsgId = row.get(2)?;
|
||||||
|
|
||||||
|
Ok((server_folder, server_uid, msg_id))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(row) = row {
|
|
||||||
let server_folder = row.try_get::<Option<String>, _>(0)?.unwrap_or_default();
|
|
||||||
let server_uid = row.try_get::<u32, _>(1)?;
|
|
||||||
let msg_id: MsgId = row.try_get(2)?;
|
|
||||||
|
|
||||||
Ok(Some((server_folder, server_uid, msg_id)))
|
Ok(res)
|
||||||
} else {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_server_uid(
|
pub async fn update_server_uid(
|
||||||
@@ -2016,13 +2039,9 @@ pub async fn update_server_uid(
|
|||||||
match context
|
match context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
|
||||||
"UPDATE msgs SET server_folder=?, server_uid=? \
|
"UPDATE msgs SET server_folder=?, server_uid=? \
|
||||||
WHERE rfc724_mid=?",
|
WHERE rfc724_mid=?",
|
||||||
)
|
paramsv![server_folder.as_ref(), server_uid, rfc724_mid],
|
||||||
.bind(server_folder.as_ref())
|
|
||||||
.bind(server_uid as i64)
|
|
||||||
.bind(rfc724_mid),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use anyhow::{bail, ensure, format_err, Result};
|
use anyhow::{bail, ensure, format_err, Result};
|
||||||
use async_std::prelude::*;
|
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
|
use lettre_email::{mime, Address, Header, MimeMultipartType, PartBuilder};
|
||||||
use sqlx::Row;
|
|
||||||
|
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::chat::{self, Chat};
|
use crate::chat::{self, Chat};
|
||||||
@@ -115,42 +113,51 @@ impl<'a> MimeFactory<'a> {
|
|||||||
if chat.is_self_talk() {
|
if chat.is_self_talk() {
|
||||||
recipients.push((from_displayname.to_string(), from_addr.to_string()));
|
recipients.push((from_displayname.to_string(), from_addr.to_string()));
|
||||||
} else {
|
} else {
|
||||||
let mut rows = context
|
context
|
||||||
.sql
|
.sql
|
||||||
.fetch(
|
.query_map(
|
||||||
sqlx::query(
|
|
||||||
"SELECT c.authname, c.addr \
|
"SELECT c.authname, c.addr \
|
||||||
FROM chats_contacts cc \
|
FROM chats_contacts cc \
|
||||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||||
)
|
paramsv![msg.chat_id],
|
||||||
.bind(msg.chat_id),
|
|row| {
|
||||||
)
|
let authname: String = row.get(0)?;
|
||||||
.await?;
|
let addr: String = row.get(1)?;
|
||||||
while let Some(row) = rows.next().await {
|
Ok((authname, addr))
|
||||||
let row = row?;
|
},
|
||||||
let authname: String = row.try_get(0)?;
|
|rows| {
|
||||||
let addr: String = row.try_get(1)?;
|
for row in rows {
|
||||||
|
let (authname, addr) = row?;
|
||||||
if !recipients_contain_addr(&recipients, &addr) {
|
if !recipients_contain_addr(&recipients, &addr) {
|
||||||
recipients.push((authname, addr));
|
recipients.push((authname, addr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if !msg.is_system_message() && context.get_config_bool(Config::MdnsEnabled).await? {
|
if !msg.is_system_message() && context.get_config_bool(Config::MdnsEnabled).await? {
|
||||||
req_mdn = true;
|
req_mdn = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let row = context
|
let (in_reply_to, references) = context
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query("SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?")
|
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
|
||||||
.bind(msg.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),
|
||||||
|
))
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let (in_reply_to, references) = (
|
|
||||||
render_rfc724_mid_list(row.try_get(0)?),
|
|
||||||
render_rfc724_mid_list(row.try_get(1)?),
|
|
||||||
);
|
|
||||||
|
|
||||||
let default_str = stock_str::status_line(context).await;
|
let default_str = stock_str::status_line(context).await;
|
||||||
let factory = MimeFactory {
|
let factory = MimeFactory {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use std::pin::Pin;
|
|||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use charset::Charset;
|
use charset::Charset;
|
||||||
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use lettre_email::mime::{self, Mime};
|
use lettre_email::mime::{self, Mime};
|
||||||
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
|
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
@@ -102,7 +103,9 @@ pub(crate) enum MailinglistType {
|
|||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive)]
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql,
|
||||||
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum SystemMessage {
|
pub enum SystemMessage {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
@@ -1249,7 +1252,8 @@ impl MimeMessage {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value(
|
||||||
sqlx::query("SELECT timestamp FROM msgs WHERE rfc724_mid=?").bind(field),
|
"SELECT timestamp FROM msgs WHERE rfc724_mid=?",
|
||||||
|
paramsv![field],
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
} else {
|
} else {
|
||||||
@@ -1920,9 +1924,8 @@ mod tests {
|
|||||||
.ctx
|
.ctx
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query("INSERT INTO msgs (rfc724_mid, timestamp) VALUES(?,?)")
|
"INSERT INTO msgs (rfc724_mid, timestamp) VALUES(?,?)",
|
||||||
.bind("Gr.beZgAF2Nn0-.oyaJOpeuT70@example.org")
|
paramsv!["Gr.beZgAF2Nn0-.oyaJOpeuT70@example.org", timestamp],
|
||||||
.bind(timestamp),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to write to the database");
|
.expect("Failed to write to the database");
|
||||||
|
|||||||
143
src/peerstate.rs
143
src/peerstate.rs
@@ -3,10 +3,6 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
|
||||||
use num_traits::FromPrimitive;
|
|
||||||
use sqlx::{query::Query, sqlite::Sqlite, Row};
|
|
||||||
|
|
||||||
use crate::aheader::{Aheader, EncryptPreference};
|
use crate::aheader::{Aheader, EncryptPreference};
|
||||||
use crate::chat;
|
use crate::chat;
|
||||||
use crate::constants::Blocked;
|
use crate::constants::Blocked;
|
||||||
@@ -15,6 +11,8 @@ use crate::events::EventType;
|
|||||||
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
||||||
use crate::sql::Sql;
|
use crate::sql::Sql;
|
||||||
use crate::stock_str;
|
use crate::stock_str;
|
||||||
|
use anyhow::{bail, Result};
|
||||||
|
use num_traits::FromPrimitive;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PeerstateKeyType {
|
pub enum PeerstateKeyType {
|
||||||
@@ -140,15 +138,12 @@ impl Peerstate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_addr(context: &Context, addr: &str) -> Result<Option<Peerstate>> {
|
pub async fn from_addr(context: &Context, addr: &str) -> Result<Option<Peerstate>> {
|
||||||
let query = sqlx::query(
|
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
||||||
"SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
|
||||||
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
||||||
verified_key, verified_key_fingerprint \
|
verified_key, verified_key_fingerprint \
|
||||||
FROM acpeerstates \
|
FROM acpeerstates \
|
||||||
WHERE addr=? COLLATE NOCASE;",
|
WHERE addr=? COLLATE NOCASE;";
|
||||||
)
|
Self::from_stmt(context, query, paramsv![addr]).await
|
||||||
.bind(addr);
|
|
||||||
Self::from_stmt(context, query).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_fingerprint(
|
pub async fn from_fingerprint(
|
||||||
@@ -156,66 +151,60 @@ impl Peerstate {
|
|||||||
_sql: &Sql,
|
_sql: &Sql,
|
||||||
fingerprint: &Fingerprint,
|
fingerprint: &Fingerprint,
|
||||||
) -> Result<Option<Peerstate>> {
|
) -> Result<Option<Peerstate>> {
|
||||||
let fp = fingerprint.hex();
|
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
||||||
let query = sqlx::query(
|
|
||||||
"SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
|
||||||
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
||||||
verified_key, verified_key_fingerprint \
|
verified_key, verified_key_fingerprint \
|
||||||
FROM acpeerstates \
|
FROM acpeerstates \
|
||||||
WHERE public_key_fingerprint=? COLLATE NOCASE \
|
WHERE public_key_fingerprint=? COLLATE NOCASE \
|
||||||
OR gossip_key_fingerprint=? COLLATE NOCASE \
|
OR gossip_key_fingerprint=? COLLATE NOCASE \
|
||||||
ORDER BY public_key_fingerprint=? DESC;",
|
ORDER BY public_key_fingerprint=? DESC;";
|
||||||
)
|
let fp = fingerprint.hex();
|
||||||
.bind(&fp)
|
Self::from_stmt(context, query, paramsv![fp, fp, fp]).await
|
||||||
.bind(&fp)
|
|
||||||
.bind(&fp);
|
|
||||||
|
|
||||||
Self::from_stmt(context, query).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn from_stmt<'q, E>(
|
async fn from_stmt(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
query: Query<'q, Sqlite, E>,
|
query: &str,
|
||||||
) -> Result<Option<Peerstate>>
|
params: Vec<&dyn crate::ToSql>,
|
||||||
where
|
) -> Result<Option<Peerstate>> {
|
||||||
E: 'q + sqlx::IntoArguments<'q, sqlx::Sqlite>,
|
let peerstate = context
|
||||||
{
|
.sql
|
||||||
if let Some(row) = context.sql.fetch_optional(query).await? {
|
.query_row_optional(query, params, |row| {
|
||||||
// all the above queries start with this: SELECT
|
// all the above queries start with this: SELECT
|
||||||
// addr, last_seen, last_seen_autocrypt, prefer_encrypted,
|
// addr, last_seen, last_seen_autocrypt, prefer_encrypted,
|
||||||
// public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
|
// public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
|
||||||
// gossip_key_fingerprint, verified_key, verified_key_fingerprint
|
// gossip_key_fingerprint, verified_key, verified_key_fingerprint
|
||||||
|
|
||||||
let peerstate = Peerstate {
|
let res = Peerstate {
|
||||||
addr: row.try_get(0)?,
|
addr: row.get(0)?,
|
||||||
last_seen: row.try_get(1)?,
|
last_seen: row.get(1)?,
|
||||||
last_seen_autocrypt: row.try_get(2)?,
|
last_seen_autocrypt: row.get(2)?,
|
||||||
prefer_encrypt: EncryptPreference::from_i32(row.try_get(3)?).unwrap_or_default(),
|
prefer_encrypt: EncryptPreference::from_i32(row.get(3)?).unwrap_or_default(),
|
||||||
public_key: row
|
public_key: row
|
||||||
.try_get::<&[u8], _>(4)
|
.get(4)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob| SignedPublicKey::from_slice(blob).ok()),
|
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok()),
|
||||||
public_key_fingerprint: row
|
public_key_fingerprint: row
|
||||||
.try_get::<Option<String>, _>(7)?
|
.get::<_, Option<String>>(7)?
|
||||||
.map(|s| s.parse::<Fingerprint>())
|
.map(|s| s.parse::<Fingerprint>())
|
||||||
.transpose()
|
.transpose()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
gossip_key: row
|
gossip_key: row
|
||||||
.try_get::<&[u8], _>(6)
|
.get(6)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob| SignedPublicKey::from_slice(blob).ok()),
|
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok()),
|
||||||
gossip_key_fingerprint: row
|
gossip_key_fingerprint: row
|
||||||
.try_get::<Option<String>, _>(8)?
|
.get::<_, Option<String>>(8)?
|
||||||
.map(|s| s.parse::<Fingerprint>())
|
.map(|s| s.parse::<Fingerprint>())
|
||||||
.transpose()
|
.transpose()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
gossip_timestamp: row.try_get(5)?,
|
gossip_timestamp: row.get(5)?,
|
||||||
verified_key: row
|
verified_key: row
|
||||||
.try_get::<&[u8], _>(9)
|
.get(9)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|blob| SignedPublicKey::from_slice(blob).ok()),
|
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok()),
|
||||||
verified_key_fingerprint: row
|
verified_key_fingerprint: row
|
||||||
.try_get::<Option<String>, _>(10)?
|
.get::<_, Option<String>>(10)?
|
||||||
.map(|s| s.parse::<Fingerprint>())
|
.map(|s| s.parse::<Fingerprint>())
|
||||||
.transpose()
|
.transpose()
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
@@ -223,10 +212,10 @@ impl Peerstate {
|
|||||||
fingerprint_changed: false,
|
fingerprint_changed: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(peerstate))
|
Ok(res)
|
||||||
} else {
|
})
|
||||||
Ok(None)
|
.await?;
|
||||||
}
|
Ok(peerstate)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recalc_fingerprint(&mut self) {
|
pub fn recalc_fingerprint(&mut self) {
|
||||||
@@ -275,9 +264,7 @@ impl Peerstate {
|
|||||||
if self.fingerprint_changed {
|
if self.fingerprint_changed {
|
||||||
if let Some(contact_id) = context
|
if let Some(contact_id) = context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value("SELECT id FROM contacts WHERE addr=?;", paramsv![self.addr])
|
||||||
sqlx::query("SELECT id FROM contacts WHERE addr=?;").bind(&self.addr),
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
let (contact_chat_id, _) =
|
let (contact_chat_id, _) =
|
||||||
@@ -437,8 +424,7 @@ impl Peerstate {
|
|||||||
pub async fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> {
|
pub async fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> {
|
||||||
if self.to_save == Some(ToSave::All) || create {
|
if self.to_save == Some(ToSave::All) || create {
|
||||||
sql.execute(
|
sql.execute(
|
||||||
(if create {
|
if create {
|
||||||
sqlx::query(
|
|
||||||
"INSERT INTO acpeerstates ( \
|
"INSERT INTO acpeerstates ( \
|
||||||
last_seen, \
|
last_seen, \
|
||||||
last_seen_autocrypt, \
|
last_seen_autocrypt, \
|
||||||
@@ -451,10 +437,8 @@ impl Peerstate {
|
|||||||
verified_key, \
|
verified_key, \
|
||||||
verified_key_fingerprint, \
|
verified_key_fingerprint, \
|
||||||
addr \
|
addr \
|
||||||
) VALUES(?,?,?,?,?,?,?,?,?,?,?)",
|
) VALUES(?,?,?,?,?,?,?,?,?,?,?)"
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
sqlx::query(
|
|
||||||
"UPDATE acpeerstates \
|
"UPDATE acpeerstates \
|
||||||
SET last_seen=?, \
|
SET last_seen=?, \
|
||||||
last_seen_autocrypt=?, \
|
last_seen_autocrypt=?, \
|
||||||
@@ -466,30 +450,33 @@ impl Peerstate {
|
|||||||
gossip_key_fingerprint=?, \
|
gossip_key_fingerprint=?, \
|
||||||
verified_key=?, \
|
verified_key=?, \
|
||||||
verified_key_fingerprint=? \
|
verified_key_fingerprint=? \
|
||||||
WHERE addr=?",
|
WHERE addr=?"
|
||||||
)
|
},
|
||||||
})
|
paramsv![
|
||||||
.bind(self.last_seen)
|
self.last_seen,
|
||||||
.bind(self.last_seen_autocrypt)
|
self.last_seen_autocrypt,
|
||||||
.bind(self.prefer_encrypt as i64)
|
self.prefer_encrypt as i64,
|
||||||
.bind(self.public_key.as_ref().map(|k| k.to_bytes()))
|
self.public_key.as_ref().map(|k| k.to_bytes()),
|
||||||
.bind(self.gossip_timestamp)
|
self.gossip_timestamp,
|
||||||
.bind(self.gossip_key.as_ref().map(|k| k.to_bytes()))
|
self.gossip_key.as_ref().map(|k| k.to_bytes()),
|
||||||
.bind(self.public_key_fingerprint.as_ref().map(|fp| fp.hex()))
|
self.public_key_fingerprint.as_ref().map(|fp| fp.hex()),
|
||||||
.bind(self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()))
|
self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()),
|
||||||
.bind(self.verified_key.as_ref().map(|k| k.to_bytes()))
|
self.verified_key.as_ref().map(|k| k.to_bytes()),
|
||||||
.bind(self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()))
|
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
|
||||||
.bind(&self.addr),
|
self.addr,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else if self.to_save == Some(ToSave::Timestamps) {
|
} else if self.to_save == Some(ToSave::Timestamps) {
|
||||||
sql.execute(
|
sql.execute(
|
||||||
sqlx::query("UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
|
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
|
||||||
WHERE addr=?;").bind(
|
WHERE addr=?;",
|
||||||
self.last_seen).bind(
|
paramsv![
|
||||||
self.last_seen_autocrypt).bind(
|
self.last_seen,
|
||||||
self.gossip_timestamp).bind(
|
self.last_seen_autocrypt,
|
||||||
&self.addr)
|
self.gossip_timestamp,
|
||||||
|
self.addr
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -506,6 +493,12 @@ impl Peerstate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<crate::key::FingerprintError> for rusqlite::Error {
|
||||||
|
fn from(_source: crate::key::FingerprintError) -> Self {
|
||||||
|
Self::InvalidColumnType(0, "Invalid fingerprint".into(), rusqlite::types::Type::Text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -638,7 +631,7 @@ mod tests {
|
|||||||
// can be loaded without errors.
|
// can be loaded without errors.
|
||||||
ctx.ctx
|
ctx.ctx
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("INSERT INTO acpeerstates (addr) VALUES(?)").bind(addr))
|
.execute("INSERT INTO acpeerstates (addr) VALUES(?)", paramsv![addr])
|
||||||
.await
|
.await
|
||||||
.expect("Failed to write to the database");
|
.expect("Failed to write to the database");
|
||||||
|
|
||||||
|
|||||||
560
src/sql.rs
560
src/sql.rs
@@ -1,19 +1,15 @@
|
|||||||
//! # SQLite wrapper
|
//! # SQLite wrapper
|
||||||
|
|
||||||
|
use async_std::sync::RwLock;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::pin::Pin;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
use async_std::prelude::*;
|
use async_std::prelude::*;
|
||||||
use async_std::sync::RwLock;
|
use rusqlite::OpenFlags;
|
||||||
use sqlx::{
|
|
||||||
pool::PoolOptions,
|
|
||||||
query::Query,
|
|
||||||
sqlite::{Sqlite, SqliteConnectOptions, SqliteJournalMode, SqlitePool, SqliteSynchronous},
|
|
||||||
Executor, IntoArguments, Row,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::chat::{add_device_msg, update_device_icon, update_saved_messages_icon};
|
use crate::chat::{add_device_msg, update_device_icon, update_saved_messages_icon};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
@@ -26,38 +22,35 @@ use crate::param::{Param, Params};
|
|||||||
use crate::peerstate::Peerstate;
|
use crate::peerstate::Peerstate;
|
||||||
use crate::stock_str;
|
use crate::stock_str;
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! paramsv {
|
||||||
|
() => {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
($($param:expr),+ $(,)?) => {
|
||||||
|
vec![$(&$param as &dyn $crate::ToSql),+]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
mod migrations;
|
mod migrations;
|
||||||
|
|
||||||
pub use self::error::*;
|
pub use self::error::*;
|
||||||
|
|
||||||
/// A wrapper around the underlying Sqlite3 object.
|
/// A wrapper around the underlying Sqlite3 object.
|
||||||
///
|
|
||||||
/// We maintain two different pools to sqlite, on for reading, one for writing.
|
|
||||||
/// This can go away once https://github.com/launchbadge/sqlx/issues/459 is implemented.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Sql {
|
pub struct Sql {
|
||||||
/// Writer pool, must only have 1 connection in it.
|
pool: RwLock<Option<r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>>>,
|
||||||
writer: RwLock<Option<SqlitePool>>,
|
|
||||||
/// Reader pool, maintains multiple connections for reading data.
|
|
||||||
reader: RwLock<Option<SqlitePool>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Sql {
|
impl Default for Sql {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
writer: RwLock::new(None),
|
pool: RwLock::new(None),
|
||||||
reader: RwLock::new(None),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Sql {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
async_std::task::block_on(self.close());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sql {
|
impl Sql {
|
||||||
pub fn new() -> Sql {
|
pub fn new() -> Sql {
|
||||||
Self::default()
|
Self::default()
|
||||||
@@ -65,76 +58,50 @@ impl Sql {
|
|||||||
|
|
||||||
/// Checks if there is currently a connection to the underlying Sqlite database.
|
/// Checks if there is currently a connection to the underlying Sqlite database.
|
||||||
pub async fn is_open(&self) -> bool {
|
pub async fn is_open(&self) -> bool {
|
||||||
// in read only mode the writer does not exists
|
self.pool.read().await.is_some()
|
||||||
self.reader.read().await.is_some()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Closes all underlying Sqlite connections.
|
/// Closes all underlying Sqlite connections.
|
||||||
pub async fn close(&self) {
|
pub async fn close(&self) {
|
||||||
if let Some(sql) = self.writer.write().await.take() {
|
let _ = self.pool.write().await.take();
|
||||||
sql.close().await;
|
// drop closes the connection
|
||||||
}
|
|
||||||
if let Some(sql) = self.reader.write().await.take() {
|
|
||||||
sql.close().await;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn new_writer_pool(dbfile: impl AsRef<Path>) -> sqlx::Result<SqlitePool> {
|
pub fn new_pool(
|
||||||
let config = SqliteConnectOptions::new()
|
dbfile: &Path,
|
||||||
.journal_mode(SqliteJournalMode::Wal)
|
readonly: bool,
|
||||||
.filename(dbfile.as_ref())
|
) -> anyhow::Result<r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>> {
|
||||||
.read_only(false)
|
let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX;
|
||||||
.busy_timeout(Duration::from_secs(100))
|
if readonly {
|
||||||
.create_if_missing(true)
|
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY);
|
||||||
.shared_cache(true)
|
} else {
|
||||||
.synchronous(SqliteSynchronous::Normal);
|
open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE);
|
||||||
|
open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE);
|
||||||
|
}
|
||||||
|
|
||||||
PoolOptions::<Sqlite>::new()
|
// this actually creates min_idle database handles just now.
|
||||||
.max_connections(1)
|
// therefore, with_init() must not try to modify the database as otherwise
|
||||||
.after_connect(|conn| {
|
// we easily get busy-errors (eg. table-creation, journal_mode etc. should be done on only one handle)
|
||||||
Box::pin(async move {
|
let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile)
|
||||||
let q = r#"
|
.with_flags(open_flags)
|
||||||
PRAGMA secure_delete=on;
|
.with_init(|c| {
|
||||||
|
c.execute_batch(&format!(
|
||||||
|
"PRAGMA secure_delete=on;
|
||||||
|
PRAGMA busy_timeout = {};
|
||||||
PRAGMA temp_store=memory; -- Avoid SQLITE_IOERR_GETTEMPPATH errors on Android
|
PRAGMA temp_store=memory; -- Avoid SQLITE_IOERR_GETTEMPPATH errors on Android
|
||||||
"#;
|
",
|
||||||
|
Duration::from_secs(10).as_millis()
|
||||||
conn.execute_many(sqlx::query(q))
|
))?;
|
||||||
.collect::<std::result::Result<Vec<_>, _>>()
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
});
|
||||||
})
|
|
||||||
.connect_with(config)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn new_reader_pool(dbfile: impl AsRef<Path>, readonly: bool) -> sqlx::Result<SqlitePool> {
|
let pool = r2d2::Pool::builder()
|
||||||
let config = SqliteConnectOptions::new()
|
.min_idle(Some(2))
|
||||||
.journal_mode(SqliteJournalMode::Wal)
|
.max_size(10)
|
||||||
.filename(dbfile.as_ref())
|
.connection_timeout(Duration::from_secs(60))
|
||||||
.read_only(readonly)
|
.build(mgr)
|
||||||
.shared_cache(true)
|
.map_err(Error::ConnectionPool)?;
|
||||||
.busy_timeout(Duration::from_secs(100))
|
Ok(pool)
|
||||||
.synchronous(SqliteSynchronous::Normal);
|
|
||||||
|
|
||||||
PoolOptions::<Sqlite>::new()
|
|
||||||
.max_connections(10)
|
|
||||||
.after_connect(|conn| {
|
|
||||||
Box::pin(async move {
|
|
||||||
let q = r#"
|
|
||||||
PRAGMA temp_store=memory; -- Avoid SQLITE_IOERR_GETTEMPPATH errors on Android
|
|
||||||
PRAGMA query_only=1; -- Protect against writes even in read-write mode
|
|
||||||
PRAGMA read_uncommitted=1; -- This helps avoid "table locked" errors in shared cache mode
|
|
||||||
"#;
|
|
||||||
|
|
||||||
conn.execute_many(sqlx::query(q))
|
|
||||||
.collect::<std::result::Result<Vec<_>, _>>()
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.connect_with(config)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens the provided database and runs any necessary migrations.
|
/// Opens the provided database and runs any necessary migrations.
|
||||||
@@ -154,15 +121,16 @@ PRAGMA read_uncommitted=1; -- This helps avoid "table locked" errors in shared c
|
|||||||
return Err(Error::SqlAlreadyOpen.into());
|
return Err(Error::SqlAlreadyOpen.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open write pool
|
*self.pool.write().await = Some(Self::new_pool(dbfile.as_ref(), readonly)?);
|
||||||
if !readonly {
|
|
||||||
*self.writer.write().await = Some(Self::new_writer_pool(&dbfile).await?);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open read pool
|
|
||||||
*self.reader.write().await = Some(Self::new_reader_pool(&dbfile, readonly).await?);
|
|
||||||
|
|
||||||
if !readonly {
|
if !readonly {
|
||||||
|
// journal_mode is persisted, it is sufficient to change it only for one handle.
|
||||||
|
self.with_conn(move |conn| {
|
||||||
|
conn.pragma_update(None, "journal_mode", &"WAL".to_string())?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
// (1) update low-level database structure.
|
// (1) update low-level database structure.
|
||||||
// this should be done before updates that use high-level objects that
|
// this should be done before updates that use high-level objects that
|
||||||
// rely themselves on the low-level structure.
|
// rely themselves on the low-level structure.
|
||||||
@@ -175,13 +143,19 @@ PRAGMA read_uncommitted=1; -- This helps avoid "table locked" errors in shared c
|
|||||||
|
|
||||||
if recalc_fingerprints {
|
if recalc_fingerprints {
|
||||||
info!(context, "[migration] recalc fingerprints");
|
info!(context, "[migration] recalc fingerprints");
|
||||||
let mut rows = self
|
let addrs = self
|
||||||
.fetch(sqlx::query("SELECT addr FROM acpeerstates;"))
|
.query_map(
|
||||||
|
"SELECT addr FROM acpeerstates;",
|
||||||
|
paramsv![],
|
||||||
|
|row| row.get::<_, String>(0),
|
||||||
|
|addrs| {
|
||||||
|
addrs
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()
|
||||||
|
.map_err(Into::into)
|
||||||
|
},
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
for addr in &addrs {
|
||||||
while let Some(row) = rows.next().await {
|
|
||||||
let row = row?;
|
|
||||||
let addr = row.try_get(0)?;
|
|
||||||
if let Some(ref mut peerstate) = Peerstate::from_addr(context, addr).await? {
|
if let Some(ref mut peerstate) = Peerstate::from_addr(context, addr).await? {
|
||||||
peerstate.recalc_fingerprint();
|
peerstate.recalc_fingerprint();
|
||||||
peerstate.save_to_db(self, false).await?;
|
peerstate.save_to_db(self, false).await?;
|
||||||
@@ -214,158 +188,161 @@ PRAGMA read_uncommitted=1; -- This helps avoid "table locked" errors in shared c
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the given query, returning the number of affected rows.
|
/// Execute the given query, returning the number of affected rows.
|
||||||
pub async fn execute<'q, E>(&self, query: Query<'q, Sqlite, E>) -> Result<u64>
|
pub async fn execute(
|
||||||
where
|
&self,
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
query: impl AsRef<str>,
|
||||||
{
|
params: Vec<&dyn crate::ToSql>,
|
||||||
let lock = self.writer.read().await;
|
) -> Result<usize> {
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
let conn = self.get_conn().await?;
|
||||||
|
let res = conn.execute(query.as_ref(), params)?;
|
||||||
let rows = pool.execute(query).await?;
|
Ok(res)
|
||||||
Ok(rows.rows_affected())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes the given query, returning the last inserted row ID.
|
/// Executes the given query, returning the last inserted row ID.
|
||||||
pub async fn insert<'q, E>(&self, query: Query<'q, Sqlite, E>) -> Result<i64>
|
pub async fn insert(
|
||||||
where
|
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
|
||||||
{
|
|
||||||
let lock = self.writer.read().await;
|
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
|
||||||
|
|
||||||
let rows = pool.execute(query).await?;
|
|
||||||
Ok(rows.last_insert_rowid())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Execute many queries.
|
|
||||||
pub async fn execute_many<'q, E>(&self, query: Query<'q, Sqlite, E>) -> Result<()>
|
|
||||||
where
|
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
|
||||||
{
|
|
||||||
let lock = self.writer.read().await;
|
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
|
||||||
|
|
||||||
pool.execute_many(query)
|
|
||||||
.collect::<sqlx::Result<Vec<_>>>()
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch the given query.
|
|
||||||
pub async fn fetch<'q, E>(
|
|
||||||
&self,
|
&self,
|
||||||
query: Query<'q, Sqlite, E>,
|
query: impl AsRef<str>,
|
||||||
) -> Result<impl Stream<Item = sqlx::Result<<Sqlite as sqlx::Database>::Row>> + Send + 'q>
|
params: Vec<&dyn crate::ToSql>,
|
||||||
where
|
) -> anyhow::Result<usize> {
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
let conn = self.get_conn().await?;
|
||||||
{
|
conn.execute(query.as_ref(), params)?;
|
||||||
let lock = self.reader.read().await;
|
Ok(usize::try_from(conn.last_insert_rowid())?)
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
|
||||||
|
|
||||||
let rows = pool.fetch(query);
|
|
||||||
Ok(rows)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch exactly one row, errors if no row is found.
|
/// Prepares and executes the statement and maps a function over the resulting rows.
|
||||||
pub async fn fetch_one<'q, E>(
|
/// Then executes the second function over the returned iterator and returns the
|
||||||
|
/// result of that function.
|
||||||
|
pub async fn query_map<T, F, G, H>(
|
||||||
&self,
|
&self,
|
||||||
query: Query<'q, Sqlite, E>,
|
sql: impl AsRef<str>,
|
||||||
) -> Result<<Sqlite as sqlx::Database>::Row>
|
params: Vec<&dyn crate::ToSql>,
|
||||||
|
f: F,
|
||||||
|
mut g: G,
|
||||||
|
) -> Result<H>
|
||||||
where
|
where
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
F: FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
|
||||||
|
G: FnMut(rusqlite::MappedRows<F>) -> Result<H>,
|
||||||
{
|
{
|
||||||
let lock = self.reader.read().await;
|
let sql = sql.as_ref();
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
|
||||||
|
|
||||||
let row = pool.fetch_one(query).await?;
|
let conn = self.get_conn().await?;
|
||||||
Ok(row)
|
let mut stmt = conn.prepare(sql)?;
|
||||||
|
let res = stmt.query_map(¶ms, f)?;
|
||||||
|
g(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetches at most one row.
|
pub async fn get_conn(
|
||||||
pub async fn fetch_optional<'e, 'q, E>(
|
|
||||||
&self,
|
&self,
|
||||||
query: Query<'q, Sqlite, E>,
|
) -> Result<r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>> {
|
||||||
) -> Result<Option<<Sqlite as sqlx::Database>::Row>>
|
let lock = self.pool.read().await;
|
||||||
|
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
||||||
|
let conn = pool.get()?;
|
||||||
|
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_conn<G, H>(&self, g: G) -> anyhow::Result<H>
|
||||||
where
|
where
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
H: Send + 'static,
|
||||||
|
G: Send
|
||||||
|
+ 'static
|
||||||
|
+ FnOnce(
|
||||||
|
r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>,
|
||||||
|
) -> anyhow::Result<H>,
|
||||||
{
|
{
|
||||||
let lock = self.reader.read().await;
|
let lock = self.pool.read().await;
|
||||||
|
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
||||||
|
let conn = pool.get()?;
|
||||||
|
|
||||||
|
g(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn with_conn_async<G, H, Fut>(&self, mut g: G) -> Result<H>
|
||||||
|
where
|
||||||
|
G: FnMut(r2d2::PooledConnection<r2d2_sqlite::SqliteConnectionManager>) -> Fut,
|
||||||
|
Fut: Future<Output = Result<H>> + Send,
|
||||||
|
{
|
||||||
|
let lock = self.pool.read().await;
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
||||||
|
|
||||||
let row = pool.fetch_optional(query).await?;
|
let conn = pool.get()?;
|
||||||
Ok(row)
|
g(conn).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used for executing `SELECT COUNT` statements only. Returns the resulting count.
|
/// Used for executing `SELECT COUNT` statements only. Returns the resulting count.
|
||||||
pub async fn count<'e, 'q, E>(&self, query: Query<'q, Sqlite, E>) -> Result<usize>
|
pub async fn count(
|
||||||
where
|
&self,
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
query: impl AsRef<str>,
|
||||||
{
|
params: Vec<&dyn crate::ToSql>,
|
||||||
use std::convert::TryFrom;
|
) -> anyhow::Result<usize> {
|
||||||
|
let count: isize = self.query_row(query, params, |row| row.get(0)).await?;
|
||||||
let row = self.fetch_one(query).await?;
|
Ok(usize::try_from(count)?)
|
||||||
let count: i64 = row.try_get(0)?;
|
|
||||||
|
|
||||||
Ok(usize::try_from(count).map_err::<anyhow::Error, _>(Into::into)?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used for executing `SELECT COUNT` statements only. Returns `true`, if the count is at least
|
/// Used for executing `SELECT COUNT` statements only. Returns `true`, if the count is at least
|
||||||
/// one, `false` otherwise.
|
/// one, `false` otherwise.
|
||||||
pub async fn exists<'e, 'q, E>(&self, query: Query<'q, Sqlite, E>) -> Result<bool>
|
pub async fn exists(&self, sql: &str, params: Vec<&dyn crate::ToSql>) -> Result<bool> {
|
||||||
where
|
let count = self.count(sql, params).await?;
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
|
||||||
{
|
|
||||||
let count = self.count(query).await?;
|
|
||||||
Ok(count > 0)
|
Ok(count > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Execute a query which is expected to return one row.
|
||||||
|
pub async fn query_row<T, F>(
|
||||||
|
&self,
|
||||||
|
query: impl AsRef<str>,
|
||||||
|
params: Vec<&dyn crate::ToSql>,
|
||||||
|
f: F,
|
||||||
|
) -> Result<T>
|
||||||
|
where
|
||||||
|
F: FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
|
||||||
|
{
|
||||||
|
let conn = self.get_conn().await?;
|
||||||
|
let res = conn.query_row(query.as_ref(), params, f)?;
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute the function inside a transaction.
|
/// Execute the function inside a transaction.
|
||||||
///
|
///
|
||||||
/// If the function returns an error, the transaction will be rolled back. If it does not return an
|
/// If the function returns an error, the transaction will be rolled back. If it does not return an
|
||||||
/// error, the transaction will be committed.
|
/// error, the transaction will be committed.
|
||||||
pub async fn transaction<F, R>(&self, callback: F) -> Result<R>
|
pub async fn transaction<G, H>(&self, callback: G) -> anyhow::Result<H>
|
||||||
where
|
where
|
||||||
F: for<'c> FnOnce(
|
H: Send + 'static,
|
||||||
&'c mut sqlx::Transaction<'_, Sqlite>,
|
G: Send + 'static + FnOnce(&mut rusqlite::Transaction<'_>) -> anyhow::Result<H>,
|
||||||
) -> Pin<Box<dyn Future<Output = Result<R>> + 'c + Send>>
|
|
||||||
+ 'static
|
|
||||||
+ Send
|
|
||||||
+ Sync,
|
|
||||||
R: Send,
|
|
||||||
{
|
{
|
||||||
let lock = self.writer.read().await;
|
self.with_conn(move |mut conn| {
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
let conn2 = &mut conn;
|
||||||
|
let mut transaction = conn2.transaction()?;
|
||||||
let mut transaction = pool.begin().await?;
|
let ret = callback(&mut transaction);
|
||||||
let ret = callback(&mut transaction).await;
|
|
||||||
|
|
||||||
match ret {
|
match ret {
|
||||||
Ok(ret) => {
|
Ok(ret) => {
|
||||||
transaction.commit().await?;
|
transaction.commit()?;
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
transaction.rollback().await?;
|
transaction.rollback()?;
|
||||||
|
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query the database if the requested table already exists.
|
/// Query the database if the requested table already exists.
|
||||||
pub async fn table_exists(&self, name: impl AsRef<str>) -> Result<bool> {
|
pub async fn table_exists(&self, name: impl AsRef<str>) -> anyhow::Result<bool> {
|
||||||
let q = format!("PRAGMA table_info(\"{}\")", name.as_ref());
|
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(())
|
||||||
|
})?;
|
||||||
|
|
||||||
let lock = self.reader.read().await;
|
Ok(exists)
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
})
|
||||||
|
.await
|
||||||
let mut rows = pool.fetch(sqlx::query(&q));
|
|
||||||
if let Some(first_row) = rows.next().await {
|
|
||||||
Ok(first_row.is_ok())
|
|
||||||
} else {
|
|
||||||
Ok(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if a column exists in a given table.
|
/// Check if a column exists in a given table.
|
||||||
@@ -373,43 +350,62 @@ PRAGMA read_uncommitted=1; -- This helps avoid "table locked" errors in shared c
|
|||||||
&self,
|
&self,
|
||||||
table_name: impl AsRef<str>,
|
table_name: impl AsRef<str>,
|
||||||
col_name: impl AsRef<str>,
|
col_name: impl AsRef<str>,
|
||||||
) -> Result<bool> {
|
) -> anyhow::Result<bool> {
|
||||||
let q = format!("PRAGMA table_info(\"{}\")", table_name.as_ref());
|
let table_name = table_name.as_ref().to_string();
|
||||||
let lock = self.reader.read().await;
|
let col_name = col_name.as_ref().to_string();
|
||||||
let pool = lock.as_ref().ok_or(Error::SqlNoConnection)?;
|
self.with_conn(move |conn| {
|
||||||
|
let mut exists = false;
|
||||||
let mut rows = pool.fetch(sqlx::query(&q));
|
|
||||||
while let Some(row) = rows.next().await {
|
|
||||||
let row = row?;
|
|
||||||
|
|
||||||
// `PRAGMA table_info` returns one row per column,
|
// `PRAGMA table_info` returns one row per column,
|
||||||
// each row containing 0=cid, 1=name, 2=type, 3=notnull, 4=dflt_value
|
// each row containing 0=cid, 1=name, 2=type, 3=notnull, 4=dflt_value
|
||||||
|
conn.pragma(None, "table_info", &table_name, |row| {
|
||||||
let curr_name: &str = row.try_get(1)?;
|
let curr_name: String = row.get(1)?;
|
||||||
if col_name.as_ref() == curr_name {
|
if col_name == curr_name {
|
||||||
return Ok(true);
|
exists = true;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(exists)
|
||||||
|
})
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(false)
|
/// Execute a query which is expected to return zero or one row.
|
||||||
|
pub async fn query_row_optional<T, F>(
|
||||||
|
&self,
|
||||||
|
sql: impl AsRef<str>,
|
||||||
|
params: Vec<&dyn crate::ToSql>,
|
||||||
|
f: F,
|
||||||
|
) -> anyhow::Result<Option<T>>
|
||||||
|
where
|
||||||
|
F: FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
|
||||||
|
{
|
||||||
|
let res = 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),
|
||||||
|
}?;
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a query which is expected to return one row and one
|
/// Executes a query which is expected to return one row and one
|
||||||
/// column. If the query does not return a value or returns SQL
|
/// column. If the query does not return a value or returns SQL
|
||||||
/// `NULL`, returns `Ok(None)`.
|
/// `NULL`, returns `Ok(None)`.
|
||||||
pub async fn query_get_value<'e, 'q, E, T>(
|
pub async fn query_get_value<T>(
|
||||||
&self,
|
&self,
|
||||||
query: Query<'q, Sqlite, E>,
|
query: &str,
|
||||||
) -> Result<Option<T>>
|
params: Vec<&dyn crate::ToSql>,
|
||||||
|
) -> anyhow::Result<Option<T>>
|
||||||
where
|
where
|
||||||
E: 'q + IntoArguments<'q, Sqlite>,
|
T: rusqlite::types::FromSql,
|
||||||
T: for<'r> sqlx::Decode<'r, Sqlite> + sqlx::Type<Sqlite>,
|
|
||||||
{
|
{
|
||||||
let res = self
|
self.query_row_optional(query, params, |row| row.get::<_, T>(0))
|
||||||
.fetch_optional(query)
|
.await
|
||||||
.await?
|
|
||||||
.map(|row| row.get::<T, _>(0));
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set private configuration options.
|
/// Set private configuration options.
|
||||||
@@ -424,26 +420,27 @@ PRAGMA read_uncommitted=1; -- This helps avoid "table locked" errors in shared c
|
|||||||
let key = key.as_ref();
|
let key = key.as_ref();
|
||||||
if let Some(value) = value {
|
if let Some(value) = value {
|
||||||
let exists = self
|
let exists = self
|
||||||
.exists(sqlx::query("SELECT COUNT(*) FROM config WHERE keyname=?;").bind(key))
|
.exists(
|
||||||
|
"SELECT COUNT(*) FROM config WHERE keyname=?;",
|
||||||
|
paramsv![key],
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
self.execute(
|
self.execute(
|
||||||
sqlx::query("UPDATE config SET value=? WHERE keyname=?;")
|
"UPDATE config SET value=? WHERE keyname=?;",
|
||||||
.bind(value)
|
paramsv![(*value).to_string(), key.to_string()],
|
||||||
.bind(key),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
self.execute(
|
self.execute(
|
||||||
sqlx::query("INSERT INTO config (keyname, value) VALUES (?, ?);")
|
"INSERT INTO config (keyname, value) VALUES (?, ?);",
|
||||||
.bind(key)
|
paramsv![key.to_string(), (*value).to_string()],
|
||||||
.bind(value),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.execute(sqlx::query("DELETE FROM config WHERE keyname=?;").bind(key))
|
self.execute("DELETE FROM config WHERE keyname=?;", paramsv![key])
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -457,7 +454,8 @@ PRAGMA read_uncommitted=1; -- This helps avoid "table locked" errors in shared c
|
|||||||
}
|
}
|
||||||
let value = self
|
let value = self
|
||||||
.query_get_value(
|
.query_get_value(
|
||||||
sqlx::query("SELECT value FROM config WHERE keyname=?;").bind(key.as_ref()),
|
"SELECT value FROM config WHERE keyname=?;",
|
||||||
|
paramsv![key.as_ref().to_string()],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.context(format!("failed to fetch raw config: {}", key.as_ref()))?;
|
.context(format!("failed to fetch raw config: {}", key.as_ref()))?;
|
||||||
@@ -539,14 +537,21 @@ pub async fn housekeeping(context: &Context) -> Result<()> {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut rows = context
|
context
|
||||||
.sql
|
.sql
|
||||||
.fetch(sqlx::query("SELECT value FROM config;"))
|
.query_map(
|
||||||
.await?;
|
"SELECT value FROM config;",
|
||||||
while let Some(row) = rows.next().await {
|
paramsv![],
|
||||||
let row: String = row?.try_get(0)?;
|
|row| row.get::<_, String>(0),
|
||||||
maybe_add_file(&mut files_in_use, row);
|
|rows| {
|
||||||
|
for row in rows {
|
||||||
|
maybe_add_file(&mut files_in_use, row?);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context("housekeeping: failed to SELECT value FROM config")?;
|
||||||
|
|
||||||
info!(context, "{} files in use.", files_in_use.len(),);
|
info!(context, "{} files in use.", files_in_use.len(),);
|
||||||
/* go through directory and delete unused files */
|
/* go through directory and delete unused files */
|
||||||
@@ -665,14 +670,22 @@ async fn maybe_add_from_param(
|
|||||||
query: &str,
|
query: &str,
|
||||||
param_id: Param,
|
param_id: Param,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut rows = sql.fetch(sqlx::query(query)).await?;
|
sql.query_map(
|
||||||
while let Some(row) = rows.next().await {
|
query,
|
||||||
let row: String = row?.try_get(0)?;
|
paramsv![],
|
||||||
let param: Params = row.parse().unwrap_or_default();
|
|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) {
|
if let Some(file) = param.get(param_id) {
|
||||||
maybe_add_file(files_in_use, file);
|
maybe_add_file(files_in_use, file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.context(format!("housekeeping: failed to add_from_param {}", query))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -681,25 +694,15 @@ async fn maybe_add_from_param(
|
|||||||
/// have a server UID.
|
/// have a server UID.
|
||||||
async fn prune_tombstones(sql: &Sql) -> Result<()> {
|
async fn prune_tombstones(sql: &Sql) -> Result<()> {
|
||||||
sql.execute(
|
sql.execute(
|
||||||
sqlx::query(
|
|
||||||
"DELETE FROM msgs \
|
"DELETE FROM msgs \
|
||||||
WHERE (chat_id = ? OR hidden) \
|
WHERE (chat_id = ? OR hidden) \
|
||||||
AND server_uid = 0",
|
AND server_uid = 0",
|
||||||
)
|
paramsv![DC_CHAT_ID_TRASH],
|
||||||
.bind(DC_CHAT_ID_TRASH),
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the SQLite version as a string; e.g., `"3.16.2"` for version 3.16.2.
|
|
||||||
pub fn version() -> &'static str {
|
|
||||||
#[allow(unsafe_code)]
|
|
||||||
let cstr = unsafe { std::ffi::CStr::from_ptr(libsqlite3_sys::sqlite3_libversion()) };
|
|
||||||
cstr.to_str()
|
|
||||||
.expect("SQLite version string is not valid UTF8 ?!")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use async_std::fs::File;
|
use async_std::fs::File;
|
||||||
@@ -789,49 +792,4 @@ mod test {
|
|||||||
let a = t.get_config(Config::Selfavatar).await.unwrap().unwrap();
|
let a = t.get_config(Config::Selfavatar).await.unwrap().unwrap();
|
||||||
assert_eq!(avatar_bytes, &async_std::fs::read(&a).await.unwrap()[..]);
|
assert_eq!(avatar_bytes, &async_std::fs::read(&a).await.unwrap()[..]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Regression test.
|
|
||||||
///
|
|
||||||
/// Previously the code checking for existence of `config` table
|
|
||||||
/// checked it with `PRAGMA table_info("config")` but did not
|
|
||||||
/// drain `SqlitePool.fetch` result, only using the first row
|
|
||||||
/// returned. As a result, prepared statement for `PRAGMA` was not
|
|
||||||
/// finalized early enough, leaving reader connection in a broken
|
|
||||||
/// state after reopening the database, when `config` table
|
|
||||||
/// existed and `PRAGMA` returned non-empty result.
|
|
||||||
///
|
|
||||||
/// Statements were not finalized due to a bug in sqlx:
|
|
||||||
/// https://github.com/launchbadge/sqlx/issues/1147
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_db_reopen() -> Result<()> {
|
|
||||||
use tempfile::tempdir;
|
|
||||||
|
|
||||||
// The context is used only for logging.
|
|
||||||
let t = TestContext::new().await;
|
|
||||||
|
|
||||||
// Create a separate empty database for testing.
|
|
||||||
let dir = tempdir()?;
|
|
||||||
let dbfile = dir.path().join("testdb.sqlite");
|
|
||||||
let sql = Sql::new();
|
|
||||||
|
|
||||||
// Create database with all the tables.
|
|
||||||
sql.open(&t, &dbfile, false).await.unwrap();
|
|
||||||
sql.close().await;
|
|
||||||
|
|
||||||
// Reopen the database
|
|
||||||
sql.open(&t, &dbfile, false).await?;
|
|
||||||
sql.execute(
|
|
||||||
sqlx::query("INSERT INTO config (keyname, value) VALUES (?, ?);")
|
|
||||||
.bind("foo")
|
|
||||||
.bind("bar"),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let value: Option<String> = sql
|
|
||||||
.query_get_value(sqlx::query("SELECT value FROM config WHERE keyname=?;").bind("foo"))
|
|
||||||
.await?;
|
|
||||||
assert_eq!(value.unwrap(), "bar");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Sqlx: {0:?}")]
|
#[error("Sqlite error: {0:?}")]
|
||||||
Sqlx(#[from] sqlx::Error),
|
Sql(#[from] rusqlite::Error),
|
||||||
|
#[error("Sqlite Connection Pool Error: {0:?}")]
|
||||||
|
ConnectionPool(#[from] r2d2::Error),
|
||||||
#[error("Sqlite: Connection closed")]
|
#[error("Sqlite: Connection closed")]
|
||||||
SqlNoConnection,
|
SqlNoConnection,
|
||||||
#[error("Sqlite: Already open")]
|
#[error("Sqlite: Already open")]
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
use async_std::prelude::*;
|
|
||||||
|
|
||||||
use super::{Result, Sql};
|
use super::{Result, Sql};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::ShowEmails;
|
use crate::constants::ShowEmails;
|
||||||
@@ -19,23 +17,16 @@ pub async fn run(context: &Context, sql: &Sql) -> Result<(bool, bool, bool)> {
|
|||||||
|
|
||||||
if !sql.table_exists("config").await? {
|
if !sql.table_exists("config").await? {
|
||||||
info!(context, "First time init: creating tables",);
|
info!(context, "First time init: creating tables",);
|
||||||
sql.transaction(move |conn| {
|
sql.transaction(move |transaction| {
|
||||||
Box::pin(async move {
|
transaction.execute_batch(TABLES)?;
|
||||||
sqlx::query(TABLES)
|
|
||||||
.execute_many(&mut *conn)
|
|
||||||
.await
|
|
||||||
.collect::<std::result::Result<Vec<_>, _>>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// set raw config inside the transaction
|
// set raw config inside the transaction
|
||||||
sqlx::query("INSERT INTO config (keyname, value) VALUES (?, ?);")
|
transaction.execute(
|
||||||
.bind(VERSION_CFG)
|
"INSERT INTO config (keyname, value) VALUES (?, ?);",
|
||||||
.bind(format!("{}", dbversion_before_update))
|
paramsv![VERSION_CFG, format!("{}", dbversion_before_update)],
|
||||||
.execute(&mut *conn)
|
)?;
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
exists_before_update = true;
|
exists_before_update = true;
|
||||||
@@ -417,9 +408,10 @@ ALTER TABLE msgs ADD COLUMN mime_modified INTEGER DEFAULT 0;"#,
|
|||||||
if dbversion < 73 {
|
if dbversion < 73 {
|
||||||
use Config::*;
|
use Config::*;
|
||||||
info!(context, "[migration] v73");
|
info!(context, "[migration] v73");
|
||||||
sql.execute(sqlx::query(
|
sql.execute(
|
||||||
r#"
|
r#"
|
||||||
CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#),
|
CREATE TABLE imap_sync (folder TEXT PRIMARY KEY, uidvalidity INTEGER DEFAULT 0, uid_next INTEGER DEFAULT 0);"#,
|
||||||
|
paramsv![]
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
for c in &[
|
for c in &[
|
||||||
@@ -479,25 +471,17 @@ impl Sql {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn execute_migration(&self, query: &'static str, version: i32) -> Result<()> {
|
async fn execute_migration(&self, query: &'static str, version: i32) -> Result<()> {
|
||||||
let query = sqlx::query(query);
|
self.transaction(move |transaction| {
|
||||||
self.transaction(move |conn| {
|
transaction.execute_batch(query)?;
|
||||||
Box::pin(async move {
|
|
||||||
query
|
|
||||||
.execute_many(&mut *conn)
|
|
||||||
.await
|
|
||||||
.collect::<std::result::Result<Vec<_>, _>>()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// set raw config inside the transaction
|
// set raw config inside the transaction
|
||||||
sqlx::query("UPDATE config SET value=? WHERE keyname=?;")
|
transaction.execute(
|
||||||
.bind(format!("{}", version))
|
"UPDATE config SET value=? WHERE keyname=?;",
|
||||||
.bind(VERSION_CFG)
|
paramsv![format!("{}", version), VERSION_CFG],
|
||||||
.execute(&mut *conn)
|
)?;
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ use async_std::{channel, pin::Pin};
|
|||||||
use async_std::{future::Future, task};
|
use async_std::{future::Future, task};
|
||||||
use chat::ChatItem;
|
use chat::ChatItem;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use sqlx::Row;
|
|
||||||
use tempfile::{tempdir, TempDir};
|
use tempfile::{tempdir, TempDir};
|
||||||
|
|
||||||
use crate::chat::{self, Chat, ChatId};
|
use crate::chat::{self, Chat, ChatId};
|
||||||
@@ -228,25 +227,22 @@ impl TestContext {
|
|||||||
let row = self
|
let row = self
|
||||||
.ctx
|
.ctx
|
||||||
.sql
|
.sql
|
||||||
.fetch_one(
|
.query_row(
|
||||||
sqlx::query(
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, foreign_id, param
|
SELECT id, foreign_id, param
|
||||||
FROM jobs
|
FROM jobs
|
||||||
WHERE action=?
|
WHERE action=?
|
||||||
ORDER BY desired_timestamp DESC;
|
ORDER BY desired_timestamp DESC;
|
||||||
"#,
|
"#,
|
||||||
)
|
paramsv![Action::SendMsgToSmtp],
|
||||||
.bind(Action::SendMsgToSmtp),
|
|row| {
|
||||||
)
|
let id: u32 = row.get(0)?;
|
||||||
.await
|
let foreign_id: u32 = row.get(1)?;
|
||||||
.and_then(|row| {
|
let param: String = row.get(2)?;
|
||||||
let id: u32 = row.try_get(0)?;
|
|
||||||
let foreign_id: u32 = row.try_get(1)?;
|
|
||||||
let param: String = row.try_get(2)?;
|
|
||||||
Ok((id, foreign_id, param))
|
Ok((id, foreign_id, param))
|
||||||
});
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
if let Ok(row) = row {
|
if let Ok(row) = row {
|
||||||
break row;
|
break row;
|
||||||
}
|
}
|
||||||
@@ -266,7 +262,7 @@ impl TestContext {
|
|||||||
.to_abs_path();
|
.to_abs_path();
|
||||||
self.ctx
|
self.ctx
|
||||||
.sql
|
.sql
|
||||||
.execute(sqlx::query("DELETE FROM jobs WHERE id=?;").bind(rowid))
|
.execute("DELETE FROM jobs WHERE id=?;", paramsv![rowid])
|
||||||
.await
|
.await
|
||||||
.expect("failed to remove job");
|
.expect("failed to remove job");
|
||||||
update_msg_state(&self.ctx, id, MessageState::OutDelivered).await;
|
update_msg_state(&self.ctx, id, MessageState::OutDelivered).await;
|
||||||
|
|||||||
41
src/token.rs
41
src/token.rs
@@ -4,12 +4,16 @@
|
|||||||
//!
|
//!
|
||||||
//! Tokens are used in countermitm verification protocols.
|
//! Tokens are used in countermitm verification protocols.
|
||||||
|
|
||||||
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
|
|
||||||
use crate::chat::ChatId;
|
use crate::chat::ChatId;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_tools::{dc_create_id, time};
|
use crate::dc_tools::{dc_create_id, time};
|
||||||
|
|
||||||
/// Token namespace
|
/// Token namespace
|
||||||
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
|
#[derive(
|
||||||
|
Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql,
|
||||||
|
)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
pub enum Namespace {
|
pub enum Namespace {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
@@ -32,25 +36,16 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: Option<Ch
|
|||||||
Some(foreign_id) => context
|
Some(foreign_id) => context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
|
||||||
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);"
|
paramsv![namespace, foreign_id, token, time()],
|
||||||
)
|
|
||||||
.bind(namespace)
|
|
||||||
.bind(foreign_id)
|
|
||||||
.bind(&token)
|
|
||||||
.bind(time()),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok(),
|
.ok(),
|
||||||
None => context
|
None => context
|
||||||
.sql
|
.sql
|
||||||
.execute(
|
.execute(
|
||||||
sqlx::query(
|
"INSERT INTO tokens (namespc, token, timestamp) VALUES (?, ?, ?);",
|
||||||
"INSERT INTO tokens (namespc, token, timestamp) VALUES (?, ?, ?);"
|
paramsv![namespace, token, time()],
|
||||||
)
|
|
||||||
.bind(namespace)
|
|
||||||
.bind(&token)
|
|
||||||
.bind(time()),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.ok(),
|
.ok(),
|
||||||
@@ -68,10 +63,9 @@ pub async fn lookup(
|
|||||||
Some(chat_id) => {
|
Some(chat_id) => {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value::<String>(
|
||||||
sqlx::query("SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;")
|
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||||
.bind(namespace)
|
paramsv![namespace, chat_id],
|
||||||
.bind(chat_id),
|
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
@@ -79,9 +73,9 @@ pub async fn lookup(
|
|||||||
None => {
|
None => {
|
||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.query_get_value(
|
.query_get_value::<String>(
|
||||||
sqlx::query("SELECT token FROM tokens WHERE namespc=? AND foreign_id=0;")
|
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=0;",
|
||||||
.bind(namespace),
|
paramsv![namespace],
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
@@ -105,9 +99,8 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> boo
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.exists(
|
.exists(
|
||||||
sqlx::query("SELECT COUNT(*) FROM tokens WHERE namespc=? AND token=?;")
|
"SELECT COUNT(*) FROM tokens WHERE namespc=? AND token=?;",
|
||||||
.bind(namespace)
|
paramsv![namespace, token],
|
||||||
.bind(token),
|
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
|||||||
Reference in New Issue
Block a user