Compare commits

..

35 Commits
1.38.0 ... sqlx

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
bjoern
9152f93a46 Merge pull request #1668 from deltachat/add-sanitise-tests
add more tests for BlobObject::sanitise_name()
2020-06-23 19:04:51 +02:00
bjoern
6a4b6fddac Merge pull request #1659 from deltachat/prep-1.39
prepare 1.39
2020-06-23 19:01:36 +02:00
B. Petersen
e3c90aff22 add more tests for BlobObject::sanitise_name() 2020-06-23 18:51:40 +02:00
B. Petersen
b7464f7a5c bump version to 1.39 2020-06-23 18:41:45 +02:00
B. Petersen
53128cc64b update changelog for 1.39 and fix the one for 1.36 2020-06-23 18:41:45 +02:00
bjoern
ccf8eeacd6 Merge pull request #1667 from deltachat/revert-1664-sanitize-filename-reader-friendly
Revert "Switch to sanitize-filename-reader-friendly"
2020-06-23 18:40:40 +02:00
bjoern
aeb8a2e260 Revert "Switch to sanitize-filename-reader-friendly"
This reverts commit 93797bc82f.
2020-06-23 18:11:18 +02:00
Alexander Krotov
93797bc82f Switch to sanitize-filename-reader-friendly
One advantage is that it does not depend on any crates, so there is a
higher chance of dropping regex crate eventually.
2020-06-23 14:37:35 +03:00
holger krekel
07236efc45 fix bcc_self to remain "0" for testrun/fivechat test accounts 2020-06-23 08:06:32 +02:00
B. Petersen
0fbddc939b update provider-db 2020-06-23 08:06:32 +02:00
Alexander Krotov
a031151587 Fix two +nightly clippy suggestions 2020-06-23 03:17:07 +03:00
B. Petersen
545ff4f7ba apply config_defaults only for unset values
instead of applying all config_defaults unconditionally
after the first configure, a config_defaults for a given key
is now applied when this key has never been set before.

this way, you can set some keys before calling configure()
and also, later versions can add new defaults for new keys
(that would require another call to configure() then, however)
2020-06-23 00:48:40 +02:00
B. Petersen
73e695537a add missing doc for bcc_self 2020-06-22 22:49:26 +02:00
B. Petersen
16e3c113b7 update ffi docs; avatar is sent with messages since end 2019 2020-06-22 22:49:26 +02:00
bjoern
88d7bf49ff Merge pull request #1658 from deltachat/remove-config-changes-interrupt
remove config changes interrupt
2020-06-22 14:18:48 +02:00
B. Petersen
74ea884aa4 remove now superfluous interrupting on watch-settings-changes 2020-06-22 13:48:33 +02:00
B. Petersen
16c53637d9 update docs to new watch-settings behavior 2020-06-22 13:47:47 +02:00
Friedel Ziegelmayer
f63f0550b0 Merge pull request #1654 from deltachat/fix-move-loops
fix(scheduler): only start watch loops if the appropriate config is set
2020-06-22 12:51:43 +02:00
Friedel Ziegelmayer
530503932b Merge pull request #1655 from deltachat/feat-update-deps
feat: update deps
2020-06-22 12:23:26 +02:00
Friedel Ziegelmayer
d2dc4edd82 Merge pull request #1657 from deltachat/fix-move-loops-inbox
perform jobs also if inbox_watch disabled
2020-06-22 12:23:10 +02:00
Alexander Krotov
8de1bc6cbd sql: fix potential panic in maybe_add_file
When maybe_add_file was called with "$BLOBDIR" it tried to remove 9
bytes from the string, while it only contains 8.
2020-06-22 13:22:37 +03:00
B. Petersen
76e39bfa7c this pr creates the inbox_loop inpendendingly of inbox_watch config-setting as the loop is also required to perform jobs. the config-setting is checked inside the loop then 2020-06-22 12:00:25 +02:00
Alexander Krotov
cf09942737 deltachat-ffi: use as_deref() as suggested by clippy 2020-06-22 12:37:29 +03:00
dignifiedquire
6fe1f01c5f feat: update deps
includes async-std@1.6.2 and smol@0.1.18 which fix various  hangs and possible deadlocks
2020-06-22 10:04:10 +02:00
dignifiedquire
f880d6188b fix(scheduler): only start watch loops if the appropriate config is set 2020-06-22 10:00:50 +02:00
bjoern
22c62ea6af Merge pull request #1649 from deltachat/freepascal
README: add link to Free Pascal bindings
2020-06-21 21:52:51 +02:00
Alexander Krotov
e3af3a24a8 README: add link to Free Pascal bindings 2020-06-21 20:14:30 +03:00
Alexander Krotov
7bfadb14ea Fix a typo (prover -> provider) 2020-06-21 20:13:06 +03:00
bjoern
75d20b899a Merge pull request #1646 from deltachat/prep-1.38
prepare 1.38
2020-06-21 12:12:02 +02:00
47 changed files with 3603 additions and 3816 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## 1.39.0
- fix handling of `mvbox_watch`, `sentbox_watch`, `inbox_watch` #1654 #1658
- fix potential panics, update dependencies #1650 #1655
## 1.38.0 ## 1.38.0
- fix sorting, esp. for multi-device - fix sorting, esp. for multi-device
@@ -21,6 +28,8 @@
- parse ndn (network delivery notification) reports - parse ndn (network delivery notification) reports
and report failed messages as such #1552 #1622 #1630 and report failed messages as such #1552 #1622 #1630
- add oauth2 support for gsuite domains #1626
- read image orientation from exif before recoding #1619 - read image orientation from exif before recoding #1619
- improve logging #1593 #1598 - improve logging #1593 #1598

