Compare commits

..

7 Commits

Author SHA1 Message Date
Simon Laux
f22263953f start with debouncing -> doesn't work because I don't know how to integrate it into the context 2024-01-11 04:46:09 +01:00
Simon Laux
2c3734153a Centralise the methods to send chatlist events
this is the first step to debouncing
2024-01-11 03:12:41 +01:00
Simon Laux
7483aa532b fix formatting 2024-01-11 02:17:11 +01:00
Simon Laux
a76a7ae781 update autogenerated files 2024-01-11 02:17:11 +01:00
Simon Laux
f4caf1688b emit exact chatid events in contact aeap and contact name change 2024-01-11 02:17:11 +01:00
Simon Laux
6442e13a1a add missing docs 2024-01-11 02:17:11 +01:00
Simon Laux
a048d6b0d1 feat: add UIChatListChanged and UIChatListItemChanged events 2024-01-11 02:17:11 +01:00
43 changed files with 7692 additions and 274 deletions

View File

@@ -1,61 +1,5 @@
# Changelog
## [1.133.0] - 2024-01-14
### Features / Changes
- Securejoin protocol implementation refinements
- Track forward and backward verification separately ([#5089](https://github.com/deltachat/deltachat-core-rust/pull/5089)) to avoid inconsistent states.
- Mark 1:1 chat as verified for Bob early. 1:1 chat with Alice is verified as soon as Alice's key is verified rather than at the end of the protocol.
- Put Message-ID into hidden headers and take it from there on receiver ([#4798](https://github.com/deltachat/deltachat-core-rust/pull/4798)). This works around servers which generate their own Message-ID and overwrite the one generated by Delta Chat.
- deltachat-repl: Enable INFO logging by default and add timestamps.
- Add `ConfigSynced` (`DC_EVENT_CONFIG_SYNCED`) event which is emitted when configuration is changed via synchronization message or synchronization message for configuration is sent. UI may refresh elments based on the configuration key which is a part of the event.
- Sync contact creation/rename across devices ([#5163](https://github.com/deltachat/deltachat-core-rust/pull/5163)).
- Encrypt MDNs ([#5175](https://github.com/deltachat/deltachat-core-rust/pull/5175)).
- Only try to configure non-strict TLS checks if explicitly set ([#5181](https://github.com/deltachat/deltachat-core-rust/pull/5181)).
### Build system
- Use released version of iroh 0.4.2 for "setup second device" feature.
### CI
- Update to Rust 1.75.0.
- Downgrade `chai` from 4.4.0 to 4.3.10.
### Documentation
- Add a link <https://www.ietf.org/archive/id/draft-bucksch-autoconfig-00.html> to autoconfig RFC draft.
- Update securejoin link in `standards.md` from <https://countermitm.readthedocs.io/> to <https://securejoin.readthedocs.io>.
- Restore "Constants" page in Doxygen >=1.9.8
### Fixes
- imap: Limit the rate of LOGIN attempts rather than connection attempts. This is to avoid having to wait for rate limiter right after switching from a bad or offline network to a working network while still guarding against reconnection loop.
- Do not ignore `peerstate.save_to_db()` errors.
- securejoin: Mark 1:1s as protected regardless of the Config::VerifiedOneOnOneChats.
- Delete received outgoing messages from SMTP queue ([#5115](https://github.com/deltachat/deltachat-core-rust/pull/5115)).
- imap: Fail fast on `LIST` errors to avoid busy loop when connection is lost.
- Split SMTP jobs already in `chat::create_send_msg_jobs()` ([#5115](https://github.com/deltachat/deltachat-core-rust/pull/5115)).
- Do not remove contents from unencrypted [Schleuder](https://schleuder.org/) mailing lists messages.
- Reset message error when scheduling resending ([#5119](https://github.com/deltachat/deltachat-core-rust/pull/5119)).
- Emit events more reliably when starting and stopping I/O ([#5101](https://github.com/deltachat/deltachat-core-rust/pull/5101)).
- Fix timestamp of chat protection info message for correct message ordering after restoring a backup ([#5088](https://github.com/deltachat/deltachat-core-rust/pull/5088)).
### Refactor
- sql: Recreate `config` table with UNIQUE constraint.
- sql: Recreate `keypairs` table to remove unused `addr` and `created` fields and move `is_default` flag to `config` table.
- Send `Secure-Join-Fingerprint` only in `*-request-with-auth`.
### Tests
- Test joining non-protected group.
- Test that read receipts don't degrade encryption.
- Test that changing default private key breaks backward verification.
- Test recovery from lost vc-contact-confirm.
- Use `wait_for_incoming_msg_event()` more.
## [1.132.1] - 2023-12-12
### Features / Changes
@@ -3413,4 +3357,3 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.131.9]: https://github.com/deltachat/deltachat-core-rust/compare/v1.131.8...v1.131.9
[1.132.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.131.9...v1.132.0
[1.132.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.132.0...v1.132.1
[1.133.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.132.1...v1.133.0

56
Cargo.lock generated
View File

@@ -738,11 +738,13 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
[[package]]
name = "clipboard-win"
version = "5.0.0"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc"
checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
"error-code",
"str-buf",
"winapi",
]
[[package]]
@@ -1089,7 +1091,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.133.0"
version = "1.132.1"
dependencies = [
"ansi_term",
"anyhow",
@@ -1108,7 +1110,7 @@ dependencies = [
"encoded-words",
"escaper",
"fast-socks5",
"fd-lock",
"fd-lock 4.0.2",
"format-flowed",
"futures",
"futures-lite",
@@ -1167,7 +1169,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.133.0"
version = "1.132.1"
dependencies = [
"anyhow",
"async-channel 2.1.1",
@@ -1191,7 +1193,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.133.0"
version = "1.132.1"
dependencies = [
"ansi_term",
"anyhow",
@@ -1206,7 +1208,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.133.0"
version = "1.132.1"
dependencies = [
"anyhow",
"deltachat",
@@ -1231,7 +1233,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.133.0"
version = "1.132.1"
dependencies = [
"anyhow",
"deltachat",
@@ -1775,9 +1777,13 @@ dependencies = [
[[package]]
name = "error-code"
version = "3.0.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc"
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
dependencies = [
"libc",
"str-buf",
]
[[package]]
name = "escaper"
@@ -1856,6 +1862,17 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fd-lock"
version = "3.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef033ed5e9bad94e55838ca0ca906db0e043f517adda0c8b79c7a8c66c93c1b5"
dependencies = [
"cfg-if",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "fd-lock"
version = "4.0.2"
@@ -2988,11 +3005,11 @@ dependencies = [
[[package]]
name = "nix"
version = "0.27.1"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053"
checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b"
dependencies = [
"bitflags 2.4.1",
"bitflags 1.3.2",
"cfg-if",
"libc",
]
@@ -4208,20 +4225,21 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "rustyline"
version = "13.0.0"
version = "12.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86"
checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9"
dependencies = [
"bitflags 2.4.1",
"cfg-if",
"clipboard-win",
"fd-lock",
"fd-lock 3.0.13",
"home",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"scopeguard",
"unicode-segmentation",
"unicode-width",
"utf8parse",
@@ -4672,6 +4690,12 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "str-buf"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
[[package]]
name = "strsim"
version = "0.10.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.133.0"
version = "1.132.1"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.70"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.133.0"
version = "1.132.1"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -9,7 +9,7 @@
<tab type="hierarchy" visible="no" title="" intro=""/>
<tab type="classmembers" visible="no" title="" intro=""/>
</tab>
<tab type="topics" visible="yes" title="Constants" intro="Here is a list of constants:"/>
<tab type="modules" visible="yes" title="Constants" intro="Here is a list of constants:"/>
<tab type="pages" visible="yes" title="" intro=""/>
<tab type="namespaces" visible="yes" title="">
<tab type="namespacelist" visible="yes" title="" intro=""/>

View File

@@ -6206,18 +6206,6 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_SELFAVATAR_CHANGED 2110
/**
* A multi-device synced config value changed. Maybe the app needs to refresh smth. For uniformity
* this is emitted on the source device too. The value isn't reported, otherwise it would be logged
* which might not be good for privacy. You can get the new value with
* `dc_get_config(context, data2)`.
*
* @param data1 0
* @param data2 (char*) Configuration key.
*/
#define DC_EVENT_CONFIG_SYNCED 2111
/**
* webxdc status update received.
* To get the received status update, use dc_get_webxdc_status_updates() with
@@ -6242,6 +6230,22 @@ void dc_event_unref(dc_event_t* event);
#define DC_EVENT_WEBXDC_INSTANCE_DELETED 2121
/**
* Inform UI that Order (and content as in chat ids) of the chatlist changed.
*
* Sometimes this is emitted together with `DC_EVENT_UI_CHATLIST_ITEM_CHANGED` such as on `DC_EVENT_INCOMING_MSG`.
*/
#define DC_EVENT_UI_CHATLIST_CHANGED 2200
/**
* Inform UI that all or a single chat list item changed and needs to be rerendered
* If `chat_id` is set to 0, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
*
* @param data1 (int) chat_id chat id of chatlist item to be rerendered, if chat_id = 0 all (cached & visible) items need to be rerendered
*/
#define DC_EVENT_UI_CHATLIST_ITEM_CHANGED 2201
/**
* @}

View File

@@ -556,9 +556,10 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::SecurejoinJoinerProgress { .. } => 2061,
EventType::ConnectivityChanged => 2100,
EventType::SelfavatarChanged => 2110,
EventType::ConfigSynced { .. } => 2111,
EventType::WebxdcStatusUpdate { .. } => 2120,
EventType::WebxdcInstanceDeleted { .. } => 2121,
EventType::UIChatListChanged => 2200,
EventType::UIChatListItemChanged { .. } => 2201,
}
}
@@ -584,9 +585,9 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| EventType::Error(_)
| EventType::ConnectivityChanged
| EventType::SelfavatarChanged
| EventType::ConfigSynced { .. }
| EventType::IncomingMsgBunch { .. }
| EventType::ErrorSelfNotInGroup(_) => 0,
| EventType::ErrorSelfNotInGroup(_)
| EventType::UIChatListChanged => 0,
EventType::MsgsChanged { chat_id, .. }
| EventType::ReactionsChanged { chat_id, .. }
| EventType::IncomingMsg { chat_id, .. }
@@ -611,6 +612,9 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
}
EventType::WebxdcStatusUpdate { msg_id, .. } => msg_id.to_u32() as libc::c_int,
EventType::WebxdcInstanceDeleted { msg_id, .. } => msg_id.to_u32() as libc::c_int,
EventType::UIChatListItemChanged { chat_id } => {
chat_id.unwrap_or_default().to_u32() as libc::c_int
}
}
}
@@ -646,7 +650,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::WebxdcInstanceDeleted { .. }
| EventType::IncomingMsgBunch { .. }
| EventType::SelfavatarChanged
| EventType::ConfigSynced { .. } => 0,
| EventType::UIChatListChanged
| EventType::UIChatListItemChanged { .. } => 0,
EventType::ChatModified(_) => 0,
EventType::MsgsChanged { msg_id, .. }
| EventType::ReactionsChanged { msg_id, .. }
@@ -708,7 +713,9 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::SelfavatarChanged
| EventType::WebxdcStatusUpdate { .. }
| EventType::WebxdcInstanceDeleted { .. }
| EventType::ChatEphemeralTimerModified { .. } => ptr::null_mut(),
| EventType::ChatEphemeralTimerModified { .. }
| EventType::UIChatListItemChanged { .. }
| EventType::UIChatListChanged => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => {
if let Some(comment) = comment {
comment.to_c_string().unwrap_or_default().into_raw()
@@ -725,10 +732,6 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
.to_c_string()
.unwrap_or_default()
.into_raw(),
EventType::ConfigSynced { key } => {
let data2 = key.to_string().to_c_string().unwrap_or_default();
data2.into_raw()
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.133.0"
version = "1.132.1"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"

File diff suppressed because it is too large Load Diff

View File

@@ -28,37 +28,55 @@ pub enum EventType {
///
/// This event should *not* be reported to the end-user using a popup or something like
/// that.
Info { msg: String },
Info {
msg: String,
},
/// Emitted when SMTP connection is established and login was successful.
SmtpConnected { msg: String },
SmtpConnected {
msg: String,
},
/// Emitted when IMAP connection is established and login was successful.
ImapConnected { msg: String },
ImapConnected {
msg: String,
},
/// Emitted when a message was successfully sent to the SMTP server.
SmtpMessageSent { msg: String },
SmtpMessageSent {
msg: String,
},
/// Emitted when an IMAP message has been marked as deleted
ImapMessageDeleted { msg: String },
ImapMessageDeleted {
msg: String,
},
/// Emitted when an IMAP message has been moved
ImapMessageMoved { msg: String },
ImapMessageMoved {
msg: String,
},
/// Emitted before going into IDLE on the Inbox folder.
ImapInboxIdle,
/// Emitted when an new file in the $BLOBDIR was created
NewBlobFile { file: String },
NewBlobFile {
file: String,
},
/// Emitted when an file in the $BLOBDIR was deleted
DeletedBlobFile { file: String },
DeletedBlobFile {
file: String,
},
/// The library-user should write a warning string to the log.
///
/// This event should *not* be reported to the end-user using a popup or something like
/// that.
Warning { msg: String },
Warning {
msg: String,
},
/// The library-user should report an error to the end-user.
///
@@ -70,14 +88,18 @@ pub enum EventType {
/// it might be better to delay showing these events until the function has really
/// failed (returned false). It should be sufficient to report only the *last* error
/// in a messasge box then.
Error { msg: String },
Error {
msg: String,
},
/// An action cannot be performed because the user is not in the group.
/// Reported eg. after a call to
/// setChatName(), setChatProfileImage(),
/// addContactToChat(), removeContactFromChat(),
/// and messages sending functions.
ErrorSelfNotInGroup { msg: String },
ErrorSelfNotInGroup {
msg: String,
},
/// Messages or chats changed. One or more messages or chats changed for various
/// reasons in the database:
@@ -88,7 +110,10 @@ pub enum EventType {
/// `chatId` is set if only a single chat is affected by the changes, otherwise 0.
/// `msgId` is set if only a single message is affected by the changes, otherwise 0.
#[serde(rename_all = "camelCase")]
MsgsChanged { chat_id: u32, msg_id: u32 },
MsgsChanged {
chat_id: u32,
msg_id: u32,
},
/// Reactions for the message changed.
#[serde(rename_all = "camelCase")]
@@ -103,7 +128,10 @@ pub enum EventType {
///
/// There is no extra #DC_EVENT_MSGS_CHANGED event send together with this event.
#[serde(rename_all = "camelCase")]
IncomingMsg { chat_id: u32, msg_id: u32 },
IncomingMsg {
chat_id: u32,
msg_id: u32,
},
/// Downloading a bunch of messages just finished. This is an experimental
/// event to allow the UI to only show one notification per message bunch,
@@ -111,31 +139,47 @@ pub enum EventType {
///
/// msg_ids contains the message ids.
#[serde(rename_all = "camelCase")]
IncomingMsgBunch { msg_ids: Vec<u32> },
IncomingMsgBunch {
msg_ids: Vec<u32>,
},
/// Messages were seen or noticed.
/// chat id is always set.
#[serde(rename_all = "camelCase")]
MsgsNoticed { chat_id: u32 },
MsgsNoticed {
chat_id: u32,
},
/// A single message is sent successfully. State changed from DC_STATE_OUT_PENDING to
/// DC_STATE_OUT_DELIVERED, see `Message.state`.
#[serde(rename_all = "camelCase")]
MsgDelivered { chat_id: u32, msg_id: u32 },
MsgDelivered {
chat_id: u32,
msg_id: u32,
},
/// A single message could not be sent. State changed from DC_STATE_OUT_PENDING or DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_FAILED, see `Message.state`.
#[serde(rename_all = "camelCase")]
MsgFailed { chat_id: u32, msg_id: u32 },
MsgFailed {
chat_id: u32,
msg_id: u32,
},
/// A single message is read by the receiver. State changed from DC_STATE_OUT_DELIVERED to
/// DC_STATE_OUT_MDN_RCVD, see `Message.state`.
#[serde(rename_all = "camelCase")]
MsgRead { chat_id: u32, msg_id: u32 },
MsgRead {
chat_id: u32,
msg_id: u32,
},
/// A single message is deleted.
#[serde(rename_all = "camelCase")]
MsgDeleted { chat_id: u32, msg_id: u32 },
MsgDeleted {
chat_id: u32,
msg_id: u32,
},
/// Chat changed. The name or the image of a chat group was changed or members were added or removed.
/// Or the verify state of a chat has changed.
@@ -145,17 +189,24 @@ pub enum EventType {
/// This event does not include ephemeral timer modification, which
/// is a separate event.
#[serde(rename_all = "camelCase")]
ChatModified { chat_id: u32 },
ChatModified {
chat_id: u32,
},
/// Chat ephemeral timer changed.
#[serde(rename_all = "camelCase")]
ChatEphemeralTimerModified { chat_id: u32, timer: u32 },
ChatEphemeralTimerModified {
chat_id: u32,
timer: u32,
},
/// Contact(s) created, renamed, blocked or deleted.
///
/// @param data1 (int) If set, this is the contact_id of an added contact that should be selected.
#[serde(rename_all = "camelCase")]
ContactsChanged { contact_id: Option<u32> },
ContactsChanged {
contact_id: Option<u32>,
},
/// Location of one or more contact has changed.
///
@@ -163,7 +214,9 @@ pub enum EventType {
/// If the locations of several contacts have been changed,
/// this parameter is set to `None`.
#[serde(rename_all = "camelCase")]
LocationChanged { contact_id: Option<u32> },
LocationChanged {
contact_id: Option<u32>,
},
/// Inform about the configuration progress started by configure().
ConfigureProgress {
@@ -181,7 +234,9 @@ pub enum EventType {
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
#[serde(rename_all = "camelCase")]
ImexProgress { progress: usize },
ImexProgress {
progress: usize,
},
/// A file has been exported. A file has been written by imex().
/// This event may be sent multiple times by a single call to imex().
@@ -191,7 +246,9 @@ pub enum EventType {
///
/// @param data2 0
#[serde(rename_all = "camelCase")]
ImexFileWritten { path: String },
ImexFileWritten {
path: String,
},
/// Progress information of a secure-join handshake from the view of the inviter
/// (Alice, the person who shows the QR code).
@@ -206,7 +263,10 @@ pub enum EventType {
/// 800=vg-member-added-received received, shown as "bob@addr securely joined GROUP", only sent for the verified-group-protocol.
/// 1000=Protocol finished for this contact.
#[serde(rename_all = "camelCase")]
SecurejoinInviterProgress { contact_id: u32, progress: usize },
SecurejoinInviterProgress {
contact_id: u32,
progress: usize,
},
/// Progress information of a secure-join handshake from the view of the joiner
/// (Bob, the person who scans the QR code).
@@ -217,7 +277,10 @@ pub enum EventType {
/// 400=vg-/vc-request-with-auth sent, typically shown as "alice@addr verified, introducing myself."
/// (Bob has verified alice and waits until Alice does the same for him)
#[serde(rename_all = "camelCase")]
SecurejoinJoinerProgress { contact_id: u32, progress: usize },
SecurejoinJoinerProgress {
contact_id: u32,
progress: usize,
},
/// The connectivity to the server changed.
/// This means that you should refresh the connectivity view
@@ -225,17 +288,8 @@ pub enum EventType {
/// getConnectivityHtml() for details.
ConnectivityChanged,
/// Deprecated by `ConfigSynced`.
SelfavatarChanged,
/// A multi-device synced config value changed. Maybe the app needs to refresh smth. For
/// uniformity this is emitted on the source device too. The value isn't here, otherwise it
/// would be logged which might not be good for privacy.
ConfigSynced {
/// Configuration key.
key: String,
},
#[serde(rename_all = "camelCase")]
WebxdcStatusUpdate {
msg_id: u32,
@@ -244,7 +298,21 @@ pub enum EventType {
/// Inform that a message containing a webxdc instance has been deleted
#[serde(rename_all = "camelCase")]
WebxdcInstanceDeleted { msg_id: u32 },
WebxdcInstanceDeleted {
msg_id: u32,
},
/// Inform UI that Order (and content as in chat ids) of the chatlist changed.
///
/// Sometimes this is emitted together with `UIChatListItemChanged` such as on IncomingMessage.
UIChatListChanged,
/// Inform UI that a single chat list item changed and needs to be rerendered
/// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
#[serde(rename_all = "camelCase")]
UIChatListItemChanged {
chat_id: Option<u32>,
},
}
impl From<CoreEventType> for EventType {
@@ -340,9 +408,6 @@ impl From<CoreEventType> for EventType {
},
CoreEventType::ConnectivityChanged => ConnectivityChanged,
CoreEventType::SelfavatarChanged => SelfavatarChanged,
CoreEventType::ConfigSynced { key } => ConfigSynced {
key: key.to_string(),
},
CoreEventType::WebxdcStatusUpdate {
msg_id,
status_update_serial,
@@ -353,6 +418,10 @@ impl From<CoreEventType> for EventType {
CoreEventType::WebxdcInstanceDeleted { msg_id } => WebxdcInstanceDeleted {
msg_id: msg_id.to_u32(),
},
CoreEventType::UIChatListItemChanged { chat_id } => UIChatListItemChanged {
chat_id: chat_id.map(|id| id.to_u32()),
},
CoreEventType::UIChatListChanged => UIChatListChanged,
}
}
}

View File

@@ -53,5 +53,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.133.0"
"version": "1.132.1"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.133.0"
version = "1.132.1"
license = "MPL-2.0"
edition = "2021"
@@ -12,7 +12,7 @@ dirs = "5"
log = "0.4.20"
pretty_env_logger = "0.5"
rusqlite = "0.30"
rustyline = "13"
rustyline = "12"
tokio = { version = "1", features = ["fs", "rt-multi-thread", "macros"] }
[features]

View File

@@ -299,8 +299,8 @@ impl Highlighter for DcHelper {
self.highlighter.highlight(line, pos)
}
fn highlight_char(&self, line: &str, pos: usize, forced: bool) -> bool {
self.highlighter.highlight_char(line, pos, forced)
fn highlight_char(&self, line: &str, pos: usize) -> bool {
self.highlighter.highlight_char(line, pos)
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.133.0"
version = "1.132.1"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -33,6 +33,7 @@ skip = [
{ name = "ed25519-dalek", version = "1.0.1" },
{ name = "ed25519", version = "1.5.3" },
{ name = "event-listener", version = "2.5.3" },
{ name = "fd-lock", version = "3.0.13" },
{ name = "getrandom", version = "<0.2" },
{ name = "h2", version = "0.3.22" },
{ name = "http-body", version = "0.4.5" },

View File

@@ -32,7 +32,6 @@ module.exports = {
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: 2021,
DC_EVENT_CHAT_MODIFIED: 2020,
DC_EVENT_CONFIGURE_PROGRESS: 2041,
DC_EVENT_CONFIG_SYNCED: 2111,
DC_EVENT_CONNECTIVITY_CHANGED: 2100,
DC_EVENT_CONTACTS_CHANGED: 2030,
DC_EVENT_DELETED_BLOB_FILE: 151,
@@ -61,6 +60,8 @@ module.exports = {
DC_EVENT_SELFAVATAR_CHANGED: 2110,
DC_EVENT_SMTP_CONNECTED: 101,
DC_EVENT_SMTP_MESSAGE_SENT: 103,
DC_EVENT_UI_CHATLIST_CHANGED: 2200,
DC_EVENT_UI_CHATLIST_ITEM_CHANGED: 2201,
DC_EVENT_WARNING: 300,
DC_EVENT_WEBXDC_INSTANCE_DELETED: 2121,
DC_EVENT_WEBXDC_STATUS_UPDATE: 2120,

View File

@@ -34,7 +34,8 @@ module.exports = {
2061: 'DC_EVENT_SECUREJOIN_JOINER_PROGRESS',
2100: 'DC_EVENT_CONNECTIVITY_CHANGED',
2110: 'DC_EVENT_SELFAVATAR_CHANGED',
2111: 'DC_EVENT_CONFIG_SYNCED',
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED'
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
2200: 'DC_EVENT_UI_CHATLIST_CHANGED',
2201: 'DC_EVENT_UI_CHATLIST_ITEM_CHANGED'
}

View File

@@ -32,7 +32,6 @@ export enum C {
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED = 2021,
DC_EVENT_CHAT_MODIFIED = 2020,
DC_EVENT_CONFIGURE_PROGRESS = 2041,
DC_EVENT_CONFIG_SYNCED = 2111,
DC_EVENT_CONNECTIVITY_CHANGED = 2100,
DC_EVENT_CONTACTS_CHANGED = 2030,
DC_EVENT_DELETED_BLOB_FILE = 151,
@@ -61,6 +60,8 @@ export enum C {
DC_EVENT_SELFAVATAR_CHANGED = 2110,
DC_EVENT_SMTP_CONNECTED = 101,
DC_EVENT_SMTP_MESSAGE_SENT = 103,
DC_EVENT_UI_CHATLIST_CHANGED = 2200,
DC_EVENT_UI_CHATLIST_ITEM_CHANGED = 2201,
DC_EVENT_WARNING = 300,
DC_EVENT_WEBXDC_INSTANCE_DELETED = 2121,
DC_EVENT_WEBXDC_STATUS_UPDATE = 2120,
@@ -320,7 +321,8 @@ export const EventId2EventName: { [key: number]: string } = {
2061: 'DC_EVENT_SECUREJOIN_JOINER_PROGRESS',
2100: 'DC_EVENT_CONNECTIVITY_CHANGED',
2110: 'DC_EVENT_SELFAVATAR_CHANGED',
2111: 'DC_EVENT_CONFIG_SYNCED',
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
2200: 'DC_EVENT_UI_CHATLIST_CHANGED',
2201: 'DC_EVENT_UI_CHATLIST_ITEM_CHANGED',
}

View File

@@ -56,5 +56,5 @@
"test:mocha": "mocha -r esm node/test/test.js --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.133.0"
"version": "1.132.1"
}

View File

@@ -1 +1 @@
2024-01-14
2023-12-12

View File

@@ -102,7 +102,7 @@ def main():
found = True
if not found:
raise SystemExit(
f"CHANGELOG.md contains no entry for version: {newversion}"
f"{changelog_name} contains no entry for version: {newversion}"
)
for toml_filename in toml_list:

View File

@@ -46,6 +46,7 @@ use crate::tools::{
create_smeared_timestamps, get_abs_path, gm2local_offset, improve_single_line_input,
smeared_time, strip_rtlo_characters, time, IsNoneOrEmpty,
};
use crate::ui_events;
use crate::webxdc::WEBXDC_SUFFIX;
/// An chat item, such as a message or a marker.
@@ -309,6 +310,8 @@ impl ChatId {
}
};
context.emit_msgs_changed_without_ids();
ui_events::emit_chatlist_changed(context);
ui_events::emit_chatlist_item_changed(context, chat_id);
Ok(chat_id)
}
@@ -425,6 +428,7 @@ impl ChatId {
}
}
}
ui_events::emit_chatlist_changed(context);
if sync.into() {
// NB: For a 1:1 chat this currently triggers `Contact::block()` on other devices.
@@ -447,6 +451,8 @@ impl ChatId {
pub(crate) async fn unblock_ex(self, context: &Context, sync: sync::Sync) -> Result<()> {
self.set_blocked(context, Blocked::Not).await?;
ui_events::emit_chatlist_changed(context);
if sync.into() {
let chat = Chat::load_from_db(context, self).await?;
// TODO: For a 1:1 chat this currently triggers `Contact::unblock()` on other devices.
@@ -457,6 +463,7 @@ impl ChatId {
.log_err(context)
.ok();
}
Ok(())
}
@@ -500,6 +507,7 @@ impl ChatId {
if self.set_blocked(context, Blocked::Not).await? {
context.emit_event(EventType::ChatModified(self));
ui_events::emit_chatlist_item_changed(context, self);
}
if sync.into() {
@@ -542,6 +550,7 @@ impl ChatId {
.await?;
context.emit_event(EventType::ChatModified(self));
ui_events::emit_chatlist_item_changed(context, self);
// make sure, the receivers will get all keys
self.reset_gossiped_timestamp(context).await?;
@@ -590,6 +599,7 @@ impl ChatId {
if protection_status_modified {
self.add_protection_msg(context, protect, contact_id, timestamp_sort)
.await?;
ui_events::emit_chatlist_item_changed(context, self);
}
Ok(())
}
@@ -676,6 +686,8 @@ impl ChatId {
.await?;
context.emit_msgs_changed_without_ids();
ui_events::emit_chatlist_changed(context);
ui_events::emit_chatlist_item_changed(context, self);
if sync.into() {
let chat = Chat::load_from_db(context, self).await?;
@@ -782,6 +794,7 @@ impl ChatId {
.await?;
context.emit_msgs_changed_without_ids();
ui_events::emit_chatlist_changed(context);
context.set_config(Config::LastHousekeeping, None).await?;
context.scheduler.interrupt_inbox().await;
@@ -791,6 +804,7 @@ impl ChatId {
msg.text = stock_str::self_deleted_msg_body(context).await;
add_device_msg(context, None, Some(&mut msg)).await?;
}
ui_events::emit_chatlist_changed(context);
Ok(())
}
@@ -3080,7 +3094,9 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
.await?;
for chat_id_in_archive in chat_ids_in_archive {
context.emit_event(EventType::MsgsNoticed(chat_id_in_archive));
ui_events::emit_chatlist_item_changed(context, chat_id_in_archive);
}
ui_events::emit_chatlist_item_changed(context, DC_CHAT_ID_ARCHIVED_LINK);
} else {
let exists = context
.sql
@@ -3107,6 +3123,7 @@ pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<()>
}
context.emit_event(EventType::MsgsNoticed(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
Ok(())
}
@@ -3174,6 +3191,7 @@ pub(crate) async fn mark_old_messages_as_noticed(
for c in changed_chats {
context.emit_event(EventType::MsgsNoticed(c));
ui_events::emit_chatlist_item_changed(context, c);
}
Ok(())
@@ -3336,6 +3354,8 @@ pub async fn create_group_chat(
}
context.emit_msgs_changed_without_ids();
ui_events::emit_chatlist_changed(context);
ui_events::emit_chatlist_item_changed(context, chat_id);
if protect == ProtectionStatus::Protected {
chat_id
@@ -3423,11 +3443,14 @@ pub(crate) async fn create_broadcast_list_ex(
let chat_id = ChatId::new(u32::try_from(row_id)?);
context.emit_msgs_changed_without_ids();
ui_events::emit_chatlist_changed(context);
if sync.into() {
let id = SyncId::Grpid(grpid);
let action = SyncAction::CreateBroadcast(chat_name);
self::sync(context, id, action).await.log_err(context).ok();
}
Ok(chat_id)
}
@@ -3698,6 +3721,7 @@ pub(crate) async fn set_muted_ex(
.await
.context(format!("Failed to set mute duration for {chat_id}"))?;
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
if sync.into() {
let chat = Chat::load_from_db(context, chat_id).await?;
chat.sync(context, SyncAction::SetMuted(duration))
@@ -3858,6 +3882,7 @@ async fn rename_ex(
sync = Nosync;
}
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
success = true;
}
}
@@ -3918,6 +3943,7 @@ pub async fn set_chat_profile_image(
context.emit_msgs_changed(chat_id, msg.id);
}
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
Ok(())
}
@@ -4063,6 +4089,8 @@ pub async fn resend_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
chat_id: msg.chat_id,
msg_id: msg.id,
});
// note(treefit): only matters if it is the last message in chat (but probably to expensive to check, debounce also solves it)
ui_events::emit_chatlist_item_changed(context, msg.chat_id);
if !create_send_msg_jobs(context, &mut msg).await?.is_empty() {
context.scheduler.interrupt_smtp().await;
}

View File

@@ -564,7 +564,6 @@ impl Context {
if sync != Sync {
return Ok(());
}
self.emit_event(EventType::ConfigSynced { key });
let Some(val) = value else {
return Ok(());
};
@@ -867,43 +866,10 @@ mod tests {
// Reset to default. Test that it's not synced because defaults may differ across client
// versions.
alice0.set_config(Config::MdnsEnabled, None).await?;
assert_eq!(alice0.get_config_bool(Config::MdnsEnabled).await?, true);
alice0
.evtracker
.get_matching(|e| {
matches!(
e,
EventType::ConfigSynced {
key: Config::MdnsEnabled
}
)
})
.await;
assert!(alice0.get_config_bool(Config::MdnsEnabled).await?);
alice0.set_config_bool(Config::MdnsEnabled, false).await?;
alice0
.evtracker
.get_matching(|e| {
matches!(
e,
EventType::ConfigSynced {
key: Config::MdnsEnabled
}
)
})
.await;
sync(&alice0, &alice1).await;
assert_eq!(alice1.get_config_bool(Config::MdnsEnabled).await?, false);
alice1
.evtracker
.get_matching(|e| {
matches!(
e,
EventType::ConfigSynced {
key: Config::MdnsEnabled
}
)
})
.await;
assert!(!alice1.get_config_bool(Config::MdnsEnabled).await?);
let show_emails = alice0.get_config_bool(Config::ShowEmails).await?;
alice0

View File

@@ -137,11 +137,20 @@ impl ServerParams {
}
fn expand_strict_tls(self) -> Vec<ServerParams> {
vec![Self {
// Strict if not set by the user or provider database.
strict_tls: Some(self.strict_tls.unwrap_or(true)),
..self
}]
if self.strict_tls.is_none() {
vec![
Self {
strict_tls: Some(true), // Strict.
..self.clone()
},
Self {
strict_tls: None, // Automatic.
..self
},
]
} else {
vec![self]
}
}
}
@@ -153,10 +162,31 @@ pub(crate) fn expand_param_vector(
domain: &str,
) -> Vec<ServerParams> {
v.into_iter()
.map(|params| {
if params.socket == Socket::Plain {
ServerParams {
// Avoid expanding plaintext configuration into configuration with and without
// `strict_tls` if `strict_tls` is set to `None` as `strict_tls` is not used for
// plaintext connections. Always setting it to "enabled", just in case.
strict_tls: Some(true),
..params
}
} else {
params
}
})
// The order of expansion is important.
//
// Ports are expanded the last, so they are changed the first. Username is only changed if
// default value (address with domain) didn't work for all available hosts and ports.
//
// Strict TLS must be expanded first, so we try all configurations with strict TLS first
// and only then try again without strict TLS. Otherwise we may lock to wrong hostname
// without strict TLS when another hostname with strict TLS is available. For example, if
// both smtp.example.net and mail.example.net are running an SMTP server, but both use a
// certificate that is only valid for mail.example.net, we want to skip smtp.example.net
// and use mail.example.net with strict TLS instead of using smtp.example.net without
// strict TLS.
.flat_map(|params| params.expand_strict_tls().into_iter())
.flat_map(|params| params.expand_usernames(addr).into_iter())
.flat_map(|params| params.expand_hostnames(domain).into_iter())
@@ -227,6 +257,22 @@ mod tests {
username: "foobar".to_string(),
strict_tls: Some(true)
},
ServerParams {
protocol: Protocol::Smtp,
hostname: "example.net".to_string(),
port: 123,
socket: Socket::Ssl,
username: "foobar".to_string(),
strict_tls: None,
},
ServerParams {
protocol: Protocol::Smtp,
hostname: "example.net".to_string(),
port: 123,
socket: Socket::Starttls,
username: "foobar".to_string(),
strict_tls: None
}
],
);
@@ -238,7 +284,7 @@ mod tests {
port: 123,
socket: Socket::Plain,
username: "foobar".to_string(),
strict_tls: Some(true),
strict_tls: None,
}],
"foobar@example.net",
"example.net",

View File

@@ -5,6 +5,7 @@
use deltachat_derive::{FromSql, ToSql};
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use tokio::time::Duration;
use crate::chat::ChatId;
@@ -214,6 +215,11 @@ pub(crate) const DC_FOLDERS_CONFIGURED_VERSION: i32 = 4;
// `max_smtp_rcpt_to` in the provider db.
pub(crate) const DEFAULT_MAX_SMTP_RCPT_TO: usize = 50;
/// How often UI events should be sent out / How much they should be debounced.
/// Defines the tick rate/delay of the debounce loop for UI events in milliseconds.
pub(crate) const UI_EVENTS_TICK_RATE: Duration = Duration::from_millis(50); // 50ms which means 20 fps
#[cfg(test)]
mod tests {
use num_traits::FromPrimitive;

View File

@@ -38,7 +38,7 @@ use crate::tools::{
duration_to_str, get_abs_path, improve_single_line_input, strip_rtlo_characters, time,
EmailAddress,
};
use crate::{chat, stock_str};
use crate::{chat, stock_str, ui_events};
/// Time during which a contact is considered as seen recently.
const SEEN_RECENTLY_SECONDS: i64 = 600;
@@ -761,6 +761,7 @@ impl Contact {
if count > 0 {
// Chat name updated
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_items_changed_for_contact(context, contact_id);
}
}
}
@@ -797,7 +798,31 @@ impl Contact {
Ok(row_id)
}).await?;
Ok((ContactId::new(row_id), sth_modified))
let contact_id = ContactId::new(row_id);
Ok((contact_id, sth_modified))
}
/// Get all chats the contact is part of
pub async fn get_chats_with_contact(
context: &Context,
contact_id: &ContactId,
) -> Result<Vec<ChatId>> {
context
.sql
.query_map(
"SELECT chat_id FROM chats_contacts WHERE contact_id=?",
(contact_id,),
|row| {
let chat_id: ChatId = row.get(0)?;
Ok(chat_id)
},
|rows| {
rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into)
},
)
.await
}
/// Add a number of contacts.
@@ -1528,6 +1553,7 @@ WHERE type=? AND id IN (
}
}
ui_events::emit_chatlist_changed(context);
Ok(())
}
@@ -1576,6 +1602,7 @@ pub(crate) async fn set_profile_image(
if changed {
contact.update_param(context).await?;
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
ui_events::emit_chatlist_item_changed_for_contacts_dm_chat(context, contact_id);
}
Ok(())
}
@@ -1782,6 +1809,10 @@ impl RecentlySeenLoop {
// Timeout, notify about contact.
if let Some(contact_id) = contact_id {
context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
ui_events::emit_chatlist_item_changed_for_contacts_dm_chat(
&context,
*contact_id,
);
unseen_queue.pop();
}
}
@@ -1811,6 +1842,10 @@ impl RecentlySeenLoop {
// Event is already in the past.
if let Some(contact_id) = contact_id {
context.emit_event(EventType::ContactsChanged(Some(*contact_id)));
ui_events::emit_chatlist_item_changed_for_contacts_dm_chat(
&context,
*contact_id,
);
}
unseen_queue.pop();
}

View File

@@ -1,5 +1,6 @@
//! Context module.
use std::borrow::BorrowMut;
use std::collections::{BTreeMap, HashMap};
use std::ffi::OsString;
use std::ops::Deref;
@@ -28,6 +29,7 @@ use crate::sql::Sql;
use crate::stock_str::StockStrings;
use crate::timesmearing::SmearedTimestamp;
use crate::tools::{duration_to_str, time};
use crate::ui_events::{self, UIEvents};
/// Builder for the [`Context`].
///
@@ -203,6 +205,7 @@ pub struct InnerContext {
pub(crate) wrong_pw_warning_mutex: Mutex<()>,
pub(crate) translated_stockstrings: StockStrings,
pub(crate) events: Events,
pub(crate) ui_events: Mutex<UIEvents>,
pub(crate) scheduler: SchedulerState,
pub(crate) ratelimit: RwLock<Ratelimit>,
@@ -367,6 +370,8 @@ impl Context {
// without starting I/O.
new_msgs_notify.notify_one();
let (ui_events, ui_events_receiver) = UIEvents::new();
let inner = InnerContext {
id,
blobdir,
@@ -378,6 +383,7 @@ impl Context {
wrong_pw_warning_mutex: Mutex::new(()),
translated_stockstrings: stockstrings,
events,
ui_events: Mutex::new(ui_events),
scheduler: SchedulerState::new(),
ratelimit: RwLock::new(Ratelimit::new(Duration::new(60, 0), 6.0)), // Allow at least 1 message every 10 seconds + a burst of 6.
quota: RwLock::new(None),
@@ -394,6 +400,8 @@ impl Context {
inner: Arc::new(inner),
};
ctx.inner.ui_events.blocking_lock().start(&ctx, ui_events_receiver);
Ok(ctx)
}
@@ -485,11 +493,15 @@ impl Context {
/// Emits a MsgsChanged event with specified chat and message ids
pub fn emit_msgs_changed(&self, chat_id: ChatId, msg_id: MsgId) {
self.emit_event(EventType::MsgsChanged { chat_id, msg_id });
ui_events::emit_chatlist_changed(self);
ui_events::emit_chatlist_item_changed(self, chat_id);
}
/// Emits an IncomingMsg event with specified chat and message ids
pub fn emit_incoming_msg(&self, chat_id: ChatId, msg_id: MsgId) {
self.emit_event(EventType::IncomingMsg { chat_id, msg_id });
ui_events::emit_chatlist_changed(self);
ui_events::emit_chatlist_item_changed(self, chat_id);
}
/// Returns a receiver for emitted events.

View File

@@ -13,7 +13,7 @@ use crate::imap::{Imap, ImapActionResult};
use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::{MimeMessage, Part};
use crate::tools::time;
use crate::{stock_str, EventType};
use crate::{stock_str, ui_events, EventType};
/// Download limits should not be used below `MIN_DOWNLOAD_LIMIT`.
///
@@ -115,6 +115,7 @@ impl MsgId {
chat_id: msg.chat_id,
msg_id: self,
});
ui_events::emit_chatlist_item_changed(context, msg.chat_id);
Ok(())
}
}

View File

@@ -4,6 +4,7 @@ use async_channel::{self as channel, Receiver, Sender, TrySendError};
use pin_project::pin_project;
mod payload;
pub(crate) mod ui_events;
pub use self::payload::EventType;

View File

@@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::chat::ChatId;
use crate::config::Config;
use crate::contact::ContactId;
use crate::ephemeral::Timer as EphemeralTimer;
use crate::message::MsgId;
@@ -262,17 +261,8 @@ pub enum EventType {
ConnectivityChanged,
/// The user's avatar changed.
/// Deprecated by `ConfigSynced`.
SelfavatarChanged,
/// A multi-device synced config value changed. Maybe the app needs to refresh smth. For
/// uniformity this is emitted on the source device too. The value isn't here, otherwise it
/// would be logged which might not be good for privacy.
ConfigSynced {
/// Configuration key.
key: Config,
},
/// Webxdc status update received.
WebxdcStatusUpdate {
/// Message ID.
@@ -287,4 +277,16 @@ pub enum EventType {
/// ID of the deleted message.
msg_id: MsgId,
},
/// Inform UI that Order (and content as in chat ids) of the chatlist changed.
///
/// Sometimes this is emitted together with `UIChatListItemChanged` such as on IncomingMessage.
UIChatListChanged,
/// Inform UI that a single chat list item changed and needs to be rerendered
/// If `chat_id` is set to None, then all currently visible chats need to be rerendered, and all not-visible items need to be cleared from cache if the UI has a cache.
UIChatListItemChanged {
/// ID of the changed chat
chat_id: Option<ChatId>,
},
}

253
src/events/ui_events.rs Normal file
View File

@@ -0,0 +1,253 @@
use crate::{
chat::{ChatId, ChatIdBlocked},
constants::UI_EVENTS_TICK_RATE,
contact::{Contact, ContactId},
context::Context,
EventType,
};
use async_channel::{self as channel, Receiver, Sender};
use tokio::{
task,
time::{sleep_until, Instant},
};
/// order or content of chatlist changes (chat ids, not the actual chatlist item)
pub(crate) fn emit_chatlist_changed(context: &Context) {
context
.ui_events
.blocking_lock()
.send_chat_list_event(context, InternalUIEvent::ChatListChanged)
}
/// Chatlist item of a specific chat changed
pub(crate) fn emit_chatlist_item_changed(context: &Context, chat_id: ChatId) {
context
.ui_events
.blocking_lock()
.send_chat_list_event(context, InternalUIEvent::ChatListItemChanged(chat_id))
}
#[allow(unused)]
/// Used when you don't know which chatlist items changed, this reloads all cached chatlist items in the UI
/// note(treefit): This is not used right now, but I know there will be a point where someone wants it
pub(crate) fn emit_unknown_chatlist_items_changed(context: &Context) {
context
.ui_events
.blocking_lock()
.send_chat_list_event(context, InternalUIEvent::UnknownChatListItemsChanged)
}
/// update event for dm chat of contact
/// used when recently seen changes and when profile image changes
pub(crate) fn emit_chatlist_item_changed_for_contacts_dm_chat(
context: &Context,
contact_id: ContactId,
) {
context
.ui_events
.blocking_lock()
.send_chat_list_event(context, InternalUIEvent::ContactDMChatChanged(contact_id))
}
/// update dm for chats that have the contact
/// used when contact changes their name or did AEAP for example
pub(crate) fn emit_chatlist_items_changed_for_contact(context: &Context, contact_id: ContactId) {
context
.ui_events
.blocking_lock()
.send_chat_list_event(context, InternalUIEvent::ContactChatsChanged(contact_id));
}
#[derive(Debug)]
pub(crate) enum InternalUIEvent {
ChatListChanged,
ChatListItemChanged(ChatId),
UnknownChatListItemsChanged,
ContactDMChatChanged(ContactId),
ContactChatsChanged(ContactId),
}
struct EventLoopTickState {
chat_list_changed: bool,
has_unknown_items: bool,
chat_ids: Vec<ChatId>,
contact_ids_dm: Vec<ContactId>,
contact_ids_chats: Vec<ContactId>,
}
impl EventLoopTickState {
fn new(capacity: usize) -> Self {
Self {
chat_list_changed: false,
has_unknown_items: false,
chat_ids: Vec::with_capacity(capacity),
contact_ids_dm: Vec::with_capacity(capacity),
contact_ids_chats: Vec::with_capacity(capacity),
}
}
fn apply_internal_ui_event(&mut self, event: InternalUIEvent) {
match event {
InternalUIEvent::ChatListChanged => {
self.chat_list_changed = true;
}
InternalUIEvent::ChatListItemChanged(chat_id) => {
self.chat_ids.push(chat_id);
}
InternalUIEvent::UnknownChatListItemsChanged => {
self.has_unknown_items = true;
}
InternalUIEvent::ContactDMChatChanged(contact_id) => {
self.contact_ids_dm.push(contact_id);
}
InternalUIEvent::ContactChatsChanged(contact_id) => {
self.contact_ids_chats.push(contact_id);
}
}
}
async fn emit_chatlist_ui_events(&mut self, context: &Context) {
if self.chat_list_changed {
context.emit_event(EventType::UIChatListChanged);
}
if self.has_unknown_items {
context.emit_event(EventType::UIChatListItemChanged { chat_id: None });
return; // since this refreshes everything no further events are needed
}
for contact_id in self
.contact_ids_dm
.iter()
.filter(|contact| !self.contact_ids_chats.contains(contact))
.collect::<Vec<&ContactId>>()
{
if let Ok(Some(chat_id)) = ChatIdBlocked::lookup_by_contact(context, *contact_id).await
{
self.chat_ids.push(chat_id.id)
}
}
// note:(treefit): could make sense to only update chats where the last message is from the contact, but the db query for that is more expensive
for contact_id in &self.contact_ids_chats {
match Contact::get_chats_with_contact(context, contact_id).await {
Ok(contacts_chat_ids) => {
self.chat_ids.extend(contacts_chat_ids);
}
Err(err) => {
warn!(
context,
"Error while getting chats for contact {} in chatlist events loop: {}",
contact_id,
err
);
}
}
}
self.chat_ids.sort();
self.chat_ids.dedup();
// TODO change event so it accepts a list of chat ids to get rid of this loop? wouldn't work with cffi unless we give it out as json
for chat_id in &self.chat_ids {
context.emit_event(EventType::UIChatListItemChanged {
chat_id: Some(*chat_id),
})
}
}
}
/// Debounces UI events
#[derive(Debug)]
pub(crate) struct UIEvents {
task_handle: Option<task::JoinHandle<()>>,
chatlist_event_queue: Sender<InternalUIEvent>,
}
impl UIEvents {
pub(crate) fn new() -> (Self, Receiver<InternalUIEvent>) {
let (chatlist_event_queue, chatlist_event_queue_recv) = channel::unbounded();
(
Self {
task_handle: None,
chatlist_event_queue,
},
chatlist_event_queue_recv,
)
}
pub(crate) fn start(
&mut self,
context: &Context,
chatlist_event_queue_recv: Receiver<InternalUIEvent>,
) {
if let Some(handle) = self.task_handle {
handle.abort()
}
self.task_handle = Some(task::spawn(Self::run_task(
context,
chatlist_event_queue_recv,
)))
}
async fn run_task(context: &Context, chatlist_event_queue: Receiver<InternalUIEvent>) {
loop {
match chatlist_event_queue.recv().await {
Ok(chatlist_event) => {
let backlog_len = chatlist_event_queue.len();
let mut tick_state = EventLoopTickState::new(backlog_len);
tick_state.apply_internal_ui_event(chatlist_event);
// get all events from the queue
while let Ok(event) = chatlist_event_queue.try_recv() {
tick_state.apply_internal_ui_event(event);
}
tick_state.emit_chatlist_ui_events(context).await;
// cooldown
sleep_until(Instant::now() + UI_EVENTS_TICK_RATE).await;
}
Err(err) => {
warn!(
context,
"Error receiving an interruption in ui chatlist events loop: {}", err
);
// Maybe the sender side is closed, so terminate the loop to avoid looping indefinitely.
return;
}
}
}
}
pub(crate) fn send_chat_list_event(&self, context: &Context, event: InternalUIEvent) {
// todo check if ui events are enabled?
if let Err(error) = self.chatlist_event_queue.try_send(event) {
warn!(
context,
"Error receiving an interruption in ui chatlist events loop: {}", error
);
}
}
}
impl Drop for UIEvents {
fn drop(&mut self) {
if let Some(handle) = &self.task_handle {
handle.abort()
}
}
}
#[cfg(test)]
mod test {
// todo tests:
// send ui events though the UIEventsLoop
// check that UIEventsLoop really ratelimits the events
// check that has_unknown_items does not send out any ids before or afterwards
// if we should make it possible to disable via config then test that as well
}

View File

@@ -42,6 +42,7 @@ use crate::socks::Socks5Config;
use crate::sql;
use crate::stock_str;
use crate::tools::{create_id, duration_to_str};
use crate::ui_events;
pub(crate) mod capabilities;
mod client;
@@ -1319,6 +1320,7 @@ impl Imap {
.with_context(|| format!("failed to set MODSEQ for folder {folder}"))?;
for updated_chat_id in updated_chat_ids {
context.emit_event(EventType::MsgsNoticed(updated_chat_id));
ui_events::emit_chatlist_item_changed(context, updated_chat_id);
}
Ok(())

View File

@@ -14,8 +14,8 @@ use crate::context::Context;
use crate::events::EventType;
use crate::message::{Message, MsgId, Viewtype};
use crate::mimeparser::SystemMessage;
use crate::stock_str;
use crate::tools::{duration_to_str, time};
use crate::{stock_str, ui_events};
/// Location record.
#[derive(Debug, Clone, Default)]
@@ -290,6 +290,7 @@ pub async fn send_locations_to_chat(
chat::add_info_msg(context, chat_id, &stock_str, now).await?;
}
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
if 0 != seconds {
context.scheduler.interrupt_location().await;
}
@@ -787,6 +788,7 @@ async fn maybe_send_locations(context: &Context) -> Result<Option<u64>> {
let stock_str = stock_str::msg_location_disabled(context).await;
chat::add_info_msg(context, chat_id, &stock_str, now).await?;
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
}
}

View File

@@ -14,22 +14,8 @@ use crate::socks::Socks5Config;
#[repr(u32)]
#[strum(serialize_all = "snake_case")]
pub enum CertificateChecks {
/// Same as AcceptInvalidCertificates if stored in the database
/// as `configured_{imap,smtp}_certificate_checks`.
///
/// Previous Delta Chat versions stored this in `configured_*`
/// if Automatic configuration
/// was selected, configuration with strict TLS checks failed
/// and configuration without strict TLS checks succeeded.
///
/// Currently Delta Chat stores only
/// `Strict` or `AcceptInvalidCertificates` variants
/// in `configured_*` settings.
///
/// `Automatic` in `{imap,smtp}_certificate_checks`
/// means that provider database setting should be taken.
/// If there is no provider database setting for certificate checks,
/// `Automatic` is the same as `Strict`.
/// Same as AcceptInvalidCertificates unless overridden by
/// `strict_tls` setting in provider database.
Automatic = 0,
Strict = 1,

View File

@@ -30,6 +30,7 @@ use crate::tools::{
buf_compress, buf_decompress, get_filebytes, get_filemeta, gm2local_offset, read_file, time,
timestamp_to_str, truncate,
};
use crate::ui_events;
/// Message ID, including reserved IDs.
///
@@ -138,6 +139,7 @@ WHERE id=?;
chat_id,
msg_id: self,
});
ui_events::emit_chatlist_item_changed(context, chat_id);
Ok(())
}
@@ -1518,9 +1520,12 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) -> Result<()> {
for modified_chat_id in modified_chat_ids {
context.emit_msgs_changed(modified_chat_id, MsgId::new(0));
ui_events::emit_chatlist_item_changed(context, modified_chat_id);
}
if !msg_ids.is_empty() {
context.emit_msgs_changed_without_ids();
ui_events::emit_chatlist_changed(context);
// Run housekeeping to delete unused blobs.
context.set_config(Config::LastHousekeeping, None).await?;
}
@@ -1653,6 +1658,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: Vec<MsgId>) -> Result<()>
for updated_chat_id in updated_chat_ids {
context.emit_event(EventType::MsgsNoticed(updated_chat_id));
ui_events::emit_chatlist_item_changed(context, updated_chat_id);
}
Ok(())
@@ -1713,6 +1719,7 @@ pub(crate) async fn set_msg_failed(
chat_id: msg.chat_id,
msg_id: msg.id,
});
ui_events::emit_chatlist_item_changed(context, msg.chat_id);
Ok(())
}

View File

@@ -351,7 +351,7 @@ impl<'a> MimeFactory<'a> {
.unwrap_or_default()
}
}
Loaded::Mdn { .. } => false,
Loaded::Mdn { .. } => true,
}
}
@@ -1571,7 +1571,6 @@ mod tests {
use crate::mimeparser::MimeMessage;
use crate::receive_imf::receive_imf;
use crate::test_utils::{get_chat_msg, TestContext, TestContextManager};
#[test]
fn test_render_email_address() {
let display_name = "ä space";
@@ -1827,37 +1826,6 @@ mod tests {
assert_eq!("Re: Hello, Bob", mf.subject_str(&t).await.unwrap());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_mdn_create_encrypted() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
bob.set_config_bool(Config::MdnsEnabled, true).await?;
let mut msg = Message::new(Viewtype::Text);
msg.param.set_int(Param::SkipAutocrypt, 1);
let chat_alice = alice.create_chat(&bob).await.id;
let sent = alice.send_msg(chat_alice, &mut msg).await;
let rcvd = bob.recv_msg(&sent).await;
message::markseen_msgs(&bob, vec![rcvd.id]).await?;
let mimefactory = MimeFactory::from_mdn(&bob, &rcvd, vec![]).await?;
let rendered_msg = mimefactory.render(&bob).await?;
assert!(!rendered_msg.is_encrypted);
let rcvd = tcm.send_recv(&alice, &bob, "Heyho").await;
message::markseen_msgs(&bob, vec![rcvd.id]).await?;
let mimefactory = MimeFactory::from_mdn(&bob, &rcvd, vec![]).await?;
let rendered_msg = mimefactory.render(&bob).await?;
// When encrypted, the MDN should be encrypted as well
assert!(rendered_msg.is_encrypted);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_subject_in_group() -> Result<()> {
async fn send_msg_get_subject(

View File

@@ -35,13 +35,12 @@ use crate::message::{
use crate::param::{Param, Params};
use crate::peerstate::Peerstate;
use crate::simplify::{simplify, SimplifiedText};
use crate::stock_str;
use crate::sync::SyncItems;
use crate::tools::{
create_smeared_timestamp, get_filemeta, parse_receive_headers, smeared_time,
strip_rtlo_characters, truncate_by_lines,
};
use crate::{location, tools};
use crate::{location, stock_str, tools, ui_events};
/// A parsed MIME message.
///
@@ -2119,6 +2118,8 @@ async fn handle_mdn(
{
update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await?;
context.emit_event(EventType::MsgRead { chat_id, msg_id });
// note(treefit): only matters if it is the last message in chat (but probably to expensive to check, debounce also solves it)
ui_events::emit_chatlist_item_changed(context, chat_id);
}
Ok(())
}

View File

@@ -15,7 +15,7 @@ use crate::key::{DcKey, Fingerprint, SignedPublicKey};
use crate::message::Message;
use crate::mimeparser::SystemMessage;
use crate::sql::Sql;
use crate::stock_str;
use crate::{stock_str, ui_events};
/// Type of the public key stored inside the peerstate.
#[derive(Debug)]
@@ -695,6 +695,9 @@ impl Peerstate {
.await?;
}
ui_events::emit_chatlist_changed(context);
// update the chats the contact is part of
ui_events::emit_chatlist_items_changed_for_contact(context, contact_id);
Ok(())
}

View File

@@ -23,7 +23,6 @@ use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::imap::{markseen_on_imap_table, GENERATED_PREFIX};
use crate::location;
use crate::log::LogExt;
use crate::message::{
self, rfc724_mid_exists, rfc724_mid_exists_and, Message, MessageState, MessengerMessage, MsgId,
@@ -40,6 +39,7 @@ use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::{buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters};
use crate::{contact, imap};
use crate::{location, ui_events};
/// This is the struct that is returned after receiving one email (aka MIME message).
///
@@ -1778,6 +1778,8 @@ async fn create_or_lookup_group(
chat::add_to_chat_contacts_table(context, new_chat_id, &members).await?;
context.emit_event(EventType::ChatModified(new_chat_id));
ui_events::emit_chatlist_changed(context);
ui_events::emit_chatlist_item_changed(context, new_chat_id);
}
if let Some(chat_id) = chat_id {
@@ -2069,6 +2071,7 @@ async fn apply_group_changes(
if send_event_chat_modified {
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
}
Ok((group_changes_msgs, better_msg))
}
@@ -2368,6 +2371,8 @@ async fn create_adhoc_group(
chat::add_to_chat_contacts_table(context, new_chat_id, member_ids).await?;
context.emit_event(EventType::ChatModified(new_chat_id));
ui_events::emit_chatlist_changed(context);
ui_events::emit_chatlist_item_changed(context, new_chat_id);
Ok(Some(new_chat_id))
}

View File

@@ -70,11 +70,8 @@ impl SchedulerState {
context.new_msgs_notify.notify_one();
let ctx = context.clone();
match Scheduler::start(&context).await {
Ok(scheduler) => {
*inner = InnerSchedulerState::Started(scheduler);
context.emit_event(EventType::ConnectivityChanged);
}
match Scheduler::start(context).await {
Ok(scheduler) => *inner = InnerSchedulerState::Started(scheduler),
Err(err) => error!(&ctx, "Failed to start IO: {:#}", err),
}
}
@@ -119,7 +116,6 @@ impl SchedulerState {
debug_logging.loop_handle.abort();
}
let prev_state = std::mem::replace(&mut *inner, new_state);
context.emit_event(EventType::ConnectivityChanged);
match prev_state {
InnerSchedulerState::Started(scheduler) => scheduler.stop(context).await,
InnerSchedulerState::Stopped | InnerSchedulerState::Paused { .. } => (),
@@ -776,7 +772,7 @@ async fn smtp_loop(
impl Scheduler {
/// Start the scheduler.
pub async fn start(ctx: &Context) -> Result<Self> {
pub async fn start(ctx: Context) -> Result<Self> {
let (smtp, smtp_handlers) = SmtpConnectionState::new();
let (smtp_start_send, smtp_start_recv) = oneshot::channel();
@@ -786,7 +782,7 @@ impl Scheduler {
let mut oboxes = Vec::new();
let mut start_recvs = Vec::new();
let (conn_state, inbox_handlers) = ImapConnectionState::new(ctx).await?;
let (conn_state, inbox_handlers) = ImapConnectionState::new(&ctx).await?;
let (inbox_start_send, inbox_start_recv) = oneshot::channel();
let handle = {
let ctx = ctx.clone();
@@ -807,7 +803,7 @@ impl Scheduler {
),
] {
if should_watch? {
let (conn_state, handlers) = ImapConnectionState::new(ctx).await?;
let (conn_state, handlers) = ImapConnectionState::new(&ctx).await?;
let (start_send, start_recv) = oneshot::channel();
let ctx = ctx.clone();
let handle = task::spawn(simple_imap_loop(ctx, start_send, handlers, meaning));

View File

@@ -25,6 +25,7 @@ use crate::stock_str;
use crate::sync::Sync::*;
use crate::token;
use crate::tools::time;
use crate::ui_events;
mod bob;
mod bobstate;
@@ -683,6 +684,7 @@ async fn secure_connection_established(
)
.await?;
context.emit_event(EventType::ChatModified(chat_id));
ui_events::emit_chatlist_item_changed(context, chat_id);
Ok(())
}

View File

@@ -618,7 +618,7 @@ async fn send_mdns(context: &Context, connection: &mut Smtp) -> Result<()> {
let more_mdns = send_mdn(context, connection).await?;
if !more_mdns {
// No more MDNs to send or one of them failed.
// No more MDNs to send.
return Ok(());
}
}
@@ -752,7 +752,7 @@ async fn send_mdn_msg_id(
}
}
/// Tries to send a single MDN. Returns true if more MDNs should be sent.
/// Tries to send a single MDN. Returns false if there are no MDNs to send.
async fn send_mdn(context: &Context, smtp: &mut Smtp) -> Result<bool> {
let mdns_enabled = context.get_config_bool(Config::MdnsEnabled).await?;
if !mdns_enabled {

View File

@@ -17,7 +17,7 @@ Seen status synchronization | IMAP CONDSTORE extension ([RFC 7162][])
Client/server identification | IMAP ID extension ([RFC 2971][])
Authorization | OAuth2 ([RFC 6749][])
End-to-end encryption | [Autocrypt Level 1][], OpenPGP ([RFC 4880][]), Security Multiparts for MIME ([RFC 1847][]) and [“Mixed Up” Encryption repairing](https://tools.ietf.org/id/draft-dkg-openpgp-pgpmime-message-mangling-00.html)
Detect/prevent active attacks | [securejoin][] protocols
Detect/prevent active attacks | [countermitm][] protocols
Compare public keys | [openpgp4fpr][] URI Scheme
Header encryption | [Protected Headers for Cryptographic E-mail](https://datatracker.ietf.org/doc/draft-autocrypt-lamps-protected-headers/)
Configuration assistance | [Autoconfigure](https://web.archive.org/web/20210402044801/https://developer.mozilla.org/en-US/docs/Mozilla/Thunderbird/Autoconfiguration) and [Autodiscover][]
@@ -29,7 +29,7 @@ Return receipts | Message Disposition Notification (MDN, [RFC 8
Locations | KML ([Open Geospatial Consortium](http://www.opengeospatial.org/standards/kml/), [Google Dev](https://developers.google.com/kml/))
[Autocrypt Level 1]: https://autocrypt.org/level1.html
[securejoin]: https://securejoin.readthedocs.io/en/latest/
[countermitm]: https://countermitm.readthedocs.io/en/latest/
[openpgp4fpr]: https://metacode.biz/openpgp/openpgp4fpr
[Autodiscover]: https://learn.microsoft.com/en-us/exchange/autodiscover-service-for-exchange-2013
[XEP-0392]: https://xmpp.org/extensions/xep-0392.html