Compare commits

...

6 Commits
1.46.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
38 changed files with 3325 additions and 3630 deletions

327
Cargo.lock generated
View File

@@ -103,10 +103,16 @@ dependencies = [
]
[[package]]
name = "aho-corasick"
version = "0.7.10"
name = "ahash"
version = "0.3.8"
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 = [
"memchr",
]
@@ -290,6 +296,15 @@ dependencies = [
"syn",
]
[[package]]
name = "atoi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0afb7287b68575f5ca0e5c7e40191cbd4be59d325781f46faa603e176eaef47"
dependencies = [
"num-traits",
]
[[package]]
name = "atty"
version = "0.2.14"
@@ -549,6 +564,15 @@ dependencies = [
"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"
@@ -714,6 +738,16 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
dependencies = [
"crossbeam-utils",
"maybe-uninit",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.3"
@@ -852,6 +886,7 @@ dependencies = [
"lazy_static",
"lettre_email",
"libc",
"libsqlite3-sys",
"log",
"mailparse",
"native-tls",
@@ -863,11 +898,8 @@ dependencies = [
"pretty_env_logger",
"proptest",
"quick-xml",
"r2d2",
"r2d2_sqlite",
"rand 0.7.3",
"regex",
"rusqlite",
"rustyline",
"sanitize-filename",
"serde",
@@ -875,6 +907,7 @@ dependencies = [
"sha2 0.9.0",
"smallvec",
"smol",
"sqlx",
"stop-token",
"strum",
"strum_macros",
@@ -983,6 +1016,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dtoa"
version = "0.4.6"
@@ -1165,18 +1204,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fast_chemail"
version = "0.9.6"
@@ -1344,6 +1371,19 @@ dependencies = [
"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]]
name = "generic-array"
version = "0.12.3"
@@ -1418,6 +1458,16 @@ dependencies = [
"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]]
name = "heck"
version = "0.3.1"
@@ -1789,6 +1839,17 @@ dependencies = [
"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]]
name = "lru-cache"
version = "0.1.2"
@@ -1815,6 +1876,12 @@ dependencies = [
"quoted_printable",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "match_cfg"
version = "0.1.0"
@@ -2115,9 +2182,9 @@ dependencies = [
[[package]]
name = "parking"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a7fad362df89617628a7508b3e9d588ade1b0ac31aa25de168193ad999c2dd4"
checksum = "1bcaa58ee64f8e4a3d02f5d8e6ed0340eae28fed6fdabd984ad1776e3b43848a"
[[package]]
name = "parking_lot"
@@ -2208,6 +2275,50 @@ dependencies = [
"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]]
name = "pin-project"
version = "0.4.22"
@@ -2376,27 +2487,6 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
[[package]]
name = "r2d2"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af"
dependencies = [
"log",
"parking_lot",
"scheduled-thread-pool",
]
[[package]]
name = "r2d2_sqlite"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed60ebe88b27ac28c0563bc0fbeaecd302ff53e3a01e5ddc2ec9f4e6c707d929"
dependencies = [
"r2d2",
"rusqlite",
]
[[package]]
name = "rand"
version = "0.4.6"
@@ -2422,6 +2512,7 @@ dependencies = [
"rand_chacha",
"rand_core 0.5.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@@ -2467,6 +2558,15 @@ dependencies = [
"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]]
name = "rand_xorshift"
version = "0.2.0"
@@ -2593,22 +2693,6 @@ dependencies = [
"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]]
name = "rust-argon2"
version = "0.7.0"
@@ -2707,13 +2791,10 @@ dependencies = [
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.4"
name = "scoped-tls"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6"
dependencies = [
"parking_lot",
]
checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
[[package]]
name = "scoped-tls"
@@ -2805,9 +2886,9 @@ dependencies = [
[[package]]
name = "serde_qs"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6e32b85107a5c8062643265a90575cc6e798cec0906ea58519b42175062ba27"
checksum = "c6f3acf84e23ab27c01cb5917551765c01c50b2000089db8fa47fe018a3260cf"
dependencies = [
"data-encoding",
"error-chain",
@@ -2893,6 +2974,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]]
name = "skeptic"
version = "0.13.4"
@@ -2935,7 +3022,7 @@ dependencies = [
"futures-util",
"libc",
"once_cell",
"scoped-tls",
"scoped-tls 1.0.0",
"slab",
"socket2",
"wepoll-sys-stjepang",
@@ -2960,6 +3047,88 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "stable_deref_trait"
version = "1.1.1"
@@ -3049,6 +3218,16 @@ dependencies = [
"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]]
name = "strsim"
version = "0.9.3"
@@ -3250,7 +3429,7 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
dependencies = [
"bytes",
"bytes 0.4.12",
"futures 0.1.29",
"log",
]
@@ -3369,9 +3548,9 @@ checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "unicode-xid"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "universal-hash"
@@ -3543,13 +3722,19 @@ dependencies = [
[[package]]
name = "wepoll-sys-stjepang"
version = "1.0.2"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "035f8ab1fcf98d41f8fd5206a97c335604162e5eb0c25c4839062093150ddc79"
checksum = "6fd319e971980166b53e17b1026812ad66c6b54063be879eb182342b55284694"
dependencies = [
"cc",
]
[[package]]
name = "whoami"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18bb55566f6f3f8440914233cf63df1eb60b801b5007d376cc46212cb8a9287c"
[[package]]
name = "widestring"
version = "0.4.2"

View File

@@ -36,9 +36,6 @@ indexmap = "1.3.0"
kamadak-exif = "0.5"
lazy_static = "1.4.0"
regex = "1.1.6"
rusqlite = { version = "0.23", features = ["bundled"] }
r2d2_sqlite = "0.16.0"
r2d2 = "0.8.5"
strum = "0.18.0"
strum_macros = "0.18.0"
backtrace = "0.3.33"
@@ -60,20 +57,22 @@ anyhow = "1.0.28"
async-trait = "0.1.31"
url = "2.1.1"
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 }
log = {version = "0.4.8", optional = true }
log = { version = "0.4.8", optional = true }
rustyline = { version = "4.1.0", optional = true }
ansi_term = { version = "0.12.1", optional = true }
[dev-dependencies]
tempfile = "3.0"
pretty_assertions = "0.6.1"
pretty_env_logger = "0.4.0"
proptest = "0.10"
async-std = { version = "1.6.0", features = ["unstable", "attributes"] }
smol = "0.1.10"
smol = "0.1.11"
log = "0.4.8"
[workspace]
members = [
@@ -98,4 +97,3 @@ internals = []
repl = ["internals", "rustyline", "log", "pretty_env_logger", "ansi_term"]
vendored = ["async-native-tls/vendored", "async-smtp/native-tls-vendored"]
nightly = ["pgp/nightly"]

View File

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

View File

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

View File

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

View File

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

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 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 {
chat::lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE)
.await
@@ -142,7 +131,7 @@ impl Chatlist {
// shown at all permanent in the chatlist.
let mut ids = if let Some(query_contact_id) = query_contact_id {
// show chats shared with a given contact
context.sql.query_map(
context.sql.query_rows(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
@@ -151,16 +140,14 @@ impl Chatlist {
SELECT id
FROM msgs
WHERE chat_id=c.id
AND (hidden=0 OR state=?1)
AND (hidden=0 OR state=?)
ORDER BY timestamp DESC, id DESC LIMIT 1)
WHERE c.id>9
AND c.blocked=0
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2)
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)
GROUP BY c.id
ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsv![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
process_row,
process_rows,
ORDER BY c.archived=? DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsx![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
).await?
} else if flag_archived_only {
// show archived chats
@@ -169,7 +156,7 @@ impl Chatlist {
// and adapting the number requires larger refactorings and seems not to be worth the effort)
context
.sql
.query_map(
.query_rows(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
@@ -185,9 +172,7 @@ impl Chatlist {
AND c.archived=1
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsv![MessageState::OutDraft],
process_row,
process_rows,
paramsx![MessageState::OutDraft],
)
.await?
} else if let Some(query) = query {
@@ -203,7 +188,7 @@ impl Chatlist {
let str_like_cmd = format!("%{}%", query);
context
.sql
.query_map(
.query_rows(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
@@ -219,9 +204,7 @@ impl Chatlist {
AND c.name LIKE ?3
GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsv![MessageState::OutDraft, skip_id, str_like_cmd],
process_row,
process_rows,
paramsx![MessageState::OutDraft, skip_id, str_like_cmd],
)
.await?
} else {
@@ -234,7 +217,7 @@ impl Chatlist {
} else {
ChatId::new(0)
};
let mut ids = context.sql.query_map(
let mut ids = context.sql.query_rows(
"SELECT c.id, m.id
FROM chats c
LEFT JOIN msgs m
@@ -250,10 +233,9 @@ impl Chatlist {
AND NOT c.archived=?3
GROUP BY c.id
ORDER BY c.id=?4 DESC, c.archived=?5 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
paramsv![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
process_row,
process_rows,
paramsx![MessageState::OutDraft, skip_id, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
).await?;
if !flag_no_specials {
if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await
{
@@ -387,15 +369,15 @@ impl Chatlist {
/// Returns the number of archived chats
pub async fn dc_get_archived_cnt(context: &Context) -> u32 {
context
let v: i32 = context
.sql
.query_get_value(
context,
.query_value(
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
paramsv![],
paramsx![],
)
.await
.unwrap_or_default()
.unwrap_or_default();
v as u32
}
async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<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.
context
.sql
.query_get_value(
context,
concat!(
"SELECT m.id",
" FROM msgs m",
" LEFT JOIN chats c",
" ON c.id=m.chat_id",
" WHERE m.state=10",
" AND m.hidden=0",
" AND c.blocked=2",
" ORDER BY m.timestamp DESC, m.id DESC;"
),
paramsv![],
.query_value(
r#"
SELECT m.id
FROM msgs m
LEFT JOIN chats c
ON c.id=m.chat_id
WHERE m.state=10
AND m.hidden=0
AND c.blocked=2
ORDER BY m.timestamp DESC, m.id DESC;
"#,
paramsx![],
)
.await
.ok()
}
#[cfg(test)]

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,14 @@
//! Contacts module
#![forbid(clippy::indexing_slicing)]
use std::convert::TryFrom;
use async_std::path::PathBuf;
use deltachat_derive::*;
use async_std::prelude::*;
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use sqlx::Row;
use crate::aheader::EncryptPreference;
use crate::chat::ChatId;
@@ -72,7 +74,7 @@ pub struct Contact {
/// Possible origins of a contact.
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, FromSql, ToSql,
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, FromPrimitive, ToPrimitive, sqlx::Type,
)]
#[repr(i32)]
pub enum Origin {
@@ -133,6 +135,31 @@ impl Default for Origin {
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 {
/// Contacts that are known, i. e. they came in via accepted contacts or
@@ -163,27 +190,18 @@ pub enum VerifiedStatus {
impl Contact {
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
.query_row(
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
FROM contacts c
WHERE c.id=?;",
paramsv![contact_id as i32],
|row| {
let contact = Self {
id: contact_id,
name: row.get::<_, String>(0)?,
authname: row.get::<_, String>(4)?,
addr: row.get::<_, String>(1)?,
blocked: row.get::<_, Option<i32>>(3)?.unwrap_or_default() != 0,
origin: row.get(2)?,
param: row.get::<_, String>(5)?.parse().unwrap_or_default(),
};
Ok(contact)
},
r#"
SELECT id, name, addr, origin, blocked, authname, param
FROM contacts
WHERE id=?;
"#,
paramsx![contact_id as i32],
)
.await?;
if contact_id == DC_CONTACT_ID_SELF {
res.name = context.stock_str(StockMessage::SelfMsg).await.to_string();
res.addr = context
@@ -269,8 +287,11 @@ impl Contact {
if context
.sql
.execute(
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh],
r#"
UPDATE msgs SET state=?
WHERE from_id=? AND state=?;
"#,
paramsx![MessageState::InNoticed, id as i32, MessageState::InFresh],
)
.await
.is_ok()
@@ -305,15 +326,15 @@ impl Contact {
if addr_cmp(addr_normalized, addr_self) {
return DC_CONTACT_ID_SELF;
}
context.sql.query_get_value(
context,
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
paramsv![
let v: i32 = context.sql.query_value(
"SELECT id FROM contacts WHERE addr=? COLLATE NOCASE AND id>? AND origin>=? AND blocked=0;",
paramsx![
addr_normalized,
DC_CONTACT_ID_LAST_SPECIAL as i32,
min_origin as u32,
min_origin as i32
],
).await.unwrap_or_default()
).await.unwrap_or_default();
v as u32
}
/// Lookup a contact and create it if it does not exist yet.
@@ -384,39 +405,32 @@ impl Contact {
let mut update_authname = false;
let mut row_id = 0;
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row(
let res: Result<(i32, String, String, Origin, String), _> = context.sql.query_row(
"SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;",
paramsv![addr.to_string()],
|row| {
let row_id = row.get(0)?;
let row_name: String = row.get(1)?;
let row_addr: String = row.get(2)?;
let row_origin: Origin = row.get(3)?;
let row_authname: String = row.get(4)?;
paramsx![addr.to_string()],
)
.await;
if !name.as_ref().is_empty() {
if !row_name.is_empty() {
if (origin >= row_origin || row_name == row_authname)
&& name.as_ref() != row_name
{
update_name = true;
}
} else {
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = res {
if !name.as_ref().is_empty() {
if !row_name.is_empty() {
if (origin >= row_origin || row_name == row_authname)
&& name.as_ref() != row_name
{
update_name = true;
}
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
} else if origin == Origin::ManuallyCreated && !row_authname.is_empty() {
// no name given on manual edit, this will update the name to the authname
} else {
update_name = true;
}
if origin == Origin::IncomingUnknownFrom && name.as_ref() != row_authname {
update_authname = true;
}
} else if origin == Origin::ManuallyCreated && !row_authname.is_empty() {
// no name given on manual edit, this will update the name to the authname
update_name = true;
}
Ok((row_id, row_name, row_addr, row_origin, row_authname))
},
)
.await {
row_id = id;
row_id = id as u32;
if origin as i32 >= row_origin as i32 && addr != row_addr {
update_addr = true;
}
@@ -435,9 +449,13 @@ impl Contact {
.sql
.execute(
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
paramsv![
new_name,
if update_addr { addr.to_string() } else { row_addr },
paramsx![
&new_name,
if update_addr {
addr.to_string()
} else {
row_addr
},
if origin > row_origin {
origin
} else {
@@ -448,7 +466,7 @@ impl Contact {
} else {
row_authname
},
row_id
row_id as i32
],
)
.await
@@ -459,7 +477,7 @@ impl Contact {
// This is one of the few duplicated data, however, getting the chat list is easier this way.
context.sql.execute(
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
paramsv![new_name, Chattype::Single, row_id]
paramsx![&new_name, Chattype::Single, row_id as i32]
).await.ok();
}
sth_modified = Modifier::Modified;
@@ -473,20 +491,17 @@ impl Contact {
.sql
.execute(
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
paramsv![
name.as_ref().to_string(),
addr,
paramsx![
name.as_ref(),
&addr,
origin,
if update_authname { name.as_ref().to_string() } else { "".to_string() }
if update_authname { name.as_ref() } else { "" }
],
)
.await
.is_ok()
{
row_id = context
.sql
.get_rowid(context, "contacts", "addr", &addr)
.await?;
row_id = context.sql.get_rowid("contacts", "addr", &addr).await?;
sth_modified = Modifier::Created;
info!(context, "added contact id={} addr={}", row_id, &addr);
} else {
@@ -573,35 +588,32 @@ impl Contact {
.map(|s| s.as_ref().to_string())
.unwrap_or_default()
);
context
.sql
.query_map(
"SELECT c.id FROM contacts c \
LEFT JOIN acpeerstates ps ON c.addr=ps.addr \
WHERE c.addr!=?1 \
AND c.id>?2 \
AND c.origin>=?3 \
AND c.blocked=0 \
AND (c.name LIKE ?4 OR c.addr LIKE ?5) \
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
ORDER BY LOWER(c.name||c.addr),c.id;",
paramsv![
self_addr,
DC_CONTACT_ID_LAST_SPECIAL as i32,
Origin::IncomingReplyTo,
s3str_like_cmd,
s3str_like_cmd,
if flag_verified_only { 0i32 } else { 1i32 },
],
|row| row.get::<_, i32>(0),
|ids| {
for id in ids {
ret.push(id? as u32);
}
Ok(())
},
)
.await?;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(
r#"
SELECT c.id FROM contacts c
LEFT JOIN acpeerstates ps ON c.addr=ps.addr
WHERE c.addr!=?
AND c.id>?
AND c.origin>=?
AND c.blocked=0
AND (c.name LIKE ? OR c.addr LIKE ?)
AND (1=? OR LENGTH(ps.verified_key_fingerprint)!=0)
ORDER BY LOWER(c.name||c.addr),c.id
"#,
)
.bind(&self_addr)
.bind(DC_CONTACT_ID_LAST_SPECIAL as i32)
.bind(Origin::IncomingReplyTo)
.bind(&s3str_like_cmd)
.bind(&s3str_like_cmd)
.bind(if flag_verified_only { 0i32 } else { 1i32 })
.fetch(&pool);
while let Some(id) = rows.next().await {
let (id,): (i32,) = id?;
ret.push(id as u32);
}
let self_name = context
.get_config(Config::Displayname)
@@ -622,17 +634,15 @@ impl Contact {
} else {
add_self = true;
context.sql.query_map(
"SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;",
paramsv![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100],
|row| row.get::<_, i32>(0),
|ids| {
for id in ids {
ret.push(id? as u32);
}
Ok(())
}
).await?;
let pool = context.sql.get_pool().await?;
let mut rows = sqlx::query_as(
"SELECT id FROM contacts WHERE addr!=? AND id>? AND origin>=? AND blocked=0 ORDER BY LOWER(name || addr), id;"
).bind(self_addr).bind(DC_CONTACT_ID_LAST_SPECIAL as i32).bind(0x100).fetch(&pool);
while let Some(id) = rows.next().await {
let (id,): (i32,) = id?;
ret.push(id as u32);
}
}
if flag_add_self && add_self {
@@ -643,32 +653,30 @@ impl Contact {
}
pub async fn get_blocked_cnt(context: &Context) -> usize {
context
let v: i32 = context
.sql
.query_get_value::<isize>(
context,
.query_value(
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
)
.await
.unwrap_or_default() as usize
.unwrap_or_default();
v as usize
}
/// Get blocked contacts.
pub async fn get_all_blocked(context: &Context) -> Vec<u32> {
context
.sql
.query_map(
.query_values(
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;",
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
|row| row.get::<_, u32>(0),
|ids| {
ids.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
)
.await
.unwrap_or_default()
.into_iter()
.map(|id: i32| id as u32)
.collect()
}
/// 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 loginparam = LoginParam::from_database(context, "configured_").await;
if peerstate.is_some()
if peerstate.is_ok()
&& peerstate
.as_ref()
.ok()
.and_then(|p| p.peek_key(PeerstateVerifiedStatus::Unverified))
.is_some()
{
@@ -754,10 +763,9 @@ impl Contact {
let count_contacts: i32 = context
.sql
.query_get_value(
context,
.query_value(
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
paramsv![contact_id as i32],
paramsx![contact_id as i32],
)
.await
.unwrap_or_default();
@@ -765,10 +773,9 @@ impl Contact {
let count_msgs: i32 = if count_contacts > 0 {
context
.sql
.query_get_value(
context,
.query_value(
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
paramsv![contact_id as i32, contact_id as i32],
paramsx![contact_id as i32, contact_id as i32],
)
.await
.unwrap_or_default()
@@ -781,7 +788,7 @@ impl Contact {
.sql
.execute(
"DELETE FROM contacts WHERE id=?;",
paramsv![contact_id as i32],
paramsx![contact_id as i32],
)
.await
{
@@ -819,7 +826,7 @@ impl Contact {
.sql
.execute(
"UPDATE contacts SET param=? WHERE id=?",
paramsv![self.param.to_string(), self.id as i32],
paramsx![self.param.to_string(), self.id as i32],
)
.await?;
Ok(())
@@ -927,7 +934,7 @@ impl Contact {
pub async fn is_verified_ex(
&self,
context: &Context,
peerstate: Option<&Peerstate<'_>>,
peerstate: Option<&Peerstate>,
) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device
@@ -942,7 +949,7 @@ impl Contact {
}
let peerstate = Peerstate::from_addr(context, &self.addr).await;
if let Some(ps) = peerstate {
if let Ok(ps) = peerstate {
if ps.verified_key.is_some() {
return VerifiedStatus::BidirectVerified;
}
@@ -977,15 +984,15 @@ impl Contact {
return 0;
}
context
let v: i32 = context
.sql
.query_get_value::<isize>(
context,
.query_value(
"SELECT COUNT(*) FROM contacts WHERE id>?;",
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
paramsx![DC_CONTACT_ID_LAST_SPECIAL as i32],
)
.await
.unwrap_or_default() as usize
.unwrap_or_default();
v as usize
}
pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool {
@@ -997,7 +1004,7 @@ impl Contact {
.sql
.exists(
"SELECT id FROM contacts WHERE id=?;",
paramsv![contact_id as i32],
paramsx![contact_id as i32],
)
.await
.unwrap_or_default()
@@ -1008,7 +1015,7 @@ impl Contact {
.sql
.execute(
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
paramsv![origin, contact_id as i32, origin],
paramsx![origin, contact_id as i32, origin],
)
.await
.is_ok()
@@ -1070,7 +1077,7 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
.sql
.execute(
"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
.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.
// 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...)
if context.sql.execute(
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
paramsv![new_blocking, 100, contact_id as i32],
).await.is_ok() {
Contact::mark_noticed(context, contact_id).await;
context.emit_event(Event::ContactsChanged(None));
}
if context
.sql
.execute(
r#"
UPDATE chats
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> {
let mut res = BTreeMap::new();
res.insert("deltachat_core_version", format!("v{}", &*DC_VERSION_STR));
res.insert("sqlite_version", rusqlite::version().to_string());
let version =
String::from_utf8(libsqlite3_sys::SQLITE_VERSION.to_vec()).expect("invalid version");
res.insert("sqlite_version", version);
res.insert("arch", (std::mem::size_of::<usize>() * 8).to_string());
res.insert("level", "awesome".into());
res
@@ -85,8 +89,6 @@ pub fn get_info() -> BTreeMap<&'static str, String> {
impl Context {
/// Creates new context.
pub async fn new(os_name: String, dbfile: PathBuf) -> Result<Context> {
// pretty_env_logger::try_init_timed().ok();
let mut blob_fname = OsString::new();
blob_fname.push(dbfile.file_name().unwrap_or_default());
blob_fname.push("-blobs");
@@ -127,10 +129,8 @@ impl Context {
let ctx = Context {
inner: Arc::new(inner),
};
ensure!(
ctx.sql.open(&ctx, &ctx.dbfile, false).await,
"Failed opening sqlite database"
);
ctx.sql.open(&ctx, &ctx.dbfile, false).await?;
Ok(ctx)
}
@@ -263,27 +263,29 @@ impl Context {
let is_configured = self.get_config_int(Config::Configured).await;
let dbversion = self
.sql
.get_raw_config_int(self, "dbversion")
.get_raw_config_int("dbversion")
.await
.unwrap_or_default();
let journal_mode = self
.sql
.query_get_value(self, "PRAGMA journal_mode;", paramsv![])
.query_value("PRAGMA journal_mode;", paramsx![])
.await
.unwrap_or_else(|| "unknown".to_string());
.unwrap_or_else(|_| "unknown".to_string());
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await;
let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await;
let bcc_self = self.get_config_int(Config::BccSelf).await;
let prv_key_cnt: Option<isize> = self
let prv_key_cnt: Option<i32> = self
.sql
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", paramsv![])
.await;
.query_value("SELECT COUNT(*) FROM keypairs;", paramsx![])
.await
.ok();
let pub_key_cnt: Option<isize> = self
let pub_key_cnt: Option<i32> = self
.sql
.query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![])
.await;
.query_value("SELECT COUNT(*) FROM acpeerstates;", paramsx![])
.await
.ok();
let fingerprint_str = match SignedPublicKey::load_self(self).await {
Ok(key) => key.fingerprint().hex(),
Err(err) => format!("<key failure: {}>", err),
@@ -295,7 +297,7 @@ impl Context {
let mvbox_move = self.get_config_int(Config::MvboxMove).await;
let folders_configured = self
.sql
.get_raw_config_int(self, "folders_configured")
.get_raw_config_int("folders_configured")
.await
.unwrap_or_default();
@@ -356,30 +358,22 @@ impl Context {
pub async fn get_fresh_msgs(&self) -> Vec<MsgId> {
let show_deaddrop: i32 = 0;
self.sql
.query_map(
concat!(
"SELECT m.id",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" LEFT JOIN chats c",
" ON m.chat_id=c.id",
" WHERE m.state=?",
" AND m.hidden=0",
" AND m.chat_id>?",
" AND ct.blocked=0",
" AND (c.blocked=0 OR c.blocked=?)",
" ORDER BY m.timestamp DESC,m.id DESC;"
),
paramsv![10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|row| row.get::<_, MsgId>(0),
|rows| {
let mut ret = Vec::new();
for row in rows {
ret.push(row?);
}
Ok(ret)
},
.query_values(
r#"
SELECT m.id
FROM msgs m
LEFT JOIN contacts ct
ON m.from_id=ct.id
LEFT JOIN chats c
ON m.chat_id=c.id
WHERE m.state=?
AND m.hidden=0
AND m.chat_id>?
AND ct.blocked=0
AND (c.blocked=0 OR c.blocked=?)
ORDER BY m.timestamp DESC,m.id DESC;
"#,
paramsx![10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
)
.await
.unwrap_or_default()
@@ -395,47 +389,36 @@ impl Context {
let strLikeBeg = format!("{}%", real_query);
let query = if !chat_id.is_unset() {
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" WHERE m.chat_id=?",
" AND m.hidden=0",
" AND ct.blocked=0",
" AND (txt LIKE ? OR ct.name LIKE ?)",
" ORDER BY m.timestamp,m.id;"
)
r#"
SELECT m.id
FROM msgs
LEFT JOIN contacts ct
ON m.from_id=ct.id
WHERE m.chat_id=?
AND m.hidden=0
AND ct.blocked=0
AND (txt LIKE ? OR ct.name LIKE ?)
ORDER BY m.timestamp,m.id;
"#
} else {
concat!(
"SELECT m.id AS id, m.timestamp AS timestamp",
" FROM msgs m",
" LEFT JOIN contacts ct",
" ON m.from_id=ct.id",
" LEFT JOIN chats c",
" ON m.chat_id=c.id",
" WHERE m.chat_id>9",
" AND m.hidden=0",
" AND (c.blocked=0 OR c.blocked=?)",
" AND ct.blocked=0",
" AND (m.txt LIKE ? OR ct.name LIKE ?)",
" ORDER BY m.timestamp DESC,m.id DESC;"
)
r#"
SELECT m.id
FROM msgs m
LEFT JOIN contacts ct
ON m.from_id=ct.id
LEFT JOIN chats c
ON m.chat_id=c.id
WHERE m.chat_id>9
AND m.hidden=0
AND (c.blocked=0 OR c.blocked=?)
AND ct.blocked=0
AND (m.txt LIKE ? OR ct.name LIKE ?)
ORDER BY m.timestamp DESC,m.id DESC;
"#
};
self.sql
.query_map(
query,
paramsv![chat_id, strLikeInText, strLikeBeg],
|row| row.get::<_, MsgId>("id"),
|rows| {
let mut ret = Vec::new();
for id in rows {
ret.push(id?);
}
Ok(ret)
},
)
.query_values(query, paramsx![chat_id, strLikeInText, strLikeBeg])
.await
.unwrap_or_default()
}

View File

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

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
/// the bit at position bitindex is 1.
pub(crate) fn listflags_has(listflags: u32, bitindex: usize) -> bool {

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -6,16 +6,10 @@
extern crate num_derive;
#[macro_use]
extern crate smallvec;
#[macro_use]
extern crate rusqlite;
extern crate strum;
#[macro_use]
extern crate strum_macros;
pub trait ToSql: rusqlite::ToSql + Send + Sync {}
impl<T: rusqlite::ToSql + Send + Sync> ToSql for T {}
#[macro_use]
pub mod log;
#[macro_use]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

1334
src/sql.rs

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

View File

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

View File

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

View File

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