mirror of
https://github.com/chatmail/core.git
synced 2026-04-10 01:22:11 +03:00
Compare commits
9 Commits
v1.137.2
...
link2xt/sq
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5820c4ce95 | ||
|
|
12ba33d9d4 | ||
|
|
60a7bbc9b5 | ||
|
|
e9f434b562 | ||
|
|
2423cb8175 | ||
|
|
65c9e72bf4 | ||
|
|
ea4d954c77 | ||
|
|
43523a96a2 | ||
|
|
2e2fa9e74f |
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -1102,7 +1102,9 @@ dependencies = [
|
||||
"async_zip",
|
||||
"backtrace",
|
||||
"base64 0.21.7",
|
||||
"bitflags 1.3.2",
|
||||
"brotli",
|
||||
"bstr",
|
||||
"chrono",
|
||||
"criterion",
|
||||
"deltachat-time",
|
||||
|
||||
@@ -46,6 +46,8 @@ async_zip = { version = "0.0.12", default-features = false, features = ["deflate
|
||||
backtrace = "0.3"
|
||||
base64 = "0.21"
|
||||
brotli = { version = "4", default-features=false, features = ["std"] }
|
||||
bitflags = "1.3"
|
||||
bstr = { version = "1.4.0", default-features=false, features = ["std", "alloc"] }
|
||||
chrono = { version = "0.4", default-features=false, features = ["clock", "std"] }
|
||||
email = { git = "https://github.com/deltachat/rust-email", branch = "master" }
|
||||
encoded-words = { git = "https://github.com/async-email/encoded-words", branch = "master" }
|
||||
|
||||
@@ -339,6 +339,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
export-keys\n\
|
||||
import-keys\n\
|
||||
export-setup\n\
|
||||
dump <filename>\n\n
|
||||
read <filename>\n\n
|
||||
poke [<eml-file>|<folder>|<addr> <key-file>]\n\
|
||||
reset <flags>\n\
|
||||
stop\n\
|
||||
@@ -514,6 +516,14 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
||||
&setup_code,
|
||||
);
|
||||
}
|
||||
"dump" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <filename> missing.");
|
||||
serialize_database(&context, arg1).await?;
|
||||
}
|
||||
"read" => {
|
||||
ensure!(!arg1.is_empty(), "Argument <filename> missing.");
|
||||
deserialize_database(&context, arg1).await?;
|
||||
}
|
||||
"poke" => {
|
||||
ensure!(poke_spec(&context, Some(arg1)).await, "Poke failed");
|
||||
}
|
||||
|
||||
@@ -76,6 +76,12 @@ class Account:
|
||||
"""Get self avatar."""
|
||||
return self.get_config("selfavatar")
|
||||
|
||||
def check_qr(self, qr):
|
||||
return self._rpc.check_qr(self.id, qr)
|
||||
|
||||
def set_config_from_qr(self, qr: str):
|
||||
self._rpc.set_config_from_qr(self.id, qr)
|
||||
|
||||
@futuremethod
|
||||
def configure(self):
|
||||
"""Configure an account."""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
from deltachat_rpc_client import Chat, SpecialContactId
|
||||
from deltachat_rpc_client import Chat, EventType, SpecialContactId
|
||||
|
||||
|
||||
def test_qr_setup_contact(acfactory, tmp_path) -> None:
|
||||
@@ -579,3 +579,40 @@ def test_securejoin_after_contact_resetup(acfactory) -> None:
|
||||
|
||||
# ac1 is still "not verified" for ac2 due to inconsistent state.
|
||||
assert not ac2_contact_ac1.get_snapshot().is_verified
|
||||
|
||||
|
||||
def test_withdraw_securejoin_qr(acfactory):
|
||||
alice, bob = acfactory.get_online_accounts(2)
|
||||
|
||||
logging.info("Alice creates a verified group")
|
||||
alice_chat = alice.create_group("Verified group", protect=True)
|
||||
assert alice_chat.get_basic_snapshot().is_protected
|
||||
logging.info("Bob joins verified group")
|
||||
|
||||
qr_code, _svg = alice_chat.get_qr_code()
|
||||
bob_chat = bob.secure_join(qr_code)
|
||||
bob.wait_for_securejoin_joiner_success()
|
||||
|
||||
snapshot = bob.get_message_by_id(bob.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Member Me ({}) added by {}.".format(bob.get_config("addr"), alice.get_config("addr"))
|
||||
assert snapshot.chat.get_basic_snapshot().is_protected
|
||||
bob_chat.leave()
|
||||
|
||||
snapshot = alice.get_message_by_id(alice.wait_for_incoming_msg_event().msg_id).get_snapshot()
|
||||
assert snapshot.text == "Group left by {}.".format(bob.get_config("addr"))
|
||||
|
||||
logging.info("Alice withdraws QR code.")
|
||||
qr = alice.check_qr(qr_code)
|
||||
assert qr["kind"] == "withdrawVerifyGroup"
|
||||
alice.set_config_from_qr(qr_code)
|
||||
|
||||
logging.info("Bob scans withdrawn QR code.")
|
||||
bob_chat = bob.secure_join(qr_code)
|
||||
|
||||
logging.info("Bob scanned withdrawn QR code")
|
||||
while True:
|
||||
event = alice.wait_for_event()
|
||||
if event.kind == EventType.MSGS_CHANGED and event.chat_id != 0:
|
||||
break
|
||||
snapshot = alice.get_message_by_id(event.msg_id).get_snapshot()
|
||||
assert snapshot.text == "Cannot establish guaranteed end-to-end encryption with {}".format(bob.get_config("addr"))
|
||||
|
||||
201
fuzz/Cargo.lock
generated
201
fuzz/Cargo.lock
generated
@@ -339,6 +339,12 @@ version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.3"
|
||||
@@ -515,9 +521,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "3.4.0"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f"
|
||||
checksum = "125740193d7fee5cc63ab9e16c2fdc4e07c74ba755cc53b327d6ea029e9fc569"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -526,9 +532,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "2.5.1"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f"
|
||||
checksum = "65622a320492e09b5e0ac436b14c54ff68199bac392d0e89a6832c4518eea525"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -953,7 +959,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "1.136.0"
|
||||
version = "1.137.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.1.1",
|
||||
@@ -989,6 +995,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"openssl-src",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"pgp",
|
||||
@@ -1785,9 +1792,9 @@ checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba"
|
||||
checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
|
||||
dependencies = [
|
||||
"fastrand 2.0.1",
|
||||
"futures-core",
|
||||
@@ -1912,9 +1919,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.24"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -2051,9 +2058,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -2062,12 +2069,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.5"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
|
||||
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
@@ -2077,12 +2096,6 @@ version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "humansize"
|
||||
version = "2.1.2"
|
||||
@@ -2094,39 +2107,58 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.23"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
|
||||
checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2 0.4.7",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.4",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2180,17 +2212,29 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.9"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
||||
checksum = "fd54d660e773627692c524beaad361aca785a4f9f5730ce91f42aabe5bce3d11"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"image-webp",
|
||||
"num-traits",
|
||||
"png",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a84a25dcae3ac487bc24ef280f9e20c79c9b1a3e5e32cbed3041d1c514aa87c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2314,12 +2358,6 @@ version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.60"
|
||||
@@ -3480,11 +3518,11 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.24"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
|
||||
checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"base64 0.22.0",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
@@ -3492,8 +3530,10 @@ dependencies = [
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -3502,7 +3542,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 2.1.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@@ -3515,7 +3555,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.50.0",
|
||||
"winreg 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3724,7 +3764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 1.0.2",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
@@ -3738,6 +3778,22 @@ dependencies = [
|
||||
"base64 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f48172685e6ff52a556baa527774f61fcaa884f59daf3375c62a3f1cd2549dab"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.7"
|
||||
@@ -4055,9 +4111,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.10.0"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
@@ -4294,22 +4350,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.38"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
|
||||
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.38"
|
||||
version = "1.0.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.107",
|
||||
"syn 2.0.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4442,9 +4498,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@@ -4514,6 +4570,28 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
@@ -5169,9 +5247,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
@@ -5265,3 +5343,18 @@ dependencies = [
|
||||
"syn 1.0.107",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec866b44a2a1fd6133d363f073ca1b179f438f99e7e5bfb1e33f7181facfe448"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
@@ -2698,7 +2698,9 @@ async fn send_msg_inner(context: &Context, chat_id: ChatId, msg: &mut Message) -
|
||||
}
|
||||
|
||||
if !prepare_send_msg(context, chat_id, msg).await?.is_empty() {
|
||||
context.emit_msgs_changed(msg.chat_id, msg.id);
|
||||
if !msg.hidden {
|
||||
context.emit_msgs_changed(msg.chat_id, msg.id);
|
||||
}
|
||||
|
||||
if msg.param.exists(Param::SetLatitude) {
|
||||
context.emit_event(EventType::LocationChanged(Some(ContactId::SELF)));
|
||||
|
||||
15
src/imex.rs
15
src/imex.rs
@@ -10,6 +10,7 @@ use futures::StreamExt;
|
||||
use futures_lite::FutureExt;
|
||||
use rand::{thread_rng, Rng};
|
||||
use tokio::fs::{self, File};
|
||||
use tokio::io::BufWriter;
|
||||
use tokio_tar::Archive;
|
||||
|
||||
use crate::blob::{BlobDirContents, BlobObject};
|
||||
@@ -816,6 +817,20 @@ async fn export_database(
|
||||
.await
|
||||
}
|
||||
|
||||
/// Serializes the database to a file.
|
||||
pub async fn serialize_database(context: &Context, filename: &str) -> Result<()> {
|
||||
let file = File::create(filename).await?;
|
||||
context.sql.serialize(BufWriter::new(file)).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deserializes the database from a file.
|
||||
pub async fn deserialize_database(context: &Context, filename: &str) -> Result<()> {
|
||||
let file = File::open(filename).await?;
|
||||
context.sql.deserialize(file).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::Duration;
|
||||
|
||||
14
src/sql.rs
14
src/sql.rs
@@ -46,10 +46,12 @@ pub(crate) fn params_iter(
|
||||
iter.iter().map(|item| item as &dyn crate::sql::ToSql)
|
||||
}
|
||||
|
||||
mod deserialize;
|
||||
mod migrations;
|
||||
mod pool;
|
||||
mod serialize;
|
||||
|
||||
use pool::Pool;
|
||||
use pool::{Pool, PooledConnection};
|
||||
|
||||
/// A wrapper around the underlying Sqlite3 object.
|
||||
#[derive(Debug)]
|
||||
@@ -363,6 +365,12 @@ impl Sql {
|
||||
self.write_mtx.lock().await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_connection(&self) -> Result<PooledConnection> {
|
||||
let lock = self.pool.read().await;
|
||||
let pool = lock.as_ref().context("no SQL connection")?;
|
||||
pool.get().await
|
||||
}
|
||||
|
||||
/// Allocates a connection and calls `function` with the connection. If `function` does write
|
||||
/// queries,
|
||||
/// - either first take a lock using `write_lock()`
|
||||
@@ -374,9 +382,7 @@ impl Sql {
|
||||
F: 'a + FnOnce(&mut Connection) -> Result<R> + Send,
|
||||
R: Send + 'static,
|
||||
{
|
||||
let lock = self.pool.read().await;
|
||||
let pool = lock.as_ref().context("no SQL connection")?;
|
||||
let mut conn = pool.get().await?;
|
||||
let mut conn = self.get_connection().await?;
|
||||
let res = tokio::task::block_in_place(move || function(&mut conn))?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
1349
src/sql/deserialize.rs
Normal file
1349
src/sql/deserialize.rs
Normal file
File diff suppressed because it is too large
Load Diff
963
src/sql/serialize.rs
Normal file
963
src/sql/serialize.rs
Normal file
@@ -0,0 +1,963 @@
|
||||
//! Database serialization module.
|
||||
//!
|
||||
//! The module contains functions to serialize database into a stream.
|
||||
//!
|
||||
//! Output format is based on [bencoding](http://bittorrent.org/beps/bep_0003.html).
|
||||
|
||||
/// Database version supported by the current serialization code.
|
||||
///
|
||||
/// Serialization code MUST be updated before increasing this number.
|
||||
///
|
||||
/// If this version is below the actual database version,
|
||||
/// serialization code is outdated.
|
||||
/// If this version is above the actual database version,
|
||||
/// migrations have to be run first to update the database.
|
||||
const SERIALIZE_DBVERSION: &str = "99";
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use rusqlite::types::ValueRef;
|
||||
use rusqlite::Transaction;
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
|
||||
use super::Sql;
|
||||
|
||||
struct Encoder<'a, W: AsyncWrite + Unpin> {
|
||||
tx: Transaction<'a>,
|
||||
|
||||
w: W,
|
||||
}
|
||||
|
||||
async fn write_bytes(w: &mut (impl AsyncWrite + Unpin), b: &[u8]) -> Result<()> {
|
||||
let bytes_len = format!("{}:", b.len());
|
||||
w.write_all(bytes_len.as_bytes()).await?;
|
||||
w.write_all(b).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_str(w: &mut (impl AsyncWrite + Unpin), s: &str) -> Result<()> {
|
||||
write_bytes(w, s.as_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_i64(w: &mut (impl AsyncWrite + Unpin), i: i64) -> Result<()> {
|
||||
let s = format!("{i}");
|
||||
w.write_all(b"i").await?;
|
||||
w.write_all(s.as_bytes()).await?;
|
||||
w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_u32(w: &mut (impl AsyncWrite + Unpin), i: u32) -> Result<()> {
|
||||
let s = format!("{i}");
|
||||
w.write_all(b"i").await?;
|
||||
w.write_all(s.as_bytes()).await?;
|
||||
w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_f64(w: &mut (impl AsyncWrite + Unpin), f: f64) -> Result<()> {
|
||||
write_bytes(w, &f.to_be_bytes()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn write_bool(w: &mut (impl AsyncWrite + Unpin), b: bool) -> Result<()> {
|
||||
if b {
|
||||
w.write_all(b"i1e").await?;
|
||||
} else {
|
||||
w.write_all(b"i0e").await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl<'a, W: AsyncWrite + Unpin> Encoder<'a, W> {
|
||||
fn new(tx: Transaction<'a>, w: W) -> Self {
|
||||
Self { tx, w }
|
||||
}
|
||||
|
||||
/// Serializes `config` table.
|
||||
async fn serialize_config(&mut self) -> Result<()> {
|
||||
// FIXME: sort the dictionary in lexicographical order
|
||||
// dbversion should be the first, so store it as "_config._dbversion"
|
||||
|
||||
let mut stmt = self.tx.prepare("SELECT keyname,value FROM config")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
self.w.write_all(b"d").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let keyname: String = row.get(0)?;
|
||||
let value: String = row.get(1)?;
|
||||
write_str(&mut self.w, &keyname).await?;
|
||||
write_str(&mut self.w, &value).await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_acpeerstates(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare("SELECT addr, backward_verified_key_id, last_seen, last_seen_autocrypt, public_key, prefer_encrypted, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let addr: String = row.get("addr")?;
|
||||
let backward_verified_key_id: Option<i64> = row.get("backward_verified_key_id")?;
|
||||
let prefer_encrypted: i64 = row.get("prefer_encrypted")?;
|
||||
|
||||
let last_seen: i64 = row.get("last_seen")?;
|
||||
|
||||
let last_seen_autocrypt: i64 = row.get("last_seen_autocrypt")?;
|
||||
let public_key: Option<Vec<u8>> = row.get("public_key")?;
|
||||
let public_key_fingerprint: Option<String> = row.get("public_key_fingerprint")?;
|
||||
|
||||
let gossip_timestamp: i64 = row.get("gossip_timestamp")?;
|
||||
let gossip_key: Option<Vec<u8>> = row.get("gossip_key")?;
|
||||
let gossip_key_fingerprint: Option<String> = row.get("gossip_key_fingerprint")?;
|
||||
|
||||
let verified_key: Option<Vec<u8>> = row.get("verified_key")?;
|
||||
let verified_key_fingerprint: Option<String> = row.get("verified_key_fingerprint")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "addr").await?;
|
||||
write_str(&mut self.w, &addr).await?;
|
||||
|
||||
if let Some(backward_verified_key_id) = backward_verified_key_id {
|
||||
write_str(&mut self.w, "backward_verified_key_id").await?;
|
||||
write_i64(&mut self.w, backward_verified_key_id).await?;
|
||||
}
|
||||
|
||||
if let Some(gossip_key) = gossip_key {
|
||||
write_str(&mut self.w, "gossip_key").await?;
|
||||
write_bytes(&mut self.w, &gossip_key).await?;
|
||||
}
|
||||
|
||||
if let Some(gossip_key_fingerprint) = gossip_key_fingerprint {
|
||||
write_str(&mut self.w, "gossip_key_fingerprint").await?;
|
||||
write_str(&mut self.w, &gossip_key_fingerprint).await?;
|
||||
}
|
||||
|
||||
write_str(&mut self.w, "gossip_timestamp").await?;
|
||||
write_i64(&mut self.w, gossip_timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "last_seen").await?;
|
||||
write_i64(&mut self.w, last_seen).await?;
|
||||
|
||||
write_str(&mut self.w, "last_seen_autocrypt").await?;
|
||||
write_i64(&mut self.w, last_seen_autocrypt).await?;
|
||||
|
||||
write_str(&mut self.w, "prefer_encrypted").await?;
|
||||
write_i64(&mut self.w, prefer_encrypted).await?;
|
||||
|
||||
if let Some(public_key) = public_key {
|
||||
write_str(&mut self.w, "public_key").await?;
|
||||
write_bytes(&mut self.w, &public_key).await?;
|
||||
}
|
||||
|
||||
if let Some(public_key_fingerprint) = public_key_fingerprint {
|
||||
write_str(&mut self.w, "public_key_fingerprint").await?;
|
||||
write_str(&mut self.w, &public_key_fingerprint).await?;
|
||||
}
|
||||
|
||||
if let Some(verified_key) = verified_key {
|
||||
write_str(&mut self.w, "verified_key").await?;
|
||||
write_bytes(&mut self.w, &verified_key).await?;
|
||||
}
|
||||
|
||||
if let Some(verified_key_fingerprint) = verified_key_fingerprint {
|
||||
write_str(&mut self.w, "verified_key_fingerprint").await?;
|
||||
write_str(&mut self.w, &verified_key_fingerprint).await?;
|
||||
}
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes chats.
|
||||
async fn serialize_chats(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare(
|
||||
"SELECT \
|
||||
id,\
|
||||
type,\
|
||||
name,\
|
||||
blocked,\
|
||||
grpid,\
|
||||
param,\
|
||||
archived,\
|
||||
gossiped_timestamp,\
|
||||
locations_send_begin,\
|
||||
locations_send_until,\
|
||||
locations_last_sent,\
|
||||
created_timestamp,\
|
||||
muted_until,\
|
||||
ephemeral_timer,\
|
||||
protected FROM chats",
|
||||
)?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: u32 = row.get("id")?;
|
||||
let typ: u32 = row.get("type")?;
|
||||
let name: String = row.get("name")?;
|
||||
let blocked: u32 = row.get("blocked")?;
|
||||
let grpid: String = row.get("grpid")?;
|
||||
let param: String = row.get("param")?;
|
||||
let archived: bool = row.get("archived")?;
|
||||
let gossiped_timestamp: i64 = row.get("gossiped_timestamp")?;
|
||||
let locations_send_begin: i64 = row.get("locations_send_begin")?;
|
||||
let locations_send_until: i64 = row.get("locations_send_until")?;
|
||||
let locations_last_sent: i64 = row.get("locations_last_sent")?;
|
||||
let created_timestamp: i64 = row.get("created_timestamp")?;
|
||||
let muted_until: i64 = row.get("muted_until")?;
|
||||
let ephemeral_timer: i64 = row.get("ephemeral_timer")?;
|
||||
let protected: u32 = row.get("protected")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "archived").await?;
|
||||
write_bool(&mut self.w, archived).await?;
|
||||
|
||||
write_str(&mut self.w, "blocked").await?;
|
||||
write_u32(&mut self.w, blocked).await?;
|
||||
|
||||
write_str(&mut self.w, "created_timestamp").await?;
|
||||
write_i64(&mut self.w, created_timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "ephemeral_timer").await?;
|
||||
write_i64(&mut self.w, ephemeral_timer).await?;
|
||||
|
||||
write_str(&mut self.w, "gossiped_timestamp").await?;
|
||||
write_i64(&mut self.w, gossiped_timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "grpid").await?;
|
||||
write_str(&mut self.w, &grpid).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_u32(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "locations_last_sent").await?;
|
||||
write_i64(&mut self.w, locations_last_sent).await?;
|
||||
|
||||
write_str(&mut self.w, "locations_send_begin").await?;
|
||||
write_i64(&mut self.w, locations_send_begin).await?;
|
||||
|
||||
write_str(&mut self.w, "locations_send_until").await?;
|
||||
write_i64(&mut self.w, locations_send_until).await?;
|
||||
|
||||
write_str(&mut self.w, "muted_until").await?;
|
||||
write_i64(&mut self.w, muted_until).await?;
|
||||
|
||||
write_str(&mut self.w, "name").await?;
|
||||
write_str(&mut self.w, &name).await?;
|
||||
|
||||
write_str(&mut self.w, "param").await?;
|
||||
write_str(&mut self.w, ¶m).await?;
|
||||
|
||||
write_str(&mut self.w, "protected").await?;
|
||||
write_u32(&mut self.w, protected).await?;
|
||||
|
||||
write_str(&mut self.w, "type").await?;
|
||||
write_u32(&mut self.w, typ).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_chats_contacts(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT chat_id, contact_id FROM chats_contacts")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let chat_id: u32 = row.get("chat_id")?;
|
||||
let contact_id: u32 = row.get("contact_id")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "chat_id").await?;
|
||||
write_u32(&mut self.w, chat_id).await?;
|
||||
|
||||
write_str(&mut self.w, "contact_id").await?;
|
||||
write_u32(&mut self.w, contact_id).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes contacts.
|
||||
async fn serialize_contacts(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare(
|
||||
"SELECT \
|
||||
id,\
|
||||
name,\
|
||||
addr,\
|
||||
origin,\
|
||||
blocked,\
|
||||
last_seen,\
|
||||
param,\
|
||||
authname,\
|
||||
selfavatar_sent,\
|
||||
status FROM contacts",
|
||||
)?;
|
||||
let mut rows = stmt.query(())?;
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: u32 = row.get("id")?;
|
||||
let name: String = row.get("name")?;
|
||||
let authname: String = row.get("authname")?;
|
||||
let addr: String = row.get("addr")?;
|
||||
let origin: u32 = row.get("origin")?;
|
||||
let blocked: Option<bool> = row.get("blocked")?;
|
||||
let blocked = blocked.unwrap_or_default();
|
||||
let last_seen: i64 = row.get("last_seen")?;
|
||||
let selfavatar_sent: i64 = row.get("selfavatar_sent")?;
|
||||
let param: String = row.get("param")?;
|
||||
let status: Option<String> = row.get("status")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "addr").await?;
|
||||
write_str(&mut self.w, &addr).await?;
|
||||
|
||||
write_str(&mut self.w, "authname").await?;
|
||||
write_str(&mut self.w, &authname).await?;
|
||||
|
||||
write_str(&mut self.w, "blocked").await?;
|
||||
write_bool(&mut self.w, blocked).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_u32(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "last_seen").await?;
|
||||
write_i64(&mut self.w, last_seen).await?;
|
||||
|
||||
write_str(&mut self.w, "name").await?;
|
||||
write_str(&mut self.w, &name).await?;
|
||||
|
||||
write_str(&mut self.w, "origin").await?;
|
||||
write_u32(&mut self.w, origin).await?;
|
||||
|
||||
// TODO: parse param instead of serializeing as is
|
||||
write_str(&mut self.w, "param").await?;
|
||||
write_str(&mut self.w, ¶m).await?;
|
||||
|
||||
write_str(&mut self.w, "selfavatar_sent").await?;
|
||||
write_i64(&mut self.w, selfavatar_sent).await?;
|
||||
|
||||
if let Some(status) = status {
|
||||
if !status.is_empty() {
|
||||
write_str(&mut self.w, "status").await?;
|
||||
write_str(&mut self.w, &status).await?;
|
||||
}
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_dns_cache(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT hostname, address, timestamp FROM dns_cache")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let hostname: String = row.get("hostname")?;
|
||||
let address: String = row.get("address")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "address").await?;
|
||||
write_str(&mut self.w, &address).await?;
|
||||
|
||||
write_str(&mut self.w, "hostname").await?;
|
||||
write_str(&mut self.w, &hostname).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_imap(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, rfc724_mid, folder, target, uid, uidvalidity FROM imap")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let rfc724_mid: String = row.get("rfc724_mid")?;
|
||||
let folder: String = row.get("folder")?;
|
||||
let target: String = row.get("target")?;
|
||||
let uid: i64 = row.get("uid")?;
|
||||
let uidvalidity: i64 = row.get("uidvalidity")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "folder").await?;
|
||||
write_str(&mut self.w, &folder).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "rfc724_mid").await?;
|
||||
write_str(&mut self.w, &rfc724_mid).await?;
|
||||
|
||||
write_str(&mut self.w, "target").await?;
|
||||
write_str(&mut self.w, &target).await?;
|
||||
|
||||
write_str(&mut self.w, "uid").await?;
|
||||
write_i64(&mut self.w, uid).await?;
|
||||
|
||||
write_str(&mut self.w, "uidvalidity").await?;
|
||||
write_i64(&mut self.w, uidvalidity).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_imap_sync(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT folder, uidvalidity, uid_next, modseq FROM imap_sync")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let folder: String = row.get("folder")?;
|
||||
let uidvalidity: i64 = row.get("uidvalidity")?;
|
||||
let uidnext: i64 = row.get("uid_next")?;
|
||||
let modseq: i64 = row.get("modseq")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "folder").await?;
|
||||
write_str(&mut self.w, &folder).await?;
|
||||
|
||||
write_str(&mut self.w, "modseq").await?;
|
||||
write_i64(&mut self.w, modseq).await?;
|
||||
|
||||
write_str(&mut self.w, "uidnext").await?;
|
||||
write_i64(&mut self.w, uidnext).await?;
|
||||
|
||||
write_str(&mut self.w, "uidvalidity").await?;
|
||||
write_i64(&mut self.w, uidvalidity).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_keypairs(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id,addr,private_key,public_key,created FROM keypairs")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: u32 = row.get("id")?;
|
||||
let addr: String = row.get("addr")?;
|
||||
let private_key: Vec<u8> = row.get("private_key")?;
|
||||
let public_key: Vec<u8> = row.get("public_key")?;
|
||||
let created: i64 = row.get("created")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "addr").await?;
|
||||
write_str(&mut self.w, &addr).await?;
|
||||
|
||||
write_str(&mut self.w, "created").await?;
|
||||
write_i64(&mut self.w, created).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_u32(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "private_key").await?;
|
||||
write_bytes(&mut self.w, &private_key).await?;
|
||||
|
||||
write_str(&mut self.w, "public_key").await?;
|
||||
write_bytes(&mut self.w, &public_key).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_leftgroups(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare("SELECT grpid FROM leftgrps")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let grpid: String = row.get("grpid")?;
|
||||
write_str(&mut self.w, &grpid).await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_locations(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, latitude, longitude, accuracy, timestamp, chat_id, from_id, independent FROM locations")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let latitude: f64 = row.get("latitude")?;
|
||||
let longitude: f64 = row.get("longitude")?;
|
||||
let accuracy: f64 = row.get("accuracy")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
let chat_id: u32 = row.get("chat_id")?;
|
||||
let from_id: u32 = row.get("from_id")?;
|
||||
let independent: u32 = row.get("independent")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "accuracy").await?;
|
||||
write_f64(&mut self.w, accuracy).await?;
|
||||
|
||||
write_str(&mut self.w, "chat_id").await?;
|
||||
write_u32(&mut self.w, chat_id).await?;
|
||||
|
||||
write_str(&mut self.w, "from_id").await?;
|
||||
write_u32(&mut self.w, from_id).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "independent").await?;
|
||||
write_u32(&mut self.w, independent).await?;
|
||||
|
||||
write_str(&mut self.w, "latitude").await?;
|
||||
write_f64(&mut self.w, latitude).await?;
|
||||
|
||||
write_str(&mut self.w, "longitude").await?;
|
||||
write_f64(&mut self.w, longitude).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes MDNs.
|
||||
async fn serialize_mdns(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT msg_id, contact_id, timestamp_sent FROM msgs_mdns")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let msg_id: u32 = row.get("msg_id")?;
|
||||
let contact_id: u32 = row.get("contact_id")?;
|
||||
let timestamp_sent: i64 = row.get("timestamp_sent")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "contact_id").await?;
|
||||
write_u32(&mut self.w, contact_id).await?;
|
||||
|
||||
write_str(&mut self.w, "msg_id").await?;
|
||||
write_u32(&mut self.w, msg_id).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp_sent").await?;
|
||||
write_i64(&mut self.w, timestamp_sent).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes messages.
|
||||
async fn serialize_messages(&mut self) -> Result<()> {
|
||||
let mut stmt = self.tx.prepare(
|
||||
"SELECT
|
||||
id,
|
||||
rfc724_mid,
|
||||
chat_id,
|
||||
from_id, to_id,
|
||||
timestamp,
|
||||
type,
|
||||
state,
|
||||
msgrmsg,
|
||||
bytes,
|
||||
txt,
|
||||
txt_raw,
|
||||
param,
|
||||
timestamp_sent,
|
||||
timestamp_rcvd,
|
||||
hidden,
|
||||
mime_compressed,
|
||||
mime_headers,
|
||||
mime_in_reply_to,
|
||||
mime_references,
|
||||
location_id FROM msgs",
|
||||
)?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let rfc724_mid: String = row.get("rfc724_mid")?;
|
||||
let chat_id: i64 = row.get("chat_id")?;
|
||||
let from_id: i64 = row.get("from_id")?;
|
||||
let to_id: i64 = row.get("to_id")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
let typ: i64 = row.get("type")?;
|
||||
let state: i64 = row.get("state")?;
|
||||
let msgrmsg: i64 = row.get("msgrmsg")?;
|
||||
let bytes: i64 = row.get("bytes")?;
|
||||
let txt: String = row.get("txt")?;
|
||||
let txt_raw: String = row.get("txt_raw")?;
|
||||
let param: String = row.get("param")?;
|
||||
let timestamp_sent: i64 = row.get("timestamp_sent")?;
|
||||
let timestamp_rcvd: i64 = row.get("timestamp_rcvd")?;
|
||||
let hidden: i64 = row.get("hidden")?;
|
||||
let mime_compressed: i64 = row.get("mime_compressed")?;
|
||||
let mime_headers: Vec<u8> =
|
||||
row.get("mime_headers")
|
||||
.or_else(|err| match row.get_ref("mime_headers")? {
|
||||
ValueRef::Null => Ok(Vec::new()),
|
||||
ValueRef::Text(text) => Ok(text.to_vec()),
|
||||
ValueRef::Blob(blob) => Ok(blob.to_vec()),
|
||||
ValueRef::Integer(_) | ValueRef::Real(_) => Err(err),
|
||||
})?;
|
||||
let mime_in_reply_to: Option<String> = row.get("mime_in_reply_to")?;
|
||||
let mime_references: Option<String> = row.get("mime_references")?;
|
||||
let location_id: i64 = row.get("location_id")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "bytes").await?;
|
||||
write_i64(&mut self.w, bytes).await?;
|
||||
|
||||
write_str(&mut self.w, "chat_id").await?;
|
||||
write_i64(&mut self.w, chat_id).await?;
|
||||
|
||||
write_str(&mut self.w, "from_id").await?;
|
||||
write_i64(&mut self.w, from_id).await?;
|
||||
|
||||
write_str(&mut self.w, "hidden").await?;
|
||||
write_i64(&mut self.w, hidden).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "location_id").await?;
|
||||
write_i64(&mut self.w, location_id).await?;
|
||||
|
||||
write_str(&mut self.w, "mime_compressed").await?;
|
||||
write_i64(&mut self.w, mime_compressed).await?;
|
||||
|
||||
write_str(&mut self.w, "mime_headers").await?;
|
||||
write_bytes(&mut self.w, &mime_headers).await?;
|
||||
|
||||
if let Some(mime_in_reply_to) = mime_in_reply_to {
|
||||
write_str(&mut self.w, "mime_in_reply_to").await?;
|
||||
write_str(&mut self.w, &mime_in_reply_to).await?;
|
||||
}
|
||||
|
||||
if let Some(mime_references) = mime_references {
|
||||
write_str(&mut self.w, "mime_references").await?;
|
||||
write_str(&mut self.w, &mime_references).await?;
|
||||
}
|
||||
|
||||
write_str(&mut self.w, "msgrmsg").await?;
|
||||
write_i64(&mut self.w, msgrmsg).await?;
|
||||
|
||||
write_str(&mut self.w, "param").await?;
|
||||
write_str(&mut self.w, ¶m).await?;
|
||||
|
||||
write_str(&mut self.w, "rfc724_mid").await?;
|
||||
write_str(&mut self.w, &rfc724_mid).await?;
|
||||
|
||||
write_str(&mut self.w, "state").await?;
|
||||
write_i64(&mut self.w, state).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp_rcvd").await?;
|
||||
write_i64(&mut self.w, timestamp_rcvd).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp_sent").await?;
|
||||
write_i64(&mut self.w, timestamp_sent).await?;
|
||||
|
||||
write_str(&mut self.w, "to_id").await?;
|
||||
write_i64(&mut self.w, to_id).await?;
|
||||
|
||||
write_str(&mut self.w, "txt").await?;
|
||||
write_str(&mut self.w, &txt).await?;
|
||||
|
||||
write_str(&mut self.w, "txt_raw").await?;
|
||||
write_str(&mut self.w, &txt_raw).await?;
|
||||
|
||||
write_str(&mut self.w, "type").await?;
|
||||
write_i64(&mut self.w, typ).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_msgs_status_updates(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, msg_id, uid, update_item FROM msgs_status_updates")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let msg_id: i64 = row.get("msg_id")?;
|
||||
let uid: String = row.get("uid")?;
|
||||
let update_item: String = row.get("update_item")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "msg_id").await?;
|
||||
write_i64(&mut self.w, msg_id).await?;
|
||||
|
||||
write_str(&mut self.w, "uid").await?;
|
||||
write_str(&mut self.w, &uid).await?;
|
||||
|
||||
write_str(&mut self.w, "update_item").await?;
|
||||
write_str(&mut self.w, &update_item).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes reactions.
|
||||
async fn serialize_reactions(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT msg_id, contact_id, reaction FROM reactions")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let msg_id: u32 = row.get("msg_id")?;
|
||||
let contact_id: u32 = row.get("contact_id")?;
|
||||
let reaction: String = row.get("reaction")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "contact_id").await?;
|
||||
write_u32(&mut self.w, contact_id).await?;
|
||||
|
||||
write_str(&mut self.w, "msg_id").await?;
|
||||
write_u32(&mut self.w, msg_id).await?;
|
||||
|
||||
write_str(&mut self.w, "reaction").await?;
|
||||
write_str(&mut self.w, &reaction).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_sending_domains(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT domain, dkim_works FROM sending_domains")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let domain: String = row.get("domain")?;
|
||||
let dkim_works: i64 = row.get("dkim_works")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "dkim_works").await?;
|
||||
write_i64(&mut self.w, dkim_works).await?;
|
||||
|
||||
write_str(&mut self.w, "domain").await?;
|
||||
write_str(&mut self.w, &domain).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize_tokens(&mut self) -> Result<()> {
|
||||
let mut stmt = self
|
||||
.tx
|
||||
.prepare("SELECT id, namespc, foreign_id, token, timestamp FROM tokens")?;
|
||||
let mut rows = stmt.query(())?;
|
||||
|
||||
self.w.write_all(b"l").await?;
|
||||
while let Some(row) = rows.next()? {
|
||||
let id: i64 = row.get("id")?;
|
||||
let namespace: u32 = row.get("namespc")?;
|
||||
let foreign_id: u32 = row.get("foreign_id")?;
|
||||
let token: String = row.get("token")?;
|
||||
let timestamp: i64 = row.get("timestamp")?;
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "foreign_id").await?;
|
||||
write_u32(&mut self.w, foreign_id).await?;
|
||||
|
||||
write_str(&mut self.w, "id").await?;
|
||||
write_i64(&mut self.w, id).await?;
|
||||
|
||||
write_str(&mut self.w, "namespace").await?;
|
||||
write_u32(&mut self.w, namespace).await?;
|
||||
|
||||
write_str(&mut self.w, "timestamp").await?;
|
||||
write_i64(&mut self.w, timestamp).await?;
|
||||
|
||||
write_str(&mut self.w, "token").await?;
|
||||
write_str(&mut self.w, &token).await?;
|
||||
|
||||
self.w.write_all(b"e").await?;
|
||||
}
|
||||
self.w.write_all(b"e").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn serialize(&mut self) -> Result<()> {
|
||||
let dbversion: String = self.tx.query_row(
|
||||
"SELECT value FROM config WHERE keyname='dbversion'",
|
||||
(),
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
if dbversion != SERIALIZE_DBVERSION {
|
||||
return Err(anyhow!(
|
||||
"cannot serialize database version {dbversion}, expected {SERIALIZE_DBVERSION}"
|
||||
));
|
||||
}
|
||||
|
||||
self.w.write_all(b"d").await?;
|
||||
|
||||
write_str(&mut self.w, "_config").await?;
|
||||
self.serialize_config().await?;
|
||||
|
||||
write_str(&mut self.w, "acpeerstates").await?;
|
||||
self.serialize_acpeerstates()
|
||||
.await
|
||||
.context("serialize autocrypt peerstates")?;
|
||||
|
||||
write_str(&mut self.w, "chats").await?;
|
||||
self.serialize_chats().await?;
|
||||
|
||||
write_str(&mut self.w, "chats_contacts").await?;
|
||||
self.serialize_chats_contacts()
|
||||
.await
|
||||
.context("serialize chats_contacts")?;
|
||||
|
||||
write_str(&mut self.w, "contacts").await?;
|
||||
self.serialize_contacts().await?;
|
||||
|
||||
write_str(&mut self.w, "dns_cache").await?;
|
||||
self.serialize_dns_cache()
|
||||
.await
|
||||
.context("serialize dns_cache")?;
|
||||
|
||||
write_str(&mut self.w, "imap").await?;
|
||||
self.serialize_imap().await.context("serialize imap")?;
|
||||
|
||||
write_str(&mut self.w, "imap_sync").await?;
|
||||
self.serialize_imap_sync()
|
||||
.await
|
||||
.context("serialize imap_sync")?;
|
||||
|
||||
write_str(&mut self.w, "keypairs").await?;
|
||||
self.serialize_keypairs().await?;
|
||||
|
||||
write_str(&mut self.w, "leftgroups").await?;
|
||||
self.serialize_leftgroups().await?;
|
||||
|
||||
write_str(&mut self.w, "locations").await?;
|
||||
self.serialize_locations().await?;
|
||||
|
||||
write_str(&mut self.w, "mdns").await?;
|
||||
self.serialize_mdns().await?;
|
||||
|
||||
write_str(&mut self.w, "messages").await?;
|
||||
self.serialize_messages()
|
||||
.await
|
||||
.context("serialize messages")?;
|
||||
|
||||
write_str(&mut self.w, "msgs_status_updates").await?;
|
||||
self.serialize_msgs_status_updates()
|
||||
.await
|
||||
.context("serialize msgs_status_updates")?;
|
||||
|
||||
write_str(&mut self.w, "reactions").await?;
|
||||
self.serialize_reactions().await?;
|
||||
|
||||
write_str(&mut self.w, "sending_domains").await?;
|
||||
self.serialize_sending_domains()
|
||||
.await
|
||||
.context("serialize sending_domains")?;
|
||||
|
||||
write_str(&mut self.w, "tokens").await?;
|
||||
self.serialize_tokens().await?;
|
||||
|
||||
// jobs table is skipped
|
||||
// multi_device_sync is skipped
|
||||
// imap_markseen is skipped, it is usually empty and the device exporting the
|
||||
// database should still be able to clear it.
|
||||
// smtp, smtp_mdns and smtp_status_updates tables are skipped, they are part of the
|
||||
// outgoing message queue.
|
||||
// devmsglabels is skipped, it is reset in `delete_and_reset_all_device_msgs()` on import
|
||||
// anyway
|
||||
// bobstate is not serialized, it is temporary for joining or adding a contact.
|
||||
//
|
||||
// TODO insert welcome message on import like done in `delete_and_reset_all_device_msgs()`?
|
||||
self.w.write_all(b"e").await?;
|
||||
self.w.flush().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Sql {
|
||||
/// Serializes the database into a bytestream.
|
||||
pub async fn serialize(&self, w: impl AsyncWrite + Unpin) -> Result<()> {
|
||||
let mut conn = self.get_connection().await?;
|
||||
|
||||
// Start a read transaction to take a database snapshot.
|
||||
let transaction = conn.transaction()?;
|
||||
let mut encoder = Encoder::new(transaction, w);
|
||||
encoder.serialize().await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user