Compare commits

...

6 Commits

Author SHA1 Message Date
dignifiedquire
df546b9d2e more type fixes 2020-06-24 12:57:39 +02:00
dignifiedquire
5c13d2322a improve some typehints 2020-06-24 11:54:43 +02:00
dignifiedquire
2d5caf9d3e fix compilation 2020-06-24 11:06:26 +02:00
dignifiedquire
876e3ed58e update deps 2020-06-24 10:55:31 +02:00
dignifiedquire
cdb5f0d536 refactor(sql): switch execute to sqlx 2020-06-24 10:54:51 +02:00
dignifiedquire
0d791bb6b3 feat: start preparations for sqlx, split out migrations 2020-06-24 10:30:34 +02:00
38 changed files with 3325 additions and 3630 deletions

327
Cargo.lock generated
View File

@@ -103,10 +103,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "aho-corasick" name = "ahash"
version = "0.7.10" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
[[package]]
name = "aho-corasick"
version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@@ -290,6 +296,15 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "atoi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0afb7287b68575f5ca0e5c7e40191cbd4be59d325781f46faa603e176eaef47"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@@ -549,6 +564,15 @@ dependencies = [
"iovec", "iovec",
] ]
[[package]]
name = "bytes"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b"
dependencies = [
"loom",
]
[[package]] [[package]]
name = "cache-padded" name = "cache-padded"
version = "1.1.0" version = "1.1.0"
@@ -714,6 +738,16 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crossbeam-channel"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
dependencies = [
"crossbeam-utils",
"maybe-uninit",
]
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.2.3" version = "0.2.3"
@@ -852,6 +886,7 @@ dependencies = [
"lazy_static", "lazy_static",
"lettre_email", "lettre_email",
"libc", "libc",
"libsqlite3-sys",
"log", "log",
"mailparse", "mailparse",
"native-tls", "native-tls",
@@ -863,11 +898,8 @@ 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",
"rustyline", "rustyline",
"sanitize-filename", "sanitize-filename",
"serde", "serde",
@@ -875,6 +907,7 @@ dependencies = [
"sha2 0.9.0", "sha2 0.9.0",
"smallvec", "smallvec",
"smol", "smol",
"sqlx",
"stop-token", "stop-token",
"strum", "strum",
"strum_macros", "strum_macros",
@@ -983,6 +1016,12 @@ 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 = "dtoa" name = "dtoa"
version = "0.4.6" version = "0.4.6"
@@ -1165,18 +1204,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]] [[package]]
name = "fast_chemail" name = "fast_chemail"
version = "0.9.6" version = "0.9.6"
@@ -1344,6 +1371,19 @@ dependencies = [
"tokio-io", "tokio-io",
] ]
[[package]]
name = "generator"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68"
dependencies = [
"cc",
"libc",
"log",
"rustc_version",
"winapi",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.12.3" version = "0.12.3"
@@ -1418,6 +1458,16 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "hashbrown"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab9b7860757ce258c89fd48d28b68c41713e597a7b09e793f6c6a6e2ea37c827"
dependencies = [
"ahash",
"autocfg 1.0.0",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.3.1" version = "0.3.1"
@@ -1789,6 +1839,17 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "loom"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7"
dependencies = [
"cfg-if",
"generator",
"scoped-tls 0.1.2",
]
[[package]] [[package]]
name = "lru-cache" name = "lru-cache"
version = "0.1.2" version = "0.1.2"
@@ -1815,6 +1876,12 @@ 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"
@@ -2115,9 +2182,9 @@ dependencies = [
[[package]] [[package]]
name = "parking" name = "parking"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a7fad362df89617628a7508b3e9d588ade1b0ac31aa25de168193ad999c2dd4" checksum = "1bcaa58ee64f8e4a3d02f5d8e6ed0340eae28fed6fdabd984ad1776e3b43848a"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
@@ -2208,6 +2275,50 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_macros",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_generator"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"rand 0.7.3",
]
[[package]]
name = "phf_macros"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
]
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "0.4.22" version = "0.4.22"
@@ -2376,27 +2487,6 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
[[package]]
name = "r2d2"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af"
dependencies = [
"log",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "r2d2_sqlite"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed60ebe88b27ac28c0563bc0fbeaecd302ff53e3a01e5ddc2ec9f4e6c707d929"
dependencies = [
"r2d2",
"rusqlite",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.4.6" version = "0.4.6"
@@ -2422,6 +2512,7 @@ dependencies = [
"rand_chacha", "rand_chacha",
"rand_core 0.5.1", "rand_core 0.5.1",
"rand_hc", "rand_hc",
"rand_pcg",
] ]
[[package]] [[package]]
@@ -2467,6 +2558,15 @@ dependencies = [
"rand_core 0.5.1", "rand_core 0.5.1",
] ]
[[package]]
name = "rand_pcg"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
dependencies = [
"rand_core 0.5.1",
]
[[package]] [[package]]
name = "rand_xorshift" name = "rand_xorshift"
version = "0.2.0" version = "0.2.0"
@@ -2593,22 +2693,6 @@ dependencies = [
"zeroize", "zeroize",
] ]
[[package]]
name = "rusqlite"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45d0fd62e1df63d254714e6cb40d0a0e82e7a1623e7a27f679d851af092ae58b"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"libsqlite3-sys",
"lru-cache",
"memchr",
"smallvec",
"time 0.1.43",
]
[[package]] [[package]]
name = "rust-argon2" name = "rust-argon2"
version = "0.7.0" version = "0.7.0"
@@ -2707,13 +2791,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "scheduled-thread-pool" name = "scoped-tls"
version = "0.2.4" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6" checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
dependencies = [
"parking_lot",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
@@ -2805,9 +2886,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_qs" name = "serde_qs"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6e32b85107a5c8062643265a90575cc6e798cec0906ea58519b42175062ba27" checksum = "c6f3acf84e23ab27c01cb5917551765c01c50b2000089db8fa47fe018a3260cf"
dependencies = [ dependencies = [
"data-encoding", "data-encoding",
"error-chain", "error-chain",
@@ -2893,6 +2974,12 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]] [[package]]
name = "skeptic" name = "skeptic"
version = "0.13.4" version = "0.13.4"
@@ -2935,7 +3022,7 @@ dependencies = [
"futures-util", "futures-util",
"libc", "libc",
"once_cell", "once_cell",
"scoped-tls", "scoped-tls 1.0.0",
"slab", "slab",
"socket2", "socket2",
"wepoll-sys-stjepang", "wepoll-sys-stjepang",
@@ -2960,6 +3047,88 @@ 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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce64a4576e1720a2e511bf3ccdb8c0f6cfed0fc265bcbaa0bd369485e02c631"
dependencies = [
"lazy_static",
"maplit",
"regex",
]
[[package]]
name = "sqlx"
version = "0.4.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"sqlx-core",
"sqlx-macros",
]
[[package]]
name = "sqlx-core"
version = "0.4.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"atoi",
"bitflags",
"byteorder",
"bytes 0.5.5",
"crossbeam-channel",
"crossbeam-queue",
"crossbeam-utils",
"either",
"futures-channel",
"futures-core",
"futures-util",
"hashbrown",
"hex",
"itoa",
"libc",
"libsqlite3-sys",
"log",
"memchr",
"once_cell",
"parking_lot",
"percent-encoding",
"phf",
"smallvec",
"sqlformat",
"sqlx-rt",
"stringprep",
"thiserror",
"url",
"whoami",
]
[[package]]
name = "sqlx-macros"
version = "0.4.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"async-std",
"dotenv",
"futures 0.3.5",
"heck",
"proc-macro2",
"quote",
"sqlx-core",
"syn",
"url",
]
[[package]]
name = "sqlx-rt"
version = "0.1.0-pre"
source = "git+https://github.com/launchbadge/sqlx#72c4e040bccfb0689529fa574124e35947c18236"
dependencies = [
"async-native-tls",
"async-std",
"native-tls",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.1.1" version = "1.1.1"
@@ -3049,6 +3218,16 @@ dependencies = [
"generic-array 0.14.2", "generic-array 0.14.2",
] ]
[[package]]
name = "stringprep"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
dependencies = [
"unicode-bidi",
"unicode-normalization",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.9.3" version = "0.9.3"
@@ -3250,7 +3429,7 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [ dependencies = [
"bytes", "bytes 0.4.12",
"futures 0.1.29", "futures 0.1.29",
"log", "log",
] ]
@@ -3369,9 +3548,9 @@ checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.2.0" 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 = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
@@ -3543,13 +3722,19 @@ dependencies = [
[[package]] [[package]]
name = "wepoll-sys-stjepang" name = "wepoll-sys-stjepang"
version = "1.0.2" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "035f8ab1fcf98d41f8fd5206a97c335604162e5eb0c25c4839062093150ddc79" checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694"
dependencies = [ dependencies = [
"cc", "cc",
] ]
[[package]]
name = "whoami"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18bb55566f6f3f8440914233cf63df1eb60b801b5007d376cc46212cb8a9287c"
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.4.2" version = "0.4.2"

View File

@@ -36,9 +36,6 @@ indexmap = "1.3.0"
kamadak-exif = "0.5" kamadak-exif = "0.5"
lazy_static = "1.4.0" lazy_static = "1.4.0"
regex = "1.1.6" regex = "1.1.6"
rusqlite = { version = "0.23", features = ["bundled"] }
r2d2_sqlite = "0.16.0"
r2d2 = "0.8.5"
strum = "0.18.0" strum = "0.18.0"
strum_macros = "0.18.0" strum_macros = "0.18.0"
backtrace = "0.3.33" backtrace = "0.3.33"
@@ -60,20 +57,22 @@ anyhow = "1.0.28"
async-trait = "0.1.31" async-trait = "0.1.31"
url = "2.1.1" url = "2.1.1"
async-std-resolver = "0.19.5" async-std-resolver = "0.19.5"
sqlx = { git = "https://github.com/launchbadge/sqlx", branch = "master", features = ["runtime-async-std", "sqlite", "macros"] }
libsqlite3-sys = { version = "0.18", features = ["bundled", "min_sqlite_version_3_7_16"] }
pretty_env_logger = { version = "0.4.0", optional = true } pretty_env_logger = { version = "0.4.0", optional = true }
log = {version = "0.4.8", optional = true } log = { version = "0.4.8", optional = true }
rustyline = { version = "4.1.0", optional = true } rustyline = { version = "4.1.0", optional = true }
ansi_term = { version = "0.12.1", optional = true } ansi_term = { version = "0.12.1", optional = true }
[dev-dependencies] [dev-dependencies]
tempfile = "3.0" tempfile = "3.0"
pretty_assertions = "0.6.1" pretty_assertions = "0.6.1"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
proptest = "0.10" proptest = "0.10"
async-std = { version = "1.6.0", features = ["unstable", "attributes"] } async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
smol = "0.1.10" smol = "0.1.11"
log = "0.4.8"
[workspace] [workspace]
members = [ members = [
@@ -98,4 +97,3 @@ internals = []
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"] repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"]
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"] vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"]
nightly = ["pgp/nightly"] nightly = ["pgp/nightly"]

View File

@@ -349,7 +349,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| Event::MsgDelivered { chat_id, .. } | Event::MsgDelivered { chat_id, .. }
| Event::MsgFailed { chat_id, .. } | Event::MsgFailed { chat_id, .. }
| Event::MsgRead { chat_id, .. } | Event::MsgRead { chat_id, .. }
| Event::ChatModified(chat_id) => chat_id.to_u32() as libc::c_int, | Event::ChatModified(chat_id) => chat_id.to_u32().unwrap_or_default() as libc::c_int,
Event::ContactsChanged(id) | Event::LocationChanged(id) => { Event::ContactsChanged(id) | Event::LocationChanged(id) => {
let id = id.unwrap_or_default(); let id = id.unwrap_or_default();
id as libc::c_int id as libc::c_int
@@ -396,7 +396,7 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| Event::IncomingMsg { msg_id, .. } | Event::IncomingMsg { msg_id, .. }
| Event::MsgDelivered { msg_id, .. } | Event::MsgDelivered { msg_id, .. }
| Event::MsgFailed { msg_id, .. } | Event::MsgFailed { msg_id, .. }
| Event::MsgRead { msg_id, .. } => msg_id.to_u32() as libc::c_int, | Event::MsgRead { msg_id, .. } => msg_id.to_u32().unwrap_or_default() as libc::c_int,
Event::SecurejoinInviterProgress { progress, .. } Event::SecurejoinInviterProgress { progress, .. }
| Event::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int, | Event::SecurejoinJoinerProgress { progress, .. } => *progress as libc::c_int,
} }
@@ -833,7 +833,8 @@ pub unsafe extern "C" fn dc_get_chat_msgs(
let arr = dc_array_t::from( let arr = dc_array_t::from(
chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag) chat::get_chat_msgs(&ctx, ChatId::new(chat_id), flags, marker_flag)
.await .await
.iter() .unwrap_or_log_default(ctx, "failed get_chat_msgs")
.into_iter()
.map(|msg_id| msg_id.to_u32()) .map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(), .collect::<Vec<u32>>(),
); );
@@ -898,7 +899,7 @@ pub unsafe extern "C" fn dc_get_fresh_msgs(
let arr = dc_array_t::from( let arr = dc_array_t::from(
ctx.get_fresh_msgs() ctx.get_fresh_msgs()
.await .await
.iter() .into_iter()
.map(|msg_id| msg_id.to_u32()) .map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(), .collect::<Vec<u32>>(),
); );
@@ -975,7 +976,8 @@ pub unsafe extern "C" fn dc_get_chat_media(
or_msg_type3, or_msg_type3,
) )
.await .await
.iter() .unwrap_or_log_default(ctx, "failed get_chat_media")
.into_iter()
.map(|msg_id| msg_id.to_u32()) .map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(), .collect::<Vec<u32>>(),
); );
@@ -1019,8 +1021,9 @@ pub unsafe extern "C" fn dc_get_next_media(
or_msg_type3, or_msg_type3,
) )
.await .await
.unwrap_or_log_default(ctx, "failed get_next_media")
.map(|msg_id| msg_id.to_u32()) .map(|msg_id| msg_id.to_u32())
.unwrap_or(0) .unwrap_or_else(|| 0)
}) })
} }
@@ -1086,7 +1089,10 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
let ctx = &*context; let ctx = &*context;
block_on(async move { block_on(async move {
let arr = dc_array_t::from(chat::get_chat_contacts(&ctx, ChatId::new(chat_id)).await); let list = chat::get_chat_contacts(&ctx, ChatId::new(chat_id))
.await
.unwrap_or_log_default(ctx, "failed get_chat_contacts");
let arr = dc_array_t::from(list);
Box::into_raw(Box::new(arr)) Box::into_raw(Box::new(arr))
}) })
} }
@@ -1107,7 +1113,7 @@ pub unsafe extern "C" fn dc_search_msgs(
let arr = dc_array_t::from( let arr = dc_array_t::from(
ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query)) ctx.search_msgs(ChatId::new(chat_id), to_string_lossy(query))
.await .await
.iter() .into_iter()
.map(|msg_id| msg_id.to_u32()) .map(|msg_id| msg_id.to_u32())
.collect::<Vec<u32>>(), .collect::<Vec<u32>>(),
); );
@@ -1296,7 +1302,12 @@ pub unsafe extern "C" fn dc_get_msg_info(
} }
let ctx = &*context; let ctx = &*context;
block_on(message::get_msg_info(&ctx, MsgId::new(msg_id))).strdup() block_on(async move {
message::get_msg_info(ctx, MsgId::new(msg_id))
.await
.unwrap_or_log_default(ctx, "failed get_msg_info")
})
.strdup()
} }
#[no_mangle] #[no_mangle]
@@ -1855,7 +1866,11 @@ pub unsafe extern "C" fn dc_set_location(
} }
let ctx = &*context; let ctx = &*context;
block_on(location::set(&ctx, latitude, longitude, accuracy)) as _ block_on(async move {
location::set(ctx, latitude, longitude, accuracy)
.await
.unwrap_or_log_default(ctx, "failed location::set")
}) as _
} }
#[no_mangle] #[no_mangle]
@@ -2255,7 +2270,12 @@ pub unsafe extern "C" fn dc_chat_get_profile_image(chat: *mut dc_chat_t) -> *mut
let ctx = &*ffi_chat.context; let ctx = &*ffi_chat.context;
block_on(async move { block_on(async move {
match ffi_chat.chat.get_profile_image(&ctx).await { match ffi_chat
.chat
.get_profile_image(&ctx)
.await
.unwrap_or_log_default(ctx, "failed get_profile_image")
{
Some(p) => p.to_string_lossy().strdup(), Some(p) => p.to_string_lossy().strdup(),
None => ptr::null_mut(), None => ptr::null_mut(),
} }
@@ -2271,7 +2291,13 @@ pub unsafe extern "C" fn dc_chat_get_color(chat: *mut dc_chat_t) -> u32 {
let ffi_chat = &*chat; let ffi_chat = &*chat;
let ctx = &*ffi_chat.context; let ctx = &*ffi_chat.context;
block_on(ffi_chat.chat.get_color(&ctx)) block_on(async move {
ffi_chat
.chat
.get_color(&ctx)
.await
.unwrap_or_log_default(ctx, "failed dc_chat_get_color")
})
} }
#[no_mangle] #[no_mangle]

View File

@@ -8,36 +8,33 @@ use quote::quote;
// data. If this assumption is violated, compiler error will point to // data. If this assumption is violated, compiler error will point to
// generated code, which is not very user-friendly. // generated code, which is not very user-friendly.
#[proc_macro_derive(ToSql)] #[proc_macro_derive(Sqlx)]
pub fn to_sql_derive(input: TokenStream) -> TokenStream { pub fn sqlx_derive(input: TokenStream) -> TokenStream {
let ast: syn::DeriveInput = syn::parse(input).unwrap(); let ast: syn::DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident; let name = &ast.ident;
let gen = quote! { let gen = quote! {
impl rusqlite::types::ToSql for #name { impl<'q> sqlx::encode::Encode<'q, sqlx::sqlite::Sqlite> for #name {
fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput> { fn encode_by_ref(&self, buf: &mut Vec<sqlx::sqlite::SqliteArgumentValue<'q>>) -> sqlx::encode::IsNull {
let num = *self as i64; num_traits::ToPrimitive::to_i32(self).expect("invalid type").encode(buf)
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)?;
Ok(num_traits::FromPrimitive::from_i64(inner).unwrap_or_default())
} }
} }
impl<'de> sqlx::decode::Decode<'de, sqlx::sqlite::Sqlite> for #name {
fn decode(value: sqlx::sqlite::SqliteValueRef) -> std::result::Result<Self, Box<dyn std::error::Error + 'static + Send + Sync>> {
let raw: i32 = sqlx::decode::Decode::decode(value)?;
Ok(num_traits::FromPrimitive::from_i32(raw).unwrap_or_default())
}
}
impl sqlx::types::Type<sqlx::sqlite::Sqlite> for #name {
fn type_info() -> sqlx::sqlite::SqliteTypeInfo {
<i32 as sqlx::types::Type<_>>::type_info()
}
}
}; };
gen.into() gen.into()
} }

View File