522
Cargo.lock generated
View File

@@ -2,9 +2,9 @@
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.12.1" version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c"
dependencies = [ dependencies = [
"gimli", "gimli",
] ]
@@ -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",
] ]
@@ -238,9 +244,9 @@ dependencies = [
[[package]] [[package]]
name = "async-std" name = "async-std"
version = "1.6.1" version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93c583a035d21e6d6f09adf48abfc55277bf48886406df370e5db6babe3ab98" checksum = "00d68a33ebc8b57800847d00787307f84a562224a14db069b0acefe4c2abbf5d"
dependencies = [ dependencies = [
"async-attributes", "async-attributes",
"async-task", "async-task",
@@ -281,15 +287,24 @@ checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.35" version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89cb5d814ab2a47fd66d3266e9efccb53ca4c740b7451043b8ffcf9a6208f3f8" checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"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"
@@ -459,6 +474,19 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c98bfd7c112b6399fef97cc0614af1cd375b27a112e552ce60f94c1b5f13cb74" checksum = "c98bfd7c112b6399fef97cc0614af1cd375b27a112e552ce60f94c1b5f13cb74"
[[package]]
name = "blocking"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d17efb70ce4421e351d61aafd90c16a20fb5bfe339fcdc32a86816280e62ce0"
dependencies = [
"futures-channel",
"futures-util",
"once_cell",
"parking",
"waker-fn",
]
[[package]] [[package]]
name = "blowfish" name = "blowfish"
version = "0.5.0" version = "0.5.0"
@@ -536,6 +564,21 @@ 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]]
name = "cache-padded"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24508e28c677875c380c20f4d28124fab6f8ed4ef929a1397d7b1a31e92f1005"
[[package]] [[package]]
name = "cargo_metadata" name = "cargo_metadata"
version = "0.6.4" version = "0.6.4"
@@ -633,6 +676,15 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd" checksum = "0dbbb57365263e881e805dc77d94697c9118fd94d8da011240555aa7b23445bd"
[[package]]
name = "concurrent-queue"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83c06aff61f2d899eb87c379df3cbf7876f14471dcab474e0b6dc90ab96c080"
dependencies = [
"cache-padded",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@@ -687,31 +739,15 @@ dependencies = [
] ]
[[package]] [[package]]
name = "crossbeam-deque" name = "crossbeam-channel"
version = "0.7.3" 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 = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
dependencies = [ dependencies = [
"crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
"maybe-uninit", "maybe-uninit",
] ]
[[package]]
name = "crossbeam-epoch"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
dependencies = [
"autocfg 1.0.0",
"cfg-if",
"crossbeam-utils",
"lazy_static",
"maybe-uninit",
"memoffset",
"scopeguard",
]
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.2.3" version = "0.2.3"
@@ -802,6 +838,12 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "data-encoding"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72aa14c04dfae8dd7d8a2b1cb7ca2152618cd01336dbfe704b8dcbf8d41dbd69"
[[package]] [[package]]
name = "deflate" name = "deflate"
version = "0.8.4" version = "0.8.4"
@@ -814,7 +856,7 @@ dependencies = [
[[package]] [[package]]
name = "deltachat" name = "deltachat"
version = "1.38.0" version = "1.39.0"
dependencies = [ dependencies = [
"ansi_term 0.12.1", "ansi_term 0.12.1",
"anyhow", "anyhow",
@@ -844,6 +886,7 @@ dependencies = [
"lazy_static", "lazy_static",
"lettre_email", "lettre_email",
"libc", "libc",
"libsqlite3-sys",
"log", "log",
"mailparse", "mailparse",
"native-tls", "native-tls",
@@ -855,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",
@@ -867,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",
@@ -886,7 +927,7 @@ dependencies = [
[[package]] [[package]]
name = "deltachat_ffi" name = "deltachat_ffi"
version = "1.38.0" version = "1.39.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std", "async-std",
@@ -976,10 +1017,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]] [[package]]
name = "dtoa" name = "dotenv"
version = "0.4.5" version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dtoa"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]] [[package]]
name = "ed25519-dalek" name = "ed25519-dalek"
@@ -1157,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"
@@ -1178,6 +1213,12 @@ dependencies = [
"ascii_utils", "ascii_utils",
] ]
[[package]]
name = "fastrand"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64b0126b293b050395b37b10489951590ed024c03d7df4f249d219c8ded7cbf"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.14" version = "1.0.14"
@@ -1330,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"
@@ -1404,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"
@@ -1489,9 +1553,9 @@ dependencies = [
[[package]] [[package]]
name = "http-types" name = "http-types"
version = "2.2.0" version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a89eaaf43f3700e78c01cb8165d1bd05155065637d26ee2f49800c95e7b62ee" checksum = "ca4221cd1c7cedf275cd0ad3c9dfe58b5acc93cdd5511c7e020a102e1995fe99"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std", "async-std",
@@ -1501,6 +1565,7 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
"serde_qs",
"serde_urlencoded", "serde_urlencoded",
"url", "url",
] ]
@@ -1554,9 +1619,9 @@ dependencies = [
[[package]] [[package]]
name = "image" name = "image"
version = "0.23.5" version = "0.23.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d534e95ad8b9d5aa614322d02352b4f1bf962254adcf02ac6f2def8be18498e8" checksum = "b5b0553fec6407d63fe2975b794dfb099f3f790bdc958823851af37b26404ab4"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"byteorder", "byteorder",
@@ -1599,12 +1664,9 @@ dependencies = [
[[package]] [[package]]
name = "infer" name = "infer"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55c406a76164eb346a829ed4b97b73cb06259078eca01adeb12e8ca308d4123" checksum = "6854dd77ddc4f9ba1a448f487e27843583d407648150426a30c2ea3a2c39490a"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "iovec" name = "iovec"
@@ -1638,9 +1700,9 @@ dependencies = [
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "0.4.5" version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]] [[package]]
name = "jpeg-decoder" name = "jpeg-decoder"
@@ -1777,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"
@@ -1803,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"
@@ -1838,15 +1917,6 @@ version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "memoffset"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
dependencies = [
"autocfg 1.0.0",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.16" version = "0.3.16"
@@ -1994,9 +2064,9 @@ dependencies = [
[[package]] [[package]]
name = "num-rational" name = "num-rational"
version = "0.2.4" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" checksum = "a5b4d7360f362cfb50dde8143501e6940b22f644be75a4cc90b2d81968908138"
dependencies = [ dependencies = [
"autocfg 1.0.0", "autocfg 1.0.0",
"num-integer", "num-integer",
@@ -2110,6 +2180,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "parking"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bcaa58ee64f8e4a3d02f5d8e6ed0340eae28fed6fdabd984ad1776e3b43848a"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.10.2" version = "0.10.2"
@@ -2199,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"
@@ -2231,18 +2351,6 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01608bfa680dafb103f9207fa944facf572e4e3e708d10de19a0d0c3d36e5f18"
dependencies = [
"crossbeam-utils",
"futures-io",
"futures-sink",
"futures-util",
]
[[package]] [[package]]
name = "pkg-config" name = "pkg-config"
version = "0.3.17" version = "0.3.17"
@@ -2379,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"
@@ -2425,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]]
@@ -2470,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"
@@ -2596,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"
@@ -2710,19 +2791,16 @@ dependencies = [
] ]
[[package]] [[package]]
name = "scheduled-thread-pool" name = "scoped-tls"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6"
dependencies = [
"parking_lot",
]
[[package]]
name = "scoped-tls-hkt"
version = "0.1.2" 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 = "c2e9d7eaddb227e8fbaaa71136ae0e1e913ca159b86c7da82f3e8f0044ad3a63" checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
@@ -2777,18 +2855,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.112" version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243" checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.112" version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57" checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -2806,6 +2884,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_qs"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f3acf84e23ab27c01cb5917551765c01c50b2000089db8fa47fe018a3260cf"
dependencies = [
"data-encoding",
"error-chain",
"percent-encoding",
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.6.1" version = "0.6.1"
@@ -2884,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"
@@ -2914,23 +3010,23 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]] [[package]]
name = "smol" name = "smol"
version = "0.1.12" version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afcf8beb4aa23cc616e3351e49585153b462637c8fec929163b54d88e522b3b0" checksum = "620cbb3c6e34da57d3a248cda0cd01cd5848164dc062e764e65d06fe3ea7aed5"
dependencies = [ dependencies = [
"async-task", "async-task",
"crossbeam-deque", "blocking",
"crossbeam-queue", "concurrent-queue",
"crossbeam-utils", "fastrand",
"futures-io", "futures-io",
"futures-util", "futures-util",
"libc", "libc",
"once_cell", "once_cell",
"piper", "scoped-tls 1.0.0",
"scoped-tls-hkt",
"slab", "slab",
"socket2", "socket2",
"wepoll-binding", "wepoll-sys-stjepang",
"winapi",
] ]
[[package]] [[package]]
@@ -2951,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"
@@ -3040,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"
@@ -3098,9 +3286,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.31" version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3229,13 +3417,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tinyvec"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
[[package]] [[package]]
name = "tokio-io" name = "tokio-io"
version = "0.1.13" 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",
] ]
@@ -3333,11 +3527,11 @@ dependencies = [
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
version = "0.1.12" 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 = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4" checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
dependencies = [ dependencies = [
"smallvec", "tinyvec",
] ]
[[package]] [[package]]
@@ -3354,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"
@@ -3427,6 +3621,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "waker-fn"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9571542c2ce85ce642e6b58b3364da2fb53526360dfb7c211add4f5c23105ff7"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.3.1" version = "2.3.1"
@@ -3521,24 +3721,20 @@ dependencies = [
] ]
[[package]] [[package]]
name = "wepoll-binding" name = "wepoll-sys-stjepang"
version = "2.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 = "374fff4ff9701ff8b6ad0d14bacd3156c44063632d8c136186ff5967d48999a7" checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694"
dependencies = [
"bitflags",
"wepoll-sys",
]
[[package]]
name = "wepoll-sys"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9082a777aed991f6769e2b654aa0cb29f1c3d615daf009829b07b66c7aff6a24"
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

@@ -1,6 +1,6 @@
[package] [package]
name = "deltachat" name = "deltachat"
version = "1.38.0" version = "1.39.0"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"] authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018" edition = "2018"
license = "MPL-2.0" license = "MPL-2.0"
@@ -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

@@ -123,6 +123,7 @@ Language bindings are available for:
- [Node.js](https://www.npmjs.com/package/deltachat-node) - [Node.js](https://www.npmjs.com/package/deltachat-node)
- [Python](https://py.delta.chat) - [Python](https://py.delta.chat)
- [Go](https://github.com/hugot/go-deltachat/) - [Go](https://github.com/hugot/go-deltachat/)
- [Free Pascal](https://github.com/deltachat/deltachat-fp/)
- **Java** and **Swift** (contained in the Android/iOS repos) - **Java** and **Swift** (contained in the Android/iOS repos)
The following "frontend" projects make use of the Rust-library The following "frontend" projects make use of the Rust-library

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "deltachat_ffi" name = "deltachat_ffi"
version = "1.38.0" version = "1.39.0"
description = "Deltachat FFI" description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"] authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018" edition = "2018"

View File

@@ -262,17 +262,25 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `selfavatar` = File containing avatar. Will immediately be copied to the * - `selfavatar` = File containing avatar. Will immediately be copied to the
* `blobdir`; the original image will not be needed anymore. * `blobdir`; the original image will not be needed anymore.
* NULL to remove the avatar. * NULL to remove the avatar.
* It is planned for future versions * As for `displayname` and `selfstatus`, also the avatar is sent to the recipients.
* to send this image together with the next messages. * To save traffic, however, the avatar is attached only as needed
* and also recoded to a reasonable size.
* - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default) * - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
* - `mdns_enabled` = 0=do not send or request read receipts, * - `mdns_enabled` = 0=do not send or request read receipts,
* 1=send and request read receipts (default) * 1=send and request read receipts (default)
* - `bcc_self` = 0=do not send a copy of outgoing messages to self (default),
* 1=send a copy of outgoing messages to self.
* Sending messages to self is needed for a proper multi-account setup,
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
* - `inbox_watch` = 1=watch `INBOX`-folder for changes (default), * - `inbox_watch` = 1=watch `INBOX`-folder for changes (default),
* 0=do not watch the `INBOX`-folder * 0=do not watch the `INBOX`-folder,
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* - `sentbox_watch`= 1=watch `Sent`-folder for changes (default), * - `sentbox_watch`= 1=watch `Sent`-folder for changes (default),
* 0=do not watch the `Sent`-folder * 0=do not watch the `Sent`-folder,
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* - `mvbox_watch` = 1=watch `DeltaChat`-folder for changes (default), * - `mvbox_watch` = 1=watch `DeltaChat`-folder for changes (default),
* 0=do not watch the `DeltaChat`-folder * 0=do not watch the `DeltaChat`-folder,
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* - `mvbox_move` = 1=heuristically detect chat-messages * - `mvbox_move` = 1=heuristically detect chat-messages
* and move them to the `DeltaChat`-folder, * and move them to the `DeltaChat`-folder,
* 0=do not move chat-messages * 0=do not move chat-messages
@@ -4307,7 +4315,7 @@ void dc_event_unref(dc_event_t* event);
*/ */
/** /**
* Prover works out-of-the-box. * Provider works out-of-the-box.
* This provider status is returned for provider where the login * This provider status is returned for provider where the login
* works by just entering the name or the email-address. * works by just entering the name or the email-address.
* *

View File

@@ -126,7 +126,7 @@ pub unsafe extern "C" fn dc_set_config(
// When ctx.set_config() fails it already logged the error. // When ctx.set_config() fails it already logged the error.
// TODO: Context::set_config() should not log this // TODO: Context::set_config() should not log this
Ok(key) => block_on(async move { Ok(key) => block_on(async move {
ctx.set_config(key, to_opt_string_lossy(value).as_ref().map(|x| x.as_str())) ctx.set_config(key, to_opt_string_lossy(value).as_deref())
.await .await
.is_ok() as libc::c_int .is_ok() as libc::c_int
}), }),
@@ -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,
} }
@@ -554,14 +554,7 @@ pub unsafe extern "C" fn dc_get_chatlist(
let qi = if query_id == 0 { None } else { Some(query_id) }; let qi = if query_id == 0 { None } else { Some(query_id) };
block_on(async move { block_on(async move {
match chatlist::Chatlist::try_load( match chatlist::Chatlist::try_load(&ctx, flags as usize, qs.as_deref(), qi).await {
&ctx,
flags as usize,
qs.as_ref().map(|x| x.as_str()),
qi,
)
.await
{
Ok(list) => { Ok(list) => {
let ffi_list = ChatlistWrapper { context, list }; let ffi_list = ChatlistWrapper { context, list };
Box::into_raw(Box::new(ffi_list)) Box::into_raw(Box::new(ffi_list))
@@ -752,13 +745,9 @@ pub unsafe extern "C" fn dc_add_device_msg(
}; };
block_on(async move { block_on(async move {
chat::add_device_msg( chat::add_device_msg(&ctx, to_opt_string_lossy(label).as_deref(), msg)
&ctx, .await
to_opt_string_lossy(label).as_ref().map(|x| x.as_str()), .unwrap_or_log_default(&ctx, "Failed to add device message")
msg,
)
.await
.unwrap_or_log_default(&ctx, "Failed to add device message")
}) })
.to_u32() .to_u32()
} }
@@ -844,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>>(),
); );
@@ -909,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>>(),
); );
@@ -986,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>>(),
); );
@@ -1030,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)
}) })
} }
@@ -1097,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))
}) })
} }
@@ -1118,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>>(),
); );
@@ -1307,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]
@@ -1866,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]
@@ -2266,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(),
} }
@@ -2282,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]
@@ -2816,7 +2831,7 @@ pub unsafe extern "C" fn dc_msg_set_file(
let ffi_msg = &mut *msg; let ffi_msg = &mut *msg;
ffi_msg.message.set_file( ffi_msg.message.set_file(
to_string_lossy(file), to_string_lossy(file),
to_opt_string_lossy(filemime).as_ref().map(|x| x.as_str()), to_opt_string_lossy(filemime).as_deref(),
) )
} }

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

@@ -349,6 +349,7 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
if hasattr(acc, "_configtracker"): if hasattr(acc, "_configtracker"):
acc._configtracker.wait_finish() acc._configtracker.wait_finish()
del acc._configtracker del acc._configtracker
acc.set_config("bcc_self", "0")
if acc.is_configured() and not acc.is_started(): if acc.is_configured() and not acc.is_started():
acc.start_io() acc.start_io()
print("{}: {} account was successfully setup".format( print("{}: {} account was successfully setup".format(

View File

@@ -723,9 +723,9 @@ class TestOnlineAccount:
def test_move_works_on_self_sent(self, acfactory): def test_move_works_on_self_sent(self, acfactory):
ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True) ac1 = acfactory.get_online_configuring_account(mvbox=True, move=True)
ac1.set_config("bcc_self", "1")
ac2 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account()
acfactory.wait_configure_and_start_io() acfactory.wait_configure_and_start_io()
ac1.set_config("bcc_self", "1")
chat = acfactory.get_accepted_chat(ac1, ac2) chat = acfactory.get_accepted_chat(ac1, ac2)
chat.send_text("message1") chat.send_text("message1")

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,

View File

@@ -660,8 +660,40 @@ mod tests {
#[test] #[test]
fn test_sanitise_name() { fn test_sanitise_name() {
let (_, ext) = let (stem, ext) =
BlobObject::sanitise_name("Я ЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯ.txt"); BlobObject::sanitise_name("Я ЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯЯ.txt");
assert_eq!(ext, ".txt"); assert_eq!(ext, ".txt");
assert!(!stem.is_empty());
// the extensions are kept together as between stem and extension a number may be added -
// and `foo.tar.gz` should become `foo-1234.tar.gz` and not `foo.tar-1234.gz`
let (stem, ext) = BlobObject::sanitise_name("wot.tar.gz");
assert_eq!(stem, "wot");
assert_eq!(ext, ".tar.gz");
let (stem, ext) = BlobObject::sanitise_name(".foo.bar");
assert_eq!(stem, "");
assert_eq!(ext, ".foo.bar");
let (stem, ext) = BlobObject::sanitise_name("foo?.bar");
assert!(stem.contains("foo"));
assert!(!stem.contains("?"));
assert_eq!(ext, ".bar");
let (stem, ext) = BlobObject::sanitise_name("no-extension");
assert_eq!(stem, "no-extension");
assert_eq!(ext, "");
let (stem, ext) = BlobObject::sanitise_name("path/ignored\\this: is* forbidden?.c");
assert_eq!(ext, ".c");
assert!(!stem.contains("path"));
assert!(!stem.contains("ignored"));
assert!(stem.contains("this"));
assert!(stem.contains("forbidden"));
assert!(!stem.contains("/"));
assert!(!stem.contains("\\"));
assert!(!stem.contains(":"));
assert!(!stem.contains("*"));
assert!(!stem.contains("?"));
} }
} }

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

@@ -11,7 +11,7 @@ use crate::dc_tools::*;
use crate::events::Event; use crate::events::Event;
use crate::message::MsgId; use crate::message::MsgId;
use crate::mimefactory::RECOMMENDED_FILE_SIZE; use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::{scheduler::InterruptInfo, stock::StockMessage}; use crate::stock::StockMessage;
/// The available configuration keys. /// The available configuration keys.
#[derive( #[derive(
@@ -120,17 +120,21 @@ pub enum Config {
} }
impl Context { impl Context {
pub async fn config_exists(&self, key: Config) -> bool {
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() {
@@ -185,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)
@@ -201,22 +205,6 @@ impl Context {
None => self.sql.set_raw_config(self, key, None).await, None => self.sql.set_raw_config(self, key, None).await,
} }
} }
Config::InboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_inbox(InterruptInfo::new(false, None)).await;
ret
}
Config::SentboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_sentbox(InterruptInfo::new(false, None))
.await;
ret
}
Config::MvboxWatch => {
let ret = self.sql.set_raw_config(self, key, value).await;
self.interrupt_mvbox(InterruptInfo::new(false, None)).await;
ret
}
Config::Selfstatus => { Config::Selfstatus => {
let def = self.stock_str(StockMessage::StatusLine).await; let def = self.stock_str(StockMessage::StatusLine).await;
let val = if value.is_none() || value.unwrap() == def { let val = if value.is_none() || value.unwrap() == def {

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.
@@ -70,16 +70,20 @@ impl Context {
async fn inner_configure(&self) -> Result<()> { async fn inner_configure(&self) -> Result<()> {
info!(self, "Configure ..."); info!(self, "Configure ...");
let was_configured_before = self.is_configured().await;
let mut param = LoginParam::from_database(self, "").await; let mut param = LoginParam::from_database(self, "").await;
let success = configure(self, &mut param).await; let success = configure(self, &mut param).await;
if let Some(provider) = provider::get_provider_info(&param.addr) { if let Some(provider) = provider::get_provider_info(&param.addr) {
if !was_configured_before { if let Some(config_defaults) = &provider.config_defaults {
if let Some(config_defaults) = &provider.config_defaults { for def in config_defaults.iter() {
for def in config_defaults.iter() { if !self.config_exists(def.key).await {
info!(self, "apply config_defaults {}={}", def.key, def.value); info!(self, "apply config_defaults {}={}", def.key, def.value);
self.set_config(def.key, Some(def.value)).await?; self.set_config(def.key, Some(def.value)).await?;
} else {
info!(
self,
"skip already set config_defaults {}={}", def.key, def.value
);
} }
} }
} }

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,9 +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::convert::TryFrom;
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;
@@ -25,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,
@@ -42,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)
} }
} }
@@ -99,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,
@@ -119,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;
@@ -132,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);
@@ -143,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) {
@@ -324,11 +305,9 @@ impl<'a> Peerstate<'a> {
pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> { pub fn render_gossip_header(&self, min_verified: PeerstateVerifiedStatus) -> Option<String> {
if let Some(key) = self.peek_key(min_verified) { if let Some(key) = self.peek_key(min_verified) {
// TODO: avoid cloning
let public_key = SignedPublicKey::try_from(key.clone()).ok()?;
let header = Aheader::new( let header = Aheader::new(
self.addr.clone(), self.addr.clone(),
public_key, key.clone(), // TODO: avoid cloning
// Autocrypt 1.1.0 specification says that // Autocrypt 1.1.0 specification says that
// `prefer-encrypt` attribute SHOULD NOT be included, // `prefer-encrypt` attribute SHOULD NOT be included,
// but we include it anyway to propagate encryption // but we include it anyway to propagate encryption
@@ -406,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,
@@ -429,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?;
@@ -458,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() {
@@ -479,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,
@@ -507,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);
} }
@@ -521,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,
@@ -555,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,
@@ -584,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

@@ -135,6 +135,24 @@ lazy_static::lazy_static! {
oauth2_authorizer: None, oauth2_authorizer: None,
}; };
// dubby.org.md: dubby.org
static ref P_DUBBY_ORG: Provider = Provider {
status: Status::OK,
before_login_hint: "",
after_login_hint: "",
overview_page: "https://providers.delta.chat/dubby-org",
server: vec![
],
config_defaults: Some(vec![
ConfigDefault { key: Config::BccSelf, value: "1" },
ConfigDefault { key: Config::SentboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxMove, value: "0" },
]),
strict_tls: true,
oauth2_authorizer: None,
};
// example.com.md: example.com, example.org // example.com.md: example.com, example.org
static ref P_EXAMPLE_COM: Provider = Provider { static ref P_EXAMPLE_COM: Provider = Provider {
status: Status::BROKEN, status: Status::BROKEN,
@@ -171,7 +189,12 @@ lazy_static::lazy_static! {
overview_page: "https://providers.delta.chat/five-chat", overview_page: "https://providers.delta.chat/five-chat",
server: vec![ server: vec![
], ],
config_defaults: None, config_defaults: Some(vec![
ConfigDefault { key: Config::BccSelf, value: "1" },
ConfigDefault { key: Config::SentboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxMove, value: "0" },
]),
strict_tls: true, strict_tls: true,
oauth2_authorizer: None, oauth2_authorizer: None,
}; };
@@ -222,6 +245,19 @@ lazy_static::lazy_static! {
oauth2_authorizer: None, oauth2_authorizer: None,
}; };
// hey.com.md: hey.com
static ref P_HEY_COM: Provider = Provider {
status: Status::BROKEN,
before_login_hint: "hey.com does not offer the standard IMAP e-mail protocol, so you cannot log in with Delta Chat to hey.com.",
after_login_hint: "",
overview_page: "https://providers.delta.chat/hey-com",
server: vec![
],
config_defaults: None,
strict_tls: false,
oauth2_authorizer: None,
};
// i.ua.md: i.ua // i.ua.md: i.ua
static ref P_I_UA: Provider = Provider { static ref P_I_UA: Provider = Provider {
status: Status::OK, status: Status::OK,
@@ -431,7 +467,12 @@ lazy_static::lazy_static! {
Server { protocol: IMAP, socket: STARTTLS, hostname: "testrun.org", port: 143, username_pattern: EMAIL }, Server { protocol: IMAP, socket: STARTTLS, hostname: "testrun.org", port: 143, username_pattern: EMAIL },
Server { protocol: SMTP, socket: STARTTLS, hostname: "testrun.org", port: 587, username_pattern: EMAIL }, Server { protocol: SMTP, socket: STARTTLS, hostname: "testrun.org", port: 587, username_pattern: EMAIL },
], ],
config_defaults: None, config_defaults: Some(vec![
ConfigDefault { key: Config::BccSelf, value: "1" },
ConfigDefault { key: Config::SentboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxWatch, value: "0" },
ConfigDefault { key: Config::MvboxMove, value: "0" },
]),
strict_tls: true, strict_tls: true,
oauth2_authorizer: None, oauth2_authorizer: None,
}; };
@@ -547,6 +588,7 @@ lazy_static::lazy_static! {
("comcast.net", &*P_COMCAST), ("comcast.net", &*P_COMCAST),
("dismail.de", &*P_DISMAIL_DE), ("dismail.de", &*P_DISMAIL_DE),
("disroot.org", &*P_DISROOT), ("disroot.org", &*P_DISROOT),
("dubby.org", &*P_DUBBY_ORG),
("example.com", &*P_EXAMPLE_COM), ("example.com", &*P_EXAMPLE_COM),
("example.org", &*P_EXAMPLE_COM), ("example.org", &*P_EXAMPLE_COM),
("fastmail.com", &*P_FASTMAIL), ("fastmail.com", &*P_FASTMAIL),
@@ -563,6 +605,7 @@ lazy_static::lazy_static! {
("gmx.info", &*P_GMX_NET), ("gmx.info", &*P_GMX_NET),
("gmx.biz", &*P_GMX_NET), ("gmx.biz", &*P_GMX_NET),
("gmx.com", &*P_GMX_NET), ("gmx.com", &*P_GMX_NET),
("hey.com", &*P_HEY_COM),
("i.ua", &*P_I_UA), ("i.ua", &*P_I_UA),
("icloud.com", &*P_ICLOUD), ("icloud.com", &*P_ICLOUD),
("me.com", &*P_ICLOUD), ("me.com", &*P_ICLOUD),

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

@@ -38,14 +38,6 @@ impl Context {
self.scheduler.read().await.interrupt_inbox(info).await; self.scheduler.read().await.interrupt_inbox(info).await;
} }
pub(crate) async fn interrupt_sentbox(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_sentbox(info).await;
}
pub(crate) async fn interrupt_mvbox(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_mvbox(info).await;
}
pub(crate) async fn interrupt_smtp(&self, info: InterruptInfo) { pub(crate) async fn interrupt_smtp(&self, info: InterruptInfo) {
self.scheduler.read().await.interrupt_smtp(info).await; self.scheduler.read().await.interrupt_smtp(info).await;
} }
@@ -84,7 +76,11 @@ async fn inbox_loop(ctx: Context, started: Sender<()>, inbox_handlers: ImapConne
} }
None => { None => {
jobs_loaded = 0; jobs_loaded = 0;
info = fetch_idle(&ctx, &mut connection, Config::ConfiguredInboxFolder).await; info = if ctx.get_config_bool(Config::InboxWatch).await {
fetch_idle(&ctx, &mut connection, Config::ConfiguredInboxFolder).await
} else {
connection.fake_idle(&ctx, None).await
};
} }
} }
} }
@@ -245,29 +241,21 @@ impl Scheduler {
let (smtp, smtp_handlers) = SmtpConnectionState::new(); let (smtp, smtp_handlers) = SmtpConnectionState::new();
let (inbox, inbox_handlers) = ImapConnectionState::new(); let (inbox, inbox_handlers) = ImapConnectionState::new();
*self = Scheduler::Running {
inbox,
mvbox,
sentbox,
smtp,
inbox_handle: None,
mvbox_handle: None,
sentbox_handle: None,
smtp_handle: None,
};
let (inbox_start_send, inbox_start_recv) = channel(1); let (inbox_start_send, inbox_start_recv) = channel(1);
if let Scheduler::Running { inbox_handle, .. } = self {
let ctx1 = ctx.clone();
*inbox_handle = Some(task::spawn(async move {
inbox_loop(ctx1, inbox_start_send, inbox_handlers).await
}));
}
let (mvbox_start_send, mvbox_start_recv) = channel(1); let (mvbox_start_send, mvbox_start_recv) = channel(1);
if let Scheduler::Running { mvbox_handle, .. } = self { let mut mvbox_handle = None;
let (sentbox_start_send, sentbox_start_recv) = channel(1);
let mut sentbox_handle = None;
let (smtp_start_send, smtp_start_recv) = channel(1);
let ctx1 = ctx.clone();
let inbox_handle = Some(task::spawn(async move {
inbox_loop(ctx1, inbox_start_send, inbox_handlers).await
}));
if ctx.get_config_bool(Config::MvboxWatch).await {
let ctx1 = ctx.clone(); let ctx1 = ctx.clone();
*mvbox_handle = Some(task::spawn(async move { mvbox_handle = Some(task::spawn(async move {
simple_imap_loop( simple_imap_loop(
ctx1, ctx1,
mvbox_start_send, mvbox_start_send,
@@ -276,12 +264,13 @@ impl Scheduler {
) )
.await .await
})); }));
} else {
mvbox_start_send.send(()).await;
} }
let (sentbox_start_send, sentbox_start_recv) = channel(1); if ctx.get_config_bool(Config::SentboxWatch).await {
if let Scheduler::Running { sentbox_handle, .. } = self {
let ctx1 = ctx.clone(); let ctx1 = ctx.clone();
*sentbox_handle = Some(task::spawn(async move { sentbox_handle = Some(task::spawn(async move {
simple_imap_loop( simple_imap_loop(
ctx1, ctx1,
sentbox_start_send, sentbox_start_send,
@@ -290,15 +279,25 @@ impl Scheduler {
) )
.await .await
})); }));
} else {
sentbox_start_send.send(()).await;
} }
let (smtp_start_send, smtp_start_recv) = channel(1); let ctx1 = ctx.clone();
if let Scheduler::Running { smtp_handle, .. } = self { let smtp_handle = Some(task::spawn(async move {
let ctx1 = ctx.clone(); smtp_loop(ctx1, smtp_start_send, smtp_handlers).await
*smtp_handle = Some(task::spawn(async move { }));
smtp_loop(ctx1, smtp_start_send, smtp_handlers).await
})); *self = Scheduler::Running {
} inbox,
mvbox,
sentbox,
smtp,
inbox_handle,
mvbox_handle,
sentbox_handle,
smtp_handle,
};
// wait for all loops to be started // wait for all loops to be started
if let Err(err) = inbox_start_recv if let Err(err) = inbox_start_recv
@@ -388,10 +387,18 @@ impl Scheduler {
smtp_handle, smtp_handle,
.. ..
} => { } => {
inbox_handle.take().expect("inbox not started").await; if let Some(handle) = inbox_handle.take() {
mvbox_handle.take().expect("mvbox not started").await; handle.await;
sentbox_handle.take().expect("sentbox not started").await; }
smtp_handle.take().expect("smtp not started").await; if let Some(handle) = mvbox_handle.take() {
handle.await;
}
if let Some(handle) = sentbox_handle.take() {
handle.await;
}
if let Some(handle) = smtp_handle.take() {
handle.await;
}
*self = Scheduler::Stopped; *self = Scheduler::Stopped;
} }

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)

1332
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()