mirror of
https://github.com/chatmail/core.git
synced 2026-06-26 17:46:37 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11b7a5c9d4 |
52
CHANGELOG.md
52
CHANGELOG.md
@@ -1,55 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
## [2.2.0] - 2025-07-14
|
||||
|
||||
### API-Changes
|
||||
|
||||
- Add chat::create_group_ex(), deprecate create_group_chat() ([#6927](https://github.com/chatmail/core/pull/6927)).
|
||||
- jsonrpc: Add CommandApi::create_group_chat_unencrypted() ([#6927](https://github.com/chatmail/core/pull/6927)).
|
||||
- [**breaking**] In ChatListItem, replace is_group and is_(out_)broadcast with chat_type property ([#7003](https://github.com/chatmail/core/pull/7003)).
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Log failed debug assertions in all configurations.
|
||||
- Donation request device message ([#6913](https://github.com/chatmail/core/pull/6913)).
|
||||
- Advance next UID even if connection fails while fetching.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Always prefer the last header.
|
||||
|
||||
### Tests
|
||||
|
||||
- Tune down DELTACHAT_SAVE_TMP_DB hint ([#6998](https://github.com/chatmail/core/pull/6998)).
|
||||
- Unencrypted group creation ([#6927](https://github.com/chatmail/core/pull/6927)).
|
||||
|
||||
## [2.1.0] - 2025-07-11
|
||||
|
||||
### Features / Changes
|
||||
|
||||
- Add account ordering functionality ([#6993](https://github.com/chatmail/core/pull/6993)).
|
||||
- feat: Make it possible to leave broadcast channels ([#6984](https://github.com/chatmail/core/pull/6984))
|
||||
- Migrations: Use tools::Time to measure time for logging.
|
||||
- Log emitted logging events with `tracing`.
|
||||
- Ensure_and_debug_assert{,_eq,_ne} macros combining `debug_assert*` and anyhow::ensure ([#6907](https://github.com/chatmail/core/pull/6907)).
|
||||
|
||||
### Fixes
|
||||
|
||||
- Use Viewtype::File for messages with invalid images, images of unknown size, images > 50 Mpx ([#6825](https://github.com/chatmail/core/pull/6825)).
|
||||
- Don't apply chat name and avatar changes from non-members.
|
||||
|
||||
### Documentation
|
||||
|
||||
- Update showpadlock ffi.
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
- cargo: Update cordyceps from 0.3.2 to 0.3.4.
|
||||
|
||||
### Tests
|
||||
|
||||
- Add option to save database on test failure ([#6992](https://github.com/chatmail/core/pull/6992)).
|
||||
|
||||
## [2.0.0] - 2025-07-09
|
||||
|
||||
This release changes the way the core handles contact keys.
|
||||
@@ -6476,5 +6426,3 @@ https://github.com/chatmail/core/pulls?q=is%3Apr+is%3Aclosed
|
||||
[1.159.5]: https://github.com/chatmail/core/compare/v1.159.4..v1.159.5
|
||||
[1.160.0]: https://github.com/chatmail/core/compare/v1.159.5..v1.160.0
|
||||
[2.0.0]: https://github.com/chatmail/core/compare/v1.160.0..v2.0.0
|
||||
[2.1.0]: https://github.com/chatmail/core/compare/v2.0.0..v2.1.0
|
||||
[2.2.0]: https://github.com/chatmail/core/compare/v2.1.0..v2.2.0
|
||||
|
||||
57
Cargo.lock
generated
57
Cargo.lock
generated
@@ -958,11 +958,11 @@ checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8"
|
||||
|
||||
[[package]]
|
||||
name = "cordyceps"
|
||||
version = "0.3.4"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a"
|
||||
checksum = "ec10f0a762d93c4498d2e97a333805cb6250d60bead623f71d8034f9a4152ba3"
|
||||
dependencies = [
|
||||
"loom",
|
||||
"loom 0.5.6",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
@@ -1285,7 +1285,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-broadcast",
|
||||
@@ -1395,7 +1395,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-channel 2.3.1",
|
||||
@@ -1417,7 +1417,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-repl"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1433,7 +1433,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -1462,7 +1462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deltachat",
|
||||
@@ -2205,6 +2205,19 @@ dependencies = [
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.8.4"
|
||||
@@ -3327,6 +3340,19 @@ version = "0.4.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator 0.7.5",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "loom"
|
||||
version = "0.7.2"
|
||||
@@ -3334,7 +3360,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"generator",
|
||||
"generator 0.8.4",
|
||||
"scoped-tls",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
@@ -3451,7 +3477,7 @@ dependencies = [
|
||||
"crossbeam-channel",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"loom",
|
||||
"loom 0.7.2",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"rustc_version",
|
||||
@@ -3826,7 +3852,7 @@ version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
|
||||
dependencies = [
|
||||
"proc-macro-crate 2.0.0",
|
||||
"proc-macro-crate 3.2.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.104",
|
||||
@@ -6724,6 +6750,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
edition = "2024"
|
||||
license = "MPL-2.0"
|
||||
rust-version = "1.85"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -4303,16 +4303,11 @@ int dc_msg_get_duration (const dc_msg_t* msg);
|
||||
|
||||
|
||||
/**
|
||||
* Check if message was correctly encrypted and signed.
|
||||
*
|
||||
* Historically, UIs showed a small padlock on the message then.
|
||||
* Today, the UIs should instead
|
||||
* show a small email-icon on the message if the message is not encrypted or signed,
|
||||
* and nothing otherwise.
|
||||
* Check if a padlock should be shown beside the message.
|
||||
*
|
||||
* @memberof dc_msg_t
|
||||
* @param msg The message object.
|
||||
* @return 1=message correctly encrypted and signed, no need to show anything; 0=show email-icon beside the message.
|
||||
* @return 1=padlock should be shown beside message, 0=do not show a padlock beside the message.
|
||||
*/
|
||||
int dc_msg_get_showpadlock (const dc_msg_t* msg);
|
||||
|
||||
@@ -7424,7 +7419,7 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// Used in status messages.
|
||||
#define DC_STR_REMOVE_MEMBER_BY_OTHER 131
|
||||
|
||||
/// "You left."
|
||||
/// "You left the group."
|
||||
///
|
||||
/// Used in status messages.
|
||||
#define DC_STR_GROUP_LEFT_BY_YOU 132
|
||||
@@ -7667,9 +7662,6 @@ void dc_event_unref(dc_event_t* event);
|
||||
/// @deprecated 2025-06-05
|
||||
#define DC_STR_SECUREJOIN_TAKES_LONGER 192
|
||||
|
||||
/// "❤️ Seems you're enjoying Delta Chat!"… (donation request device message)
|
||||
#define DC_STR_DONATION_REQUEST 193
|
||||
|
||||
/// "Contact". Deprecated, currently unused.
|
||||
#define DC_STR_CONTACT 200
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-jsonrpc"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
description = "DeltaChat JSON-RPC API"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
|
||||
@@ -224,14 +224,6 @@ impl CommandApi {
|
||||
self.accounts.read().await.get_selected_account_id()
|
||||
}
|
||||
|
||||
/// Set the order of accounts.
|
||||
/// The provided list should contain all account IDs in the desired order.
|
||||
/// If an account ID is missing from the list, it will be appended at the end.
|
||||
/// If the list contains non-existent account IDs, they will be ignored.
|
||||
async fn set_accounts_order(&self, order: Vec<u32>) -> Result<()> {
|
||||
self.accounts.write().await.set_accounts_order(order).await
|
||||
}
|
||||
|
||||
/// Get a list of all configured accounts.
|
||||
async fn get_all_accounts(&self) -> Result<Vec<Account>> {
|
||||
let mut accounts = Vec::new();
|
||||
@@ -953,7 +945,7 @@ impl CommandApi {
|
||||
Ok(contacts.iter().map(|id| id.to_u32()).collect::<Vec<u32>>())
|
||||
}
|
||||
|
||||
/// Create a new encrypted group chat (with key-contacts).
|
||||
/// Create a new group chat.
|
||||
///
|
||||
/// After creation,
|
||||
/// the group has one member with the ID DC_CONTACT_ID_SELF
|
||||
@@ -971,24 +963,14 @@ impl CommandApi {
|
||||
///
|
||||
/// @param protect If set to 1 the function creates group with protection initially enabled.
|
||||
/// Only verified members are allowed in these groups
|
||||
/// and end-to-end-encryption is always enabled.
|
||||
async fn create_group_chat(&self, account_id: u32, name: String, protect: bool) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
let protect = match protect {
|
||||
true => ProtectionStatus::Protected,
|
||||
false => ProtectionStatus::Unprotected,
|
||||
};
|
||||
chat::create_group_ex(&ctx, Some(protect), &name)
|
||||
.await
|
||||
.map(|id| id.to_u32())
|
||||
}
|
||||
|
||||
/// Create a new unencrypted group chat.
|
||||
///
|
||||
/// Same as [`Self::create_group_chat`], but the chat is unencrypted and can only have
|
||||
/// address-contacts.
|
||||
async fn create_group_chat_unencrypted(&self, account_id: u32, name: String) -> Result<u32> {
|
||||
let ctx = self.get_context(account_id).await?;
|
||||
chat::create_group_ex(&ctx, None, &name)
|
||||
chat::create_group_chat(&ctx, protect, &name)
|
||||
.await
|
||||
.map(|id| id.to_u32())
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ pub enum ChatListItemFetchResult {
|
||||
name: String,
|
||||
avatar_path: Option<String>,
|
||||
color: String,
|
||||
chat_type: u32,
|
||||
last_updated: Option<i64>,
|
||||
summary_text1: String,
|
||||
summary_text2: String,
|
||||
@@ -55,7 +54,6 @@ pub enum ChatListItemFetchResult {
|
||||
///
|
||||
/// See also `is_key_contact` on `Contact`.
|
||||
is_encrypted: bool,
|
||||
/// deprecated 2025-07, use chat_type instead
|
||||
is_group: bool,
|
||||
fresh_message_counter: usize,
|
||||
is_self_talk: bool,
|
||||
@@ -66,6 +64,10 @@ pub enum ChatListItemFetchResult {
|
||||
is_pinned: bool,
|
||||
is_muted: bool,
|
||||
is_contact_request: bool,
|
||||
/// Deprecated 2025-07, alias for is_out_broadcast
|
||||
is_broadcast: bool,
|
||||
/// true if the chat type is OutBroadcast
|
||||
is_out_broadcast: bool,
|
||||
/// contact id if this is a dm chat (for view profile entry in context menu)
|
||||
dm_chat_contact: Option<u32>,
|
||||
was_seen_recently: bool,
|
||||
@@ -155,7 +157,6 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
name: chat.get_name().to_owned(),
|
||||
avatar_path,
|
||||
color,
|
||||
chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
|
||||
last_updated,
|
||||
summary_text1,
|
||||
summary_text2,
|
||||
@@ -173,6 +174,8 @@ pub(crate) async fn get_chat_list_item_by_id(
|
||||
is_pinned: visibility == ChatVisibility::Pinned,
|
||||
is_muted: chat.is_muted(),
|
||||
is_contact_request: chat.is_contact_request(),
|
||||
is_broadcast: chat.get_type() == Chattype::OutBroadcast,
|
||||
is_out_broadcast: chat.get_type() == Chattype::OutBroadcast,
|
||||
dm_chat_contact,
|
||||
was_seen_recently,
|
||||
last_message_type: message_type,
|
||||
|
||||
@@ -54,5 +54,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "2.2.0"
|
||||
"version": "2.0.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/chatmail/core"
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat-rpc-client"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
description = "Python client for Delta Chat core JSON-RPC interface"
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-rpc-server"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
description = "DeltaChat JSON-RPC server"
|
||||
edition = "2021"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -15,5 +15,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "index.d.ts",
|
||||
"version": "2.2.0"
|
||||
"version": "2.0.0"
|
||||
}
|
||||
|
||||
@@ -25,15 +25,16 @@ skip = [
|
||||
{ name = "derive_more-impl", version = "1.0.0" },
|
||||
{ name = "derive_more", version = "1.0.0" },
|
||||
{ name = "event-listener", version = "2.5.3" },
|
||||
{ name = "generator", version = "0.7.5" },
|
||||
{ name = "getrandom", version = "0.2.12" },
|
||||
{ name = "hashbrown", version = "0.14.5" },
|
||||
{ name = "heck", version = "0.4.1" },
|
||||
{ name = "http", version = "0.2.12" },
|
||||
{ name = "linux-raw-sys", version = "0.4.14" },
|
||||
{ name = "loom", version = "0.5.6" },
|
||||
{ name = "lru", version = "0.12.3" },
|
||||
{ name = "netlink-packet-route", version = "0.17.1" },
|
||||
{ name = "nom", version = "7.1.3" },
|
||||
{ name = "proc-macro-crate", version = "2.0.0" },
|
||||
{ name = "rand_chacha", version = "0.3.1" },
|
||||
{ name = "rand_core", version = "0.6.4" },
|
||||
{ name = "rand", version = "0.8.5" },
|
||||
@@ -49,7 +50,6 @@ skip = [
|
||||
{ name = "syn", version = "1.0.109" },
|
||||
{ name = "thiserror-impl", version = "1.0.69" },
|
||||
{ name = "thiserror", version = "1.0.69" },
|
||||
{ name = "toml_edit", version = "0.20.7" },
|
||||
{ name = "wasi", version = "0.11.0+wasi-snapshot-preview1" },
|
||||
{ name = "windows" },
|
||||
{ name = "windows_aarch64_gnullvm" },
|
||||
@@ -67,7 +67,6 @@ skip = [
|
||||
{ name = "windows_x86_64_gnu" },
|
||||
{ name = "windows_x86_64_gnullvm" },
|
||||
{ name = "windows_x86_64_msvc" },
|
||||
{ name = "winnow", version = "0.5.40" },
|
||||
{ name = "zerocopy", version = "0.7.32" },
|
||||
]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "deltachat"
|
||||
version = "2.2.0"
|
||||
version = "2.0.0"
|
||||
description = "Python bindings for the Delta Chat Core library using CFFI against the Rust-implemented libdeltachat"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.8"
|
||||
|
||||
@@ -1 +1 @@
|
||||
2025-07-14
|
||||
2025-07-09
|
||||
@@ -1,6 +1,6 @@
|
||||
//! # Account manager module.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
use std::future::Future;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@@ -270,51 +270,9 @@ impl Accounts {
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a list of all account ids in the user-configured order.
|
||||
/// Get a list of all account ids.
|
||||
pub fn get_all(&self) -> Vec<u32> {
|
||||
let mut ordered_ids = Vec::new();
|
||||
let mut all_ids: BTreeSet<u32> = self.accounts.keys().copied().collect();
|
||||
|
||||
// First, add accounts in the configured order
|
||||
for &id in &self.config.inner.accounts_order {
|
||||
if all_ids.remove(&id) {
|
||||
ordered_ids.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Then add any accounts not in the order list (newly added accounts)
|
||||
for id in all_ids {
|
||||
ordered_ids.push(id);
|
||||
}
|
||||
|
||||
ordered_ids
|
||||
}
|
||||
|
||||
/// Sets the order of accounts.
|
||||
///
|
||||
/// The provided list should contain all account IDs in the desired order.
|
||||
/// If an account ID is missing from the list, it will be appended at the end.
|
||||
/// If the list contains non-existent account IDs, they will be ignored.
|
||||
pub async fn set_accounts_order(&mut self, order: Vec<u32>) -> Result<()> {
|
||||
let existing_ids: BTreeSet<u32> = self.accounts.keys().copied().collect();
|
||||
|
||||
// Filter out non-existent account IDs
|
||||
let mut filtered_order: Vec<u32> = order
|
||||
.into_iter()
|
||||
.filter(|id| existing_ids.contains(id))
|
||||
.collect();
|
||||
|
||||
// Add any missing account IDs at the end
|
||||
for &id in &existing_ids {
|
||||
if !filtered_order.contains(&id) {
|
||||
filtered_order.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
self.config.inner.accounts_order = filtered_order;
|
||||
self.config.sync().await?;
|
||||
self.emit_event(EventType::AccountsChanged);
|
||||
Ok(())
|
||||
self.accounts.keys().copied().collect()
|
||||
}
|
||||
|
||||
/// Starts background tasks such as IMAP and SMTP loops for all accounts.
|
||||
@@ -457,10 +415,6 @@ struct InnerConfig {
|
||||
pub selected_account: u32,
|
||||
pub next_id: u32,
|
||||
pub accounts: Vec<AccountConfig>,
|
||||
/// Ordered list of account IDs, representing the user's preferred order.
|
||||
/// If an account ID is not in this list, it will be appended at the end.
|
||||
#[serde(default)]
|
||||
pub accounts_order: Vec<u32>,
|
||||
}
|
||||
|
||||
impl Drop for Config {
|
||||
@@ -527,7 +481,6 @@ impl Config {
|
||||
accounts: Vec::new(),
|
||||
selected_account: 0,
|
||||
next_id: 1,
|
||||
accounts_order: Vec::new(),
|
||||
};
|
||||
if !lock {
|
||||
let cfg = Self {
|
||||
@@ -660,10 +613,6 @@ impl Config {
|
||||
uuid,
|
||||
});
|
||||
self.inner.next_id += 1;
|
||||
|
||||
// Add new account to the end of the order list
|
||||
self.inner.accounts_order.push(id);
|
||||
|
||||
id
|
||||
};
|
||||
|
||||
@@ -685,10 +634,6 @@ impl Config {
|
||||
// remove account from the configs
|
||||
self.inner.accounts.remove(idx);
|
||||
}
|
||||
|
||||
// Remove from order list as well
|
||||
self.inner.accounts_order.retain(|&x| x != id);
|
||||
|
||||
if self.inner.selected_account == id {
|
||||
// reset selected account
|
||||
self.inner.selected_account = self
|
||||
|
||||
116
src/chat.rs
116
src/chat.rs
@@ -33,7 +33,6 @@ use crate::ephemeral::{Timer as EphemeralTimer, start_chat_ephemeral_timers};
|
||||
use crate::events::EventType;
|
||||
use crate::location;
|
||||
use crate::log::{LogExt, error, info, warn};
|
||||
use crate::logged_debug_assert;
|
||||
use crate::message::{self, Message, MessageState, MsgId, Viewtype};
|
||||
use crate::mimefactory::MimeFactory;
|
||||
use crate::mimeparser::SystemMessage;
|
||||
@@ -1340,18 +1339,14 @@ impl ChatId {
|
||||
|
||||
let mut ret = stock_str::e2e_available(context).await + "\n";
|
||||
|
||||
for &contact_id in get_chat_contacts(context, self)
|
||||
for contact_id in get_chat_contacts(context, self)
|
||||
.await?
|
||||
.iter()
|
||||
.filter(|&contact_id| !contact_id.is_special())
|
||||
{
|
||||
let contact = Contact::get_by_id(context, contact_id).await?;
|
||||
let contact = Contact::get_by_id(context, *contact_id).await?;
|
||||
let addr = contact.get_addr();
|
||||
logged_debug_assert!(
|
||||
context,
|
||||
contact.is_key_contact(),
|
||||
"get_encryption_info: contact {contact_id} is not a key-contact."
|
||||
);
|
||||
debug_assert!(contact.is_key_contact());
|
||||
let fingerprint = contact
|
||||
.fingerprint()
|
||||
.context("Contact does not have a fingerprint in encrypted chat")?;
|
||||
@@ -2910,12 +2905,10 @@ async fn prepare_send_msg(
|
||||
// If the chat is a contact request, let the user accept it later.
|
||||
msg.param.get_cmd() == SystemMessage::SecurejoinMessage
|
||||
}
|
||||
// Allow to send "Member removed" messages so we can leave the group/broadcast.
|
||||
// Allow to send "Member removed" messages so we can leave the group.
|
||||
// Necessary checks should be made anyway before removing contact
|
||||
// from the chat.
|
||||
CantSendReason::NotAMember | CantSendReason::InBroadcast => {
|
||||
msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup
|
||||
}
|
||||
CantSendReason::NotAMember => msg.param.get_cmd() == SystemMessage::MemberRemovedFromGroup,
|
||||
CantSendReason::MissingKey => msg
|
||||
.param
|
||||
.get_bool(Param::ForcePlaintext)
|
||||
@@ -2967,9 +2960,6 @@ async fn prepare_send_msg(
|
||||
let row_ids = create_send_msg_jobs(context, msg)
|
||||
.await
|
||||
.context("Failed to create send jobs")?;
|
||||
if !row_ids.is_empty() {
|
||||
donation_request_maybe(context).await.log_err(context).ok();
|
||||
}
|
||||
Ok(row_ids)
|
||||
}
|
||||
|
||||
@@ -3214,31 +3204,6 @@ pub async fn send_videochat_invitation(context: &Context, chat_id: ChatId) -> Re
|
||||
send_msg(context, chat_id, &mut msg).await
|
||||
}
|
||||
|
||||
async fn donation_request_maybe(context: &Context) -> Result<()> {
|
||||
let secs_between_checks = 30 * 24 * 60 * 60;
|
||||
let now = time();
|
||||
let ts = context
|
||||
.get_config_i64(Config::DonationRequestNextCheck)
|
||||
.await?;
|
||||
if ts > now {
|
||||
return Ok(());
|
||||
}
|
||||
let msg_cnt = context.sql.count(
|
||||
"SELECT COUNT(*) FROM msgs WHERE state>=? AND hidden=0",
|
||||
(MessageState::OutDelivered,),
|
||||
);
|
||||
let ts = if ts == 0 || msg_cnt.await? < 100 {
|
||||
now.saturating_add(secs_between_checks)
|
||||
} else {
|
||||
let mut msg = Message::new_text(stock_str::donation_request(context).await);
|
||||
add_device_msg(context, None, Some(&mut msg)).await?;
|
||||
i64::MAX
|
||||
};
|
||||
context
|
||||
.set_config_internal(Config::DonationRequestNextCheck, Some(&ts.to_string()))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Chat message list request options.
|
||||
#[derive(Debug)]
|
||||
pub struct MessageListOptions {
|
||||
@@ -3657,31 +3622,15 @@ pub async fn get_past_chat_contacts(context: &Context, chat_id: ChatId) -> Resul
|
||||
}
|
||||
|
||||
/// Creates a group chat with a given `name`.
|
||||
/// Deprecated on 2025-06-21, use `create_group_ex()`.
|
||||
pub async fn create_group_chat(
|
||||
context: &Context,
|
||||
protect: ProtectionStatus,
|
||||
name: &str,
|
||||
chat_name: &str,
|
||||
) -> Result<ChatId> {
|
||||
create_group_ex(context, Some(protect), name).await
|
||||
}
|
||||
|
||||
/// Creates a group chat.
|
||||
///
|
||||
/// * `encryption` - If `Some`, the chat is encrypted (with key-contacts) and can be protected.
|
||||
/// * `name` - Chat name.
|
||||
pub async fn create_group_ex(
|
||||
context: &Context,
|
||||
encryption: Option<ProtectionStatus>,
|
||||
name: &str,
|
||||
) -> Result<ChatId> {
|
||||
let chat_name = sanitize_single_line(name);
|
||||
let chat_name = sanitize_single_line(chat_name);
|
||||
ensure!(!chat_name.is_empty(), "Invalid chat name");
|
||||
|
||||
let grpid = match encryption {
|
||||
Some(_) => create_id(),
|
||||
None => String::new(),
|
||||
};
|
||||
let grpid = create_id();
|
||||
|
||||
let timestamp = create_smeared_timestamp(context);
|
||||
let row_id = context
|
||||
@@ -3701,8 +3650,7 @@ pub async fn create_group_ex(
|
||||
chatlist_events::emit_chatlist_changed(context);
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat_id);
|
||||
|
||||
if encryption == Some(ProtectionStatus::Protected) {
|
||||
let protect = ProtectionStatus::Protected;
|
||||
if protect == ProtectionStatus::Protected {
|
||||
chat_id
|
||||
.set_protection_for_timestamp_sort(context, protect, timestamp, None)
|
||||
.await?;
|
||||
@@ -4131,6 +4079,8 @@ pub async fn remove_contact_from_chat(
|
||||
"Cannot remove special contact"
|
||||
);
|
||||
|
||||
let mut msg = Message::new(Viewtype::default());
|
||||
|
||||
let chat = Chat::load_from_db(context, chat_id).await?;
|
||||
if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
|
||||
if !chat.is_self_in_chat(context).await? {
|
||||
@@ -4160,10 +4110,19 @@ pub async fn remove_contact_from_chat(
|
||||
// in case of the database becoming inconsistent due to a bug.
|
||||
if let Some(contact) = Contact::get_by_id_optional(context, contact_id).await? {
|
||||
if chat.typ == Chattype::Group && chat.is_promoted() {
|
||||
let addr = contact.get_addr();
|
||||
|
||||
let res = send_member_removal_msg(context, chat_id, contact_id, addr).await;
|
||||
|
||||
msg.viewtype = Viewtype::Text;
|
||||
if contact_id == ContactId::SELF {
|
||||
msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
|
||||
} else {
|
||||
msg.text =
|
||||
stock_str::msg_del_member_local(context, contact_id, ContactId::SELF)
|
||||
.await;
|
||||
}
|
||||
msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
|
||||
msg.param.set(Param::Arg, contact.get_addr().to_lowercase());
|
||||
msg.param
|
||||
.set(Param::ContactAddedRemoved, contact.id.to_u32() as i32);
|
||||
let res = send_msg(context, chat_id, &mut msg).await;
|
||||
if contact_id == ContactId::SELF {
|
||||
res?;
|
||||
set_group_explicitly_left(context, &chat.grpid).await?;
|
||||
@@ -4182,11 +4141,6 @@ pub async fn remove_contact_from_chat(
|
||||
chat.sync_contacts(context).await.log_err(context).ok();
|
||||
}
|
||||
}
|
||||
} else if chat.typ == Chattype::InBroadcast && contact_id == ContactId::SELF {
|
||||
// For incoming broadcast channels, it's not possible to remove members,
|
||||
// but it's possible to leave:
|
||||
let self_addr = context.get_primary_self_addr().await?;
|
||||
send_member_removal_msg(context, chat_id, contact_id, &self_addr).await?;
|
||||
} else {
|
||||
bail!("Cannot remove members from non-group chats.");
|
||||
}
|
||||
@@ -4194,28 +4148,6 @@ pub async fn remove_contact_from_chat(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_member_removal_msg(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
contact_id: ContactId,
|
||||
addr: &str,
|
||||
) -> Result<MsgId> {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
|
||||
if contact_id == ContactId::SELF {
|
||||
msg.text = stock_str::msg_group_left_local(context, ContactId::SELF).await;
|
||||
} else {
|
||||
msg.text = stock_str::msg_del_member_local(context, contact_id, ContactId::SELF).await;
|
||||
}
|
||||
|
||||
msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup);
|
||||
msg.param.set(Param::Arg, addr.to_lowercase());
|
||||
msg.param
|
||||
.set(Param::ContactAddedRemoved, contact_id.to_u32());
|
||||
|
||||
send_msg(context, chat_id, &mut msg).await
|
||||
}
|
||||
|
||||
async fn set_group_explicitly_left(context: &Context, grpid: &str) -> Result<()> {
|
||||
if !is_group_explicitly_left(context, grpid).await? {
|
||||
context
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::ephemeral::Timer;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::imex::{ImexMode, has_backup, imex};
|
||||
use crate::message::{MessengerMessage, delete_msgs};
|
||||
use crate::mimeparser::{self, MimeMessage};
|
||||
use crate::mimeparser;
|
||||
use crate::receive_imf::receive_imf;
|
||||
use crate::test_utils::{
|
||||
AVATAR_64x64_BYTES, AVATAR_64x64_DEDUPLICATED, TestContext, TestContextManager,
|
||||
@@ -374,10 +374,7 @@ async fn test_member_add_remove() -> Result<()> {
|
||||
// Alice leaves the chat.
|
||||
remove_contact_from_chat(&alice, alice_chat_id, ContactId::SELF).await?;
|
||||
let sent = alice.pop_sent_msg().await;
|
||||
assert_eq!(
|
||||
sent.load_from_db().await.get_text(),
|
||||
stock_str::msg_group_left_local(&alice, ContactId::SELF).await
|
||||
);
|
||||
assert_eq!(sent.load_from_db().await.get_text(), "You left the group.");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -2934,108 +2931,6 @@ async fn test_broadcast_channel_protected_listid() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that if Bob leaves a broadcast channel,
|
||||
/// Alice (the channel owner) won't see him as a member anymore,
|
||||
/// but won't be notified about this in any way.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_leave_broadcast() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
|
||||
tcm.section("Alice creates broadcast channel with Bob.");
|
||||
let alice_chat_id = create_broadcast(alice, "foo".to_string()).await?;
|
||||
let bob_contact = alice.add_or_lookup_contact(bob).await.id;
|
||||
add_contact_to_chat(alice, alice_chat_id, bob_contact).await?;
|
||||
|
||||
tcm.section("Alice sends first message to broadcast.");
|
||||
let sent_msg = alice.send_text(alice_chat_id, "Hello!").await;
|
||||
let bob_msg = bob.recv_msg(&sent_msg).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(alice, alice_chat_id).await?.len(), 1);
|
||||
|
||||
// Clear events so that we can later check
|
||||
// that the 'Broadcast channel left' message didn't trigger IncomingMsg:
|
||||
alice.evtracker.clear_events();
|
||||
|
||||
// Shift the time so that we can later check the "Broadcast channel left" message's timestamp:
|
||||
SystemTime::shift(Duration::from_secs(60));
|
||||
|
||||
tcm.section("Bob leaves the broadcast channel.");
|
||||
let bob_chat_id = bob_msg.chat_id;
|
||||
bob_chat_id.accept(bob).await?;
|
||||
remove_contact_from_chat(bob, bob_chat_id, ContactId::SELF).await?;
|
||||
|
||||
let leave_msg = bob.pop_sent_msg().await;
|
||||
alice.recv_msg_trash(&leave_msg).await;
|
||||
|
||||
assert_eq!(get_chat_contacts(alice, alice_chat_id).await?.len(), 0);
|
||||
|
||||
alice.emit_event(EventType::Test);
|
||||
alice
|
||||
.evtracker
|
||||
.get_matching(|ev| match ev {
|
||||
EventType::Test => true,
|
||||
EventType::IncomingMsg { .. } => {
|
||||
panic!("'Broadcast channel left' message should be silent")
|
||||
}
|
||||
EventType::MsgsNoticed(..) => {
|
||||
panic!("'Broadcast channel left' message shouldn't clear notifications")
|
||||
}
|
||||
EventType::MsgsChanged { .. } => {
|
||||
panic!("Broadcast channels should be left silently, without any message");
|
||||
}
|
||||
_ => false,
|
||||
})
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that if Bob leaves a broadcast channel with one device,
|
||||
/// the other device shows a correct info message "You left.".
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_leave_broadcast_multidevice() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob0 = &tcm.bob().await;
|
||||
let bob1 = &tcm.bob().await;
|
||||
|
||||
tcm.section("Alice creates broadcast channel with Bob.");
|
||||
let alice_chat_id = create_broadcast(alice, "foo".to_string()).await?;
|
||||
let bob_contact = alice.add_or_lookup_contact(bob0).await.id;
|
||||
add_contact_to_chat(alice, alice_chat_id, bob_contact).await?;
|
||||
|
||||
tcm.section("Alice sends first message to broadcast.");
|
||||
let sent_msg = alice.send_text(alice_chat_id, "Hello!").await;
|
||||
let bob0_hello = bob0.recv_msg(&sent_msg).await;
|
||||
let bob1_hello = bob1.recv_msg(&sent_msg).await;
|
||||
|
||||
tcm.section("Bob leaves the broadcast channel with his first device.");
|
||||
let bob_chat_id = bob0_hello.chat_id;
|
||||
bob_chat_id.accept(bob0).await?;
|
||||
remove_contact_from_chat(bob0, bob_chat_id, ContactId::SELF).await?;
|
||||
|
||||
let leave_msg = bob0.pop_sent_msg().await;
|
||||
let parsed = MimeMessage::from_bytes(bob1, leave_msg.payload().as_bytes(), None).await?;
|
||||
assert_eq!(
|
||||
parsed.parts[0].msg,
|
||||
stock_str::msg_group_left_remote(bob0).await
|
||||
);
|
||||
|
||||
let rcvd = bob1.recv_msg(&leave_msg).await;
|
||||
|
||||
assert_eq!(rcvd.chat_id, bob1_hello.chat_id);
|
||||
assert!(rcvd.is_info());
|
||||
assert_eq!(rcvd.get_info_type(), SystemMessage::MemberRemovedFromGroup);
|
||||
assert_eq!(
|
||||
rcvd.text,
|
||||
stock_str::msg_group_left_local(bob1, ContactId::SELF).await
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_for_contact_with_blocked() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
@@ -4698,32 +4593,6 @@ async fn test_no_key_contacts_in_adhoc_chats() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that key-contacts cannot be added to an unencrypted (ad hoc) group and the group and
|
||||
/// messages report that they are unencrypted.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_create_unencrypted_group_chat() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = &tcm.alice().await;
|
||||
let bob = &tcm.bob().await;
|
||||
let charlie = &tcm.charlie().await;
|
||||
|
||||
let chat_id = create_group_ex(alice, None, "Group chat").await?;
|
||||
let bob_key_contact_id = alice.add_or_lookup_contact_id(bob).await;
|
||||
let charlie_address_contact_id = alice.add_or_lookup_address_contact_id(charlie).await;
|
||||
|
||||
let res = add_contact_to_chat(alice, chat_id, bob_key_contact_id).await;
|
||||
assert!(res.is_err());
|
||||
|
||||
add_contact_to_chat(alice, chat_id, charlie_address_contact_id).await?;
|
||||
|
||||
let chat = Chat::load_from_db(alice, chat_id).await?;
|
||||
assert!(!chat.is_encrypted(alice).await?);
|
||||
let sent_msg = alice.send_text(chat_id, "Hello").await;
|
||||
let msg = Message::load_from_db(alice, sent_msg.sender_msg_id).await?;
|
||||
assert!(!msg.get_showpadlock());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tests that avatar cannot be set in ad hoc groups.
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_no_avatar_in_adhoc_chats() -> Result<()> {
|
||||
|
||||
@@ -369,9 +369,6 @@ pub enum Config {
|
||||
#[strum(props(default = "0"))]
|
||||
DisableIdle,
|
||||
|
||||
/// Timestamp of the next check for donation request need.
|
||||
DonationRequestNextCheck,
|
||||
|
||||
/// Defines the max. size (in bytes) of messages downloaded automatically.
|
||||
/// 0 = no limit.
|
||||
#[strum(props(default = "0"))]
|
||||
|
||||
@@ -37,7 +37,7 @@ use crate::mimeparser::AvatarAction;
|
||||
use crate::param::{Param, Params};
|
||||
use crate::sync::{self, Sync::*};
|
||||
use crate::tools::{SystemTime, duration_to_str, get_abs_path, time};
|
||||
use crate::{chat, chatlist_events, ensure_and_debug_assert_ne, stock_str};
|
||||
use crate::{chat, chatlist_events, stock_str};
|
||||
|
||||
/// Time during which a contact is considered as seen recently.
|
||||
const SEEN_RECENTLY_SECONDS: i64 = 600;
|
||||
@@ -1922,10 +1922,9 @@ pub(crate) async fn mark_contact_id_as_verified(
|
||||
contact_id: ContactId,
|
||||
verifier_id: ContactId,
|
||||
) -> Result<()> {
|
||||
ensure_and_debug_assert_ne!(
|
||||
contact_id,
|
||||
verifier_id,
|
||||
"Contact cannot be verified by self",
|
||||
debug_assert_ne!(
|
||||
contact_id, verifier_id,
|
||||
"Contact cannot be verified by self"
|
||||
);
|
||||
context
|
||||
.sql
|
||||
|
||||
@@ -27,7 +27,6 @@ use crate::events::{Event, EventEmitter, EventType, Events};
|
||||
use crate::imap::{FolderMeaning, Imap, ServerMetadata};
|
||||
use crate::key::{load_self_secret_key, self_fingerprint};
|
||||
use crate::log::{info, warn};
|
||||
use crate::logged_debug_assert;
|
||||
use crate::login_param::{ConfiguredLoginParam, EnteredLoginParam};
|
||||
use crate::message::{self, Message, MessageState, MsgId};
|
||||
use crate::param::{Param, Params};
|
||||
@@ -661,16 +660,8 @@ impl Context {
|
||||
/// or [`Self::emit_msgs_changed_without_msg_id`] should be used
|
||||
/// instead of this function.
|
||||
pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
|
||||
logged_debug_assert!(
|
||||
self,
|
||||
!chat_id.is_unset(),
|
||||
"emit_msgs_changed: chat_id is unset."
|
||||
);
|
||||
logged_debug_assert!(
|
||||
self,
|
||||
!msg_id.is_unset(),
|
||||
"emit_msgs_changed: msg_id is unset."
|
||||
);
|
||||
debug_assert!(!chat_id.is_unset());
|
||||
debug_assert!(!msg_id.is_unset());
|
||||
|
||||
self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
|
||||
chatlist_events::emit_chatlist_changed(self);
|
||||
@@ -679,11 +670,7 @@ impl Context {
|
||||
|
||||
/// Emits a MsgsChanged event with specified chat and without message id.
|
||||
pub fn emit_msgs_changed_without_msg_id(&self, chat_id: ChatId) {
|
||||
logged_debug_assert!(
|
||||
self,
|
||||
!chat_id.is_unset(),
|
||||
"emit_msgs_changed_without_msg_id: chat_id is unset."
|
||||
);
|
||||
debug_assert!(!chat_id.is_unset());
|
||||
|
||||
self.emit_event(EventType::MsgsChanged {
|
||||
chat_id,
|
||||
@@ -1054,12 +1041,6 @@ impl Context {
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"donation_request_next_check",
|
||||
self.get_config_i64(Config::DonationRequestNextCheck)
|
||||
.await?
|
||||
.to_string(),
|
||||
);
|
||||
res.insert(
|
||||
"first_key_contacts_msg_id",
|
||||
self.sql
|
||||
|
||||
@@ -213,18 +213,17 @@ impl Session {
|
||||
|
||||
let mut uid_message_ids: BTreeMap<u32, String> = BTreeMap::new();
|
||||
uid_message_ids.insert(uid, rfc724_mid);
|
||||
let (sender, receiver) = async_channel::unbounded();
|
||||
self.fetch_many_msgs(
|
||||
context,
|
||||
folder,
|
||||
uidvalidity,
|
||||
vec![uid],
|
||||
&uid_message_ids,
|
||||
false,
|
||||
sender,
|
||||
)
|
||||
.await?;
|
||||
if receiver.recv().await.is_err() {
|
||||
let (last_uid, _received) = self
|
||||
.fetch_many_msgs(
|
||||
context,
|
||||
folder,
|
||||
uidvalidity,
|
||||
vec![uid],
|
||||
&uid_message_ids,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
if last_uid.is_none() {
|
||||
bail!("Failed to fetch UID {uid}");
|
||||
}
|
||||
Ok(())
|
||||
|
||||
137
src/imap.rs
137
src/imap.rs
@@ -14,7 +14,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context as _, Result, bail, ensure, format_err};
|
||||
use async_channel::{self, Receiver, Sender};
|
||||
use async_channel::Receiver;
|
||||
use async_imap::types::{Fetch, Flag, Name, NameAttribute, UnsolicitedResponse};
|
||||
use deltachat_contact_tools::ContactAddress;
|
||||
use futures::{FutureExt as _, StreamExt, TryStreamExt};
|
||||
@@ -562,7 +562,7 @@ impl Imap {
|
||||
let read_cnt = msgs.len();
|
||||
|
||||
let download_limit = context.download_limit().await?;
|
||||
let mut uids_fetch = Vec::<(u32, bool /* partially? */)>::with_capacity(msgs.len() + 1);
|
||||
let mut uids_fetch = Vec::<(_, bool /* partially? */)>::with_capacity(msgs.len() + 1);
|
||||
let mut uid_message_ids = BTreeMap::new();
|
||||
let mut largest_uid_skipped = None;
|
||||
let delete_target = context.get_delete_msgs_target().await?;
|
||||
@@ -695,72 +695,51 @@ impl Imap {
|
||||
self.connectivity.set_working(context).await;
|
||||
}
|
||||
|
||||
let (sender, receiver) = async_channel::unbounded();
|
||||
|
||||
// Actually download messages.
|
||||
let mut largest_uid_fetched: u32 = 0;
|
||||
let mut received_msgs = Vec::with_capacity(uids_fetch.len());
|
||||
let mut uids_fetch_in_batch = Vec::with_capacity(max(uids_fetch.len(), 1));
|
||||
let mut fetch_partially = false;
|
||||
uids_fetch.push((0, !uids_fetch.last().unwrap_or(&(0, false)).1));
|
||||
for (uid, fp) in uids_fetch {
|
||||
if fp != fetch_partially {
|
||||
let (largest_uid_fetched_in_batch, received_msgs_in_batch) = session
|
||||
.fetch_many_msgs(
|
||||
context,
|
||||
folder,
|
||||
uid_validity,
|
||||
uids_fetch_in_batch.split_off(0),
|
||||
&uid_message_ids,
|
||||
fetch_partially,
|
||||
)
|
||||
.await
|
||||
.context("fetch_many_msgs")?;
|
||||
received_msgs.extend(received_msgs_in_batch);
|
||||
largest_uid_fetched = max(
|
||||
largest_uid_fetched,
|
||||
largest_uid_fetched_in_batch.unwrap_or(0),
|
||||
);
|
||||
fetch_partially = fp;
|
||||
}
|
||||
uids_fetch_in_batch.push(uid);
|
||||
}
|
||||
|
||||
// Advance uid_next to the maximum of the largest known UID plus 1
|
||||
// and mailbox UIDNEXT.
|
||||
// Largest known UID is normally less than UIDNEXT,
|
||||
// but a message may have arrived between determining UIDNEXT
|
||||
// and executing the FETCH command.
|
||||
let mailbox_uid_next = session
|
||||
.selected_mailbox
|
||||
.as_ref()
|
||||
.with_context(|| format!("Expected {folder:?} to be selected"))?
|
||||
.uid_next
|
||||
.unwrap_or_default();
|
||||
let new_uid_next = max(
|
||||
max(largest_uid_fetched, largest_uid_skipped.unwrap_or(0)) + 1,
|
||||
mailbox_uid_next,
|
||||
);
|
||||
|
||||
let update_uids_future = async {
|
||||
let mut largest_uid_fetched: u32 = 0;
|
||||
|
||||
while let Ok((uid, received_msg_opt)) = receiver.recv().await {
|
||||
largest_uid_fetched = max(largest_uid_fetched, uid);
|
||||
if let Some(received_msg) = received_msg_opt {
|
||||
received_msgs.push(received_msg)
|
||||
}
|
||||
}
|
||||
|
||||
largest_uid_fetched
|
||||
};
|
||||
|
||||
let actually_download_messages_future = async move {
|
||||
let mut uids_fetch_in_batch = Vec::with_capacity(max(uids_fetch.len(), 1));
|
||||
let mut fetch_partially = false;
|
||||
uids_fetch.push((0, !uids_fetch.last().unwrap_or(&(0, false)).1));
|
||||
for (uid, fp) in uids_fetch {
|
||||
if fp != fetch_partially {
|
||||
session
|
||||
.fetch_many_msgs(
|
||||
context,
|
||||
folder,
|
||||
uid_validity,
|
||||
uids_fetch_in_batch.split_off(0),
|
||||
&uid_message_ids,
|
||||
fetch_partially,
|
||||
sender.clone(),
|
||||
)
|
||||
.await
|
||||
.context("fetch_many_msgs")?;
|
||||
fetch_partially = fp;
|
||||
}
|
||||
uids_fetch_in_batch.push(uid);
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let (largest_uid_fetched, fetch_res) =
|
||||
tokio::join!(update_uids_future, actually_download_messages_future);
|
||||
|
||||
// Advance uid_next to the largest fetched UID plus 1.
|
||||
//
|
||||
// This may be larger than `mailbox_uid_next`
|
||||
// if the message has arrived after selecting mailbox
|
||||
// and determining its UIDNEXT and before prefetch.
|
||||
let mut new_uid_next = largest_uid_fetched + 1;
|
||||
if fetch_res.is_ok() {
|
||||
// If we have successfully fetched all messages we planned during prefetch,
|
||||
// then we have covered at least the range between old UIDNEXT
|
||||
// and UIDNEXT of the mailbox at the time of selecting it.
|
||||
new_uid_next = max(new_uid_next, mailbox_uid_next);
|
||||
|
||||
new_uid_next = max(new_uid_next, largest_uid_skipped.unwrap_or(0) + 1);
|
||||
}
|
||||
if new_uid_next > old_uid_next {
|
||||
set_uid_next(context, folder, new_uid_next).await?;
|
||||
}
|
||||
@@ -773,10 +752,6 @@ impl Imap {
|
||||
|
||||
chat::mark_old_messages_as_noticed(context, received_msgs).await?;
|
||||
|
||||
// Now fail if fetching failed, so we will
|
||||
// establish a new session if this one is broken.
|
||||
fetch_res?;
|
||||
|
||||
Ok(read_cnt > 0)
|
||||
}
|
||||
|
||||
@@ -1325,19 +1300,9 @@ impl Session {
|
||||
|
||||
/// Fetches a list of messages by server UID.
|
||||
///
|
||||
/// Sends pairs of UID and info about each downloaded message to the provided channel.
|
||||
/// Received message info is optional because UID may be ignored
|
||||
/// if the message has a `\Deleted` flag.
|
||||
///
|
||||
/// The channel is used to return the results because the function may fail
|
||||
/// due to network errors before it finishes fetching all the messages.
|
||||
/// In this case caller still may want to process all the results
|
||||
/// received over the channel and persist last seen UID in the database
|
||||
/// before bubbling up the failure.
|
||||
///
|
||||
/// Returns the last UID fetched successfully and the info about each downloaded message.
|
||||
/// If the message is incorrect or there is a failure to write a message to the database,
|
||||
/// it is skipped and the error is logged.
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) async fn fetch_many_msgs(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
@@ -1346,10 +1311,12 @@ impl Session {
|
||||
request_uids: Vec<u32>,
|
||||
uid_message_ids: &BTreeMap<u32, String>,
|
||||
fetch_partially: bool,
|
||||
received_msgs_channel: Sender<(u32, Option<ReceivedMsg>)>,
|
||||
) -> Result<()> {
|
||||
) -> Result<(Option<u32>, Vec<ReceivedMsg>)> {
|
||||
let mut last_uid = None;
|
||||
let mut received_msgs = Vec::new();
|
||||
|
||||
if request_uids.is_empty() {
|
||||
return Ok(());
|
||||
return Ok((last_uid, received_msgs));
|
||||
}
|
||||
|
||||
for (request_uids, set) in build_sequence_sets(&request_uids)? {
|
||||
@@ -1435,7 +1402,7 @@ impl Session {
|
||||
|
||||
if is_deleted {
|
||||
info!(context, "Not processing deleted msg {}.", request_uid);
|
||||
received_msgs_channel.send((request_uid, None)).await?;
|
||||
last_uid = Some(request_uid);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1446,7 +1413,7 @@ impl Session {
|
||||
context,
|
||||
"Not processing message {} without a BODY.", request_uid
|
||||
);
|
||||
received_msgs_channel.send((request_uid, None)).await?;
|
||||
last_uid = Some(request_uid);
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -1478,15 +1445,15 @@ impl Session {
|
||||
.await
|
||||
{
|
||||
Ok(received_msg) => {
|
||||
received_msgs_channel
|
||||
.send((request_uid, received_msg))
|
||||
.await?;
|
||||
if let Some(m) = received_msg {
|
||||
received_msgs.push(m);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(context, "receive_imf error: {:#}.", err);
|
||||
received_msgs_channel.send((request_uid, None)).await?;
|
||||
}
|
||||
};
|
||||
last_uid = Some(request_uid)
|
||||
}
|
||||
|
||||
// If we don't process the whole response, IMAP client is left in a broken state where
|
||||
@@ -1510,7 +1477,7 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok((last_uid, received_msgs))
|
||||
}
|
||||
|
||||
/// Retrieves server metadata if it is supported.
|
||||
|
||||
@@ -20,7 +20,6 @@ use crate::constants::{Chattype, DC_FROM_HANDSHAKE};
|
||||
use crate::contact::{Contact, ContactId, Origin};
|
||||
use crate::context::Context;
|
||||
use crate::e2ee::EncryptHelper;
|
||||
use crate::ensure_and_debug_assert;
|
||||
use crate::ephemeral::Timer as EphemeralTimer;
|
||||
use crate::key::self_fingerprint;
|
||||
use crate::key::{DcKey, SignedPublicKey};
|
||||
@@ -309,7 +308,7 @@ impl MimeFactory {
|
||||
} else if id == ContactId::SELF {
|
||||
member_fingerprints.push(self_fingerprint.to_string());
|
||||
} else {
|
||||
ensure_and_debug_assert!(member_fingerprints.is_empty(), "If some past member is a key-contact, all other past members should be key-contacts too");
|
||||
debug_assert!(member_fingerprints.is_empty(), "If some past member is a key-contact, all other past members should be key-contacts too");
|
||||
}
|
||||
}
|
||||
member_timestamps.push(add_timestamp);
|
||||
@@ -360,7 +359,7 @@ impl MimeFactory {
|
||||
// if we are leaving the group.
|
||||
past_member_fingerprints.push(self_fingerprint.to_string());
|
||||
} else {
|
||||
ensure_and_debug_assert!(past_member_fingerprints.is_empty(), "If some past member is a key-contact, all other past members should be key-contacts too");
|
||||
debug_assert!(past_member_fingerprints.is_empty(), "If some past member is a key-contact, all other past members should be key-contacts too");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,8 +367,8 @@ impl MimeFactory {
|
||||
}
|
||||
}
|
||||
|
||||
ensure_and_debug_assert!(member_timestamps.len() >= to.len());
|
||||
ensure_and_debug_assert!(member_fingerprints.is_empty() || member_fingerprints.len() >= to.len());
|
||||
debug_assert!(member_timestamps.len() >= to.len());
|
||||
debug_assert!(member_fingerprints.is_empty() || member_fingerprints.len() >= to.len());
|
||||
|
||||
if to.len() > 1 {
|
||||
if let Some(position) = to.iter().position(|(_, x)| x == &from_addr) {
|
||||
@@ -446,7 +445,7 @@ impl MimeFactory {
|
||||
};
|
||||
let attach_selfavatar = Self::should_attach_selfavatar(context, &msg).await;
|
||||
|
||||
ensure_and_debug_assert!(
|
||||
debug_assert!(
|
||||
member_timestamps.is_empty()
|
||||
|| to.len() + past_members.len() == member_timestamps.len()
|
||||
);
|
||||
@@ -669,7 +668,7 @@ impl MimeFactory {
|
||||
));
|
||||
}
|
||||
|
||||
ensure_and_debug_assert!(
|
||||
debug_assert!(
|
||||
self.member_timestamps.is_empty()
|
||||
|| to.len() + past_members.len() == self.member_timestamps.len()
|
||||
);
|
||||
@@ -790,7 +789,7 @@ impl MimeFactory {
|
||||
}
|
||||
|
||||
if let Loaded::Message { chat, .. } = &self.loaded {
|
||||
if chat.typ == Chattype::OutBroadcast || chat.typ == Chattype::InBroadcast {
|
||||
if chat.typ == Chattype::OutBroadcast {
|
||||
headers.push((
|
||||
"List-ID",
|
||||
mail_builder::headers::text::Text::new(format!(
|
||||
@@ -1320,10 +1319,7 @@ impl MimeFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if chat.typ == Chattype::Group
|
||||
|| chat.typ == Chattype::OutBroadcast
|
||||
|| chat.typ == Chattype::InBroadcast
|
||||
{
|
||||
if chat.typ == Chattype::Group || chat.typ == Chattype::OutBroadcast {
|
||||
headers.push((
|
||||
"Chat-Group-Name",
|
||||
mail_builder::headers::text::Text::new(chat.name.to_string()).into(),
|
||||
|
||||
@@ -1568,16 +1568,20 @@ impl MimeMessage {
|
||||
for field in fields {
|
||||
// lowercasing all headers is technically not correct, but makes things work better
|
||||
let key = field.get_key().to_lowercase();
|
||||
if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
|
||||
match addrparse_header(field) {
|
||||
Ok(addrlist) => {
|
||||
*chat_disposition_notification_to = addrlist.extract_single_info();
|
||||
if !headers.contains_key(&key) || // key already exists, only overwrite known types (protected headers)
|
||||
is_known(&key) || key.starts_with("chat-")
|
||||
{
|
||||
if key == HeaderDef::ChatDispositionNotificationTo.get_headername() {
|
||||
match addrparse_header(field) {
|
||||
Ok(addrlist) => {
|
||||
*chat_disposition_notification_to = addrlist.extract_single_info();
|
||||
}
|
||||
Err(e) => warn!(context, "Could not read {} address: {}", key, e),
|
||||
}
|
||||
Err(e) => warn!(context, "Could not read {} address: {}", key, e),
|
||||
} else {
|
||||
let value = field.get_value();
|
||||
headers.insert(key.to_string(), value);
|
||||
}
|
||||
} else {
|
||||
let value = field.get_value();
|
||||
headers.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
let recipients_new = get_recipients(fields);
|
||||
@@ -2005,6 +2009,28 @@ pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the header overwrites outer header
|
||||
/// when it comes from protected headers.
|
||||
fn is_known(key: &str) -> bool {
|
||||
matches!(
|
||||
key,
|
||||
"return-path"
|
||||
| "date"
|
||||
| "from"
|
||||
| "sender"
|
||||
| "reply-to"
|
||||
| "to"
|
||||
| "cc"
|
||||
| "bcc"
|
||||
| "message-id"
|
||||
| "in-reply-to"
|
||||
| "references"
|
||||
| "subject"
|
||||
| "secure-join"
|
||||
| "list-id"
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns if the header is hidden and must be ignored in the IMF section.
|
||||
pub(crate) fn is_hidden(key: &str) -> bool {
|
||||
matches!(
|
||||
|
||||
@@ -14,9 +14,7 @@ use mailparse::SingleInfo;
|
||||
use num_traits::FromPrimitive;
|
||||
use regex::Regex;
|
||||
|
||||
use crate::chat::{
|
||||
self, Chat, ChatId, ChatIdBlocked, ProtectionStatus, remove_from_chat_contacts_table,
|
||||
};
|
||||
use crate::chat::{self, Chat, ChatId, ChatIdBlocked, ProtectionStatus};
|
||||
use crate::config::Config;
|
||||
use crate::constants::{Blocked, Chattype, DC_CHAT_ID_TRASH, EDITED_PREFIX, ShowEmails};
|
||||
use crate::contact::{Contact, ContactId, Origin, mark_contact_id_as_verified};
|
||||
@@ -31,7 +29,6 @@ use crate::key::self_fingerprint_opt;
|
||||
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
||||
use crate::log::LogExt;
|
||||
use crate::log::{info, warn};
|
||||
use crate::logged_debug_assert;
|
||||
use crate::message::{
|
||||
self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
|
||||
};
|
||||
@@ -45,7 +42,7 @@ use crate::simplify;
|
||||
use crate::stock_str;
|
||||
use crate::sync::Sync::*;
|
||||
use crate::tools::{self, buf_compress, remove_subject_prefix};
|
||||
use crate::{chatlist_events, ensure_and_debug_assert, ensure_and_debug_assert_eq, location};
|
||||
use crate::{chatlist_events, location};
|
||||
use crate::{contact, imap};
|
||||
|
||||
/// This is the struct that is returned after receiving one email (aka MIME message).
|
||||
@@ -1457,7 +1454,7 @@ async fn do_chat_assignment(
|
||||
false => None,
|
||||
};
|
||||
if let Some(chat) = chat {
|
||||
ensure_and_debug_assert!(chat.typ == Chattype::Single);
|
||||
debug_assert!(chat.typ == Chattype::Single);
|
||||
let mut new_protection = match verified_encryption {
|
||||
VerifiedEncryption::Verified => ProtectionStatus::Protected,
|
||||
VerifiedEncryption::NotVerified(_) => ProtectionStatus::Unprotected,
|
||||
@@ -1690,9 +1687,7 @@ async fn add_parts(
|
||||
_ if chat.id.is_special() => GroupChangesInfo::default(),
|
||||
Chattype::Single => GroupChangesInfo::default(),
|
||||
Chattype::Mailinglist => GroupChangesInfo::default(),
|
||||
Chattype::OutBroadcast => {
|
||||
apply_out_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
|
||||
}
|
||||
Chattype::OutBroadcast => GroupChangesInfo::default(),
|
||||
Chattype::Group => {
|
||||
apply_group_changes(
|
||||
context,
|
||||
@@ -1706,7 +1701,7 @@ async fn add_parts(
|
||||
.await?
|
||||
}
|
||||
Chattype::InBroadcast => {
|
||||
apply_in_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
|
||||
apply_broadcast_changes(context, mime_parser, &mut chat, from_id).await?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2142,7 +2137,7 @@ RETURNING id
|
||||
// afterwards insert additional parts.
|
||||
replace_msg_id = None;
|
||||
|
||||
ensure_and_debug_assert!(!row_id.is_special());
|
||||
debug_assert!(!row_id.is_special());
|
||||
created_db_entries.push(row_id);
|
||||
}
|
||||
|
||||
@@ -2405,9 +2400,7 @@ async fn lookup_chat_by_reply(
|
||||
// lookup by reply should never be needed
|
||||
// as we can directly assign the message to the chat
|
||||
// by its group ID.
|
||||
ensure_and_debug_assert!(
|
||||
mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted()
|
||||
);
|
||||
debug_assert!(mime_parser.get_chat_group_id().is_none() || !mime_parser.was_encrypted());
|
||||
|
||||
// Try to assign message to the same chat as the parent message.
|
||||
let Some(parent_chat_id) = ChatId::lookup_by_message(parent) else {
|
||||
@@ -3445,30 +3438,7 @@ async fn apply_mailinglist_changes(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn apply_out_broadcast_changes(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
chat: &mut Chat,
|
||||
from_id: ContactId,
|
||||
) -> Result<GroupChangesInfo> {
|
||||
ensure!(chat.typ == Chattype::OutBroadcast);
|
||||
|
||||
if let Some(_removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
|
||||
// The sender of the message left the broadcast channel
|
||||
remove_from_chat_contacts_table(context, chat.id, from_id).await?;
|
||||
|
||||
return Ok(GroupChangesInfo {
|
||||
better_msg: Some("".to_string()),
|
||||
added_removed_id: None,
|
||||
silent: true,
|
||||
extra_msgs: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
Ok(GroupChangesInfo::default())
|
||||
}
|
||||
|
||||
async fn apply_in_broadcast_changes(
|
||||
async fn apply_broadcast_changes(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
chat: &mut Chat,
|
||||
@@ -3489,15 +3459,6 @@ async fn apply_in_broadcast_changes(
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(_removed_addr) = mime_parser.get_header(HeaderDef::ChatGroupMemberRemoved) {
|
||||
// The only member added/removed message that is ever sent is "I left.",
|
||||
// so, this is the only case we need to handle here
|
||||
if from_id == ContactId::SELF {
|
||||
better_msg
|
||||
.get_or_insert(stock_str::msg_group_left_local(context, ContactId::SELF).await);
|
||||
}
|
||||
}
|
||||
|
||||
if send_event_chat_modified {
|
||||
context.emit_event(EventType::ChatModified(chat.id));
|
||||
chatlist_events::emit_chatlist_item_changed(context, chat.id);
|
||||
@@ -3766,7 +3727,7 @@ async fn add_or_lookup_key_contacts_by_address_list(
|
||||
}
|
||||
}
|
||||
|
||||
ensure_and_debug_assert_eq!(contact_ids.len(), address_list.len(),);
|
||||
debug_assert_eq!(contact_ids.len(), address_list.len());
|
||||
Ok(contact_ids)
|
||||
}
|
||||
|
||||
@@ -3836,11 +3797,7 @@ async fn lookup_key_contact_by_fingerprint(
|
||||
context: &Context,
|
||||
fingerprint: &str,
|
||||
) -> Result<Option<ContactId>> {
|
||||
logged_debug_assert!(
|
||||
context,
|
||||
!fingerprint.is_empty(),
|
||||
"lookup_key_contact_by_fingerprint: fingerprint is empty."
|
||||
);
|
||||
debug_assert!(!fingerprint.is_empty());
|
||||
if fingerprint.is_empty() {
|
||||
// Avoid accidentally looking up a non-key-contact.
|
||||
return Ok(None);
|
||||
@@ -3924,7 +3881,7 @@ async fn lookup_key_contacts_by_address_list(
|
||||
contact_ids.push(contact_id);
|
||||
}
|
||||
}
|
||||
ensure_and_debug_assert_eq!(address_list.len(), contact_ids.len(),);
|
||||
debug_assert_eq!(address_list.len(), contact_ids.len());
|
||||
Ok(contact_ids)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ use crate::events::EventType;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::key::{DcKey, Fingerprint, load_self_public_key};
|
||||
use crate::log::{error, info, warn};
|
||||
use crate::logged_debug_assert;
|
||||
use crate::message::{Message, Viewtype};
|
||||
use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||
use crate::param::Param;
|
||||
@@ -33,10 +32,9 @@ use qrinvite::QrInvite;
|
||||
use crate::token::Namespace;
|
||||
|
||||
fn inviter_progress(context: &Context, contact_id: ContactId, progress: usize) {
|
||||
logged_debug_assert!(
|
||||
context,
|
||||
debug_assert!(
|
||||
progress <= 1000,
|
||||
"inviter_progress: contact {contact_id}, progress={progress}, but value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success."
|
||||
"value in range 0..1000 expected with: 0=error, 1..999=progress, 1000=success"
|
||||
);
|
||||
context.emit_event(EventType::SecurejoinInviterProgress {
|
||||
contact_id,
|
||||
|
||||
@@ -285,7 +285,7 @@ pub enum StockMessage {
|
||||
#[strum(props(fallback = "Member %1$s removed by %2$s."))]
|
||||
MsgDelMemberBy = 131,
|
||||
|
||||
#[strum(props(fallback = "You left."))]
|
||||
#[strum(props(fallback = "You left the group."))]
|
||||
MsgYouLeftGroup = 132,
|
||||
|
||||
#[strum(props(fallback = "Group left by %1$s."))]
|
||||
@@ -413,16 +413,6 @@ pub enum StockMessage {
|
||||
|
||||
#[strum(props(fallback = "Establishing guaranteed end-to-end encryption, please wait…"))]
|
||||
SecurejoinWait = 190,
|
||||
|
||||
#[strum(props(fallback = "❤️ Seems you're enjoying Delta Chat!
|
||||
|
||||
Please consider donating to help that Delta Chat stays free for everyone.
|
||||
|
||||
While Delta Chat is free to use and open source, development costs money.
|
||||
Help keeping us to keep Delta Chat independent and make it more awesome in the future.
|
||||
|
||||
https://delta.chat/donate"))]
|
||||
DonationRequest = 193,
|
||||
}
|
||||
|
||||
impl StockMessage {
|
||||
@@ -695,7 +685,7 @@ pub(crate) async fn msg_group_left_remote(context: &Context) -> String {
|
||||
translated(context, StockMessage::MsgILeftGroup).await
|
||||
}
|
||||
|
||||
/// Stock string: `You left.` or `Group left by %1$s.`.
|
||||
/// Stock string: `You left the group.` or `Group left by %1$s.`.
|
||||
pub(crate) async fn msg_group_left_local(context: &Context, by_contact: ContactId) -> String {
|
||||
if by_contact == ContactId::SELF {
|
||||
translated(context, StockMessage::MsgYouLeftGroup).await
|
||||
@@ -795,11 +785,6 @@ pub(crate) async fn securejoin_wait(context: &Context) -> String {
|
||||
translated(context, StockMessage::SecurejoinWait).await
|
||||
}
|
||||
|
||||
/// Stock string: `❤️ Seems you're enjoying Delta Chat!`…
|
||||
pub(crate) async fn donation_request(context: &Context) -> String {
|
||||
translated(context, StockMessage::DonationRequest).await
|
||||
}
|
||||
|
||||
/// Stock string: `Scan to chat with %1$s`.
|
||||
pub(crate) async fn setup_contact_qr_description(
|
||||
context: &Context,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
//!
|
||||
//! This private module is only compiled for test runs.
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::env::current_dir;
|
||||
use std::fmt::Write;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::panic;
|
||||
@@ -754,7 +753,7 @@ impl TestContext {
|
||||
pub async fn add_or_lookup_address_contact(&self, other: &TestContext) -> Contact {
|
||||
let contact_id = self.add_or_lookup_address_contact_id(other).await;
|
||||
let contact = Contact::get_by_id(&self.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.is_key_contact(), false);
|
||||
debug_assert_eq!(contact.is_key_contact(), false);
|
||||
contact
|
||||
}
|
||||
|
||||
@@ -1064,25 +1063,6 @@ impl Drop for TestContext {
|
||||
// Print the chats if runtime still exists.
|
||||
handle.block_on(async move {
|
||||
self.print_chats().await;
|
||||
|
||||
// If you set this to true, and a test fails,
|
||||
// the sql databases will be saved into the current working directory
|
||||
// so that you can examine them.
|
||||
if std::env::var("DELTACHAT_SAVE_TMP_DB").is_ok() {
|
||||
let _: u32 = self
|
||||
.sql
|
||||
.query_get_value("PRAGMA wal_checkpoint;", ())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let from = self.get_dbfile();
|
||||
let target = current_dir()
|
||||
.unwrap()
|
||||
.join(format!("test-account-{}.db", self.name()));
|
||||
tokio::fs::copy(from, &target).await.unwrap();
|
||||
eprintln!("Copied database from {from:?} to {target:?}\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1166,11 +1146,6 @@ impl Drop for InnerLogSink {
|
||||
while let Ok(event) = self.events.try_recv() {
|
||||
print_logevent(&event);
|
||||
}
|
||||
if std::env::var("DELTACHAT_SAVE_TMP_DB").is_err() {
|
||||
eprintln!(
|
||||
"note: If you want to examine the database files, set environment variable DELTACHAT_SAVE_TMP_DB=1"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
50
src/tools.rs
50
src/tools.rs
@@ -763,55 +763,5 @@ pub(crate) fn inc_and_check<T: PrimInt + AddAssign + std::fmt::Debug>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns early with an error if a condition is not satisfied.
|
||||
/// In non-optimized builds, panics instead if so.
|
||||
#[macro_export]
|
||||
macro_rules! ensure_and_debug_assert {
|
||||
($($arg:tt)*) => {
|
||||
debug_assert!($($arg)*);
|
||||
anyhow::ensure!($($arg)*);
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns early with an error on two expressions inequality.
|
||||
/// In non-optimized builds, panics instead if so.
|
||||
#[macro_export]
|
||||
macro_rules! ensure_and_debug_assert_eq {
|
||||
($left:expr, $right:expr, $($arg:tt)*) => {
|
||||
match (&$left, &$right) {
|
||||
(left_val, right_val) => {
|
||||
debug_assert_eq!(left_val, right_val, $($arg)*);
|
||||
anyhow::ensure!(left_val == right_val, $($arg)*);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns early with an error on two expressions equality.
|
||||
/// In non-optimized builds, panics instead if so.
|
||||
#[macro_export]
|
||||
macro_rules! ensure_and_debug_assert_ne {
|
||||
($left:expr, $right:expr, $($arg:tt)*) => {
|
||||
match (&$left, &$right) {
|
||||
(left_val, right_val) => {
|
||||
debug_assert_ne!(left_val, right_val, $($arg)*);
|
||||
anyhow::ensure!(left_val != right_val, $($arg)*);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Logs a warning if a condition is not satisfied.
|
||||
/// In non-optimized builds, panics also if so.
|
||||
#[macro_export]
|
||||
macro_rules! logged_debug_assert {
|
||||
($ctx:expr, $cond:expr, $($arg:tt)*) => {
|
||||
if !$cond {
|
||||
warn!($ctx, $($arg)*);
|
||||
}
|
||||
debug_assert!($cond, $($arg)*);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tools_tests;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Group#Chat#10: Group chat [3 member(s)]
|
||||
--------------------------------------------------------------------------------
|
||||
Msg#10🔒: (Contact#Contact#10): Hi! I created a group. [FRESH]
|
||||
Msg#11🔒: Me (Contact#Contact#Self): You left. [INFO] √
|
||||
Msg#11🔒: Me (Contact#Contact#Self): You left the group. [INFO] √
|
||||
Msg#12🔒: (Contact#Contact#10): Member charlie@example.net added by alice@example.org. [FRESH][INFO]
|
||||
Msg#13🔒: (Contact#Contact#10): What a silence! [FRESH]
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user