@@ -28,7 +28,7 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 1 { if 0 != bits & 1 {
context context
.sql() .sql()
.execute("DELETE FROM jobs;", paramsv![]) .execute("DELETE FROM jobs;", paramsx![])
.await .await
.unwrap(); .unwrap();
println!("(1) Jobs reset."); println!("(1) Jobs reset.");
@@ -36,7 +36,7 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 2 { if 0 != bits & 2 {
context context
.sql() .sql()
.execute("DELETE FROM acpeerstates;", paramsv![]) .execute("DELETE FROM acpeerstates;", paramsx![])
.await .await
.unwrap(); .unwrap();
println!("(2) Peerstates reset."); println!("(2) Peerstates reset.");
@@ -44,7 +44,7 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 4 { if 0 != bits & 4 {
context context
.sql() .sql()
.execute("DELETE FROM keypairs;", paramsv![]) .execute("DELETE FROM keypairs;", paramsx![])
.await .await
.unwrap(); .unwrap();
println!("(4) Private keypairs reset."); println!("(4) Private keypairs reset.");
@@ -52,35 +52,35 @@ async fn reset_tables(context: &Context, bits: i32) {
if 0 != bits & 8 { if 0 != bits & 8 {
context context
.sql() .sql()
.execute("DELETE FROM contacts WHERE id>9;", paramsv![]) .execute("DELETE FROM contacts WHERE id>9;", paramsx![])
.await .await
.unwrap(); .unwrap();
context context
.sql() .sql()
.execute("DELETE FROM chats WHERE id>9;", paramsv![]) .execute("DELETE FROM chats WHERE id>9;", paramsx![])
.await .await
.unwrap(); .unwrap();
context context
.sql() .sql()
.execute("DELETE FROM chats_contacts;", paramsv![]) .execute("DELETE FROM chats_contacts;", paramsx![])
.await .await
.unwrap(); .unwrap();
context context
.sql() .sql()
.execute("DELETE FROM msgs WHERE id>9;", paramsv![]) .execute("DELETE FROM msgs WHERE id>9;", paramsx![])
.await .await
.unwrap(); .unwrap();
context context
.sql() .sql()
.execute( .execute(
"DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';", "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';",
paramsv![], paramsx![],
) )
.await .await
.unwrap(); .unwrap();
context context
.sql() .sql()
.execute("DELETE FROM leftgrps;", paramsv![]) .execute("DELETE FROM leftgrps;", paramsx![])
.await .await
.unwrap(); .unwrap();
println!("(8) Rest but server config reset."); println!("(8) Rest but server config reset.");
@@ -118,7 +118,7 @@ async fn poke_spec(context: &Context, spec: Option<&str>) -> bool {
.await .await
.unwrap(); .unwrap();
} else { } else {
let rs = context.sql().get_raw_config(context, "import_spec").await; let rs = context.sql().get_raw_config("import_spec").await;
if rs.is_none() { if rs.is_none() {
error!(context, "Import: No file or folder given."); error!(context, "Import: No file or folder given.");
return false; return false;
@@ -276,7 +276,7 @@ async fn log_contactlist(context: &Context, contacts: &[u32]) {
} }
); );
let peerstate = Peerstate::from_addr(context, &addr).await; let peerstate = Peerstate::from_addr(context, &addr).await;
if peerstate.is_some() && contact_id != 1 as libc::c_uint { if peerstate.is_ok() && contact_id != 1 as libc::c_uint {
line2 = format!( line2 = format!(
", prefer-encrypt={}", ", prefer-encrypt={}",
peerstate.as_ref().unwrap().prefer_encrypt peerstate.as_ref().unwrap().prefer_encrypt
@@ -484,7 +484,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
context.maybe_network().await; context.maybe_network().await;
} }
"housekeeping" => { "housekeeping" => {
sql::housekeeping(&context).await; sql::housekeeping(&context).await?;
} }
"listchats" | "listarchived" | "chats" => { "listchats" | "listarchived" | "chats" => {
let listflags = if arg0 == "listarchived" { 0x01 } else { 0 }; let listflags = if arg0 == "listarchived" { 0x01 } else { 0 };
@@ -573,8 +573,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(sel_chat.is_some(), "Failed to select chat"); 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(), 0x1, None).await?;
let members = chat::get_chat_contacts(&context, sel_chat.id).await; let members = chat::get_chat_contacts(&context, sel_chat.id).await?;
let subtitle = if sel_chat.is_device_talk() { let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string() "device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && !members.is_empty() { } else if sel_chat.get_type() == Chattype::Single && !members.is_empty() {
@@ -594,7 +594,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
} else { } else {
"" ""
}, },
match sel_chat.get_profile_image(&context).await { match sel_chat.get_profile_image(&context).await? {
Some(icon) => match icon.to_str() { Some(icon) => match icon.to_str() {
Some(icon) => format!(" Icon: {}", icon), Some(icon) => format!(" Icon: {}", icon),
_ => " Icon: Err".to_string(), _ => " Icon: Err".to_string(),
@@ -691,7 +691,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
ensure!(sel_chat.is_some(), "No chat selected."); ensure!(sel_chat.is_some(), "No chat selected.");
let contacts = let contacts =
chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await; chat::get_chat_contacts(&context, sel_chat.as_ref().unwrap().get_id()).await?;
println!("Memberlist:"); println!("Memberlist:");
log_contactlist(&context, &contacts).await; log_contactlist(&context, &contacts).await;
@@ -762,7 +762,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let latitude = arg1.parse()?; let latitude = arg1.parse()?;
let longitude = arg2.parse()?; let longitude = arg2.parse()?;
let continue_streaming = location::set(&context, latitude, longitude, 0.).await; let continue_streaming = location::set(&context, latitude, longitude, 0.).await?;
if continue_streaming { if continue_streaming {
println!("Success, streaming should be continued."); println!("Success, streaming should be continued.");
} else { } else {
@@ -858,7 +858,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
Viewtype::Gif, Viewtype::Gif,
Viewtype::Video, Viewtype::Video,
) )
.await; .await?;
println!("{} images or videos: ", images.len()); println!("{} images or videos: ", images.len());
for (i, data) in images.iter().enumerate() { for (i, data) in images.iter().enumerate() {
if 0 == i { if 0 == i {
@@ -892,7 +892,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
"msginfo" => { "msginfo" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing."); ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let id = MsgId::new(arg1.parse()?); let id = MsgId::new(arg1.parse()?);
let res = message::get_msg_info(&context, id).await; let res = message::get_msg_info(&context, id).await?;
println!("{}", res); println!("{}", res);
} }
"listfresh" => { "listfresh" => {

View File

@@ -6,13 +6,15 @@ use std::collections::BTreeMap;
use std::str::FromStr; use std::str::FromStr;
use std::{fmt, str}; use std::{fmt, str};
use deltachat_derive::*;
use crate::contact::*; use crate::contact::*;
use crate::context::Context; use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap}; use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::{DcKey, SignedPublicKey}; use crate::key::{DcKey, SignedPublicKey};
/// Possible values for encryption preference /// Possible values for encryption preference
#[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive)] #[derive(PartialEq, Eq, Debug, Clone, Copy, FromPrimitive, ToPrimitive, Sqlx)]
#[repr(u8)] #[repr(u8)]
pub enum EncryptPreference { pub enum EncryptPreference {
NoPreference = 0, NoPreference = 0,

File diff suppressed because it is too large Load Diff

View File

@@ -105,17 +105,6 @@ impl Chatlist {
let mut add_archived_link_item = false; let mut add_archived_link_item = false;
let process_row = |row: &rusqlite::Row| {
let chat_id: ChatId = row.get(0)?;
let msg_id: MsgId = row.get(1).unwrap_or_default();
Ok((chat_id, msg_id))
};
let process_rows = |rows: rusqlite::MappedRows<_>| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
};
let skip_id = if flag_for_forwarding { let skip_id = if flag_for_forwarding {
chat::lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE) chat::lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
.await .await
@@ -142,7 +131,7 @@ impl Chatlist {
// shown at all permanent in the chatlist. // shown at all permanent in the chatlist.
let mut ids = 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.query_map( context.sql.query_rows(
"SELECT c.id, m.id "SELECT c.id, m.id
FROM chats c FROM chats c
LEFT JOIN msgs m LEFT JOIN msgs m
@@ -151,16 +140,14 @@ impl Chatlist {
SELECT id SELECT id
FROM msgs FROM msgs
WHERE chat_id=c.id WHERE chat_id=c.id
AND (hidden=0 OR state=?1) AND (hidden=0 OR state=?)
ORDER BY timestamp DESC, id DESC LIMIT 1) ORDER BY timestamp DESC, id DESC LIMIT 1)
WHERE c.id>9 WHERE c.id>9
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=?)
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=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsv![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], paramsx![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
process_row,
process_rows,
).await? ).await?
} else if flag_archived_only { } else if flag_archived_only {
// show archived chats // show archived chats
@@ -169,7 +156,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
.query_map( .query_rows(
"SELECT c.id, m.id "SELECT c.id, m.id
FROM chats c FROM chats c
LEFT JOIN msgs m LEFT JOIN msgs m
@@ -185,9 +172,7 @@ 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], paramsx![MessageState::OutDraft],
process_row,
process_rows,
) )
.await? .await?
} else if let Some(query) = query { } else if let Some(query) = query {
@@ -203,7 +188,7 @@ impl Chatlist {
let str_like_cmd = format!("%{}%", query); let str_like_cmd = format!("%{}%", query);
context context
.sql .sql
.query_map( .query_rows(
"SELECT c.id, m.id "SELECT c.id, m.id
FROM chats c FROM chats c
LEFT JOIN msgs m LEFT JOIN msgs m
@@ -219,9 +204,7 @@ 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], paramsx![MessageState::OutDraft, skip_id, str_like_cmd],
process_row,
process_rows,
) )
.await? .await?
} else { } else {
@@ -234,7 +217,7 @@ impl Chatlist {
} else { } else {
ChatId::new(0) ChatId::new(0)
}; };
let mut ids = context.sql.query_map( let mut ids = context.sql.query_rows(
"SELECT c.id, m.id "SELECT c.id, m.id
FROM chats c FROM chats c
LEFT JOIN msgs m LEFT JOIN msgs m
@@ -250,10 +233,9 @@ impl Chatlist {
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], paramsx![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
process_row,
process_rows,
).await?; ).await?;
if !flag_no_specials { if !flag_no_specials {
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await
{ {
@@ -387,15 +369,15 @@ impl Chatlist {
/// Returns the number of archived chats /// Returns the number of archived chats
pub async fn dc_get_archived_cnt(context: &Context) -> u32 { pub async fn dc_get_archived_cnt(context: &Context) -> u32 {
context let v: i32 = context
.sql .sql
.query_get_value( .query_value(
context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;", "SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
paramsv![], paramsx![],
) )
.await .await
.unwrap_or_default() .unwrap_or_default();
v as u32
} }
async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> { async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
@@ -403,21 +385,21 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
// sufficient as there are typically only few fresh messages. // sufficient as there are typically only few fresh messages.
context context
.sql .sql
.query_get_value( .query_value(
context, r#"
concat!( SELECT m.id
"SELECT m.id", FROM msgs m
" FROM msgs m", LEFT JOIN chats c
" LEFT JOIN chats c", ON c.id=m.chat_id
" ON c.id=m.chat_id", WHERE m.state=10
" WHERE m.state=10", 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;" "#,
), paramsx![],
paramsv![],
) )
.await .await
.ok()
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -121,20 +121,20 @@ pub enum Config {
impl Context { impl Context {
pub async fn config_exists(&self, key: Config) -> bool { pub async fn config_exists(&self, key: Config) -> bool {
self.sql.get_raw_config(self, key).await.is_some() self.sql.get_raw_config(key).await.is_some()
} }
/// Get a configuration key. Returns `None` if no value is set, and no default value found. /// Get a configuration key. Returns `None` if no value is set, and no default value found.
pub async fn get_config(&self, key: Config) -> Option<String> { pub async fn get_config(&self, key: Config) -> Option<String> {
let value = match key { let value = match key {
Config::Selfavatar => { Config::Selfavatar => {
let rel_path = self.sql.get_raw_config(self, key).await; let rel_path = self.sql.get_raw_config(key).await;
rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned()) rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned())
} }
Config::SysVersion => Some((&*DC_VERSION_STR).clone()), Config::SysVersion => Some((&*DC_VERSION_STR).clone()),
Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)), Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)),
Config::SysConfigKeys => Some(get_config_keys_string()), Config::SysConfigKeys => Some(get_config_keys_string()),
_ => self.sql.get_raw_config(self, key).await, _ => self.sql.get_raw_config(key).await,
}; };
if value.is_some() { if value.is_some() {
@@ -189,7 +189,7 @@ impl Context {
match key { match key {
Config::Selfavatar => { Config::Selfavatar => {
self.sql self.sql
.execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![]) .execute("UPDATE contacts SET selfavatar_sent=0;", paramsx![])
.await?; .await?;
self.sql self.sql
.set_raw_config_bool(self, "attach_selfavatar", true) .set_raw_config_bool(self, "attach_selfavatar", true)

View File

@@ -37,7 +37,7 @@ macro_rules! progress {
impl Context { impl Context {
/// Checks if the context is already configured. /// Checks if the context is already configured.
pub async fn is_configured(&self) -> bool { pub async fn is_configured(&self) -> bool {
self.sql.get_raw_config_bool(self, "configured").await self.sql.get_raw_config_bool("configured").await
} }
/// Configures this account with the currently set parameters. /// Configures this account with the currently set parameters.

View File

@@ -1,7 +1,6 @@
//! # Constants //! # Constants
#![allow(dead_code)] #![allow(dead_code)]
use deltachat_derive::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -25,12 +24,11 @@ const DC_MVBOX_MOVE_DEFAULT: i32 = 1;
Eq, Eq,
FromPrimitive, FromPrimitive,
ToPrimitive, ToPrimitive,
FromSql,
ToSql,
Serialize, Serialize,
Deserialize, Deserialize,
sqlx::Type,
)] )]
#[repr(u8)] #[repr(i32)]
pub enum Blocked { pub enum Blocked {
Not = 0, Not = 0,
Manually = 1, Manually = 1,
@@ -43,8 +41,8 @@ impl Default for Blocked {
} }
} }
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
#[repr(u8)] #[repr(i32)]
pub enum ShowEmails { pub enum ShowEmails {
Off = 0, Off = 0,
AcceptedContacts = 1, AcceptedContacts = 1,
@@ -57,8 +55,8 @@ impl Default for ShowEmails {
} }
} }
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
#[repr(u8)] #[repr(i32)]
pub enum MediaQuality { pub enum MediaQuality {
Balanced = 0, Balanced = 0,
Worse = 1, Worse = 1,
@@ -70,7 +68,7 @@ impl Default for MediaQuality {
} }
} }
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, FromSql, ToSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, sqlx::Type)]
#[repr(u8)] #[repr(u8)]
pub enum KeyGenType { pub enum KeyGenType {
Default = 0, Default = 0,
@@ -127,13 +125,12 @@ pub const DC_CHAT_ID_LAST_SPECIAL: u32 = 9;
Eq, Eq,
FromPrimitive, FromPrimitive,
ToPrimitive, ToPrimitive,
FromSql,
ToSql,
IntoStaticStr, IntoStaticStr,
Serialize, Serialize,
Deserialize, Deserialize,
sqlx::Type,
)] )]
#[repr(u32)] #[repr(i32)]
pub enum Chattype { pub enum Chattype {
Undefined = 0, Undefined = 0,
Single = 100, Single = 100,
@@ -243,10 +240,9 @@ pub const DC_FOLDERS_CONFIGURED_VERSION: i32 = 3;
Eq, Eq,
FromPrimitive, FromPrimitive,
ToPrimitive, ToPrimitive,
FromSql,
ToSql,
Serialize, Serialize,
Deserialize, Deserialize,
sqlx::Type,
)] )]
#[repr(i32)] #[repr(i32)]
pub enum Viewtype { pub enum Viewtype {

View File

@@ -1,12 +1,14 @@
//! Contacts module //! Contacts module
#![forbid(clippy::indexing_slicing)] #![forbid(clippy::indexing_slicing)]
use std::convert::TryFrom;
use async_std::path::PathBuf; use async_std::path::PathBuf;
use deltachat_derive::*; use async_std::prelude::*;
use itertools::Itertools; use itertools::Itertools;
use lazy_static::lazy_static; use lazy_static::lazy_static;
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;
@@ -72,7 +74,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, FromSql, ToSql, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, sqlx::Type,
)] )]
#[repr(i32)] #[repr(i32)]
pub enum Origin { pub enum Origin {
@@ -133,6 +135,31 @@ impl Default for Origin {
Origin::Unknown Origin::Unknown
} }
} }
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Contact {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
let contact = Self {
id: u32::try_from(row.try_get::<i32, _>("id")?)
.map_err(|err| sqlx::Error::Decode(err.into()))?,
name: row
.try_get::<Option<String>, _>("name")?
.unwrap_or_default(),
authname: row
.try_get::<Option<String>, _>("authname")?
.unwrap_or_default(),
addr: row.try_get::<String, _>("addr")?,
blocked: row
.try_get::<Option<i32>, _>("blocked")?
.unwrap_or_default()
!= 0,
origin: row.try_get("origin")?,
param: row
.try_get::<String, _>("param")?
.parse()
.unwrap_or_default(),
};
Ok(contact)
}
}
impl Origin { impl Origin {
/// Contacts that are known, i. e. they came in via accepted contacts or /// Contacts that are known, i. e. they came in via accepted contacts or
@@ -163,27 +190,18 @@ 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 mut res = context let mut res: Contact = context
.sql .sql
.query_row( .query_row(
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param r#"
FROM contacts c SELECT id, name, addr, origin, blocked, authname, param
WHERE c.id=?;", FROM contacts
paramsv![contact_id as i32], WHERE id=?;
|row| { "#,
let contact = Self { paramsx![contact_id as i32],
id: contact_id,
name: row.get::<_, String>(0)?,
authname: row.get::<_, String>(4)?,
addr: row.get::<_, String>(1)?,
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
origin: row.get(2)?,
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
};
Ok(contact)
},
) )
.await?; .await?;
if contact_id == DC_CONTACT_ID_SELF { if contact_id == DC_CONTACT_ID_SELF {
res.name = context.stock_str(StockMessage::SelfMsg).await.to_string(); res.name = context.stock_str(StockMessage::SelfMsg).await.to_string();
res.addr = context res.addr = context
@@ -269,8 +287,11 @@ impl Contact {
if context if context
.sql .sql
.execute( .execute(
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;", r#"
paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh], UPDATE msgs SET state=?
WHERE from_id=? AND state=?;
"#,
paramsx![MessageState::InNoticed, id as i32, MessageState::InFresh],
) )
.await .await
.is_ok() .is_ok()
@@ -305,15 +326,15 @@ impl Contact {
if addr_cmp(addr_normalized, addr_self) { if addr_cmp(addr_normalized, addr_self) {
return DC_CONTACT_ID_SELF; return DC_CONTACT_ID_SELF;
} }
context.sql.query_get_value( let v: i32 = context.sql.query_value(
context, "SELECT id FROM contacts WHERE addr=? COLLATE NOCASE AND id>? AND origin>=? AND blocked=0;",
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;", paramsx![
paramsv![
addr_normalized, addr_normalized,
DC_CONTACT_ID_LAST_SPECIAL as i32, DC_CONTACT_ID_LAST_SPECIAL as i32,
min_origin as u32, min_origin as i32
], ],
).await.unwrap_or_default() ).await.unwrap_or_default();
v as u32
} }
/// Lookup a contact and create it if it does not exist yet. /// Lookup a contact and create it if it does not exist yet.
@@ -384,39 +405,32 @@ impl Contact {
let mut update_authname = false; let mut update_authname = false;
let mut row_id = 0; let mut row_id = 0;
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row( let res: Result<(i32, String, String, Origin, String), _> = context.sql.query_row(
"SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;", "SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;",
paramsv![addr.to_string()], paramsx![addr.to_string()],
|row| { )
let row_id = row.get(0)?; .await;
let row_name: String = row.get(1)?;
let row_addr: String = row.get(2)?;
let row_origin: Origin = row.get(3)?;
let row_authname: String = row.get(4)?;
if !name.as_ref().is_empty() { if let Ok((id, row_name, row_addr, row_origin, row_authname)) = res {
if !row_name.is_empty() { if !name.as_ref().is_empty() {
if (origin >= row_origin || row_name == row_authname) if !row_name.is_empty() {
&& name.as_ref() != row_name if (origin >= row_origin || row_name == row_authname)
{ && name.as_ref() != row_name
update_name = true; {
}
} else {
update_name = true; update_name = true;
} }
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname { } else {
update_authname = true;
}
} else if origin == Origin::ManuallyCreated && !row_authname.is_empty() {
// no name given on manual edit, this will update the name to the authname
update_name = true; update_name = true;
} }
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
} else if origin == Origin::ManuallyCreated && !row_authname.is_empty() {
// no name given on manual edit, this will update the name to the authname
update_name = true;
}
Ok((row_id, row_name, row_addr, row_origin, row_authname)) row_id = id as u32;
},
)
.await {
row_id = 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;
} }
@@ -435,9 +449,13 @@ impl Contact {
.sql .sql
.execute( .execute(
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
paramsv![ paramsx![
new_name, &new_name,
if update_addr { addr.to_string() } else { row_addr }, if update_addr {
addr.to_string()
} else {
row_addr
},
if origin > row_origin { if origin > row_origin {
origin origin
} else { } else {
@@ -448,7 +466,7 @@ impl Contact {
} else { } else {
row_authname row_authname
}, },
row_id row_id as i32
], ],
) )
.await .await
@@ -459,7 +477,7 @@ impl Contact {
// 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.
context.sql.execute( context.sql.execute(
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
paramsv![new_name, Chattype::Single, row_id] paramsx![&new_name, Chattype::Single, row_id as i32]
).await.ok(); ).await.ok();
} }
sth_modified = Modifier::Modified; sth_modified = Modifier::Modified;
@@ -473,20 +491,17 @@ impl Contact {
.sql .sql
.execute( .execute(
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
paramsv![ paramsx![
name.as_ref().to_string(), name.as_ref(),
addr, &addr,
origin, origin,
if update_authname { name.as_ref().to_string() } else { "".to_string() } if update_authname { name.as_ref() } else { "" }
], ],
) )
.await .await
.is_ok() .is_ok()
{ {
row_id = context row_id = context.sql.get_rowid("contacts", "addr", &addr).await?;
.sql
.get_rowid(context, "contacts", "addr", &addr)
.await?;
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 {
@@ -573,35 +588,32 @@ impl Contact {
.map(|s| s.as_ref().to_string()) .map(|s| s.as_ref().to_string())
.unwrap_or_default() .unwrap_or_default()
); );
context let pool = context.sql.get_pool().await?;
.sql let mut rows = sqlx::query_as(
.query_map( r#"
"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!=?
AND c.id>?2 \ AND c.id>?
AND c.origin>=?3 \ AND c.origin>=?
AND c.blocked=0 \ AND c.blocked=0
AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ AND (c.name LIKE ? OR c.addr LIKE ?)
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ AND (1=? OR LENGTH(ps.verified_key_fingerprint)!=0)
ORDER BY LOWER(c.name||c.addr),c.id;", ORDER BY LOWER(c.name||c.addr),c.id
paramsv![ "#,
self_addr, )
DC_CONTACT_ID_LAST_SPECIAL as i32, .bind(&self_addr)
Origin::IncomingReplyTo, .bind(DC_CONTACT_ID_LAST_SPECIAL as i32)
s3str_like_cmd, .bind(Origin::IncomingReplyTo)
s3str_like_cmd, .bind(&s3str_like_cmd)
if flag_verified_only { 0i32 } else { 1i32 }, .bind(&s3str_like_cmd)
], .bind(if flag_verified_only { 0i32 } else { 1i32 })
|row| row.get::<_, i32>(0), .fetch(&pool);
|ids| {
for id in ids { while let Some(id) = rows.next().await {
ret.push(id? as u32); let (id,): (i32,) = id?;
} ret.push(id as u32);
Ok(()) }
},
)
.await?;
let self_name = context let self_name = context
.get_config(Config::Displayname) .get_config(Config::Displayname)
@@ -622,17 +634,15 @@ impl Contact {
} else { } else {
add_self = true; add_self = true;
context.sql.query_map( let pool = context.sql.get_pool().await?;
"SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;", let mut rows = sqlx::query_as(
paramsv![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100], "SELECT id FROM contacts WHERE addr!=? AND id>? AND origin>=? AND blocked=0 ORDER BY LOWER(name || addr), id;"
|row| row.get::<_, i32>(0), ).bind(self_addr).bind(DC_CONTACT_ID_LAST_SPECIAL as i32).bind(0x100).fetch(&pool);
|ids| {
for id in ids { while let Some(id) = rows.next().await {
ret.push(id? as u32); let (id,): (i32,) = id?;
} ret.push(id as u32);
Ok(()) }
}
).await?;
} }
if flag_add_self && add_self { if flag_add_self && add_self {
@@ -643,32 +653,30 @@ impl Contact {
} }
pub async fn get_blocked_cnt(context: &Context) -> usize { pub async fn get_blocked_cnt(context: &Context) -> usize {
context let v: i32 = context
.sql .sql
.query_get_value::<isize>( .query_value(
context,
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
) )
.await .await
.unwrap_or_default() as usize .unwrap_or_default();
v as usize
} }
/// Get blocked contacts. /// Get blocked contacts.
pub async fn get_all_blocked(context: &Context) -> Vec<u32> { pub async fn get_all_blocked(context: &Context) -> Vec<u32> {
context context
.sql .sql
.query_map( .query_values(
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;", "SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;",
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
|row| row.get::<_, u32>(0),
|ids| {
ids.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
.into_iter()
.map(|id: i32| id as u32)
.collect()
} }
/// Returns a textual summary of the encryption state for the contact. /// Returns a textual summary of the encryption state for the contact.
@@ -683,9 +691,10 @@ impl Contact {
let peerstate = Peerstate::from_addr(context, &contact.addr).await; let peerstate = Peerstate::from_addr(context, &contact.addr).await;
let loginparam = LoginParam::from_database(context, "configured_").await; let loginparam = LoginParam::from_database(context, "configured_").await;
if peerstate.is_some() if peerstate.is_ok()
&& peerstate && peerstate
.as_ref() .as_ref()
.ok()
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified)) .and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
.is_some() .is_some()
{ {
@@ -754,10 +763,9 @@ impl Contact {
let count_contacts: i32 = context let count_contacts: i32 = context
.sql .sql
.query_get_value( .query_value(
context,
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
paramsv![contact_id as i32], paramsx![contact_id as i32],
) )
.await .await
.unwrap_or_default(); .unwrap_or_default();
@@ -765,10 +773,9 @@ impl Contact {
let count_msgs: i32 = if count_contacts > 0 { let count_msgs: i32 = if count_contacts > 0 {
context context
.sql .sql
.query_get_value( .query_value(
context,
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
paramsv![contact_id as i32, contact_id as i32], paramsx![contact_id as i32, contact_id as i32],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -781,7 +788,7 @@ impl Contact {
.sql .sql
.execute( .execute(
"DELETE FROM contacts WHERE id=?;", "DELETE FROM contacts WHERE id=?;",
paramsv![contact_id as i32], paramsx![contact_id as i32],
) )
.await .await
{ {
@@ -819,7 +826,7 @@ impl Contact {
.sql .sql
.execute( .execute(
"UPDATE contacts SET param=? WHERE id=?", "UPDATE contacts SET param=? WHERE id=?",
paramsv![self.param.to_string(), self.id as i32], paramsx![self.param.to_string(), self.id as i32],
) )
.await?; .await?;
Ok(()) Ok(())
@@ -927,7 +934,7 @@ impl Contact {
pub async fn is_verified_ex( pub async fn is_verified_ex(
&self, &self,
context: &Context, context: &Context,
peerstate: Option<&Peerstate<'_>>, peerstate: Option<&Peerstate>,
) -> VerifiedStatus { ) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key // We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device // on this device
@@ -942,7 +949,7 @@ impl Contact {
} }
let peerstate = Peerstate::from_addr(context, &self.addr).await; let peerstate = Peerstate::from_addr(context, &self.addr).await;
if let Some(ps) = peerstate { if let Ok(ps) = peerstate {
if ps.verified_key.is_some() { if ps.verified_key.is_some() {
return VerifiedStatus::BidirectVerified; return VerifiedStatus::BidirectVerified;
} }
@@ -977,15 +984,15 @@ impl Contact {
return 0; return 0;
} }
context let v: i32 = context
.sql .sql
.query_get_value::<isize>( .query_value(
context,
"SELECT COUNT(*) FROM contacts WHERE id>?;", "SELECT COUNT(*) FROM contacts WHERE id>?;",
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32], paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
) )
.await .await
.unwrap_or_default() as usize .unwrap_or_default();
v as usize
} }
pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
@@ -997,7 +1004,7 @@ impl Contact {
.sql .sql
.exists( .exists(
"SELECT id FROM contacts WHERE id=?;", "SELECT id FROM contacts WHERE id=?;",
paramsv![contact_id as i32], paramsx![contact_id as i32],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -1008,7 +1015,7 @@ impl Contact {
.sql .sql
.execute( .execute(
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;", "UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
paramsv![origin, contact_id as i32, origin], paramsx![origin, contact_id as i32, origin],
) )
.await .await
.is_ok() .is_ok()
@@ -1070,7 +1077,7 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
.sql .sql
.execute( .execute(
"UPDATE contacts SET blocked=? WHERE id=?;", "UPDATE contacts SET blocked=? WHERE id=?;",
paramsv![new_blocking as i32, contact_id as i32], paramsx![new_blocking as i32, contact_id as i32],
) )
.await .await
.is_ok() .is_ok()
@@ -1080,13 +1087,23 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
// (Maybe, beside normal chats (type=100) we should also block group chats with only this user. // (Maybe, beside normal chats (type=100) we should also block group chats with only this user.
// However, I'm not sure about this point; it may be confusing if the user wants to add other people; // However, I'm not sure about this point; it may be confusing if the user wants to add other people;
// this would result in recreating the same group...) // this would result in recreating the same group...)
if context.sql.execute( if context
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);", .sql
paramsv![new_blocking, 100, contact_id as i32], .execute(
).await.is_ok() { r#"
Contact::mark_noticed(context, contact_id).await; UPDATE chats
context.emit_event(Event::ContactsChanged(None)); SET blocked=?
} WHERE type=? AND id
IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);
"#,
paramsx![new_blocking, 100i32, contact_id as i32],
)
.await
.is_ok()
{
Contact::mark_noticed(context, contact_id).await;
context.emit_event(Event::ContactsChanged(None));
}
} }
} }
} }

View File

@@ -76,7 +76,11 @@ 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", rusqlite::version().to_string());
let version =
String::from_utf8(libsqlite3_sys::SQLITE_VERSION.to_vec()).expect("invalid version");
res.insert("sqlite_version", version);
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string()); res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
res.insert("level", "awesome".into()); res.insert("level", "awesome".into());
res res
@@ -85,8 +89,6 @@ pub fn get_info() -> BTreeMap<&'static str, String> {
impl Context { impl Context {
/// Creates new context. /// Creates new context.
pub async fn new(os_name: String, dbfile: PathBuf) -> Result<Context> { pub async fn new(os_name: String, dbfile: PathBuf) -> Result<Context> {
// pretty_env_logger::try_init_timed().ok();
let mut blob_fname = OsString::new(); let mut blob_fname = OsString::new();
blob_fname.push(dbfile.file_name().unwrap_or_default()); blob_fname.push(dbfile.file_name().unwrap_or_default());
blob_fname.push("-blobs"); blob_fname.push("-blobs");
@@ -127,10 +129,8 @@ impl Context {
let ctx = Context { let ctx = Context {
inner: Arc::new(inner), inner: Arc::new(inner),
}; };
ensure!(
ctx.sql.open(&ctx, &ctx.dbfile, false).await, ctx.sql.open(&ctx, &ctx.dbfile, false).await?;
"Failed opening sqlite database"
);
Ok(ctx) Ok(ctx)
} }
@@ -263,27 +263,29 @@ impl Context {
let is_configured = self.get_config_int(Config::Configured).await; let is_configured = self.get_config_int(Config::Configured).await;
let dbversion = self let dbversion = self
.sql .sql
.get_raw_config_int(self, "dbversion") .get_raw_config_int("dbversion")
.await .await
.unwrap_or_default(); .unwrap_or_default();
let journal_mode = self let journal_mode = self
.sql .sql
.query_get_value(self, "PRAGMA journal_mode;", paramsv![]) .query_value("PRAGMA journal_mode;", paramsx![])
.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;
let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await; let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await;
let bcc_self = self.get_config_int(Config::BccSelf).await; let bcc_self = self.get_config_int(Config::BccSelf).await;
let prv_key_cnt: Option<isize> = self let prv_key_cnt: Option<i32> = self
.sql .sql
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", paramsv![]) .query_value("SELECT COUNT(*) FROM keypairs;", paramsx![])
.await; .await
.ok();
let pub_key_cnt: Option<isize> = self let pub_key_cnt: Option<i32> = self
.sql .sql
.query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![]) .query_value("SELECT COUNT(*) FROM acpeerstates;", paramsx![])
.await; .await
.ok();
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(),
Err(err) => format!("<key failure: {}>", err), Err(err) => format!("<key failure: {}>", err),
@@ -295,7 +297,7 @@ impl Context {
let mvbox_move = self.get_config_int(Config::MvboxMove).await; let mvbox_move = self.get_config_int(Config::MvboxMove).await;
let folders_configured = self let folders_configured = self
.sql .sql
.get_raw_config_int(self, "folders_configured") .get_raw_config_int("folders_configured")
.await .await
.unwrap_or_default(); .unwrap_or_default();
@@ -356,30 +358,22 @@ impl Context {
pub async fn get_fresh_msgs(&self) -> Vec<MsgId> { pub async fn get_fresh_msgs(&self) -> Vec<MsgId> {
let show_deaddrop: i32 = 0; let show_deaddrop: i32 = 0;
self.sql self.sql
.query_map( .query_values(
concat!( r#"
"SELECT m.id", SELECT m.id
" FROM msgs m", FROM msgs m
" LEFT JOIN contacts ct", LEFT JOIN contacts ct
" ON m.from_id=ct.id", ON m.from_id=ct.id
" LEFT JOIN chats c", LEFT JOIN chats c
" ON m.chat_id=c.id", ON m.chat_id=c.id
" WHERE m.state=?", WHERE m.state=?
" AND m.hidden=0", AND m.hidden=0
" AND m.chat_id>?", AND m.chat_id>?
" AND ct.blocked=0", AND ct.blocked=0
" AND (c.blocked=0 OR c.blocked=?)", AND (c.blocked=0 OR c.blocked=?)
" ORDER BY m.timestamp DESC,m.id DESC;" ORDER BY m.timestamp DESC,m.id DESC;
), "#,
paramsv![10, 9, if 0 != show_deaddrop { 2 } else { 0 }], paramsx![10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|row| row.get::<_, MsgId>(0),
|rows| {
let mut ret = Vec::new();
for row in rows {
ret.push(row?);
}
Ok(ret)
},
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -395,47 +389,36 @@ impl Context {
let strLikeBeg = format!("{}%", real_query); let strLikeBeg = format!("{}%", real_query);
let query = if !chat_id.is_unset() { let query = if !chat_id.is_unset() {
concat!( r#"
"SELECT m.id AS id, m.timestamp AS timestamp", SELECT m.id
" FROM msgs m", FROM msgs
" LEFT JOIN contacts ct", LEFT JOIN contacts ct
" ON m.from_id=ct.id", ON m.from_id=ct.id
" WHERE m.chat_id=?", WHERE m.chat_id=?
" AND m.hidden=0", AND m.hidden=0
" AND ct.blocked=0", AND ct.blocked=0
" AND (txt LIKE ? OR ct.name LIKE ?)", AND (txt LIKE ? OR ct.name LIKE ?)
" ORDER BY m.timestamp,m.id;" ORDER BY m.timestamp,m.id;
) "#
} else { } else {
concat!( r#"
"SELECT m.id AS id, m.timestamp AS timestamp", SELECT m.id
" FROM msgs m", FROM msgs m
" LEFT JOIN contacts ct", LEFT JOIN contacts ct
" ON m.from_id=ct.id", ON m.from_id=ct.id
" LEFT JOIN chats c", LEFT JOIN chats c
" ON m.chat_id=c.id", ON m.chat_id=c.id
" WHERE m.chat_id>9", WHERE m.chat_id>9
" AND m.hidden=0", AND m.hidden=0
" AND (c.blocked=0 OR c.blocked=?)", AND (c.blocked=0 OR c.blocked=?)
" AND ct.blocked=0", AND ct.blocked=0
" AND (m.txt LIKE ? OR ct.name LIKE ?)", AND (m.txt LIKE ? OR ct.name LIKE ?)
" ORDER BY m.timestamp DESC,m.id DESC;" ORDER BY m.timestamp DESC,m.id DESC;
) "#
}; };
self.sql self.sql
.query_map( .query_values(query, paramsx![chat_id, strLikeInText, strLikeBeg])
query,
paramsv![chat_id, strLikeInText, strLikeBeg],
|row| row.get::<_, MsgId>("id"),
|rows| {
let mut ret = Vec::new();
for id in rows {
ret.push(id?);
}
Ok(ret)
},
)
.await .await
.unwrap_or_default() .unwrap_or_default()
} }

View File

@@ -1,9 +1,9 @@
use async_std::prelude::*;
use itertools::join; use itertools::join;
use mailparse::SingleInfo;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use mailparse::SingleInfo;
use crate::chat::{self, Chat, ChatId}; use crate::chat::{self, Chat, ChatId};
use crate::config::Config; use crate::config::Config;
use crate::constants::*; use crate::constants::*;
@@ -652,9 +652,6 @@ async fn add_parts(
let icnt = mime_parser.parts.len(); let icnt = mime_parser.parts.len();
let subject = mime_parser.get_subject().unwrap_or_default(); let subject = mime_parser.get_subject().unwrap_or_default();
let mut parts = std::mem::replace(&mut mime_parser.parts, Vec::new());
let server_folder = server_folder.as_ref().to_string();
let location_kml_is = mime_parser.location_kml.is_some(); let location_kml_is = mime_parser.location_kml.is_some();
let is_system_message = mime_parser.is_system_message; let is_system_message = mime_parser.is_system_message;
let mime_headers = if save_mime_headers { let mime_headers = if save_mime_headers {
@@ -663,51 +660,43 @@ async fn add_parts(
None None
}; };
let sent_timestamp = *sent_timestamp; let sent_timestamp = *sent_timestamp;
let is_hidden = *hidden;
let chat_id = *chat_id; let chat_id = *chat_id;
let is_mdn = !mime_parser.mdn_reports.is_empty(); let is_mdn = !mime_parser.mdn_reports.is_empty();
// TODO: can this clone be avoided? for part in &mut mime_parser.parts {
let rfc724_mid = rfc724_mid.to_string(); let mut txt_raw = "".to_string();
let (new_parts, ids, is_hidden) = context let is_location_kml =
.sql location_kml_is && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty());
.with_conn(move |mut conn| {
let mut ids = Vec::with_capacity(parts.len());
let mut is_hidden = is_hidden;
for part in &mut parts { if is_mdn || is_location_kml {
let mut txt_raw = "".to_string(); *hidden = true;
let mut stmt = conn.prepare_cached( if state == MessageState::InFresh {
"INSERT INTO msgs \ state = MessageState::InNoticed;
(rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ }
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \ }
bytes, hidden, mime_headers, mime_in_reply_to, mime_references, error) \
VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?, ?);",
)?;
let is_location_kml = location_kml_is if part.typ == Viewtype::Text {
&& icnt == 1 let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default();
&& (part.msg == "-location-" || part.msg.is_empty()); txt_raw = format!("{}\n\n{}", subject, msg_raw);
}
if is_system_message != SystemMessage::Unknown {
part.param.set_int(Param::Cmd, is_system_message as i32);
}
if is_mdn || is_location_kml { context
is_hidden = true; .sql
if state == MessageState::InFresh { .execute(
state = MessageState::InNoticed; r#"
} INSERT INTO msgs (
} rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp,
timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param,
if part.typ == Viewtype::Text { bytes, hidden, mime_headers, mime_in_reply_to, mime_references)
let msg_raw = part.msg_raw.as_ref().cloned().unwrap_or_default(); VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
txt_raw = format!("{}\n\n{}", subject, msg_raw); "#,
} paramsx![
if is_system_message != SystemMessage::Unknown { rfc724_mid.to_owned(),
part.param.set_int(Param::Cmd, is_system_message as i32); server_folder.as_ref().to_owned(),
}
stmt.execute(paramsv![
rfc724_mid,
server_folder,
server_uid as i32, server_uid as i32,
chat_id, chat_id,
from_id as i32, from_id as i32,
@@ -718,38 +707,30 @@ async fn add_parts(
part.typ, part.typ,
state, state,
is_dc_message, is_dc_message,
part.msg, part.msg.clone(),
// txt_raw might contain invalid utf8 // txt_raw might contain invalid utf8
txt_raw, txt_raw,
part.param.to_string(), part.param.to_string(),
part.bytes as isize, part.bytes as i64,
is_hidden, *hidden,
mime_headers, mime_headers.clone(),
mime_in_reply_to, mime_in_reply_to.clone(),
mime_references, mime_references.clone(),
part.error, ],
])?; )
.await?;
drop(stmt); let msg_id = MsgId::new(
ids.push(MsgId::new(crate::sql::get_rowid( context
&mut conn, .sql
"msgs", .get_rowid("msgs", "rfc724_mid", &rfc724_mid)
"rfc724_mid", .await?,
&rfc724_mid, );
)?));
}
Ok((parts, ids, is_hidden))
})
.await?;
if let Some(id) = ids.iter().last() { *insert_msg_id = msg_id;
*insert_msg_id = *id; created_db_entries.push((chat_id, msg_id));
} }
*hidden = is_hidden;
created_db_entries.extend(ids.iter().map(|id| (chat_id, *id)));
mime_parser.parts = new_parts;
info!( 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,
@@ -867,18 +848,17 @@ async fn calc_sort_timestamp(
// get newest non fresh message for this chat // get newest non fresh message for this chat
// update sort_timestamp if less than that // update sort_timestamp if less than that
if is_fresh_msg { if is_fresh_msg {
let last_msg_time: Option<i64> = context let last_msg_time: Result<i32, _> = context
.sql .sql
.query_get_value( .query_value(
context,
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?", "SELECT MAX(timestamp) FROM msgs WHERE chat_id=? AND state>?",
paramsv![chat_id, MessageState::InFresh], paramsx![chat_id, MessageState::InFresh],
) )
.await; .await;
if let Some(last_msg_time) = last_msg_time { if let Ok(last_msg_time) = last_msg_time {
if last_msg_time > sort_timestamp { if last_msg_time as i64 > sort_timestamp {
sort_timestamp = last_msg_time; sort_timestamp = last_msg_time as i64;
} }
} }
} }
@@ -1155,7 +1135,7 @@ async fn create_or_lookup_group(
.sql .sql
.execute( .execute(
"UPDATE chats SET name=? WHERE id=?;", "UPDATE chats SET name=? WHERE id=?;",
paramsv![grpname.to_string(), chat_id], paramsx![grpname, chat_id],
) )
.await .await
.is_ok() .is_ok()
@@ -1191,7 +1171,7 @@ async fn create_or_lookup_group(
.sql .sql
.execute( .execute(
"DELETE FROM chats_contacts WHERE chat_id=?;", "DELETE FROM chats_contacts WHERE chat_id=?;",
paramsv![chat_id], paramsx![chat_id],
) )
.await .await
.ok(); .ok();
@@ -1273,10 +1253,10 @@ async fn create_or_lookup_adhoc_group(
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids).await?; let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids).await?;
if !chat_ids.is_empty() { if !chat_ids.is_empty() {
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ","); let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
let res = context let res: Result<(ChatId, Option<Blocked>), _> = context
.sql .sql
.query_row( .query_row(
format!( &format!(
"SELECT c.id, "SELECT c.id,
c.blocked c.blocked
FROM chats c FROM chats c
@@ -1288,19 +1268,13 @@ async fn create_or_lookup_adhoc_group(
LIMIT 1;", LIMIT 1;",
chat_ids_str chat_ids_str
), ),
paramsv![], paramsx![],
|row| {
Ok((
row.get::<_, ChatId>(0)?,
row.get::<_, Option<Blocked>>(1)?.unwrap_or_default(),
))
},
) )
.await; .await;
if let Ok((id, id_blocked)) = res { if let Ok((id, id_blocked)) = res {
/* success, chat found */ /* success, chat found */
return Ok((id, id_blocked)); return Ok((id, id_blocked.unwrap_or_default()));
} }
} }
@@ -1332,7 +1306,7 @@ async fn create_or_lookup_adhoc_group(
// create a new ad-hoc group // create a new ad-hoc group
// - there is no need to check if this group exists; otherwise we would have caught it above // - there is no need to check if this group exists; otherwise we would have caught it above
let grpid = create_adhoc_grp_id(context, &member_ids).await; let grpid = create_adhoc_grp_id(context, &member_ids).await?;
if grpid.is_empty() { if grpid.is_empty() {
warn!( warn!(
context, context,
@@ -1372,7 +1346,7 @@ async fn create_group_record(
) -> ChatId { ) -> ChatId {
if context.sql.execute( if context.sql.execute(
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);",
paramsv![ paramsx![
if VerifiedStatus::Unverified != create_verified { if VerifiedStatus::Unverified != create_verified {
Chattype::VerifiedGroup Chattype::VerifiedGroup
} else { } else {
@@ -1381,7 +1355,7 @@ async fn create_group_record(
grpname.as_ref(), grpname.as_ref(),
grpid.as_ref(), grpid.as_ref(),
create_blocked, create_blocked,
time(), time()
], ],
).await ).await
.is_err() .is_err()
@@ -1396,7 +1370,7 @@ async fn create_group_record(
} }
let row_id = context let row_id = context
.sql .sql
.get_rowid(context, "chats", "grpid", grpid.as_ref()) .get_rowid("chats", "grpid", grpid.as_ref())
.await .await
.unwrap_or_default(); .unwrap_or_default();
@@ -1411,7 +1385,7 @@ async fn create_group_record(
chat_id chat_id
} }
async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String { async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> Result<String> {
/* algorithm: /* algorithm:
- sort normalized, lowercased, e-mail addresses alphabetically - sort normalized, lowercased, e-mail addresses alphabetically
- put all e-mail addresses into a single string, separate the address by a single comma - put all e-mail addresses into a single string, separate the address by a single comma
@@ -1425,30 +1399,20 @@ async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
.unwrap_or_else(|| "no-self".to_string()) .unwrap_or_else(|| "no-self".to_string())
.to_lowercase(); .to_lowercase();
let members = context let query = format!(
.sql "SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
.query_map( member_ids_str
format!( );
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF let mut addrs: Vec<String> = context.sql.query_values(&query, paramsx![]).await?;
member_ids_str addrs.sort();
),
paramsv![],
|row| row.get::<_, String>(0),
|rows| {
let mut addrs = rows.collect::<std::result::Result<Vec<_>, _>>()?;
addrs.sort();
let mut acc = member_cs.clone();
for addr in &addrs {
acc += ",";
acc += &addr.to_lowercase();
}
Ok(acc)
},
)
.await
.unwrap_or_else(|_| member_cs);
hex_hash(&members) let mut acc = member_cs;
for addr in &addrs {
acc += ",";
acc += &addr.to_lowercase();
}
Ok(hex_hash(&acc))
} }
fn hex_hash(s: impl AsRef<str>) -> String { fn hex_hash(s: impl AsRef<str>) -> String {
@@ -1475,8 +1439,7 @@ async fn search_chat_ids_by_contact_ids(
if !contact_ids.is_empty() { if !contact_ids.is_empty() {
contact_ids.sort(); contact_ids.sort();
let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ","); let contact_ids_str = join(contact_ids.iter().map(|x| x.to_string()), ",");
context.sql.query_map( let query = format!(
format!(
"SELECT DISTINCT cc.chat_id, cc.contact_id "SELECT DISTINCT cc.chat_id, cc.contact_id
FROM chats_contacts cc FROM chats_contacts cc
LEFT JOIN chats c ON c.id=cc.chat_id LEFT JOIN chats c ON c.id=cc.chat_id
@@ -1485,37 +1448,37 @@ async fn search_chat_ids_by_contact_ids(
AND cc.contact_id!=1 AND cc.contact_id!=1
ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
contact_ids_str contact_ids_str
), );
paramsv![],
|row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)),
|rows| {
let mut last_chat_id = ChatId::new(0);
let mut matches = 0;
let mut mismatches = 0;
for row in rows { let pool = context.sql.get_pool().await?;
let (chat_id, contact_id) = row?; let mut rows = sqlx::query_as(&query).fetch(&pool);
if chat_id != last_chat_id {
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
last_chat_id = chat_id;
matches = 0;
mismatches = 0;
}
if matches < contact_ids.len() && contact_id == contact_ids[matches] {
matches += 1;
} else {
mismatches += 1;
}
}
let mut last_chat_id = ChatId::new(0);
let mut matches = 0;
let mut mismatches = 0;
while let Some(row) = rows.next().await {
let (chat_id, contact_id): (ChatId, i32) = row?;
let contact_id = contact_id as u32;
if chat_id != last_chat_id {
if matches == contact_ids.len() && mismatches == 0 { if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id); chat_ids.push(last_chat_id);
} }
Ok(()) last_chat_id = chat_id;
matches = 0;
mismatches = 0;
} }
).await?; if matches < contact_ids.len() && contact_id == contact_ids[matches] {
matches += 1;
} else {
mismatches += 1;
}
}
if matches == contact_ids.len() && mismatches == 0 {
chat_ids.push(last_chat_id);
}
} }
} }
@@ -1539,8 +1502,10 @@ async fn check_verified_properties(
if from_id != DC_CONTACT_ID_SELF { if from_id != DC_CONTACT_ID_SELF {
let peerstate = Peerstate::from_addr(context, contact.get_addr()).await; let peerstate = Peerstate::from_addr(context, contact.get_addr()).await;
if peerstate.is_none() if peerstate.is_err()
|| contact.is_verified_ex(context, peerstate.as_ref()).await || contact
.is_verified_ex(context, peerstate.as_ref().ok())
.await
!= VerifiedStatus::BidirectVerified != VerifiedStatus::BidirectVerified
{ {
bail!( bail!(
@@ -1549,7 +1514,7 @@ async fn check_verified_properties(
); );
} }
if let Some(peerstate) = peerstate { if let Ok(peerstate) = peerstate {
ensure!( ensure!(
peerstate.has_verified_key(&mimeparser.signatures), peerstate.has_verified_key(&mimeparser.signatures),
"The message was sent with non-verified encryption." "The message was sent with non-verified encryption."
@@ -1566,36 +1531,30 @@ async fn check_verified_properties(
} }
let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ","); let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ",");
let rows = context let query = format!(
.sql "SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
.query_map(
format!(
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ", LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
to_ids_str to_ids_str
), );
paramsv![],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))), let pool = context.sql.get_pool().await?;
|rows| { let mut rows = sqlx::query_as(&query).fetch(&pool);
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into) while let Some(row) = rows.next().await {
}, let (to_addr, is_verified): (String, i32) = row?;
)
.await?;
for (to_addr, _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 mut is_verified = _is_verified != 0; let mut is_verified = is_verified != 0;
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
if mimeparser.gossipped_addr.contains(&to_addr) { if mimeparser.gossipped_addr.contains(&to_addr) {
if let Some(mut peerstate) = peerstate { if let Ok(mut peerstate) = peerstate {
// if we're here, we know the gossip key is verified: // if we're here, we know the gossip key is verified:
// - use the gossip-key as verified-key if there is no verified-key // - use the gossip-key as verified-key if there is no verified-key
// - OR if the verified-key does not match public-key or gossip-key // - OR if the verified-key does not match public-key or gossip-key
@@ -1685,7 +1644,7 @@ async fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
LEFT JOIN chats c ON m.chat_id=c.id \ LEFT JOIN chats c ON m.chat_id=c.id \
WHERE m.rfc724_mid=? \ WHERE m.rfc724_mid=? \
AND m.chat_id>9 AND c.blocked=0;", AND m.chat_id>9 AND c.blocked=0;",
paramsv![rfc724_mid], paramsx![rfc724_mid],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -1731,7 +1690,7 @@ async fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
.sql .sql
.exists( .exists(
"SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;", "SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;",
paramsv![rfc724_mid], paramsx![rfc724_mid],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -2002,14 +1961,32 @@ mod tests {
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert_eq!(chat.typ, Chattype::Single); assert_eq!(chat.typ, Chattype::Single);
assert_eq!(chat.name, "Bob"); assert_eq!(chat.name, "Bob");
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 1); assert_eq!(
assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 1); chat::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
1
);
assert_eq!(
chat::get_chat_msgs(&t.ctx, chat_id, 0, None)
.await
.unwrap()
.len(),
1
);
// receive a non-delta-message from Bob, shows up because of the show_emails setting // receive a non-delta-message from Bob, shows up because of the show_emails setting
dc_receive_imf(&t.ctx, ONETOONE_NOREPLY_MAIL, "INBOX", 2, false) dc_receive_imf(&t.ctx, ONETOONE_NOREPLY_MAIL, "INBOX", 2, false)
.await .await
.unwrap(); .unwrap();
assert_eq!(chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.len(), 2); assert_eq!(
chat::get_chat_msgs(&t.ctx, chat_id, 0, None)
.await
.unwrap()
.len(),
2
);
// let Bob create an adhoc-group by a non-delta-message, shows up because of the show_emails setting // let Bob create an adhoc-group by a non-delta-message, shows up because of the show_emails setting
dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 3, false) dc_receive_imf(&t.ctx, GRP_MAIL, "INBOX", 3, false)
@@ -2023,7 +2000,13 @@ mod tests {
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert_eq!(chat.typ, Chattype::Group); assert_eq!(chat.typ, Chattype::Group);
assert_eq!(chat.name, "group with Alice, Bob and Claire"); assert_eq!(chat.name, "group with Alice, Bob and Claire");
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3); assert_eq!(
chat::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
3
);
} }
#[async_std::test] #[async_std::test]
@@ -2047,7 +2030,13 @@ mod tests {
let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); let chat = chat::Chat::load_from_db(&t.ctx, chat_id).await.unwrap();
assert_eq!(chat.typ, Chattype::Group); assert_eq!(chat.typ, Chattype::Group);
assert_eq!(chat.name, "group with Alice, Bob and Claire"); assert_eq!(chat.name, "group with Alice, Bob and Claire");
assert_eq!(chat::get_chat_contacts(&t.ctx, chat_id).await.len(), 3); assert_eq!(
chat::get_chat_contacts(&t.ctx, chat_id)
.await
.unwrap()
.len(),
3
);
} }
#[async_std::test] #[async_std::test]
@@ -2073,7 +2062,10 @@ mod tests {
.unwrap(); .unwrap();
chat::add_contact_to_chat(&t.ctx, group_id, bob_id).await; chat::add_contact_to_chat(&t.ctx, group_id, bob_id).await;
assert_eq!( assert_eq!(
chat::get_chat_msgs(&t.ctx, group_id, 0, None).await.len(), chat::get_chat_msgs(&t.ctx, group_id, 0, None)
.await
.unwrap()
.len(),
0 0
); );
group_id group_id
@@ -2116,7 +2108,9 @@ mod tests {
) )
.await .await
.unwrap(); .unwrap();
let msgs = chat::get_chat_msgs(&t.ctx, group_id, 0, None).await; let msgs = chat::get_chat_msgs(&t.ctx, group_id, 0, None)
.await
.unwrap();
assert_eq!(msgs.len(), 1); assert_eq!(msgs.len(), 1);
let msg_id = msgs.first().unwrap(); let msg_id = msgs.first().unwrap();
let msg = message::Message::load_from_db(&t.ctx, msg_id.clone()) let msg = message::Message::load_from_db(&t.ctx, msg_id.clone())
@@ -2167,7 +2161,10 @@ mod tests {
) )
.await.unwrap(); .await.unwrap();
assert_eq!( assert_eq!(
chat::get_chat_msgs(&t.ctx, group_id, 0, None).await.len(), chat::get_chat_msgs(&t.ctx, group_id, 0, None)
.await
.unwrap()
.len(),
1 1
); );
let msg = message::Message::load_from_db(&t.ctx, msg_id.clone()) let msg = message::Message::load_from_db(&t.ctx, msg_id.clone())
@@ -2251,7 +2248,7 @@ mod tests {
.get_authname(), .get_authname(),
"Имя, Фамилия", "Имя, Фамилия",
); );
let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await; let msgs = chat::get_chat_msgs(&t.ctx, chat_id, 0, None).await.unwrap();
assert_eq!(msgs.len(), 1); assert_eq!(msgs.len(), 1);
let msg_id = msgs.first().unwrap(); let msg_id = msgs.first().unwrap();
let msg = message::Message::load_from_db(&t.ctx, msg_id.clone()) let msg = message::Message::load_from_db(&t.ctx, msg_id.clone())
@@ -2515,7 +2512,9 @@ mod tests {
assert_eq!(msg.state, MessageState::OutFailed); assert_eq!(msg.state, MessageState::OutFailed);
let msgs = chat::get_chat_msgs(&t.ctx, msg.chat_id, 0, None).await; let msgs = chat::get_chat_msgs(&t.ctx, msg.chat_id, 0, None)
.await
.unwrap();
let last_msg = Message::load_from_db(&t.ctx, *msgs.last().unwrap()) let last_msg = Message::load_from_db(&t.ctx, *msgs.last().unwrap())
.await .await
.unwrap(); .unwrap();

View File

@@ -568,14 +568,6 @@ 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)
}
}
/// Utility to check if a in the binary represantion of listflags /// Utility to check if a in the binary represantion of listflags
/// the bit at position bitindex is 1. /// the bit at position bitindex is 1.
pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool { pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool {

View File

@@ -91,7 +91,7 @@ impl EncryptHelper {
context: &Context, context: &Context,
min_verified: PeerstateVerifiedStatus, min_verified: PeerstateVerifiedStatus,
mail_to_encrypt: lettre_email::PartBuilder, mail_to_encrypt: lettre_email::PartBuilder,
peerstates: Vec<(Option<Peerstate<'_>>, &str)>, peerstates: Vec<(Option<Peerstate>, &str)>,
) -> Result<String> { ) -> Result<String> {
let mut keyring: Keyring<SignedPublicKey> = Keyring::new(); let mut keyring: Keyring<SignedPublicKey> = Keyring::new();
@@ -132,7 +132,7 @@ pub async fn try_decrypt(
let autocryptheader = Aheader::from_headers(context, &from, &mail.headers); let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
if message_time > 0 { if message_time > 0 {
peerstate = Peerstate::from_addr(context, &from).await; peerstate = Peerstate::from_addr(context, &from).await.ok();
if let Some(ref mut peerstate) = peerstate { if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader { if let Some(ref header) = autocryptheader {
@@ -143,7 +143,7 @@ pub async fn try_decrypt(
peerstate.save_to_db(&context.sql, false).await?; peerstate.save_to_db(&context.sql, false).await?;
} }
} else if let Some(ref header) = autocryptheader { } else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time); let p = Peerstate::from_header(header, message_time);
p.save_to_db(&context.sql, true).await?; p.save_to_db(&context.sql, true).await?;
peerstate = Some(p); peerstate = Some(p);
} }
@@ -155,7 +155,7 @@ pub async fn try_decrypt(
let mut signatures = HashSet::default(); let mut signatures = HashSet::default();
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &from).await; peerstate = Peerstate::from_addr(&context, &from).await.ok();
} }
if let Some(peerstate) = peerstate { if let Some(peerstate) = peerstate {
if peerstate.degrade_event.is_some() { if peerstate.degrade_event.is_some() {

View File

@@ -473,7 +473,7 @@ impl Imap {
folder: S, folder: S,
) -> (u32, u32) { ) -> (u32, u32) {
let key = format!("imap.mailbox.{}", folder.as_ref()); let key = format!("imap.mailbox.{}", folder.as_ref());
if let Some(entry) = context.sql.get_raw_config(context, &key).await { if let Some(entry) = context.sql.get_raw_config(&key).await {
// the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>` // the entry has the format `imap.mailbox.<folder>=<uidvalidity>:<lastseenuid>`
let mut parts = entry.split(':'); let mut parts = entry.split(':');
( (
@@ -1129,10 +1129,7 @@ impl Imap {
context: &Context, context: &Context,
create_mvbox: bool, create_mvbox: bool,
) -> Result<()> { ) -> Result<()> {
let folders_configured = context let folders_configured = context.sql.get_raw_config_int("folders_configured").await;
.sql
.get_raw_config_int(context, "folders_configured")
.await;
if folders_configured.unwrap_or_default() >= DC_FOLDERS_CONFIGURED_VERSION { if folders_configured.unwrap_or_default() >= DC_FOLDERS_CONFIGURED_VERSION {
return Ok(()); return Ok(());
} }
@@ -1298,7 +1295,7 @@ impl Imap {
.sql .sql
.execute( .execute(
"UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?", "UPDATE msgs SET server_folder='',server_uid=0 WHERE server_folder=?",
paramsv![folder], paramsx![folder],
) )
.await .await
{ {

View File

@@ -96,21 +96,21 @@ pub async fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result
let name = name.to_string_lossy(); let name = name.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") { if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new(); let sql = Sql::new();
if sql.open(context, &path, true).await { sql.open(context, &path, true).await?;
let curr_backup_time = sql let curr_backup_time = sql
.get_raw_config_int(context, "backup_time") .get_raw_config_int("backup_time")
.await .await
.unwrap_or_default(); .unwrap_or_default();
if curr_backup_time > newest_backup_time { if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path); newest_backup_path = Some(path);
newest_backup_time = curr_backup_time; newest_backup_time = curr_backup_time;
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close().await;
} }
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close().await;
} }
} }
} }
match newest_backup_path { match newest_backup_path {
Some(path) => Ok(path.to_string_lossy().into_owned()), Some(path) => Ok(path.to_string_lossy().into_owned()),
None => bail!("no backup found in {}", dir_name.display()), None => bail!("no backup found in {}", dir_name.display()),
@@ -245,7 +245,7 @@ pub fn create_setup_code(_context: &Context) -> String {
} }
async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> {
if !context.sql.get_raw_config_bool(context, "bcc_self").await { if !context.sql.get_raw_config_bool("bcc_self").await {
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
// TODO: define this as a stockstring once the wording is settled. // TODO: define this as a stockstring once the wording is settled.
msg.text = Some( msg.text = Some(
@@ -396,6 +396,7 @@ async fn imex_inner(
Ok(()) Ok(())
} }
Err(err) => { Err(err) => {
error!(context, "IMEX FAILED: {}", err);
context.emit_event(Event::ImexProgress(0)); context.emit_event(Event::ImexProgress(0));
bail!("IMEX FAILED to complete: {}", err); bail!("IMEX FAILED to complete: {}", err);
} }
@@ -428,51 +429,35 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
); );
/* error already logged */ /* error already logged */
/* re-open copied database file */ /* re-open copied database file */
ensure!( context
context .sql
.sql .open(&context, &context.get_dbfile(), false)
.open(&context, &context.get_dbfile(), false) .await?;
.await,
"could not re-open db"
);
delete_and_reset_all_device_msgs(&context).await?; delete_and_reset_all_device_msgs(&context).await?;
let total_files_cnt = context let total_files_cnt: i32 = context
.sql .sql
.query_get_value::<isize>(context, "SELECT COUNT(*) FROM backup_blobs;", paramsv![]) .query_value("SELECT COUNT(*) FROM backup_blobs;", paramsx![])
.await .await
.unwrap_or_default() as usize; .unwrap_or_default();
let total_files_cnt = total_files_cnt as usize;
info!( info!(
context, context,
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
); );
let files = context let pool = context.sql.get_pool().await?;
.sql let mut files = sqlx::query_as("SELECT file_name, file_content FROM backup_blobs ORDER BY id;")
.query_map( .fetch(&pool);
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
paramsv![],
|row| {
let name: String = row.get(0)?;
let blob: Vec<u8> = row.get(1)?;
Ok((name, blob)) let mut processed_files_cnt = 0;
}, while let Some(files_result) = files.next().await {
|files| { let (file_name, file_blob): (String, Vec<u8>) = files_result?;
files
.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await?;
let mut all_files_extracted = true;
for (processed_files_cnt, (file_name, file_blob)) in files.into_iter().enumerate() {
if context.shall_stop_ongoing().await { if context.shall_stop_ongoing().await {
all_files_extracted = false;
break; break;
} }
let mut permille = processed_files_cnt * 1000 / total_files_cnt; let mut permille = processed_files_cnt * 1000 / total_files_cnt;
if permille < 10 { if permille < 10 {
permille = 10 permille = 10
@@ -487,19 +472,22 @@ async fn import_backup(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?;
processed_files_cnt += 1;
} }
if all_files_extracted { ensure!(
// only delete backup_blobs if all files were successfully extracted processed_files_cnt == total_files_cnt,
context "received stop signal"
.sql );
.execute("DROP TABLE backup_blobs;", paramsv![])
.await?; context
context.sql.execute("VACUUM;", paramsv![]).await.ok(); .sql
Ok(()) .execute("DROP TABLE backup_blobs;", paramsx![])
} else { .await?;
bail!("received stop signal"); context.sql.execute("VACUUM;", paramsx![]).await?;
}
Ok(())
} }
/******************************************************************************* /*******************************************************************************
@@ -515,9 +503,9 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let dest_path_filename = dc_get_next_backup_path(dir, now).await?; let dest_path_filename = dc_get_next_backup_path(dir, now).await?;
let dest_path_string = dest_path_filename.to_string_lossy().to_string(); let dest_path_string = dest_path_filename.to_string_lossy().to_string();
sql::housekeeping(context).await; sql::housekeeping(context).await?;
context.sql.execute("VACUUM;", paramsv![]).await.ok(); context.sql.execute("VACUUM;", paramsx![]).await.ok();
// we close the database during the copy of the dbfile // we close the database during the copy of the dbfile
context.sql.close().await; context.sql.close().await;
@@ -531,7 +519,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
context context
.sql .sql
.open(&context, &context.get_dbfile(), false) .open(&context, &context.get_dbfile(), false)
.await; .await?;
if !copied { if !copied {
bail!( bail!(
@@ -541,17 +529,9 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
); );
} }
let dest_sql = Sql::new(); let dest_sql = Sql::new();
ensure!( dest_sql.open(context, &dest_path_filename, false).await?;
dest_sql.open(context, &dest_path_filename, false).await,
"could not open exported database {}",
dest_path_string
);
let res = match add_files_to_export(context, &dest_sql).await { let res = match add_files_to_export(context, &dest_sql).await {
Err(err) => {
dc_delete_file(context, &dest_path_filename).await;
error!(context, "backup failed: {}", err);
Err(err)
}
Ok(()) => { Ok(()) => {
dest_sql dest_sql
.set_raw_config_int(context, "backup_time", now as i32) .set_raw_config_int(context, "backup_time", now as i32)
@@ -559,6 +539,11 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
context.emit_event(Event::ImexFileWritten(dest_path_filename)); context.emit_event(Event::ImexFileWritten(dest_path_filename));
Ok(()) Ok(())
} }
Err(err) => {
dc_delete_file(context, &dest_path_filename).await;
error!(context, "backup failed: {}", err);
Err(err)
}
}; };
dest_sql.close().await; dest_sql.close().await;
@@ -571,7 +556,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
if !sql.table_exists("backup_blobs").await? { if !sql.table_exists("backup_blobs").await? {
sql.execute( sql.execute(
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
paramsv![], paramsx![],
) )
.await?; .await?;
} }
@@ -583,41 +568,38 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
sql.with_conn_async(|conn| async move { // scan directory, pass 2: copy files
// scan directory, pass 2: copy files let mut dir_handle = async_std::fs::read_dir(&dir).await?;
let mut dir_handle = async_std::fs::read_dir(&dir).await?;
let mut processed_files_cnt = 0; let mut processed_files_cnt = 0;
while let Some(entry) = dir_handle.next().await { while let Some(entry) = dir_handle.next().await {
let entry = entry?; let entry = entry?;
if context.shall_stop_ongoing().await { if context.shall_stop_ongoing().await {
return Ok(()); return Ok(());
} }
processed_files_cnt += 1; processed_files_cnt += 1;
let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10);
context.emit_event(Event::ImexProgress(permille)); context.emit_event(Event::ImexProgress(permille));
let name_f = entry.file_name(); let name_f = entry.file_name();
let name = name_f.to_string_lossy(); let name = name_f.to_string_lossy();
if name.starts_with("delta-chat") && name.ends_with(".bak") { if name.starts_with("delta-chat") && name.ends_with(".bak") {
continue;
}
info!(context, "EXPORT: copying filename={}", name);
let curr_path_filename = context.get_blobdir().join(entry.file_name());
if let Ok(buf) = dc_read_file(context, &curr_path_filename).await {
if buf.is_empty() {
continue; continue;
} }
info!(context, "EXPORT: copying filename={}", name); // bail out if we can't insert
let curr_path_filename = context.get_blobdir().join(entry.file_name()); sql.execute(
if let Ok(buf) = dc_read_file(context, &curr_path_filename).await { "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
if buf.is_empty() { paramsx![name.as_ref(), buf],
continue; )
} .await?;
// bail out if we can't insert
let mut stmt = conn.prepare_cached(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
)?;
stmt.execute(paramsv![name, buf])?;
}
} }
Ok(()) }
})
.await?;
Ok(()) Ok(())
} }
@@ -680,30 +662,19 @@ 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 keys = context let pool = context.sql.get_pool().await?;
.sql
.query_map(
"SELECT id, public_key, private_key, is_default FROM keypairs;",
paramsv![],
|row| {
let id = row.get(0)?;
let public_key_blob: Vec<u8> = row.get(1)?;
let public_key = SignedPublicKey::from_slice(&public_key_blob);
let private_key_blob: Vec<u8> = row.get(2)?;
let private_key = SignedSecretKey::from_slice(&private_key_blob);
let is_default: i32 = row.get(3)?;
Ok((id, public_key, private_key, is_default)) let mut keys = sqlx::query_as("SELECT id, public_key, private_key, is_default FROM keypairs;")
}, .fetch(&pool);
|keys| {
keys.collect::<std::result::Result<Vec<_>, _>>() while let Some(keys_result) = keys.next().await {
.map_err(Into::into) let (id, public_key_blob, private_key_blob, is_default): (i64, Vec<u8>, Vec<u8>, i32) =
}, keys_result?;
) let public_key = SignedPublicKey::from_slice(&public_key_blob);
.await?; let private_key = SignedSecretKey::from_slice(&private_key_blob);
for (id, public_key, private_key, is_default) in keys {
let id = Some(id).filter(|_| is_default != 0); 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)
.await .await
@@ -727,6 +698,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
} }
ensure!(export_errors == 0, "errors while exporting keys"); ensure!(export_errors == 0, "errors while exporting keys");
Ok(()) Ok(())
} }

View File

@@ -6,13 +6,13 @@
use std::fmt; use std::fmt;
use std::future::Future; use std::future::Future;
use deltachat_derive::{FromSql, ToSql};
use itertools::Itertools;
use rand::{thread_rng, Rng};
use async_smtp::smtp::response::Category; use async_smtp::smtp::response::Category;
use async_smtp::smtp::response::Code; use async_smtp::smtp::response::Code;
use async_smtp::smtp::response::Detail; use async_smtp::smtp::response::Detail;
use async_std::prelude::*;
use deltachat_derive::*;
use itertools::Itertools;
use rand::{thread_rng, Rng};
use crate::blob::BlobObject; use crate::blob::BlobObject;
use crate::chat::{self, ChatId}; use crate::chat::{self, ChatId};
@@ -37,7 +37,7 @@ 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, FromSql, ToSql)] #[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[repr(i32)] #[repr(i32)]
pub(crate) enum Thread { pub(crate) enum Thread {
Unknown = 0, Unknown = 0,
@@ -75,17 +75,7 @@ impl Default for Thread {
} }
#[derive( #[derive(
Debug, Debug, Display, Copy, Clone, PartialEq, Eq, PartialOrd, FromPrimitive, ToPrimitive, Sqlx,
Display,
Copy,
Clone,
PartialEq,
Eq,
PartialOrd,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
)] )]
#[repr(i32)] #[repr(i32)]
pub enum Action { pub enum Action {
@@ -155,6 +145,32 @@ impl fmt::Display for Job {
} }
} }
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Job {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
let foreign_id: i32 = row.try_get("foreign_id")?;
if foreign_id < 0 {
return Err(sqlx::Error::Decode(
anyhow::anyhow!("invalid foreign_id").into(),
));
}
Ok(Job {
job_id: row.try_get::<i32, _>("id")? as u32,
action: row.try_get("action")?,
foreign_id: foreign_id as u32,
desired_timestamp: row.try_get_unchecked("desired_timestamp")?,
added_timestamp: row.try_get_unchecked("added_timestamp")?,
tries: row.try_get::<i32, _>("tries")? as u32,
param: row
.try_get::<String, _>("param")?
.parse()
.unwrap_or_default(),
pending_error: None,
})
}
}
impl Job { impl Job {
pub fn new(action: Action, foreign_id: u32, param: Params, delay_seconds: i64) -> Self { pub fn new(action: Action, foreign_id: u32, param: Params, delay_seconds: i64) -> Self {
let timestamp = time(); let timestamp = time();
@@ -180,7 +196,7 @@ impl Job {
if self.job_id != 0 { if self.job_id != 0 {
context context
.sql .sql
.execute("DELETE FROM jobs WHERE id=?;", paramsv![self.job_id as i32]) .execute("DELETE FROM jobs WHERE id=?;", paramsx![self.job_id as i32])
.await?; .await?;
} }
@@ -200,22 +216,22 @@ impl Job {
.sql .sql
.execute( .execute(
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
paramsv![ paramsx![
self.desired_timestamp, self.desired_timestamp,
self.tries as i64, self.tries as i64,
self.param.to_string(), self.param.to_string(),
self.job_id as i32, self.job_id as i32
], ],
) )
.await?; .await?;
} else { } else {
context.sql.execute( context.sql.execute(
"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 (?,?,?,?,?,?);",
paramsv![ paramsx![
self.added_timestamp, self.added_timestamp,
thread, thread,
self.action, self.action,
self.foreign_id, self.foreign_id as i32,
self.param.to_string(), self.param.to_string(),
self.desired_timestamp self.desired_timestamp
] ]
@@ -394,40 +410,32 @@ impl Job {
context: &Context, context: &Context,
contact_id: u32, contact_id: u32,
) -> sql::Result<(Vec<u32>, Vec<String>)> { ) -> sql::Result<(Vec<u32>, Vec<String>)> {
// Extract message IDs from job parameters
let res: Vec<(u32, MsgId)> = context
.sql
.query_map(
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
paramsv![contact_id, self.job_id],
|row| {
let job_id: u32 = row.get(0)?;
let params_str: String = row.get(1)?;
let params: Params = params_str.parse().unwrap_or_default();
Ok((job_id, params))
},
|jobs| {
let res = jobs
.filter_map(|row| {
let (job_id, params) = row.ok()?;
let msg_id = params.get_msg_id()?;
Some((job_id, msg_id))
})
.collect();
Ok(res)
},
)
.await?;
// Load corresponding RFC724 message IDs
let mut job_ids = Vec::new(); let mut job_ids = Vec::new();
let mut rfc724_mids = Vec::new(); let mut rfc724_mids = Vec::new();
for (job_id, msg_id) in res {
if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await { let pool = context.sql.get_pool().await?;
job_ids.push(job_id);
rfc724_mids.push(rfc724_mid); let mut rows = sqlx::query_as("SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?")
.bind(contact_id as i64)
.bind(self.job_id as i64)
.fetch(&pool);
while let Some(row) = rows.next().await {
let (job_id, params): (i64, String) = row?;
let params: Params = params.parse().unwrap_or_default();
let msg_id = params.get_msg_id().unwrap_or_default();
match Message::load_from_db(context, msg_id).await {
Ok(Message { rfc724_mid, .. }) => {
job_ids.push(job_id as u32);
rfc724_mids.push(rfc724_mid);
}
Err(err) => {
warn!(context, "failed to load mdn job message: {}", err);
}
} }
} }
Ok((job_ids, rfc724_mids)) Ok((job_ids, rfc724_mids))
} }
@@ -664,21 +672,27 @@ 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("DELETE FROM jobs WHERE action=?;", paramsv![action]) .execute("DELETE FROM jobs WHERE action=?;", paramsx![action])
.await .await
.is_ok() .is_ok()
} }
/// Remove jobs with specified IDs. /// Remove jobs with specified IDs.
async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
use sqlx::Arguments;
let mut args = sqlx::sqlite::SqliteArguments::default();
for job_id in job_ids {
args.add(*job_id as i32);
}
context context
.sql .sql
.execute( .execute(
format!( &format!(
"DELETE FROM jobs WHERE id IN({})", "DELETE FROM jobs WHERE id IN({})",
job_ids.iter().map(|_| "?").join(",") job_ids.iter().map(|_| "?").join(",")
), ),
job_ids.iter().map(|i| i as &dyn crate::ToSql).collect(), args,
) )
.await?; .await?;
Ok(()) Ok(())
@@ -687,7 +701,7 @@ async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
pub async fn action_exists(context: &Context, action: Action) -> bool { pub async fn action_exists(context: &Context, action: Action) -> bool {
context context
.sql .sql
.exists("SELECT id FROM jobs WHERE action=?;", paramsv![action]) .exists("SELECT id FROM jobs WHERE action=?;", paramsx![action])
.await .await
.unwrap_or_default() .unwrap_or_default()
} }
@@ -696,11 +710,7 @@ async fn set_delivered(context: &Context, msg_id: MsgId) {
message::update_msg_state(context, msg_id, MessageState::OutDelivered).await; 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( .query_value("SELECT chat_id FROM msgs WHERE id=?", paramsx![])
context,
"SELECT chat_id FROM msgs WHERE id=?",
paramsv![msg_id],
)
.await .await
.unwrap_or_default(); .unwrap_or_default();
context.emit_event(Event::MsgDelivered { chat_id, msg_id }); context.emit_event(Event::MsgDelivered { chat_id, msg_id });
@@ -834,12 +844,12 @@ async fn load_imap_deletion_msgid(context: &Context) -> sql::Result<Option<MsgId
context context
.sql .sql
.query_row_optional( .query_value_optional(
"SELECT id FROM msgs \ r#"
WHERE timestamp < ? \ SELECT id FROM msgs
AND server_uid != 0", WHERE timestamp < ? AND server_uid != 0
paramsv![threshold_timestamp], "#,
|row| row.get::<_, MsgId>(0), paramsx![threshold_timestamp],
) )
.await .await
} else { } else {
@@ -975,7 +985,9 @@ async fn perform_job_action(
Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await, Action::MarkseenMsgOnImap => job.markseen_msg_on_imap(context, connection.inbox()).await,
Action::MoveMsg => job.move_msg(context, connection.inbox()).await, Action::MoveMsg => job.move_msg(context, connection.inbox()).await,
Action::Housekeeping => { Action::Housekeeping => {
sql::housekeeping(context).await; if let Err(err) = sql::housekeeping(context).await {
error!(context, "housekeeping failed: {}", err);
}
Status::Finished(Ok(())) Status::Finished(Ok(()))
} }
}; };
@@ -1067,9 +1079,8 @@ pub(crate) async fn load_next(
info!(context, "loading job for {}-thread", thread); info!(context, "loading job for {}-thread", thread);
let query; let query;
let params; let params: Box<dyn Fn() -> sqlx::sqlite::SqliteArguments<'static> + 'static + Send>;
let t = time(); let t = time();
let m;
let thread_i = thread as i64; let thread_i = thread as i64;
if let Some(msg_id) = info.msg_id { if let Some(msg_id) = info.msg_id {
@@ -1080,8 +1091,7 @@ WHERE thread=? AND foreign_id=?
ORDER BY action DESC, added_timestamp ORDER BY action DESC, added_timestamp
LIMIT 1; LIMIT 1;
"#; "#;
m = msg_id; params = Box::new(move || paramsx![thread_i, msg_id]);
params = paramsv![thread_i, m];
} 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.
@@ -1092,7 +1102,7 @@ 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]; params = Box::new(move || paramsx![thread_i, 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
@@ -1104,27 +1114,12 @@ 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]; params = Box::new(move || paramsx![thread_i]);
}; };
let job = loop { let job: Option<Job> = loop {
let job_res = context let p = params();
.sql let job_res = context.sql.query_row_optional(query, p).await;
.query_row_optional(query, params.clone(), |row| {
let job = Job {
job_id: row.get("id")?,
action: row.get("action")?,
foreign_id: row.get("foreign_id")?,
desired_timestamp: row.get("desired_timestamp")?,
added_timestamp: row.get("added_timestamp")?,
tries: row.get("tries")?,
param: row.get::<_, String>("param")?.parse().unwrap_or_default(),
pending_error: None,
};
Ok(job)
})
.await;
match job_res { match job_res {
Ok(job) => break job, Ok(job) => break job,
@@ -1133,15 +1128,13 @@ LIMIT 1;
info!(context, "cleaning up job, because of {}", err); info!(context, "cleaning up job, because of {}", err);
// TODO: improve by only doing a single query // TODO: improve by only doing a single query
match context let p = params();
.sql let id: Result<i32, _> = context.sql.query_value(query, p).await;
.query_row(query, params.clone(), |row| row.get::<_, i32>(0)) match id {
.await
{
Ok(id) => { Ok(id) => {
context context
.sql .sql
.execute("DELETE FROM jobs WHERE id=?;", paramsv![id]) .execute("DELETE FROM jobs WHERE id=?;", paramsx![id])
.await .await
.ok(); .ok();
} }
@@ -1191,7 +1184,7 @@ mod tests {
"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![ paramsx![
now, now,
Thread::from(Action::MoveMsg), Thread::from(Action::MoveMsg),
Action::MoveMsg, Action::MoveMsg,

View File

@@ -116,22 +116,21 @@ impl DcKey for SignedPublicKey {
type KeyType = SignedPublicKey; type KeyType = SignedPublicKey;
async fn load_self(context: &Context) -> Result<Self::KeyType> { async fn load_self(context: &Context) -> Result<Self::KeyType> {
match context let res: std::result::Result<Vec<u8>, _> = context
.sql .sql
.query_row( .query_value(
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![], paramsx![],
|row| row.get::<_, Vec<u8>>(0),
) )
.await .await;
{ match res {
Ok(bytes) => Self::from_slice(&bytes), Ok(bytes) => Self::from_slice(&bytes),
Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => {
let keypair = generate_keypair(context).await?; let keypair = generate_keypair(context).await?;
Ok(keypair.public) Ok(keypair.public)
} }
@@ -161,22 +160,21 @@ impl DcKey for SignedSecretKey {
type KeyType = SignedSecretKey; type KeyType = SignedSecretKey;
async fn load_self(context: &Context) -> Result<Self::KeyType> { async fn load_self(context: &Context) -> Result<Self::KeyType> {
match context let res: std::result::Result<Vec<u8>, _> = context
.sql .sql
.query_row( .query_value(
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![], paramsx![],
|row| row.get::<_, Vec<u8>>(0),
) )
.await .await;
{ match res {
Ok(bytes) => Self::from_slice(&bytes), Ok(bytes) => Self::from_slice(&bytes),
Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => {
let keypair = generate_keypair(context).await?; let keypair = generate_keypair(context).await?;
Ok(keypair.secret) Ok(keypair.secret)
} }
@@ -227,26 +225,25 @@ async fn generate_keypair(context: &Context) -> Result<KeyPair> {
let _guard = context.generating_key_mutex.lock().await; let _guard = context.generating_key_mutex.lock().await;
// 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 let res: std::result::Result<(Vec<u8>, Vec<u8>), _> = context
.sql .sql
.query_row( .query_row(
r#" r#"
SELECT public_key, private_key SELECT public_key, private_key
FROM keypairs FROM keypairs
WHERE addr=?1 WHERE addr=?
AND is_default=1; AND is_default=1;
"#, "#,
paramsv![addr], paramsx![addr.to_string()],
|row| Ok((row.get::<_, Vec<u8>>(0)?, row.get::<_, Vec<u8>>(1)?)),
) )
.await .await;
{ match res {
Ok((pub_bytes, sec_bytes)) => Ok(KeyPair { Ok((pub_bytes, sec_bytes)) => Ok(KeyPair {
addr, addr,
public: SignedPublicKey::from_slice(&pub_bytes)?, public: SignedPublicKey::from_slice(&pub_bytes)?,
secret: SignedSecretKey::from_slice(&sec_bytes)?, secret: SignedSecretKey::from_slice(&sec_bytes)?,
}), }),
Err(sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { Err(sql::Error::Sqlx(sqlx::Error::RowNotFound)) => {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await) let keytype = KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await)
.unwrap_or_default(); .unwrap_or_default();
@@ -321,14 +318,14 @@ pub async fn store_self_keypair(
.sql .sql
.execute( .execute(
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;", "DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
paramsv![public_key, secret_key], paramsx![&public_key, &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("UPDATE keypairs SET is_default=0;", paramsv![]) .execute("UPDATE keypairs SET is_default=0;", paramsx![])
.await .await
.map_err(|err| SaveKeyError::new("failed to clear default", err))?; .map_err(|err| SaveKeyError::new("failed to clear default", err))?;
} }
@@ -340,7 +337,7 @@ pub async fn store_self_keypair(
let addr = keypair.addr.to_string(); let addr = keypair.addr.to_string();
let t = time(); let t = time();
let params = paramsv![addr, is_default, public_key, secret_key, t]; let params = paramsx![addr, is_default, public_key, secret_key, t];
context context
.sql .sql
.execute( .execute(
@@ -616,10 +613,12 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
let ctx1 = ctx.clone(); let ctx1 = ctx.clone();
let nrows = || async { let nrows = || async {
ctx1.sql let val: i32 = ctx1
.query_get_value::<u32>(&ctx1, "SELECT COUNT(*) FROM keypairs;", paramsv![]) .sql
.query_value("SELECT COUNT(*) FROM keypairs;", paramsx![])
.await .await
.unwrap() .unwrap();
val as usize
}; };
assert_eq!(nrows().await, 0); assert_eq!(nrows().await, 0);
store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default) store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default)

View File

@@ -6,16 +6,10 @@
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]

View File

@@ -1,5 +1,6 @@
//! Location handling //! Location handling
use async_std::prelude::*;
use bitflags::bitflags; use bitflags::bitflags;
use quick_xml::events::{BytesEnd, BytesStart, BytesText}; use quick_xml::events::{BytesEnd, BytesStart, BytesText};
@@ -37,6 +38,34 @@ impl Location {
} }
} }
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Location {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
let msg_id = row.try_get::<i32, _>("msg_id")? as u32;
let txt: String = row.try_get("txt")?;
let marker = if msg_id != 0 && is_marker(&txt) {
Some(txt)
} else {
None
};
let loc = Location {
location_id: row.try_get::<i32, _>("id")? as u32,
latitude: row.try_get("latitude")?,
longitude: row.try_get("longitude")?,
accuracy: row.try_get("accuracy")?,
timestamp: row.try_get("timestamp")?,
independent: row.try_get::<i32, _>("independent")? as u32,
msg_id,
contact_id: row.try_get::<i32, _>("from_id")? as u32,
chat_id: row.try_get("chat_id")?,
marker,
};
Ok(loc)
}
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct Kml { pub struct Kml {
pub addr: Option<String>, pub addr: Option<String>,
@@ -197,14 +226,16 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
if context if context
.sql .sql
.execute( .execute(
"UPDATE chats \ r#"
SET locations_send_begin=?, \ UPDATE chats
locations_send_until=? \ SET locations_send_begin=?,
WHERE id=?", locations_send_until=?
paramsv![ WHERE id=?
"#,
paramsx![
if 0 != seconds { now } else { 0 }, if 0 != seconds { now } else { 0 },
if 0 != seconds { now + seconds } else { 0 }, if 0 != seconds { now + seconds } else { 0 },
chat_id, chat_id
], ],
) )
.await .await
@@ -260,53 +291,60 @@ pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) ->
.sql .sql
.exists( .exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", "SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
paramsv![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], paramsx![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
} }
pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> bool { pub async fn set(
context: &Context,
latitude: f64,
longitude: f64,
accuracy: f64,
) -> Result<bool, Error> {
if latitude == 0.0 && longitude == 0.0 { if latitude == 0.0 && longitude == 0.0 {
return true; return Ok(true);
} }
let mut continue_streaming = false; let mut continue_streaming = false;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as("SELECT id FROM chats WHERE locations_send_until>?;")
.bind(time())
.fetch(&pool);
if let Ok(chats) = context while let Some(row) = rows.next().await {
.sql let (chat_id,): (i64,) = row?;
.query_map(
"SELECT id FROM chats WHERE locations_send_until>?;", if let Err(err) = context
paramsv![time()], .sql
|row| row.get::<_, i32>(0), .execute(
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into), "INSERT INTO locations \
) (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
.await paramsx![
{ latitude,
for chat_id in chats { longitude,
if let Err(err) = context.sql.execute( accuracy,
"INSERT INTO locations \ time(),
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);", chat_id,
paramsv![ DC_CONTACT_ID_SELF as i32
latitude, ],
longitude, )
accuracy, .await
time(), {
chat_id, warn!(context, "failed to store location {:?}", err);
DC_CONTACT_ID_SELF, } else {
] continue_streaming = true;
).await {
warn!(context, "failed to store location {:?}", err);
} else {
continue_streaming = true;
}
} }
if continue_streaming {
context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF)));
};
schedule_maybe_send_locations(context, false).await;
} }
continue_streaming if continue_streaming {
context.emit_event(Event::LocationChanged(Some(DC_CONTACT_ID_SELF)));
};
schedule_maybe_send_locations(context, false).await;
Ok(continue_streaming)
} }
pub async fn get_range( pub async fn get_range(
@@ -319,16 +357,21 @@ pub async fn get_range(
if timestamp_to == 0 { if timestamp_to == 0 {
timestamp_to = time() + 10; timestamp_to = time() + 10;
} }
context context
.sql .sql
.query_map( .query_rows(
"SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent, \ r#"
COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt \ SELECT l.id, l.latitude, l.longitude, l.accuracy, l.timestamp, l.independent,
FROM locations l LEFT JOIN msgs m ON l.id=m.location_id WHERE (? OR l.chat_id=?) \ COALESCE(m.id, 0) AS msg_id, l.from_id, l.chat_id, COALESCE(m.txt, '') AS txt
AND (? OR l.from_id=?) \ FROM locations l
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \ LEFT JOIN msgs m ON l.id=m.location_id
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;", WHERE (? OR l.chat_id=?)
paramsv![ AND (? OR l.from_id=?)
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?))
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;
"#,
paramsx![
if chat_id.is_unset() { 1 } else { 0 }, if chat_id.is_unset() { 1 } else { 0 },
chat_id, chat_id,
if contact_id == 0 { 1 } else { 0 }, if contact_id == 0 { 1 } else { 0 },
@@ -336,36 +379,6 @@ pub async fn get_range(
timestamp_from, timestamp_from,
timestamp_to, timestamp_to,
], ],
|row| {
let msg_id = row.get(6)?;
let txt: String = row.get(9)?;
let marker = if msg_id != 0 && is_marker(&txt) {
Some(txt)
} else {
None
};
let loc = Location {
location_id: row.get(0)?,
latitude: row.get(1)?,
longitude: row.get(2)?,
accuracy: row.get(3)?,
timestamp: row.get(4)?,
independent: row.get(5)?,
msg_id,
contact_id: row.get(7)?,
chat_id: row.get(8)?,
marker,
};
Ok(loc)
},
|locations| {
let mut ret = Vec::new();
for location in locations {
ret.push(location?);
}
Ok(ret)
},
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -379,7 +392,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("DELETE FROM locations;", paramsv![]) .execute("DELETE FROM locations;", paramsx![])
.await?; .await?;
context.emit_event(Event::LocationChanged(None)); context.emit_event(Event::LocationChanged(None));
Ok(()) Ok(())
@@ -393,16 +406,10 @@ 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) = context.sql.query_row( let (locations_send_begin, locations_send_until, locations_last_sent): (i64, i64, i64) = context.sql.query_row(
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;", "SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
paramsv![chat_id], |row| { paramsx![chat_id]
let send_begin: i64 = row.get(0)?; ).await?;
let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?;
Ok((send_begin, send_until, last_sent))
})
.await?;
let now = time(); let now = time();
let mut location_count = 0; let mut location_count = 0;
@@ -413,40 +420,41 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)
self_addr, self_addr,
); );
context.sql.query_map( let pool = context.sql.get_pool().await?;
"SELECT id, latitude, longitude, accuracy, timestamp \ let mut rows = sqlx::query_as(
FROM locations WHERE from_id=? \ r#"
AND timestamp>=? \ SELECT id, latitude, longitude, accuracy, timestamp
AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?)) \ FROM locations
AND independent=0 \ WHERE from_id=?
GROUP BY timestamp \ AND timestamp>=?
ORDER BY timestamp;", AND (timestamp>=? OR timestamp=(SELECT MAX(timestamp) FROM locations WHERE from_id=?))
paramsv![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF], AND independent=0
|row| { GROUP BY timestamp
let location_id: i32 = row.get(0)?; ORDER BY timestamp;
let latitude: f64 = row.get(1)?; "#,
let longitude: f64 = row.get(2)?; )
let accuracy: f64 = row.get(3)?; .bind(DC_CONTACT_ID_SELF as i32)
let timestamp = get_kml_timestamp(row.get(4)?); .bind(locations_send_begin)
.bind(locations_last_sent)
.bind(DC_CONTACT_ID_SELF as i32)
.fetch(&pool);
while let Some(row) = rows.next().await {
let (location_id, latitude, longitude, accuracy, timestamp): (i32, f64, f64, f64, i64) =
row?;
let timestamp = get_kml_timestamp(timestamp);
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n",
timestamp,
accuracy,
longitude,
latitude
);
location_count += 1;
last_added_location_id = location_id as u32;
}
Ok((location_id, latitude, longitude, accuracy, timestamp))
},
|rows| {
for row in rows {
let (location_id, latitude, longitude, accuracy, timestamp) = row?;
ret += &format!(
"<Placemark><Timestamp><when>{}</when></Timestamp><Point><coordinates accuracy=\"{}\">{},{}</coordinates></Point></Placemark>\n",
timestamp,
accuracy,
longitude,
latitude
);
location_count += 1;
last_added_location_id = location_id as u32;
}
Ok(())
}
).await?;
ret += "</Document>\n</kml>"; ret += "</Document>\n</kml>";
} }
@@ -488,7 +496,7 @@ pub async fn set_kml_sent_timestamp(
.sql .sql
.execute( .execute(
"UPDATE chats SET locations_last_sent=? WHERE id=?;", "UPDATE chats SET locations_last_sent=? WHERE id=?;",
paramsv![timestamp, chat_id], paramsx![timestamp, chat_id],
) )
.await?; .await?;
Ok(()) Ok(())
@@ -503,7 +511,7 @@ pub async fn set_msg_location_id(
.sql .sql
.execute( .execute(
"UPDATE msgs SET location_id=? WHERE id=?;", "UPDATE msgs SET location_id=? WHERE id=?;",
paramsv![location_id, msg_id], paramsx![location_id as i32, msg_id],
) )
.await?; .await?;
@@ -530,50 +538,51 @@ pub async fn save(
accuracy, accuracy,
.. ..
} = location; } = location;
let (loc_id, ts) = context
let exists: Option<i32> = context
.sql .sql
.with_conn(move |mut conn| { .query_value_optional(
let mut stmt_test = conn "SELECT id FROM locations WHERE timestamp=? AND from_id=?",
.prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?; paramsx![timestamp, contact_id as i32],
let mut stmt_insert = conn.prepare_cached( )
"INSERT INTO locations\ .await?;
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \
VALUES (?,?,?,?,?,?,?);",
)?;
let exists = stmt_test.exists(paramsv![timestamp, contact_id as i32])?; if independent || exists.is_none() {
context
if independent || !exists { .sql
stmt_insert.execute(paramsv![ .execute(
r#"
INSERT INTO locations
(timestamp, from_id, chat_id, latitude, longitude, accuracy, independent)
VALUES (?,?,?,?,?,?,?);
"#,
paramsx![
timestamp, timestamp,
contact_id as i32, contact_id as i32,
chat_id, chat_id,
latitude, latitude,
longitude, longitude,
accuracy, accuracy,
independent, independent
])?; ],
)
.await?;
if timestamp > newest_timestamp { if timestamp > newest_timestamp {
// okay to drop, as we use cached prepared statements // okay to drop, as we use cached prepared statements
drop(stmt_test); newest_timestamp = timestamp;
drop(stmt_insert); newest_location_id = context
newest_timestamp = timestamp; .sql
newest_location_id = crate::sql::get_rowid2( .get_rowid2(
&mut conn, "locations",
"locations", "timestamp",
"timestamp", timestamp,
timestamp, "from_id",
"from_id", contact_id as i32,
contact_id as i32, )
)?; .await?;
} }
} }
Ok((newest_location_id, newest_timestamp))
})
.await?;
newest_timestamp = ts;
newest_location_id = loc_id;
} }
Ok(newest_location_id) Ok(newest_location_id)
@@ -587,85 +596,75 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
" ----------------- MAYBE_SEND_LOCATIONS -------------- ", " ----------------- MAYBE_SEND_LOCATIONS -------------- ",
); );
let rows = context let pool = match context.sql.get_pool().await {
.sql Ok(pool) => pool,
.query_map( Err(err) => {
"SELECT id, locations_send_begin, locations_last_sent \ return job::Status::Finished(Err(err.into()));
FROM chats \ }
WHERE locations_send_until>?;", };
paramsv![now],
|row| {
let chat_id: ChatId = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?;
let locations_last_sent: i64 = row.get(2)?;
continue_streaming = true;
// be a bit tolerant as the timer may not align exactly with time(NULL) let mut rows = sqlx::query_as(
if now - locations_last_sent < (60 - 3) { r#"
Ok(None) SELECT id, locations_send_begin, locations_last_sent
} else { FROM chats
Ok(Some((chat_id, locations_send_begin, locations_last_sent))) WHERE locations_send_until>?;
} "#,
}, )
|rows| { .bind(now)
rows.filter_map(|v| v.transpose()) .fetch(&pool);
.collect::<Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await;
if rows.is_ok() { while let Some(row) = rows.next().await {
let msgs = context let (chat_id, locations_send_begin, locations_last_sent): (ChatId, i64, i64) = match row {
Ok(res) => res,
Err(err) => {
warn!(context, "invalid row: {}", err);
continue;
}
};
continue_streaming = true;
// be a bit tolerant as the timer may not align exactly with time(NULL)
if now - locations_last_sent < (60 - 3) {
continue;
}
let exists = context
.sql .sql
.with_conn(move |conn| { .exists(
let rows = rows.unwrap(); r#"
SELECT id
let mut stmt_locations = conn.prepare_cached( FROM locations
"SELECT id \ WHERE from_id=?
FROM locations \ AND timestamp>=?
WHERE from_id=? \ AND timestamp>?
AND timestamp>=? \ AND independent=0
AND timestamp>? \ ORDER BY timestamp;",
AND independent=0 \ "#,
ORDER BY timestamp;", paramsx![
)?; DC_CONTACT_ID_SELF as i32,
locations_send_begin,
let mut msgs = Vec::new(); locations_last_sent,
for (chat_id, locations_send_begin, locations_last_sent) in &rows { ],
if !stmt_locations )
.exists(paramsv![
DC_CONTACT_ID_SELF,
*locations_send_begin,
*locations_last_sent,
])
.unwrap_or_default()
{
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
} else {
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = Message::new(Viewtype::Text);
msg.hidden = true;
msg.param.set_cmd(SystemMessage::LocationOnly);
msgs.push((*chat_id, msg));
}
}
Ok(msgs)
})
.await .await
.unwrap_or_default(); .unwrap_or_default();
for (chat_id, mut msg) in msgs.into_iter() { if !exists {
// if there is no new location, there's nothing to send.
// however, maybe we want to bypass this test eg. 15 minutes
} else {
// pending locations are attached automatically to every message,
// so also to this empty text message.
// DC_CMD_LOCATION is only needed to create a nicer subject.
//
// for optimisation and to avoid flooding the sending queue,
// we could sending these messages only if we're really online.
// the easiest way to determine this, is to check for an empty message queue.
// (might not be 100%, however, as positions are sent combined later
// and dc_set_location() is typically called periodically, this is ok)
let mut msg = Message::new(Viewtype::Text);
msg.hidden = true;
msg.param.set_cmd(SystemMessage::LocationOnly);
// TODO: better error handling // TODO: better error handling
chat::send_msg(context, chat_id, &mut msg) chat::send_msg(context, chat_id, &mut msg)
.await .await
@@ -676,6 +675,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
if continue_streaming { if continue_streaming {
schedule_maybe_send_locations(context, true).await; schedule_maybe_send_locations(context, true).await;
} }
job::Status::Finished(Ok(())) job::Status::Finished(Ok(()))
} }
@@ -689,13 +689,12 @@ pub(crate) async fn job_maybe_send_locations_ended(
let chat_id = ChatId::new(job.foreign_id); let chat_id = ChatId::new(job.foreign_id);
let (send_begin, send_until) = job_try!( let (send_begin, send_until): (i64, i64) = job_try!(
context context
.sql .sql
.query_row( .query_row(
"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], paramsx![chat_id],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
) )
.await .await
); );
@@ -707,8 +706,8 @@ pub(crate) async fn job_maybe_send_locations_ended(
if !(send_begin == 0 && send_until == 0) { if !(send_begin == 0 && send_until == 0) {
// not streaming, device-message already sent // not streaming, device-message already sent
job_try!(context.sql.execute( job_try!(context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
paramsv![chat_id], paramsx![chat_id],
).await); ).await);
let stock_str = context let stock_str = context

View File

@@ -56,63 +56,54 @@ impl LoginParam {
let key = format!("{}addr", prefix); let key = format!("{}addr", prefix);
let addr = sql let addr = sql
.get_raw_config(context, key) .get_raw_config(key)
.await .await
.unwrap_or_default() .unwrap_or_default()
.trim() .trim()
.to_string(); .to_string();
let key = format!("{}mail_server", prefix); let key = format!("{}mail_server", prefix);
let mail_server = sql.get_raw_config(context, key).await.unwrap_or_default(); let mail_server = sql.get_raw_config(key).await.unwrap_or_default();
let key = format!("{}mail_port", prefix); let key = format!("{}mail_port", prefix);
let mail_port = sql let mail_port = sql.get_raw_config_int(key).await.unwrap_or_default();
.get_raw_config_int(context, key)
.await
.unwrap_or_default();
let key = format!("{}mail_user", prefix); let key = format!("{}mail_user", prefix);
let mail_user = sql.get_raw_config(context, key).await.unwrap_or_default(); let mail_user = sql.get_raw_config(key).await.unwrap_or_default();
let key = format!("{}mail_pw", prefix); let key = format!("{}mail_pw", prefix);
let mail_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); let mail_pw = sql.get_raw_config(key).await.unwrap_or_default();
let key = format!("{}imap_certificate_checks", prefix); let key = format!("{}imap_certificate_checks", prefix);
let imap_certificate_checks = let imap_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { if let Some(certificate_checks) = sql.get_raw_config_int(key).await {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else { } else {
Default::default() Default::default()
}; };
let key = format!("{}send_server", prefix); let key = format!("{}send_server", prefix);
let send_server = sql.get_raw_config(context, key).await.unwrap_or_default(); let send_server = sql.get_raw_config(key).await.unwrap_or_default();
let key = format!("{}send_port", prefix); let key = format!("{}send_port", prefix);
let send_port = sql let send_port = sql.get_raw_config_int(key).await.unwrap_or_default();
.get_raw_config_int(context, key)
.await
.unwrap_or_default();
let key = format!("{}send_user", prefix); let key = format!("{}send_user", prefix);
let send_user = sql.get_raw_config(context, key).await.unwrap_or_default(); let send_user = sql.get_raw_config(key).await.unwrap_or_default();
let key = format!("{}send_pw", prefix); let key = format!("{}send_pw", prefix);
let send_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); let send_pw = sql.get_raw_config(key).await.unwrap_or_default();
let key = format!("{}smtp_certificate_checks", prefix); let key = format!("{}smtp_certificate_checks", prefix);
let smtp_certificate_checks = let smtp_certificate_checks =
if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { if let Some(certificate_checks) = sql.get_raw_config_int(key).await {
num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() num_traits::FromPrimitive::from_i32(certificate_checks).unwrap()
} else { } else {
Default::default() Default::default()
}; };
let key = format!("{}server_flags", prefix); let key = format!("{}server_flags", prefix);
let server_flags = sql let server_flags = sql.get_raw_config_int(key).await.unwrap_or_default();
.get_raw_config_int(context, key)
.await
.unwrap_or_default();
LoginParam { LoginParam {
addr, addr,

View File

@@ -1,4 +1,4 @@
use deltachat_derive::{FromSql, ToSql}; use deltachat_derive::*;
use crate::key::Fingerprint; use crate::key::Fingerprint;
@@ -22,7 +22,7 @@ pub struct Lot {
} }
#[repr(u8)] #[repr(u8)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
pub enum Meaning { pub enum Meaning {
None = 0, None = 0,
Text1Draft = 1, Text1Draft = 1,
@@ -67,7 +67,7 @@ impl Lot {
} }
#[repr(i32)] #[repr(i32)]
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
pub enum LotState { pub enum LotState {
// Default // Default
Undefined = 0, Undefined = 0,

View File

@@ -1,7 +1,8 @@
//! # Messages and their identifiers //! # Messages and their identifiers
use async_std::path::{Path, PathBuf}; use async_std::path::{Path, PathBuf};
use deltachat_derive::{FromSql, ToSql}; use async_std::prelude::*;
use deltachat_derive::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@@ -33,7 +34,20 @@ 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, Copy, Clone, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Debug,
Copy,
Clone,
Default,
PartialEq,
Eq,
Hash,
PartialOrd,
Ord,
Serialize,
Deserialize,
ToPrimitive,
FromPrimitive,
Sqlx,
)] )]
pub struct MsgId(u32); pub struct MsgId(u32);
@@ -92,7 +106,7 @@ impl MsgId {
.sql .sql
.execute( .execute(
"UPDATE msgs SET chat_id=?, txt='', txt_raw='' WHERE id=?", "UPDATE msgs SET chat_id=?, txt='', txt_raw='' WHERE id=?",
paramsv![chat_id, self], paramsx![chat_id, self],
) )
.await?; .await?;
@@ -105,11 +119,11 @@ impl MsgId {
// 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("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![self]) .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsx![self])
.await?; .await?;
context context
.sql .sql
.execute("DELETE FROM msgs WHERE id=?;", paramsv![self]) .execute("DELETE FROM msgs WHERE id=?;", paramsx![self])
.await?; .await?;
Ok(()) Ok(())
} }
@@ -123,10 +137,12 @@ impl MsgId {
context context
.sql .sql
.execute( .execute(
"UPDATE msgs \ r#"
SET server_folder='', server_uid=0 \ UPDATE msgs
WHERE id=?", SET server_folder='', server_uid=0
paramsv![self], WHERE id=?
"#,
paramsx![self],
) )
.await?; .await?;
Ok(()) Ok(())
@@ -156,41 +172,6 @@ impl std::fmt::Display for MsgId {
} }
} }
/// Allow converting [MsgId] to an SQLite type.
///
/// This allows you to directly store [MsgId] into the database.
///
/// # Errors
///
/// This **does** ensure that no special message IDs are written into
/// the database and the conversion will fail if this is not the case.
impl rusqlite::types::ToSql for MsgId {
fn to_sql(&self) -> rusqlite::Result<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
@@ -201,16 +182,7 @@ impl rusqlite::types::FromSql for MsgId {
pub struct InvalidMsgId; pub struct InvalidMsgId;
#[derive( #[derive(
Debug, Debug, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive, Serialize, Deserialize, Sqlx,
Copy,
Clone,
PartialEq,
FromPrimitive,
ToPrimitive,
FromSql,
ToSql,
Serialize,
Deserialize,
)] )]
#[repr(u8)] #[repr(u8)]
pub(crate) enum MessengerMessage { pub(crate) enum MessengerMessage {
@@ -259,6 +231,58 @@ pub struct Message {
pub(crate) param: Params, pub(crate) param: Params,
} }
impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Message {
fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
let id = row.try_get("id")?;
let text;
if let Some(buf) = row.try_get_unchecked::<Option<&[u8]>, _>("txt")? {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
eprintln!(
"dc_msg_load_from_db: could not get text column as non-lossy utf8 id {}",
id
);
text = String::from_utf8_lossy(buf).into_owned();
}
} else {
text = "".to_string();
}
Ok(Message {
id,
rfc724_mid: row.try_get::<String, _>("rfc724mid")?,
in_reply_to: row.try_get::<Option<String>, _>("mime_in_reply_to")?,
server_folder: row.try_get::<Option<String>, _>("server_folder")?,
server_uid: row.try_get_unchecked::<i64, _>("server_uid")? as u32,
chat_id: row.try_get("chat_id")?,
from_id: row.try_get::<i32, _>("from_id")? as u32,
to_id: row.try_get::<i32, _>("to_id")? as u32,
timestamp_sort: row.try_get_unchecked("timestamp")?,
timestamp_sent: row.try_get_unchecked("timestamp_sent")?,
timestamp_rcvd: row.try_get_unchecked("timestamp_rcvd")?,
viewtype: row.try_get("type")?,
state: row.try_get("state")?,
is_dc_message: row.try_get("msgrmsg")?,
text: Some(text),
param: row
.try_get::<String, _>("param")?
.parse()
.unwrap_or_default(),
starred: row.try_get::<i32, _>("starred")? > 0,
hidden: row.try_get::<i32, _>("hidden")? > 0,
location_id: row.try_get_unchecked::<i64, _>("location")? as u32,
chat_blocked: row
.try_get::<Option<Blocked>, _>("blocked")?
.unwrap_or_default(),
error: row.try_get("error")?,
})
}
}
impl Message { impl Message {
pub fn new(viewtype: Viewtype) -> Self { pub fn new(viewtype: Viewtype) -> Self {
let mut msg = Message::default(); let mut msg = Message::default();
@@ -272,7 +296,7 @@ impl Message {
!id.is_special(), !id.is_special(),
"Can not load special message IDs from DB." "Can not load special message IDs from DB."
); );
let msg = context let msg: Message = context
.sql .sql
.query_row( .query_row(
concat!( concat!(
@@ -301,56 +325,7 @@ impl Message {
" 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=?;"
), ),
paramsv![id], paramsx![id],
|row| {
let mut msg = Message::default();
// msg.id = row.get::<_, AnyMsgId>("id")?;
msg.id = row.get("id")?;
msg.rfc724_mid = row.get::<_, String>("rfc724mid")?;
msg.in_reply_to = row.get::<_, Option<String>>("mime_in_reply_to")?;
msg.server_folder = row.get::<_, Option<String>>("server_folder")?;
msg.server_uid = row.get("server_uid")?;
msg.chat_id = row.get("chat_id")?;
msg.from_id = row.get("from_id")?;
msg.to_id = row.get("to_id")?;
msg.timestamp_sort = row.get("timestamp")?;
msg.timestamp_sent = row.get("timestamp_sent")?;
msg.timestamp_rcvd = row.get("timestamp_rcvd")?;
msg.viewtype = row.get("type")?;
msg.state = row.get("state")?;
msg.error = row.get("error")?;
msg.is_dc_message = row.get("msgrmsg")?;
let text;
if let rusqlite::types::ValueRef::Text(buf) = row.get_raw("txt") {
if let Ok(t) = String::from_utf8(buf.to_vec()) {
text = t;
} else {
warn!(
context,
concat!(
"dc_msg_load_from_db: could not get ",
"text column as non-lossy utf8 id {}"
),
id
);
text = String::from_utf8_lossy(buf).into_owned();
}
} else {
text = "".to_string();
}
msg.text = Some(text);
msg.param = row.get::<_, String>("param")?.parse().unwrap_or_default();
msg.starred = row.get("starred")?;
msg.hidden = row.get("hidden")?;
msg.location_id = row.get("location")?;
msg.chat_blocked = row
.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default();
Ok(msg)
},
) )
.await?; .await?;
@@ -654,7 +629,7 @@ impl Message {
.sql .sql
.execute( .execute(
"UPDATE msgs SET param=? WHERE id=?;", "UPDATE msgs SET param=? WHERE id=?;",
paramsv![self.param.to_string(), self.id], paramsx![self.param.to_string(), self.id],
) )
.await .await
.is_ok() .is_ok()
@@ -669,10 +644,9 @@ impl Message {
Eq, Eq,
FromPrimitive, FromPrimitive,
ToPrimitive, ToPrimitive,
ToSql,
FromSql,
Serialize, Serialize,
Deserialize, Deserialize,
sqlx::Type,
)] )]
#[repr(i32)] #[repr(i32)]
pub enum MessageState { pub enum MessageState {
@@ -844,29 +818,16 @@ impl Lot {
} }
} }
pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> Result<String, Error> {
let mut ret = String::new(); let mut ret = String::new();
let msg = Message::load_from_db(context, msg_id).await; let msg = Message::load_from_db(context, msg_id).await?;
if msg.is_err() {
return ret;
}
let msg = msg.unwrap_or_default();
let rawtxt: Option<String> = context let rawtxt: Option<String> = context
.sql .sql
.query_get_value( .query_value("SELECT txt_raw FROM msgs WHERE id=?;", paramsx![msg_id])
context, .await?;
"SELECT txt_raw FROM msgs WHERE id=?;",
paramsv![msg_id],
)
.await;
if rawtxt.is_none() {
ret += &format!("Cannot load message {}.", msg_id);
return ret;
}
let rawtxt = rawtxt.unwrap_or_default(); let rawtxt = rawtxt.unwrap_or_default();
let rawtxt = dc_truncate(rawtxt.trim(), 100_000); let rawtxt = dc_truncate(rawtxt.trim(), 100_000);
@@ -875,8 +836,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
let name = Contact::load_from_db(context, msg.from_id) let name = Contact::load_from_db(context, msg.from_id)
.await .await
.map(|contact| contact.get_name_n_addr()) .map(|contact| contact.get_name_n_addr())?;
.unwrap_or_default();
ret += &format!(" by {}", name); ret += &format!(" by {}", name);
ret += "\n"; ret += "\n";
@@ -893,35 +853,28 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO { if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
// device-internal message, no further details needed // device-internal message, no further details needed
return ret; return Ok(ret);
} }
if let Ok(rows) = context let pool = context.sql.get_pool().await?;
.sql
.query_map(
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
paramsv![msg_id],
|row| {
let contact_id: i32 = row.get(0)?;
let ts: i64 = row.get(1)?;
Ok((contact_id, ts))
},
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await
{
for (contact_id, ts) in rows {
let fts = dc_timestamp_to_str(ts);
ret += &format!("Read: {}", fts);
let name = Contact::load_from_db(context, contact_id as u32) let mut rows =
.await sqlx::query_as("SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;")
.map(|contact| contact.get_name_n_addr()) .bind(msg_id)
.unwrap_or_default(); .fetch(&pool);
ret += &format!(" by {}", name); while let Some(row) = rows.next().await {
ret += "\n"; let (contact_id, ts): (i32, i32) = row?;
}
let fts = dc_timestamp_to_str(ts as i64);
ret += &format!("Read: {}", fts);
let name = Contact::load_from_db(context, contact_id as u32)
.await
.map(|contact| contact.get_name_n_addr())?;
ret += &format!(" by {}", name);
ret += "\n";
} }
ret += &format!("State: {}", msg.state); ret += &format!("State: {}", msg.state);
@@ -978,7 +931,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
} }
} }
ret Ok(ret)
} }
pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> { pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
@@ -1006,12 +959,12 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> { pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
context context
.sql .sql
.query_get_value( .query_value(
context,
"SELECT mime_headers FROM msgs WHERE id=?;", "SELECT mime_headers FROM msgs WHERE id=?;",
paramsv![msg_id], paramsx![msg_id],
) )
.await .await
.ok()
} }
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
@@ -1050,7 +1003,7 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
.sql .sql
.execute( .execute(
"DELETE FROM locations WHERE independent = 1 AND id=?;", "DELETE FROM locations WHERE independent = 1 AND id=?;",
paramsv![location_id as i32], paramsx![location_id as i32],
) )
.await .await
.is_ok() .is_ok()
@@ -1061,56 +1014,40 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> bool {
return false; return false;
} }
let msgs = context
.sql
.with_conn(move |conn| {
let mut stmt = conn.prepare_cached(concat!(
"SELECT",
" m.state AS state,",
" c.blocked AS blocked",
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=? AND m.chat_id>9"
))?;
let mut msgs = Vec::with_capacity(msg_ids.len());
for id in msg_ids.into_iter() {
let query_res = stmt.query_row(paramsv![id], |row| {
Ok((
row.get::<_, MessageState>("state")?,
row.get::<_, Option<Blocked>>("blocked")?
.unwrap_or_default(),
))
});
if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res {
continue;
}
let (state, blocked) = query_res.map_err(Into::<anyhow::Error>::into)?;
msgs.push((id, state, blocked));
}
Ok(msgs)
})
.await
.unwrap_or_default();
let mut send_event = false; let mut send_event = false;
for id in msg_ids.into_iter() {
let query_res: Result<Option<(MessageState, Option<Blocked>)>, _> = context
.sql
.query_row_optional(
r#"
SELECT
m.state
c.blocked
FROM msgs m LEFT JOIN chats c ON c.id = m.chat_id
WHERE m.id = ? AND m.chat_id > 9
"#,
paramsx![id],
)
.await;
for (id, curr_state, curr_blocked) in msgs.into_iter() { if let Ok(Some((state, blocked))) = query_res {
if curr_blocked == Blocked::Not { let blocked = blocked.unwrap_or_default();
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed { if blocked == Blocked::Not {
update_msg_state(context, id, MessageState::InSeen).await; if state == MessageState::InFresh || state == MessageState::InNoticed {
info!(context, "Seen message {}.", id); update_msg_state(context, id, MessageState::InSeen).await;
info!(context, "Seen message {}.", id);
job::add( job::add(
context, context,
job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0), job::Job::new(Action::MarkseenMsgOnImap, id.to_u32(), Params::new(), 0),
) )
.await; .await;
send_event = true;
}
} else if state == MessageState::InFresh {
update_msg_state(context, id, MessageState::InNoticed).await;
send_event = true; send_event = true;
} }
} else if curr_state == MessageState::InFresh {
update_msg_state(context, id, MessageState::InNoticed).await;
send_event = true;
} }
} }
@@ -1129,7 +1066,7 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt
.sql .sql
.execute( .execute(
"UPDATE msgs SET state=? WHERE id=?;", "UPDATE msgs SET state=? WHERE id=?;",
paramsv![state, msg_id], paramsx![state, msg_id],
) )
.await .await
.is_ok() .is_ok()
@@ -1139,17 +1076,22 @@ pub async fn star_msgs(context: &Context, msg_ids: Vec<MsgId>, star: bool) -> bo
if msg_ids.is_empty() { if msg_ids.is_empty() {
return false; return false;
} }
context
.sql for msg_id in msg_ids.into_iter() {
.with_conn(move |conn| { if context
let mut stmt = conn.prepare("UPDATE msgs SET starred=? WHERE id=?;")?; .sql
for msg_id in msg_ids.into_iter() { .execute(
stmt.execute(paramsv![star as i32, msg_id])?; "UPDATE msgs SET starred=? WHERE id=?;",
} paramsx![star as i32, msg_id],
Ok(()) )
}) .await
.await .is_err()
.is_ok() {
return false;
}
}
true
} }
/// Returns a summary test. /// Returns a summary test.
@@ -1240,12 +1182,9 @@ pub async fn exists(context: &Context, msg_id: MsgId) -> bool {
let chat_id: Option<ChatId> = context let chat_id: Option<ChatId> = context
.sql .sql
.query_get_value( .query_value("SELECT chat_id FROM msgs WHERE id=?;", paramsx![msg_id])
context, .await
"SELECT chat_id FROM msgs WHERE id=?;", .ok();
paramsv![msg_id],
)
.await;
if let Some(chat_id) = chat_id { if let Some(chat_id) = chat_id {
!chat_id.is_trash() !chat_id.is_trash()
@@ -1271,7 +1210,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl
.sql .sql
.execute( .execute(
"UPDATE msgs SET state=?, error=? WHERE id=?;", "UPDATE msgs SET state=?, error=? WHERE id=?;",
paramsv![msg.state, error, msg_id], paramsx![msg.state, error, msg_id],
) )
.await .await
{ {
@@ -1297,28 +1236,19 @@ pub async fn handle_mdn(
return None; return None;
} }
let res = context let res: Result<(MsgId, ChatId, Chattype, MessageState), _> = context
.sql .sql
.query_row( .query_row(
concat!( r#"
"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,
" m.state AS state", m.state AS state
" 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;"#,
), paramsx![rfc724_mid],
paramsv![rfc724_mid],
|row| {
Ok((
row.get::<_, MsgId>("msg_id")?,
row.get::<_, ChatId>("chat_id")?,
row.get::<_, Chattype>("type")?,
row.get::<_, MessageState>("state")?,
))
},
) )
.await; .await;
if let Err(ref err) = res { if let Err(ref err) = res {
@@ -1336,7 +1266,7 @@ pub async fn handle_mdn(
.sql .sql
.exists( .exists(
"SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;", "SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;",
paramsv![msg_id, from_id as i32,], paramsx![msg_id, from_id as i32,],
) )
.await .await
.unwrap_or_default(); .unwrap_or_default();
@@ -1344,7 +1274,7 @@ pub async fn handle_mdn(
if !mdn_already_in_table { if !mdn_already_in_table {
context.sql.execute( context.sql.execute(
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);", "INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
paramsv![msg_id, from_id as i32, timestamp_sent], paramsx![msg_id, from_id as i32, timestamp_sent],
) )
.await .await
.unwrap_or_default(); // TODO: better error handling .unwrap_or_default(); // TODO: better error handling
@@ -1356,15 +1286,16 @@ pub async fn handle_mdn(
read_by_all = true; read_by_all = true;
} else { } else {
// send event about new state // send event about new state
let ist_cnt = context let ist_cnt: i32 = context
.sql .sql
.query_get_value::<isize>( .query_value(
context,
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;", "SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
paramsv![msg_id], paramsx![msg_id],
) )
.await .await
.unwrap_or_default() as usize; .unwrap_or_default();
let ist_cnt = ist_cnt as usize;
/* /*
Groupsize: Min. MDNs Groupsize: Min. MDNs
@@ -1405,25 +1336,18 @@ pub(crate) async fn handle_ndn(
return; return;
} }
let res = context let res: Result<(MsgId, ChatId, Chattype), _> = context
.sql .sql
.query_row( .query_row(
concat!( r#"
"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], paramsx![&failed.rfc724_mid],
|row| {
Ok((
row.get::<_, MsgId>("msg_id")?,
row.get::<_, ChatId>("chat_id")?,
row.get::<_, Chattype>("type")?,
))
},
) )
.await; .await;
if let Err(ref err) = res { if let Err(ref err) = res {
@@ -1461,12 +1385,13 @@ pub(crate) async fn handle_ndn(
pub async fn get_real_msg_cnt(context: &Context) -> i32 { pub async fn get_real_msg_cnt(context: &Context) -> i32 {
match context match context
.sql .sql
.query_row( .query_value(
"SELECT COUNT(*) \ r#"
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ SELECT COUNT(*)
WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;", FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
paramsv![], WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;
|row| row.get(0), "#,
paramsx![],
) )
.await .await
{ {
@@ -1479,17 +1404,17 @@ pub async fn get_real_msg_cnt(context: &Context) -> i32 {
} }
pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize { pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize {
match context let res: Result<i32, _> = context
.sql .sql
.query_row( .query_value(
"SELECT COUNT(*) \ r#"
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ SELECT COUNT(*)
WHERE c.blocked=2;", FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id
paramsv![], WHERE c.blocked=2;"#,
|row| row.get::<_, isize>(0), paramsx![],
) )
.await .await;
{ match res {
Ok(res) => res as usize, Ok(res) => res as usize,
Err(err) => { Err(err) => {
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err); error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
@@ -1509,37 +1434,39 @@ pub async fn estimate_deletion_cnt(
.0; .0;
let threshold_timestamp = time() - seconds; let threshold_timestamp = time() - seconds;
let cnt: isize = if from_server { let cnt: i32 = if from_server {
context context
.sql .sql
.query_row( .query_value(
"SELECT COUNT(*) r#"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], paramsx![
|row| row.get(0), DC_MSG_ID_LAST_SPECIAL as i32,
threshold_timestamp,
self_chat_id
],
) )
.await? .await?
} else { } else {
context context
.sql .sql
.query_row( .query_value(
"SELECT COUNT(*) r#"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![ paramsx![
DC_MSG_ID_LAST_SPECIAL, DC_MSG_ID_LAST_SPECIAL as i32,
threshold_timestamp, threshold_timestamp,
self_chat_id, self_chat_id,
ChatId::new(DC_CHAT_ID_TRASH) ChatId::new(DC_CHAT_ID_TRASH)
], ],
|row| row.get(0),
) )
.await? .await?
}; };
@@ -1554,10 +1481,9 @@ pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
// check the number of messages with the same rfc724_mid // check the number of messages with the same rfc724_mid
match context match context
.sql .sql
.query_row( .query_value(
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=? AND NOT server_uid = 0", "SELECT COUNT(*) FROM msgs WHERE rfc724_mid=? AND NOT server_uid = 0",
paramsv![rfc724_mid], paramsx![rfc724_mid],
|row| row.get(0),
) )
.await .await
{ {
@@ -1578,22 +1504,15 @@ pub(crate) async fn rfc724_mid_exists(
return Ok(None); return Ok(None);
} }
let res = context let res: Option<(Option<String>, i32, MsgId)> = context
.sql .sql
.query_row_optional( .query_row_optional(
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?", "SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
paramsv![rfc724_mid], paramsx![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?;
Ok(res) Ok(res.map(|(a, b, c)| (a.unwrap_or_default(), b as u32, c)))
} }
pub async fn update_server_uid( pub async fn update_server_uid(
@@ -1605,9 +1524,8 @@ pub async fn update_server_uid(
match context match context
.sql .sql
.execute( .execute(
"UPDATE msgs SET server_folder=?, server_uid=? \ "UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?",
WHERE rfc724_mid=?", paramsx![server_folder.as_ref(), server_uid as i32, rfc724_mid],
paramsv![server_folder.as_ref(), server_uid, rfc724_mid],
) )
.await .await
{ {

View File

@@ -1,3 +1,4 @@
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};
@@ -87,30 +88,26 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
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 {
context let pool = context.sql.get_pool().await?;
.sql
.query_map( let mut rows = sqlx::query_as(
"SELECT c.authname, c.addr \ r#"
FROM chats_contacts cc \ SELECT c.authname, c.addr
LEFT JOIN contacts c ON cc.contact_id=c.id \ FROM chats_contacts cc
WHERE cc.chat_id=? AND cc.contact_id>9;", LEFT JOIN contacts c ON cc.contact_id=c.id
paramsv![msg.chat_id], WHERE cc.chat_id=? AND cc.contact_id>9;
|row| { "#,
let authname: String = row.get(0)?; )
let addr: String = row.get(1)?; .bind(msg.chat_id)
Ok((authname, addr)) .fetch(&pool);
},
|rows| { while let Some(row) = rows.next().await {
for row in rows { let (authname, addr): (String, String) = row?;
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?;
let command = msg.param.get_cmd(); let command = msg.param.get_cmd();
@@ -125,18 +122,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.sql .sql
.query_row( .query_row(
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?", "SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
paramsv![msg.id], paramsx![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
.map(|(in_reply_to, references): (String, String)| {
(
render_rfc724_mid_list(&in_reply_to),
render_rfc724_mid_list(&references),
)
})?;
let default_str = context let default_str = context
.stock_str(StockMessage::StatusLine) .stock_str(StockMessage::StatusLine)
@@ -211,7 +205,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
Ok(res) Ok(res)
} }
async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate<'_>>, &str)>, Error> { async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate>, &str)>, Error> {
let self_addr = self let self_addr = self
.context .context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
@@ -225,7 +219,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.filter(|(_, addr)| addr != &self_addr) .filter(|(_, addr)| addr != &self_addr)
{ {
res.push(( res.push((
Peerstate::from_addr(self.context, addr).await, Peerstate::from_addr(self.context, addr).await.ok(),
addr.as_str(), addr.as_str(),
)); ));
} }

View File

@@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use deltachat_derive::{FromSql, ToSql}; use deltachat_derive::*;
use lazy_static::lazy_static; use lazy_static::lazy_static;
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};
@@ -64,7 +64,7 @@ pub(crate) enum AvatarAction {
Change(String), Change(String),
} }
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[repr(i32)] #[repr(i32)]
pub enum SystemMessage { pub enum SystemMessage {
Unknown = 0, Unknown = 0,
@@ -989,12 +989,12 @@ async fn update_gossip_peerstates(
.iter() .iter()
.any(|info| info.addr == header.addr.to_lowercase()) .any(|info| info.addr == header.addr.to_lowercase())
{ {
let mut peerstate = Peerstate::from_addr(context, &header.addr).await; let mut peerstate = Peerstate::from_addr(context, &header.addr).await.ok();
if let Some(ref mut peerstate) = peerstate { if let Some(ref mut peerstate) = peerstate {
peerstate.apply_gossip(header, message_time); peerstate.apply_gossip(header, message_time);
peerstate.save_to_db(&context.sql, false).await?; peerstate.save_to_db(&context.sql, false).await?;
} else { } else {
let p = Peerstate::from_gossip(context, header, message_time); let p = Peerstate::from_gossip(header, message_time);
p.save_to_db(&context.sql, true).await?; p.save_to_db(&context.sql, true).await?;
peerstate = Some(p); peerstate = Some(p);
} }

View File

@@ -95,10 +95,7 @@ pub async fn dc_get_oauth2_access_token(
// read generated token // read generated token
if !regenerate && !is_expired(context).await { if !regenerate && !is_expired(context).await {
let access_token = context let access_token = context.sql.get_raw_config("oauth2_access_token").await;
.sql
.get_raw_config(context, "oauth2_access_token")
.await;
if access_token.is_some() { if access_token.is_some() {
// success // success
return access_token; return access_token;
@@ -106,13 +103,10 @@ pub async fn dc_get_oauth2_access_token(
} }
// generate new token: build & call auth url // generate new token: build & call auth url
let refresh_token = context let refresh_token = context.sql.get_raw_config("oauth2_refresh_token").await;
.sql
.get_raw_config(context, "oauth2_refresh_token")
.await;
let refresh_token_for = context let refresh_token_for = context
.sql .sql
.get_raw_config(context, "oauth2_refresh_token_for") .get_raw_config("oauth2_refresh_token_for")
.await .await
.unwrap_or_else(|| "unset".into()); .unwrap_or_else(|| "unset".into());
@@ -122,7 +116,7 @@ pub async fn dc_get_oauth2_access_token(
( (
context context
.sql .sql
.get_raw_config(context, "oauth2_pending_redirect_uri") .get_raw_config("oauth2_pending_redirect_uri")
.await .await
.unwrap_or_else(|| "unset".into()), .unwrap_or_else(|| "unset".into()),
oauth2.init_token, oauth2.init_token,
@@ -136,7 +130,7 @@ pub async fn dc_get_oauth2_access_token(
( (
context context
.sql .sql
.get_raw_config(context, "oauth2_redirect_uri") .get_raw_config("oauth2_redirect_uri")
.await .await
.unwrap_or_else(|| "unset".into()), .unwrap_or_else(|| "unset".into()),
oauth2.refresh_token, oauth2.refresh_token,
@@ -360,7 +354,7 @@ impl Oauth2 {
async fn is_expired(context: &Context) -> bool { async fn is_expired(context: &Context) -> bool {
let expire_timestamp = context let expire_timestamp = context
.sql .sql
.get_raw_config_int64(context, "oauth2_timestamp_expires") .get_raw_config_int64("oauth2_timestamp_expires")
.await .await
.unwrap_or_default(); .unwrap_or_default();

View File

@@ -1,8 +1,8 @@
//! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module //! # [Autocrypt Peer State](https://autocrypt.org/level1.html#peer-state-management) module
use std::collections::HashSet;
use std::fmt;
use num_traits::FromPrimitive; use std::collections::HashSet;
use anyhow::Result;
use crate::aheader::*; use crate::aheader::*;
use crate::context::Context; use crate::context::Context;
@@ -24,8 +24,8 @@ pub enum PeerstateVerifiedStatus {
} }
/// Peerstate represents the state of an Autocrypt peer. /// Peerstate represents the state of an Autocrypt peer.
pub struct Peerstate<'a> { #[derive(Debug, PartialEq, Eq)]
pub context: &'a Context, pub struct Peerstate {
pub addr: String, pub addr: String,
pub last_seen: i64, pub last_seen: i64,
pub last_seen_autocrypt: i64, pub last_seen_autocrypt: i64,
@@ -41,43 +41,49 @@ pub struct Peerstate<'a> {
pub degrade_event: Option<DegradeEvent>, pub degrade_event: Option<DegradeEvent>,
} }
impl<'a> PartialEq for Peerstate<'a> { impl<'a> sqlx::FromRow<'a, sqlx::sqlite::SqliteRow> for Peerstate {
fn eq(&self, other: &Peerstate) -> bool { fn from_row(row: &sqlx::sqlite::SqliteRow) -> Result<Self, sqlx::Error> {
self.addr == other.addr use sqlx::Row;
&& self.last_seen == other.last_seen
&& self.last_seen_autocrypt == other.last_seen_autocrypt
&& self.prefer_encrypt == other.prefer_encrypt
&& self.public_key == other.public_key
&& self.public_key_fingerprint == other.public_key_fingerprint
&& self.gossip_key == other.gossip_key
&& self.gossip_timestamp == other.gossip_timestamp
&& self.gossip_key_fingerprint == other.gossip_key_fingerprint
&& self.verified_key == other.verified_key
&& self.verified_key_fingerprint == other.verified_key_fingerprint
&& self.to_save == other.to_save
&& self.degrade_event == other.degrade_event
}
}
impl<'a> Eq for Peerstate<'a> {} let mut res = Self::new(row.try_get("addr")?);
impl<'a> fmt::Debug for Peerstate<'a> { res.last_seen = row.try_get_unchecked("last_seen")?;
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { res.last_seen_autocrypt = row.try_get_unchecked("last_seen_autocrypt")?;
f.debug_struct("Peerstate") res.prefer_encrypt = row.try_get("prefer_encrypted")?;
.field("addr", &self.addr) res.gossip_timestamp = row.try_get_unchecked("gossip_timestamp")?;
.field("last_seen", &self.last_seen)
.field("last_seen_autocrypt", &self.last_seen_autocrypt) res.public_key_fingerprint = row
.field("prefer_encrypt", &self.prefer_encrypt) .try_get::<Option<String>, _>("public_key_fingerprint")?
.field("public_key", &self.public_key) .map(|fp| fp.parse::<Fingerprint>())
.field("public_key_fingerprint", &self.public_key_fingerprint) .transpose()
.field("gossip_key", &self.gossip_key) .map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
.field("gossip_timestamp", &self.gossip_timestamp) res.gossip_key_fingerprint = row
.field("gossip_key_fingerprint", &self.gossip_key_fingerprint) .try_get::<Option<String>, _>("gossip_key_fingerprint")?
.field("verified_key", &self.verified_key) .map(|fp| fp.parse::<Fingerprint>())
.field("verified_key_fingerprint", &self.verified_key_fingerprint) .transpose()
.field("to_save", &self.to_save) .map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
.field("degrade_event", &self.degrade_event) res.verified_key_fingerprint = row
.finish() .try_get::<Option<String>, _>("verified_key_fingerprint")?
.map(|fp| fp.parse::<Fingerprint>())
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.public_key = row
.try_get::<Option<&[u8]>, _>("public_key")?
.map(|blob| SignedPublicKey::from_slice(blob))
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.gossip_key = row
.try_get::<Option<&[u8]>, _>("gossip_key")?
.map(|blob| SignedPublicKey::from_slice(blob))
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
res.verified_key = row
.try_get::<Option<&[u8]>, _>("verified_key")?
.map(|blob| SignedPublicKey::from_slice(blob))
.transpose()
.map_err(|err| sqlx::Error::Decode(Box::new(err)))?;
Ok(res)
} }
} }
@@ -98,10 +104,9 @@ pub enum DegradeEvent {
FingerprintChanged = 0x02, FingerprintChanged = 0x02,
} }
impl<'a> Peerstate<'a> { impl Peerstate {
pub fn new(context: &'a Context, addr: String) -> Self { pub fn new(addr: String) -> Self {
Peerstate { Peerstate {
context,
addr, addr,
last_seen: 0, last_seen: 0,
last_seen_autocrypt: 0, last_seen_autocrypt: 0,
@@ -118,8 +123,8 @@ impl<'a> Peerstate<'a> {
} }
} }
pub fn from_header(context: &'a Context, header: &Aheader, message_time: i64) -> Self { pub fn from_header(header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(context, header.addr.clone()); let mut res = Self::new(header.addr.clone());
res.last_seen = message_time; res.last_seen = message_time;
res.last_seen_autocrypt = message_time; res.last_seen_autocrypt = message_time;
@@ -131,8 +136,8 @@ impl<'a> Peerstate<'a> {
res res
} }
pub fn from_gossip(context: &'a Context, gossip_header: &Aheader, message_time: i64) -> Self { pub fn from_gossip(gossip_header: &Aheader, message_time: i64) -> Self {
let mut res = Self::new(context, gossip_header.addr.clone()); let mut res = Self::new(gossip_header.addr.clone());
res.gossip_timestamp = message_time; res.gossip_timestamp = message_time;
res.to_save = Some(ToSave::All); res.to_save = Some(ToSave::All);
@@ -142,76 +147,53 @@ impl<'a> Peerstate<'a> {
res res
} }
pub async fn from_addr(context: &'a Context, addr: &str) -> Option<Peerstate<'a>> { pub async fn from_addr(context: &Context, addr: &str) -> Result<Peerstate> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;"; let query = r#"
Self::from_stmt(context, query, paramsv![addr]).await SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key,
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint,
verified_key, verified_key_fingerprint
FROM acpeerstates
WHERE addr=? COLLATE NOCASE;
"#;
Self::from_stmt(context, query, paramsx![addr]).await
} }
pub async fn from_fingerprint( pub async fn from_fingerprint(
context: &'a Context, context: &Context,
_sql: &Sql,
fingerprint: &Fingerprint, fingerprint: &Fingerprint,
) -> Option<Peerstate<'a>> { ) -> Result<Peerstate> {
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \ let query = r#"
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \ SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key,
verified_key, verified_key_fingerprint \ gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint,
FROM acpeerstates \ verified_key, verified_key_fingerprint
WHERE public_key_fingerprint=? COLLATE NOCASE \ FROM acpeerstates
OR gossip_key_fingerprint=? COLLATE NOCASE \ WHERE public_key_fingerprint=? COLLATE NOCASE
ORDER BY public_key_fingerprint=? DESC;"; OR gossip_key_fingerprint=? COLLATE NOCASE
let fp = fingerprint.hex(); ORDER BY public_key_fingerprint=? DESC;
Self::from_stmt(context, query, paramsv![fp, fp, fp]).await "#;
let fingerprint = fingerprint.hex();
Self::from_stmt(
context,
query,
paramsx![&fingerprint, &fingerprint, &fingerprint],
)
.await
} }
async fn from_stmt( async fn from_stmt<'a, P: sqlx::IntoArguments<'a, sqlx::sqlite::Sqlite> + 'a>(
context: &'a Context, context: &Context,
query: &str, query: &'a str,
params: Vec<&dyn crate::ToSql>, params: P,
) -> Option<Peerstate<'a>> { ) -> Result<Peerstate> {
context /* all the above queries start with this: SELECT
.sql addr, last_seen, last_seen_autocrypt, prefer_encrypted,
.query_row(query, params, |row| { public_key, gossip_timestamp, gossip_key, public_key_fingerprint,
/* all the above queries start with this: SELECT gossip_key_fingerprint, verified_key, verified_key_fingerprint
addr, last_seen, last_seen_autocrypt, prefer_encrypted, */
public_key, gossip_timestamp, gossip_key, public_key_fingerprint, let peerstate = context.sql.query_row(query, params).await?;
gossip_key_fingerprint, verified_key, verified_key_fingerprint
*/
let mut res = Self::new(context, row.get(0)?);
res.last_seen = row.get(1)?; Ok(peerstate)
res.last_seen_autocrypt = row.get(2)?;
res.prefer_encrypt = EncryptPreference::from_i32(row.get(3)?).unwrap_or_default();
res.gossip_timestamp = row.get(5)?;
res.public_key_fingerprint = row
.get::<_, Option<String>>(7)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.gossip_key_fingerprint = row
.get::<_, Option<String>>(8)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.verified_key_fingerprint = row
.get::<_, Option<String>>(10)?
.map(|s| s.parse::<Fingerprint>())
.transpose()?;
res.public_key = row
.get(4)
.ok()
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
res.gossip_key = row
.get(6)
.ok()
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
res.verified_key = row
.get(9)
.ok()
.and_then(|blob: Vec<u8>| SignedPublicKey::from_slice(&blob).ok());
Ok(res)
})
.await
.ok()
} }
pub fn recalc_fingerprint(&mut self) { pub fn recalc_fingerprint(&mut self) {
@@ -403,19 +385,21 @@ impl<'a> Peerstate<'a> {
if create { if create {
sql.execute( sql.execute(
"INSERT INTO acpeerstates (addr) VALUES(?);", "INSERT INTO acpeerstates (addr) VALUES(?);",
paramsv![self.addr], paramsx![&self.addr],
) )
.await?; .await?;
} }
if self.to_save == Some(ToSave::All) || create { if self.to_save == Some(ToSave::All) || create {
sql.execute( sql.execute(
"UPDATE acpeerstates \ r#"
SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \ UPDATE acpeerstates
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \ SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?,
verified_key=?, verified_key_fingerprint=? \ public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?,
WHERE addr=?;", verified_key=?, verified_key_fingerprint=?
paramsv![ WHERE addr=?;
"#,
paramsx![
self.last_seen, self.last_seen,
self.last_seen_autocrypt, self.last_seen_autocrypt,
self.prefer_encrypt as i64, self.prefer_encrypt as i64,
@@ -426,18 +410,17 @@ impl<'a> Peerstate<'a> {
self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()), self.gossip_key_fingerprint.as_ref().map(|fp| fp.hex()),
self.verified_key.as_ref().map(|k| k.to_bytes()), self.verified_key.as_ref().map(|k| k.to_bytes()),
self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()), self.verified_key_fingerprint.as_ref().map(|fp| fp.hex()),
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(
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \ "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? WHERE addr=?;",
WHERE addr=?;", paramsx![
paramsv![
self.last_seen, self.last_seen,
self.last_seen_autocrypt, self.last_seen_autocrypt,
self.gossip_timestamp, self.gossip_timestamp,
self.addr &self.addr
], ],
) )
.await?; .await?;
@@ -455,18 +438,11 @@ impl<'a> Peerstate<'a> {
} }
} }
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::*;
use crate::test_utils::*; use crate::test_utils::*;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tempfile::TempDir;
#[async_std::test] #[async_std::test]
async fn test_peerstate_save_to_db() { async fn test_peerstate_save_to_db() {
@@ -476,7 +452,6 @@ mod tests {
let pub_key = alice_keypair().public; let pub_key = alice_keypair().public;
let mut peerstate = Peerstate { let mut peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(), addr: addr.into(),
last_seen: 10, last_seen: 10,
last_seen_autocrypt: 11, last_seen_autocrypt: 11,
@@ -504,10 +479,9 @@ mod tests {
// clear to_save, as that is not persissted // clear to_save, as that is not persissted
peerstate.to_save = None; peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new); assert_eq!(peerstate, peerstate_new);
let peerstate_new2 = let peerstate_new2 = Peerstate::from_fingerprint(&ctx.ctx, &pub_key.fingerprint())
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint()) .await
.await .expect("failed to load peerstate from db");
.expect("failed to load peerstate from db");
assert_eq!(peerstate, peerstate_new2); assert_eq!(peerstate, peerstate_new2);
} }
@@ -518,7 +492,6 @@ mod tests {
let pub_key = alice_keypair().public; let pub_key = alice_keypair().public;
let peerstate = Peerstate { let peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(), addr: addr.into(),
last_seen: 10, last_seen: 10,
last_seen_autocrypt: 11, last_seen_autocrypt: 11,
@@ -552,7 +525,6 @@ mod tests {
let pub_key = alice_keypair().public; let pub_key = alice_keypair().public;
let mut peerstate = Peerstate { let mut peerstate = Peerstate {
context: &ctx.ctx,
addr: addr.into(), addr: addr.into(),
last_seen: 10, last_seen: 10,
last_seen_autocrypt: 11, last_seen_autocrypt: 11,
@@ -581,11 +553,4 @@ mod tests {
peerstate.to_save = None; peerstate.to_save = None;
assert_eq!(peerstate, peerstate_new); assert_eq!(peerstate, peerstate_new);
} }
// TODO: don't copy this from stress.rs
#[allow(dead_code)]
struct TestContext {
ctx: Context,
dir: TempDir,
}
} }

View File

@@ -138,10 +138,10 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let mut lot = Lot::new(); let mut lot = Lot::new();
// retrieve known state for this fingerprint // retrieve known state for this fingerprint
let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint).await; let peerstate = Peerstate::from_fingerprint(context, &fingerprint).await;
if invitenumber.is_none() || auth.is_none() { if invitenumber.is_none() || auth.is_none() {
if let Some(peerstate) = peerstate { if let Ok(peerstate) = peerstate {
lot.state = LotState::QrFprOk; lot.state = LotState::QrFprOk;
lot.id = Contact::add_or_lookup( lot.id = Contact::add_or_lookup(

View File

@@ -152,81 +152,69 @@ async fn get_self_fingerprint(context: &Context) -> Option<Fingerprint> {
} }
} }
async fn cleanup(
context: &Context,
contact_chat_id: ChatId,
ongoing_allocated: bool,
join_vg: bool,
) -> ChatId {
let mut bob = context.bob.write().await;
bob.expects = 0;
let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS {
if join_vg {
chat::get_chat_id_by_grpid(
context,
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
)
.await
.unwrap_or((ChatId::new(0), false, Blocked::Not))
.0
} else {
contact_chat_id
}
} else {
ChatId::new(0)
};
bob.qr_scan = None;
if ongoing_allocated {
context.free_ongoing().await;
}
ret_chat_id
}
/// Take a scanned QR-code and do the setup-contact/join-group handshake. /// Take a scanned QR-code and do the setup-contact/join-group handshake.
/// See the ffi-documentation for more details. /// See the ffi-documentation for more details.
pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
if context.alloc_ongoing().await.is_err() { if let Err(err) = context.alloc_ongoing().await {
return cleanup(&context, ChatId::new(0), false, false).await; error!(context, "SecureJoin Error: {}", err);
return ChatId::new(0);
} }
securejoin(context, qr).await match securejoin(context, qr).await {
Ok(id) => id,
Err(err) => {
error!(context, "SecureJoin Error: {}", err);
ChatId::new(0)
}
}
} }
async fn securejoin(context: &Context, qr: &str) -> ChatId { /// Bob - the joiner's side
/*======================================================== /// Step 2 in "Setup verified contact" protocol
==== Bob - the joiner's side ===== async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, anyhow::Error> {
==== Step 2 in "Setup verified contact" protocol ===== struct DropGuard<'a> {
========================================================*/ context: &'a Context,
}
let mut contact_chat_id = ChatId::new(0); impl Drop for DropGuard<'_> {
let mut join_vg: bool = false; fn drop(&mut self) {
async_std::task::block_on(async {
let mut bob = self.context.bob.write().await;
bob.expects = 0;
bob.qr_scan = None;
self.context.free_ongoing().await;
});
}
}
info!(context, "Requesting secure-join ...",); let _guard = DropGuard { context: &context };
info!(context, "Requesting secure-join ...");
ensure_secret_key_exists(context).await.ok(); ensure_secret_key_exists(context).await.ok();
let qr_scan = check_qr(context, &qr).await; let qr_scan = check_qr(context, &qr).await;
if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup
{ {
error!(context, "Unknown QR code.",); bail!("Unknown QR code.");
return cleanup(&context, contact_chat_id, true, join_vg).await;
} }
contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await { let contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await {
Ok(chat_id) => chat_id, Ok(chat_id) => chat_id,
Err(_) => { Err(_) => {
error!(context, "Unknown contact."); bail!("Unknown contact.");
return cleanup(&context, contact_chat_id, true, join_vg).await;
} }
}; };
if context.shall_stop_ongoing().await { if context.shall_stop_ongoing().await {
return cleanup(&context, contact_chat_id, true, join_vg).await; bail!("Interrupted");
} }
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
let join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
{ {
let mut bob = context.bob.write().await; let mut bob = context.bob.write().await;
bob.status = 0; bob.status = 0;
bob.qr_scan = Some(qr_scan); bob.qr_scan = Some(qr_scan);
} }
if fingerprint_equals_sender(
let fp_equals_sender = fingerprint_equals_sender(
context, context,
context context
.bob .bob
@@ -240,21 +228,22 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
.unwrap(), .unwrap(),
contact_chat_id, contact_chat_id,
) )
.await .await?;
{
if fp_equals_sender {
// the scanned fingerprint matches Alice's key, // the scanned fingerprint matches Alice's key,
// we can proceed to step 4b) directly and save two mails // we can proceed to step 4b) directly and save two mails
info!(context, "Taking protocol shortcut."); info!(context, "Taking protocol shortcut.");
context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM; context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!( joiner_progress!(
context, context,
chat_id_2_contact_id(context, contact_chat_id).await, chat_id_2_contact_id(context, contact_chat_id).await?,
400 400
); );
let own_fingerprint = get_self_fingerprint(context).await; let own_fingerprint = get_self_fingerprint(context).await;
// Bob -> Alice // Bob -> Alice
if let Err(err) = send_handshake_msg( send_handshake_msg(
context, context,
contact_chat_id, contact_chat_id,
if join_vg { if join_vg {
@@ -270,16 +259,12 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
"".to_string() "".to_string()
}, },
) )
.await .await?;
{
error!(context, "failed to send handshake message: {}", err);
return cleanup(&context, contact_chat_id, true, join_vg).await;
}
} else { } else {
context.bob.write().await.expects = DC_VC_AUTH_REQUIRED; context.bob.write().await.expects = DC_VC_AUTH_REQUIRED;
// Bob -> Alice // Bob -> Alice
if let Err(err) = send_handshake_msg( send_handshake_msg(
context, context,
contact_chat_id, contact_chat_id,
if join_vg { "vg-request" } else { "vc-request" }, if join_vg { "vg-request" } else { "vc-request" },
@@ -287,11 +272,7 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
None, None,
"", "",
) )
.await .await?;
{
error!(context, "failed to send handshake message: {}", err);
return cleanup(&context, contact_chat_id, true, join_vg).await;
}
} }
if join_vg { if join_vg {
@@ -299,12 +280,23 @@ async fn securejoin(context: &Context, qr: &str) -> ChatId {
while !context.shall_stop_ongoing().await { while !context.shall_stop_ongoing().await {
async_std::task::sleep(Duration::from_millis(50)).await; async_std::task::sleep(Duration::from_millis(50)).await;
} }
cleanup(&context, contact_chat_id, true, join_vg).await let bob = context.bob.read().await;
if bob.status == DC_BOB_SUCCESS {
let id = chat::get_chat_id_by_grpid(
context,
bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(),
)
.await?
.0;
Ok(id)
} else {
bail!("Failed to join");
}
} else { } else {
// for a one-to-one-chat, the chat is already known, return the chat-id, // for a one-to-one-chat, the chat is already known, return the chat-id,
// the verification runs in background // the verification runs in background
context.free_ongoing().await; context.free_ongoing().await;
contact_chat_id Ok(contact_chat_id)
} }
} }
@@ -351,12 +343,12 @@ async fn send_handshake_msg(
Ok(()) Ok(())
} }
async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> Result<u32, Error> {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await; let contacts = chat::get_chat_contacts(context, contact_chat_id).await?;
if contacts.len() == 1 { if contacts.len() == 1 {
contacts[0] Ok(contacts[0])
} else { } else {
0 Ok(0)
} }
} }
@@ -364,22 +356,24 @@ async fn fingerprint_equals_sender(
context: &Context, context: &Context,
fingerprint: &Fingerprint, fingerprint: &Fingerprint,
contact_chat_id: ChatId, contact_chat_id: ChatId,
) -> bool { ) -> Result<bool, Error> {
let contacts = chat::get_chat_contacts(context, contact_chat_id).await; let contacts = chat::get_chat_contacts(context, contact_chat_id).await?;
if contacts.len() == 1 { if contacts.len() == 1 {
if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await { if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await {
if let Some(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await { if let Ok(peerstate) = Peerstate::from_addr(context, contact.get_addr()).await {
if peerstate.public_key_fingerprint.is_some() if peerstate.public_key_fingerprint.is_some()
&& fingerprint == peerstate.public_key_fingerprint.as_ref().unwrap() && fingerprint == peerstate.public_key_fingerprint.as_ref().unwrap()
{ {
return true; return Ok(true);
} }
} }
} }
} }
false
Ok(false)
} }
#[derive(Debug, thiserror::Error)] #[derive(Debug, thiserror::Error)]
pub(crate) enum HandshakeError { pub(crate) enum HandshakeError {
#[error("Can not be called with special contact ID")] #[error("Can not be called with special contact ID")]
@@ -400,6 +394,8 @@ pub(crate) enum HandshakeError {
MsgSendFailed(#[source] Error), MsgSendFailed(#[source] Error),
#[error("Failed to parse fingerprint")] #[error("Failed to parse fingerprint")]
BadFingerprint(#[from] crate::key::FingerprintError), BadFingerprint(#[from] crate::key::FingerprintError),
#[error("{0}")]
Other(#[from] Error),
} }
/// What to do with a Secure-Join handshake message after it was handled. /// What to do with a Secure-Join handshake message after it was handled.
@@ -533,20 +529,20 @@ pub(crate) async fn handle_securejoin_handshake(
"Not encrypted." "Not encrypted."
}, },
) )
.await; .await?;
context.bob.write().await.status = 0; // secure-join failed context.bob.write().await.status = 0; // secure-join failed
context.stop_ongoing().await; context.stop_ongoing().await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id)
.await .await?
{ {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on joiner-side.", "Fingerprint mismatch on joiner-side.",
) )
.await; .await?;
context.bob.write().await.status = 0; // secure-join failed context.bob.write().await.status = 0; // secure-join failed
context.stop_ongoing().await; context.stop_ongoing().await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
@@ -589,7 +585,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id, contact_chat_id,
"Fingerprint not provided.", "Fingerprint not provided.",
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
@@ -599,16 +595,16 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id, contact_chat_id,
"Auth not encrypted.", "Auth not encrypted.",
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await { if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await? {
could_not_establish_secure_connection( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on inviter-side.", "Fingerprint mismatch on inviter-side.",
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
info!(context, "Fingerprint verified.",); info!(context, "Fingerprint verified.",);
@@ -621,13 +617,13 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id, contact_chat_id,
"Auth not provided.", "Auth not provided.",
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
if !token::exists(context, token::Namespace::Auth, &auth_0).await { if !token::exists(context, token::Namespace::Auth, &auth_0).await {
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.") could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.")
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
if mark_peer_as_verified(context, &fingerprint).await.is_err() { if mark_peer_as_verified(context, &fingerprint).await.is_err() {
@@ -636,12 +632,12 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on inviter-side.", "Fingerprint mismatch on inviter-side.",
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await; Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await;
info!(context, "Auth verified.",); info!(context, "Auth verified.",);
secure_connection_established(context, contact_chat_id).await; secure_connection_established(context, contact_chat_id).await?;
emit_event!(context, Event::ContactsChanged(Some(contact_id))); emit_event!(context, Event::ContactsChanged(Some(contact_id)));
inviter_progress!(context, contact_id, 600); inviter_progress!(context, contact_id, 600);
if join_vg { if join_vg {
@@ -744,7 +740,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id, contact_chat_id,
"Contact confirm message not encrypted.", "Contact confirm message not encrypted.",
) )
.await; .await?;
context.bob.write().await.status = 0; context.bob.write().await.status = 0;
return Ok(abort_retval); return Ok(abort_retval);
} }
@@ -758,7 +754,7 @@ pub(crate) async fn handle_securejoin_handshake(
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on joiner-side.", "Fingerprint mismatch on joiner-side.",
) )
.await; .await?;
return Ok(abort_retval); return Ok(abort_retval);
} }
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined).await; Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined).await;
@@ -776,7 +772,7 @@ pub(crate) async fn handle_securejoin_handshake(
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return Ok(abort_retval); return Ok(abort_retval);
} }
secure_connection_established(context, contact_chat_id).await; secure_connection_established(context, contact_chat_id).await?;
context.bob.write().await.expects = 0; context.bob.write().await.expects = 0;
// Bob -> Alice // Bob -> Alice
@@ -901,7 +897,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
contact_chat_id, contact_chat_id,
"Message not encrypted correctly.", "Message not encrypted correctly.",
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint) let fingerprint: Fingerprint = match mime_message.get(HeaderDef::SecureJoinFingerprint)
@@ -913,7 +909,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
contact_chat_id, contact_chat_id,
"Fingerprint not provided, please update Delta Chat on all your devices.", "Fingerprint not provided, please update Delta Chat on all your devices.",
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
@@ -923,7 +919,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
contact_chat_id, contact_chat_id,
format!("Fingerprint mismatch on observing {}.", step).as_ref(), format!("Fingerprint mismatch on observing {}.", step).as_ref(),
) )
.await; .await?;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
Ok(if step.as_str() == "vg-member-added" { Ok(if step.as_str() == "vg-member-added" {
@@ -936,8 +932,11 @@ pub(crate) async fn observe_securejoin_on_other_device(
} }
} }
async fn secure_connection_established(context: &Context, contact_chat_id: ChatId) { async fn secure_connection_established(
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await; context: &Context,
contact_chat_id: ChatId,
) -> Result<(), HandshakeError> {
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await?;
let contact = Contact::get_by_id(context, contact_id).await; let contact = Contact::get_by_id(context, contact_id).await;
let addr = if let Ok(ref contact) = contact { let addr = if let Ok(ref contact) = contact {
@@ -950,14 +949,16 @@ async fn secure_connection_established(context: &Context, contact_chat_id: ChatI
.await; .await;
chat::add_info_msg(context, contact_chat_id, msg).await; chat::add_info_msg(context, contact_chat_id, msg).await;
emit_event!(context, Event::ChatModified(contact_chat_id)); emit_event!(context, Event::ChatModified(contact_chat_id));
Ok(())
} }
async fn could_not_establish_secure_connection( async fn could_not_establish_secure_connection(
context: &Context, context: &Context,
contact_chat_id: ChatId, contact_chat_id: ChatId,
details: &str, details: &str,
) { ) -> Result<(), HandshakeError> {
let contact_id = chat_id_2_contact_id(context, contact_chat_id).await; let contact_id = chat_id_2_contact_id(context, contact_chat_id).await?;
let contact = Contact::get_by_id(context, contact_id).await; let contact = Contact::get_by_id(context, contact_id).await;
let msg = context let msg = context
.stock_string_repl_str( .stock_string_repl_str(
@@ -972,12 +973,11 @@ async fn could_not_establish_secure_connection(
chat::add_info_msg(context, contact_chat_id, &msg).await; chat::add_info_msg(context, contact_chat_id, &msg).await;
error!(context, "{} ({})", &msg, details); error!(context, "{} ({})", &msg, details);
Ok(())
} }
async fn mark_peer_as_verified(context: &Context, fingerprint: &Fingerprint) -> Result<(), Error> { async fn mark_peer_as_verified(context: &Context, fingerprint: &Fingerprint) -> Result<(), Error> {
if let Some(ref mut peerstate) = if let Ok(ref mut peerstate) = Peerstate::from_fingerprint(context, fingerprint).await {
Peerstate::from_fingerprint(context, &context.sql, fingerprint).await
{
if peerstate.set_verified( if peerstate.set_verified(
PeerstateKeyType::PublicKey, PeerstateKeyType::PublicKey,
fingerprint, fingerprint,
@@ -1031,31 +1031,21 @@ fn encrypted_and_signed(
} }
} }
pub async fn handle_degrade_event( pub async fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> {
context: &Context,
peerstate: &Peerstate<'_>,
) -> Result<(), Error> {
// - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal // - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal
// - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes // - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes
// together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother // together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
// with things they cannot fix, so the user is just kicked from the verified group // with things they cannot fix, so the user is just kicked from the verified group
// (and he will know this and can fix this) // (and he will know this and can fix this)
if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event { if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event {
let contact_id: i32 = match context let contact_id: i32 = context
.sql .sql
.query_get_value( .query_value(
context,
"SELECT id FROM contacts WHERE addr=?;", "SELECT id FROM contacts WHERE addr=?;",
paramsv![peerstate.addr], paramsx![&peerstate.addr],
) )
.await .await?;
{
None => bail!(
"contact with peerstate.addr {:?} not found",
&peerstate.addr
),
Some(contact_id) => contact_id,
};
if contact_id > 0 { if contact_id > 0 {
let (contact_chat_id, _) = let (contact_chat_id, _) =
chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop) chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop)

1334
src/sql.rs

File diff suppressed because it is too large Load Diff

13
src/sql/macros.rs Normal file
View File

@@ -0,0 +1,13 @@
#[macro_export]
macro_rules! paramsx {
() => {
sqlx::sqlite::SqliteArguments::default()
};
($($param:expr),+ $(,)?) => {{
use sqlx::Arguments;
let mut args = sqlx::sqlite::SqliteArguments::default();
$(args.add($param);)+
args
}};
}

378
src/sql/migrations.rs Normal file
View File

@@ -0,0 +1,378 @@
use super::{Error, Result, Sql};
use crate::constants::ShowEmails;
use crate::context::Context;
/// Executes all migrations required to get from the passed in `dbversion` to the latest.
pub async fn run(
context: &Context,
sql: &Sql,
dbversion: i32,
exists_before_update: bool,
) -> Result<()> {
let migrate = |version: i32, stmt: &'static str| async move {
if dbversion < version {
info!(context, "[migration] v{}", version);
sql.execute_batch(stmt).await?;
sql.set_raw_config_int(context, "dbversion", version)
.await?;
}
Ok::<_, Error>(())
};
migrate(
0,
r#"
CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);
CREATE INDEX config_index1 ON config (keyname);
CREATE TABLE contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT DEFAULT '',
addr TEXT DEFAULT '' COLLATE NOCASE,
origin INTEGER DEFAULT 0,
blocked INTEGER DEFAULT 0,
last_seen INTEGER DEFAULT 0,
param TEXT DEFAULT '');
CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);
CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);
INSERT INTO contacts (id,name,origin) VALUES
(1,'self',262144), (2,'info',262144), (3,'rsvd',262144),
(4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144),
(7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);
CREATE TABLE chats (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type INTEGER DEFAULT 0,
name TEXT DEFAULT '',
draft_timestamp INTEGER DEFAULT 0,
draft_txt TEXT DEFAULT '',
blocked INTEGER DEFAULT 0,
grpid TEXT DEFAULT '',
param TEXT DEFAULT '');
CREATE INDEX chats_index1 ON chats (grpid);
CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);
CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);
INSERT INTO chats (id,type,name) VALUES
(1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'),
(4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'),
(7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');
CREATE TABLE msgs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rfc724_mid TEXT DEFAULT '',
server_folder TEXT DEFAULT '',
server_uid INTEGER DEFAULT 0,
chat_id INTEGER DEFAULT 0,
from_id INTEGER DEFAULT 0,
to_id INTEGER DEFAULT 0,
timestamp INTEGER DEFAULT 0,
type INTEGER DEFAULT 0,
state INTEGER DEFAULT 0,
msgrmsg INTEGER DEFAULT 1,
bytes INTEGER DEFAULT 0,
txt TEXT DEFAULT '',
txt_raw TEXT DEFAULT '',
param TEXT DEFAULT '');
CREATE INDEX msgs_index1 ON msgs (rfc724_mid);
CREATE INDEX msgs_index2 ON msgs (chat_id);
CREATE INDEX msgs_index3 ON msgs (timestamp);
CREATE INDEX msgs_index4 ON msgs (state);
INSERT INTO msgs (id,msgrmsg,txt) VALUES
(1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'),
(4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'),
(8,0,'rsvd'), (9,0,'daymarker');
CREATE TABLE jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
added_timestamp INTEGER,
desired_timestamp INTEGER DEFAULT 0,
action INTEGER,
foreign_id INTEGER,
param TEXT DEFAULT '');
CREATE INDEX jobs_index1 ON jobs (desired_timestamp);
"#,
)
.await?;
migrate(
1,
r#"
CREATE TABLE leftgrps (
id INTEGER PRIMARY KEY,
grpid TEXT DEFAULT '');
CREATE INDEX leftgrps_index1 ON leftgrps (grpid);
"#,
)
.await?;
migrate(
2,
r#"
ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';
"#,
)
.await?;
migrate(
7,
r#"
CREATE TABLE keypairs (
id INTEGER PRIMARY KEY,
addr TEXT DEFAULT '' COLLATE NOCASE,
is_default INTEGER DEFAULT 0,
private_key,
public_key,
created INTEGER DEFAULT 0);
"#,
)
.await?;
migrate(
10,
r#"
CREATE TABLE acpeerstates (
id INTEGER PRIMARY KEY,
addr TEXT DEFAULT '' COLLATE NOCASE,
last_seen INTEGER DEFAULT 0,
last_seen_autocrypt INTEGER DEFAULT 0,
public_key,
prefer_encrypted INTEGER DEFAULT 0);
"#,
)
.await?;
migrate(
12,
r#"
CREATE TABLE msgs_mdns (
msg_id INTEGER,
contact_id INTEGER);
CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);
"#,
)
.await?;
migrate(
17,
r#"
ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;
CREATE INDEX chats_index2 ON chats (archived);
ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;
CREATE INDEX msgs_index5 ON msgs (starred);
"#,
)
.await?;
migrate(
18,
r#"
ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;
ALTER TABLE acpeerstates ADD COLUMN gossip_key;
"#,
)
.await?;
// chat.id=1 and chat.id=2 are the old deaddrops,
// the current ones are defined by chats.blocked=2
migrate(
27,
r#"
DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;
CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);
ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
34,
r#"
ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;
ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;
ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';
ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';
CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);
CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);
"#,
)
.await?;
migrate(
39,
r#"
CREATE TABLE tokens (
id INTEGER PRIMARY KEY,
namespc INTEGER DEFAULT 0,
foreign_id INTEGER DEFAULT 0,
token TEXT DEFAULT '',
timestamp INTEGER DEFAULT 0);
ALTER TABLE acpeerstates ADD COLUMN verified_key;
ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';
CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);
"#,
)
.await?;
migrate(
40,
r#"
ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
44,
r#"
ALTER TABLE msgs ADD COLUMN mime_headers TEXT;
"#,
)
.await?;
migrate(
46,
r#"
ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;
ALTER TABLE msgs ADD COLUMN mime_references TEXT;
"#,
)
.await?;
migrate(
47,
r#"
ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;
"#,
)
.await?;
// NOTE: move_state is not used anymore
migrate(
48,
r#"
ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;
"#,
)
.await?;
migrate(
49,
r#"
ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;
"#,
)
.await?;
if dbversion < 50 {
info!(context, "[migration] v50");
// installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly;
// keep this default and use DC_SHOW_EMAILS_NO
// only for new installations
if exists_before_update {
sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32)
.await?;
}
sql.set_raw_config_int(context, "dbversion", 50).await?;
}
// the messages containing _only_ locations
// are also added to the database as _hidden_.
migrate(
53,
r#"
CREATE TABLE locations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
latitude REAL DEFAULT 0.0,
longitude REAL DEFAULT 0.0,
accuracy REAL DEFAULT 0.0,
timestamp INTEGER DEFAULT 0,
chat_id INTEGER DEFAULT 0,
from_id INTEGER DEFAULT 0);
CREATE INDEX locations_index1 ON locations (from_id);
CREATE INDEX locations_index2 ON locations (timestamp);
ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;
ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;
ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;
CREATE INDEX chats_index3 ON chats (locations_send_until);
"#,
)
.await?;
migrate(
54,
r#"
ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;
CREATE INDEX msgs_index6 ON msgs (location_id);
"#,
)
.await?;
migrate(
55,
r#"
ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
59,
r#"
CREATE TABLE devmsglabels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT,
msg_id INTEGER DEFAULT 0);
CREATE INDEX devmsglabels_index1 ON devmsglabels (label);
"#,
)
.await?;
// records in the devmsglabels are kept when the message is deleted.
// so, msg_id may or may not exist.
if dbversion < 59 && exists_before_update && sql.get_raw_config_int("bcc_self").await.is_none()
{
sql.set_raw_config_int(context, "bcc_self", 1).await?;
}
migrate(
60,
r#"
ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
61,
r#"
ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
62,
r#"
ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;
"#,
)
.await?;
migrate(
63,
r#"
UPDATE chats SET grpid='' WHERE type=100;
"#,
)
.await?;
migrate(
64,
r#"
ALTER TABLE msgs ADD COLUMN error TEXT DEFAULT '';
"#,
)
.await?;
Ok(())
}

706
src/sql/mod.rs Normal file
View File

@@ -0,0 +1,706 @@
//! # SQLite wrapper
use std::collections::HashSet;
use std::path::Path;
use async_std::prelude::*;
use async_std::sync::RwLock;
use sqlx::sqlite::*;
use crate::chat::{update_device_icon, update_saved_messages_icon};
use crate::constants::DC_CHAT_ID_TRASH;
use crate::context::Context;
use crate::dc_tools::*;
use crate::param::*;
use crate::peerstate::*;
#[macro_use]
mod macros;
mod migrations;
pub use macros::*;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Sqlite: Connection closed")]
SqlNoConnection,
#[error("Sqlite: Already open")]
SqlAlreadyOpen,
#[error("Sqlite: Failed to open")]
SqlFailedToOpen,
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0:?}")]
BlobError(#[from] crate::blob::BlobError),
#[error("{0}")]
Other(#[from] crate::error::Error),
#[error("{0}")]
Sqlx(#[from] sqlx::Error),
#[error("{0}: {1}")]
SqlxWithContext(String, #[source] sqlx::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
/// A wrapper around the underlying Sqlite3 object.
#[derive(Debug)]
pub struct Sql {
xpool: RwLock<Option<sqlx::SqlitePool>>,
}
impl Default for Sql {
fn default() -> Self {
Self {
xpool: RwLock::new(None),
}
}
}
impl Sql {
pub fn new() -> Sql {
Self::default()
}
/// Returns `true` if there is a working sqlite connection, `false` otherwise.
pub async fn is_open(&self) -> bool {
let pool = self.xpool.read().await;
pool.is_some() && !pool.as_ref().unwrap().is_closed()
}
/// Shuts down all sqlite connections.
pub async fn close(&self) {
if let Some(pool) = self.xpool.write().await.take() {
pool.close().await;
}
}
pub async fn open<T: AsRef<Path>>(
&self,
context: &Context,
dbfile: T,
readonly: bool,
) -> crate::error::Result<()> {
if let Err(err) = open(context, self, dbfile, readonly).await {
return match err.downcast_ref::<Error>() {
Some(Error::SqlAlreadyOpen) => Err(err),
_ => {
self.close().await;
Err(err)
}
};
}
Ok(())
}
/// Execute a single query.
pub async fn execute<'a, P>(&self, statement: &'a str, params: P) -> Result<usize>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let count = sqlx::query_with(statement, params).execute(xpool).await?;
Ok(count as usize)
}
/// Execute a list of statements, without any bindings
pub async fn execute_batch(&self, statement: &str) -> Result<()> {
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
sqlx::query(statement.as_ref()).execute(xpool).await?;
Ok(())
}
pub async fn get_pool(&self) -> Result<SqlitePool> {
let lock = self.xpool.read().await;
lock.as_ref().cloned().ok_or_else(|| Error::SqlNoConnection)
}
/// Starts a new transaction.
pub async fn begin(
&self,
) -> Result<sqlx::Transaction<'static, Sqlite, sqlx::pool::PoolConnection<Sqlite>>> {
let lock = self.xpool.read().await;
let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let tx = pool.begin().await?;
Ok(tx)
}
/// Execute a query which is expected to return zero or more rows.
pub async fn query_rows<'a, T, P>(&self, statement: &'a str, params: P) -> Result<Vec<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let rows = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row))
.fetch_all(xpool)
.await?;
Ok(rows)
}
/// Execute a query which is expected to return zero or more rows.
pub async fn query_values<'a, T, P>(&self, statement: &'a str, params: P) -> Result<Vec<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::decode::Decode<'b, Sqlite>,
T: sqlx::Type<Sqlite>,
T: 'static + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let rows = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| {
let (val,): (T,) = sqlx::FromRow::from_row(&row)?;
Ok(val)
})
.fetch_all(xpool)
.await?;
Ok(rows)
}
/// Return `true` if a query in the SQL statement it executes returns one or more
/// rows and false if the SQL returns an empty set.
pub async fn exists<'a, P>(&self, statement: &'a str, params: P) -> Result<bool>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let mut rows = sqlx::query_with(statement, params).fetch(xpool);
match rows.next().await {
Some(Ok(_)) => Ok(true),
None => Ok(false),
Some(Err(sqlx::Error::RowNotFound)) => Ok(false),
Some(Err(err)) => Err(Error::SqlxWithContext(
format!("exists: '{}'", statement),
err,
)),
}
}
/// Execute a query which is expected to return one row.
pub async fn query_row<'a, T, P>(&self, statement: &'a str, params: P) -> Result<T>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let row = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row))
.fetch_one(xpool)
.await?;
Ok(row)
}
/// Execute a query which is expected to return zero or one row.
pub async fn query_row_optional<'a, T, P>(
&self,
statement: &'a str,
params: P,
) -> Result<Option<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::FromRow<'b, SqliteRow> + Unpin + Send,
{
let lock = self.xpool.read().await;
let xpool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?;
let row = sqlx::query_with(statement.as_ref(), params)
.try_map(|row: SqliteRow| sqlx::FromRow::from_row(&row))
.fetch_optional(xpool)
.await?;
Ok(row)
}
pub async fn query_value_optional<'a, T, P>(
&self,
statement: &'a str,
params: P,
) -> Result<Option<T>>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::decode::Decode<'b, Sqlite>,
T: sqlx::Type<Sqlite>,
T: 'static + Unpin + Send,
{
match self.query_row_optional(statement, params).await? {
Some((val,)) => Ok(Some(val)),
None => Ok(None),
}
}
pub async fn query_value<'a, T, P>(&self, statement: &'a str, params: P) -> Result<T>
where
P: sqlx::IntoArguments<'a, Sqlite> + 'a,
T: for<'b> sqlx::decode::Decode<'b, Sqlite>,
T: sqlx::Type<Sqlite>,
T: 'static + Unpin + Send,
{
let (val,): (T,) = self.query_row(statement, params).await?;
Ok(val)
}
pub async fn table_exists(&self, name: impl AsRef<str>) -> Result<bool> {
self.exists(
"SELECT name FROM sqlite_master WHERE type = 'table' AND name=?",
paramsx![name.as_ref()],
)
.await
}
/// Set private configuration options.
///
/// Setting `None` deletes the value. On failure an error message
/// will already have been logged.
pub async fn set_raw_config(
&self,
_context: &Context,
key: impl AsRef<str>,
value: Option<&str>,
) -> Result<()> {
let key = key.as_ref();
if let Some(ref value) = value {
let exists = self
.exists("SELECT value FROM config WHERE keyname=?;", paramsx![key])
.await?;
if exists {
self.execute(
"UPDATE config SET value=? WHERE keyname=?;",
paramsx![value, key],
)
.await?;
} else {
self.execute(
"INSERT INTO config (keyname, value) VALUES (?, ?);",
paramsx![key, value],
)
.await?;
}
} else {
self.execute("DELETE FROM config WHERE keyname=?;", paramsx![key])
.await?;
}
Ok(())
}
/// Get configuration options from the database.
pub async fn get_raw_config(&self, key: impl AsRef<str>) -> Option<String> {
if !self.is_open().await || key.as_ref().is_empty() {
return None;
}
self.query_row(
"SELECT value FROM config WHERE keyname=?;",
paramsx![key.as_ref().to_string()],
)
.await
.ok()
.map(|(res,)| res)
}
pub async fn set_raw_config_int(
&self,
context: &Context,
key: impl AsRef<str>,
value: i32,
) -> Result<()> {
self.set_raw_config(context, key, Some(&format!("{}", value)))
.await
}
pub async fn get_raw_config_int(&self, key: impl AsRef<str>) -> Option<i32> {
self.get_raw_config(key).await.and_then(|s| s.parse().ok())
}
pub async fn get_raw_config_bool(&self, key: impl AsRef<str>) -> bool {
// Not the most obvious way to encode bool as string, but it is matter
// of backward compatibility.
let res = self.get_raw_config_int(key).await;
res.unwrap_or_default() > 0
}
pub async fn set_raw_config_bool<T>(&self, context: &Context, key: T, value: bool) -> Result<()>
where
T: AsRef<str>,
{
let value = if value { Some("1") } else { None };
self.set_raw_config(context, key, value).await
}
pub async fn set_raw_config_int64(
&self,
context: &Context,
key: impl AsRef<str>,
value: i64,
) -> Result<()> {
self.set_raw_config(context, key, Some(&format!("{}", value)))
.await
}
pub async fn get_raw_config_int64(&self, key: impl AsRef<str>) -> Option<i64> {
self.get_raw_config(key).await.and_then(|r| r.parse().ok())
}
/// Alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above.
/// the ORDER BY ensures, this function always returns the most recent id,
/// eg. if a Message-ID is split into different messages.
pub async fn get_rowid(
&self,
table: impl AsRef<str>,
field: impl AsRef<str>,
value: impl AsRef<str>,
) -> Result<u32> {
// alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above.
// the ORDER BY ensures, this function always returns the most recent id,
// eg. if a Message-ID is split into different messages.
let query = format!(
"SELECT id FROM {} WHERE {}=? ORDER BY id DESC",
table.as_ref(),
field.as_ref(),
);
let res: i32 = self.query_value(&query, paramsx![value.as_ref()]).await?;
Ok(res as u32)
}
pub async fn get_rowid2(
&self,
table: impl AsRef<str>,
field: impl AsRef<str>,
value: i64,
field2: impl AsRef<str>,
value2: i32,
) -> Result<u32> {
let query = format!(
"SELECT id FROM {} WHERE {}=? AND {}=? ORDER BY id DESC",
table.as_ref(),
field.as_ref(),
field2.as_ref(),
);
let res: i32 = self
.query_value(query.as_ref(), paramsx![value, value2])
.await?;
Ok(res as u32)
}
}
pub async fn housekeeping(context: &Context) -> Result<()> {
let mut files_in_use = HashSet::new();
let mut unreferenced_count = 0usize;
info!(context, "Start housekeeping...");
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM msgs WHERE chat_id!=3 AND type!=10;",
Param::File,
)
.await?;
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM jobs;",
Param::File,
)
.await?;
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM chats;",
Param::ProfileImage,
)
.await?;
maybe_add_from_param(
context,
&mut files_in_use,
"SELECT param FROM contacts;",
Param::ProfileImage,
)
.await?;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as("SELECT value FROM config;").fetch(&pool);
while let Some(row) = rows.next().await {
let (row,): (String,) = row?;
maybe_add_file(&mut files_in_use, row);
}
info!(context, "{} files in use.", files_in_use.len(),);
/* go through directory and delete unused files */
let p = context.get_blobdir();
match async_std::fs::read_dir(p).await {
Ok(mut dir_handle) => {
/* avoid deletion of files that are just created to build a message object */
let diff = std::time::Duration::from_secs(60 * 60);
let keep_files_newer_than = std::time::SystemTime::now().checked_sub(diff).unwrap();
while let Some(entry) = dir_handle.next().await {
if entry.is_err() {
break;
}
let entry = entry.unwrap();
let name_f = entry.file_name();
let name_s = name_f.to_string_lossy();
if is_file_in_use(&files_in_use, None, &name_s)
|| is_file_in_use(&files_in_use, Some(".increation"), &name_s)
|| is_file_in_use(&files_in_use, Some(".waveform"), &name_s)
|| is_file_in_use(&files_in_use, Some("-preview.jpg"), &name_s)
{
continue;
}
unreferenced_count += 1;
if let Ok(stats) = async_std::fs::metadata(entry.path()).await {
let recently_created =
stats.created().is_ok() && stats.created().unwrap() > keep_files_newer_than;
let recently_modified = stats.modified().is_ok()
&& stats.modified().unwrap() > keep_files_newer_than;
let recently_accessed = stats.accessed().is_ok()
&& stats.accessed().unwrap() > keep_files_newer_than;
if recently_created || recently_modified || recently_accessed {
info!(
context,
"Housekeeping: Keeping new unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name(),
);
continue;
}
}
info!(
context,
"Housekeeping: Deleting unreferenced file #{}: {:?}",
unreferenced_count,
entry.file_name()
);
let path = entry.path();
dc_delete_file(context, path).await;
}
}
Err(err) => {
warn!(
context,
"Housekeeping: Cannot open {}. ({})",
context.get_blobdir().display(),
err
);
}
}
if let Err(err) = prune_tombstones(context).await {
warn!(
context,
"Houskeeping: Cannot prune message tombstones: {}", err
);
}
info!(context, "Housekeeping done.",);
Ok(())
}
fn is_file_in_use(files_in_use: &HashSet<String>, namespc_opt: Option<&str>, name: &str) -> bool {
let name_to_check = if let Some(namespc) = namespc_opt {
let name_len = name.len();
let namespc_len = namespc.len();
if name_len <= namespc_len || !name.ends_with(namespc) {
return false;
}
&name[..name_len - namespc_len]
} else {
name
};
files_in_use.contains(name_to_check)
}
fn maybe_add_file(files_in_use: &mut HashSet<String>, file: impl AsRef<str>) {
if !file.as_ref().starts_with("$BLOBDIR/") {
return;
}
files_in_use.insert(file.as_ref()[9..].into());
}
async fn maybe_add_from_param(
context: &Context,
files_in_use: &mut HashSet<String>,
query: &str,
param_id: Param,
) -> Result<()> {
info!(context, "maybe_add_from_param: {}", query);
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(query).fetch(&pool);
while let Some(row) = rows.next().await {
let (row,): (String,) = row?;
info!(context, "param: {}", &row);
let param: Params = row.parse().unwrap_or_default();
if let Some(file) = param.get(param_id) {
info!(context, "got file: {:?} {}", param_id, file);
maybe_add_file(files_in_use, file);
}
}
Ok(())
}
#[allow(clippy::cognitive_complexity)]
async fn open(
context: &Context,
sql: &Sql,
dbfile: impl AsRef<Path>,
readonly: bool,
) -> crate::error::Result<()> {
if sql.is_open().await {
error!(
context,
"Cannot open, database \"{:?}\" already opened.",
dbfile.as_ref(),
);
return Err(Error::SqlAlreadyOpen.into());
}
if readonly {
// TODO: readonly mode
}
let xpool = sqlx::SqlitePool::builder()
.min_size(1)
.max_size(4)
.build(&format!("sqlite://{}", dbfile.as_ref().to_string_lossy()))
.await?;
{
*sql.xpool.write().await = Some(xpool)
}
if !readonly {
let mut exists_before_update = false;
let mut dbversion_before_update: i32 = -1;
if sql.table_exists("config").await? {
exists_before_update = true;
if let Some(version) = sql.get_raw_config_int("dbversion").await {
dbversion_before_update = version;
}
}
// (1) update low-level database structure.
// this should be done before updates that use high-level objects that
// rely themselves on the low-level structure.
// --------------------------------------------------------------------
migrations::run(context, &sql, dbversion_before_update, exists_before_update).await?;
// general updates
// (2) updates that require high-level objects
// (the structure is complete now and all objects are usable)
// --------------------------------------------------------------------
let mut recalc_fingerprints = false;
let mut update_icons = false;
if dbversion_before_update < 34 {
recalc_fingerprints = true;
}
if dbversion_before_update < 61 {
update_icons = true;
}
if recalc_fingerprints {
info!(context, "[migration] recalc fingerprints");
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as("SELECT addr FROM acpeerstates;").fetch(&pool);
while let Some(addr) = rows.next().await {
let (addr,): (String,) = addr?;
if let Ok(ref mut peerstate) = Peerstate::from_addr(context, &addr).await {
peerstate.recalc_fingerprint();
peerstate.save_to_db(sql, false).await?;
}
}
}
if update_icons {
update_saved_messages_icon(context).await?;
update_device_icon(context).await?;
}
}
info!(context, "Opened {:?}.", dbfile.as_ref(),);
Ok(())
}
/// Removes from the database locally deleted messages that also don't
/// have a server UID.
async fn prune_tombstones(context: &Context) -> Result<()> {
context
.sql
.execute(
r#"
DELETE FROM msgs
WHERE (chat_id = ? OR hidden)
AND server_uid = 0
"#,
paramsx![DC_CHAT_ID_TRASH as i32],
)
.await?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_maybe_add_file() {
let mut files = Default::default();
maybe_add_file(&mut files, "$BLOBDIR/hello");
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
maybe_add_file(&mut files, "world2.txt");
maybe_add_file(&mut files, "$BLOBDIR");
assert!(files.contains("hello"));
assert!(files.contains("world.txt"));
assert!(!files.contains("world2.txt"));
assert!(!files.contains("$BLOBDIR"));
}
#[test]
fn test_is_file_in_use() {
let mut files = Default::default();
maybe_add_file(&mut files, "$BLOBDIR/hello");
maybe_add_file(&mut files, "$BLOBDIR/world.txt");
maybe_add_file(&mut files, "world2.txt");
assert!(is_file_in_use(&files, None, "hello"));
assert!(!is_file_in_use(&files, Some(".txt"), "hello"));
assert!(is_file_in_use(&files, Some("-suffix"), "world.txt-suffix"));
}
}

View File

@@ -360,7 +360,7 @@ impl Context {
// create saved-messages chat; // create saved-messages chat;
// we do this only once, if the user has deleted the chat, he can recreate it manually. // we do this only once, if the user has deleted the chat, he can recreate it manually.
if !self.sql.get_raw_config_bool(&self, "self-chat-added").await { if !self.sql.get_raw_config_bool("self-chat-added").await {
self.sql self.sql
.set_raw_config_bool(&self, "self-chat-added", true) .set_raw_config_bool(&self, "self-chat-added", true)
.await?; .await?;

View File

@@ -27,10 +27,20 @@ impl TestContext {
/// ///
/// [Context]: crate::context::Context /// [Context]: crate::context::Context
pub async fn new() -> Self { pub async fn new() -> Self {
pretty_env_logger::try_init_timed().ok();
let dir = tempdir().unwrap(); let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite"); let dbfile = dir.path().join("db.sqlite");
let ctx = Context::new("FakeOS".into(), dbfile.into()).await.unwrap(); let ctx = Context::new("FakeOS".into(), dbfile.into()).await.unwrap();
Self { ctx, dir } let events = ctx.get_event_emitter();
async_std::task::spawn(async move {
while let Some(event) = events.recv().await {
log::info!("{:?}", event);
}
});
TestContext { ctx, dir }
} }
/// Create a new configured [TestContext]. /// Create a new configured [TestContext].

View File

@@ -11,7 +11,7 @@ use crate::context::Context;
use crate::dc_tools::*; use crate::dc_tools::*;
/// Token namespace /// Token namespace
#[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, Sqlx)]
#[repr(i32)] #[repr(i32)]
pub enum Namespace { pub enum Namespace {
Unknown = 0, Unknown = 0,
@@ -34,7 +34,7 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -
.sql .sql
.execute( .execute(
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
paramsv![namespace, foreign_id, token, time()], paramsx![namespace, foreign_id, &token, time()],
) )
.await .await
.ok(); .ok();
@@ -44,12 +44,12 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -
pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> { pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
context context
.sql .sql
.query_get_value::<String>( .query_value(
context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
paramsv![namespace, foreign_id], paramsx![namespace, foreign_id],
) )
.await .await
.ok()
} }
pub async fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { pub async fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String {
@@ -65,7 +65,7 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> boo
.sql .sql
.exists( .exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;", "SELECT id FROM tokens WHERE namespc=? AND token=?;",
paramsv![namespace, token], paramsx![namespace, token],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()