Skip to content

api: add ChatListChanged and ChatListItemChanged events #4476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 51 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
ce1b1fb
feat: add `UIChatListChanged` and `UIChatListItemChanged` events
Simon-Laux Jun 15, 2023
7a54606
add missing docs
Simon-Laux Jun 15, 2023
c145177
emit exact chatid events in contact aeap and contact name change
Simon-Laux Jun 15, 2023
aab849c
fix formatting
Simon-Laux Jun 19, 2023
ae5ae66
Centralise the methods to send chatlist events
Simon-Laux Jan 11, 2024
883dc23
draft smart "debouncing" (agreating values until it is fetched)
Simon-Laux Mar 17, 2024
ecb73d0
Apply suggestions from code review
Simon-Laux Mar 20, 2024
305bfc6
remove debouncing draft
Simon-Laux Mar 20, 2024
d015ecc
remove the most expensive event (on contact name change)
Simon-Laux Mar 20, 2024
416e23d
don't use `block_on` in `emit_chatlist_item_changed_for_contact_chat`
Simon-Laux Mar 20, 2024
50382f9
cargo fmt
Simon-Laux Mar 20, 2024
13a87eb
carify docs of event
Simon-Laux Mar 20, 2024
c978f0f
remove the UI prefix from the events
Simon-Laux Mar 20, 2024
00cbe0f
Chatlist instead of ChatList
Simon-Laux Mar 25, 2024
ac27956
rm unused imports
Simon-Laux Mar 25, 2024
76f041f
rename events
Simon-Laux Mar 25, 2024
5ff8d3d
charify docs
Simon-Laux Mar 25, 2024
8d1fd16
first batch of tests
Simon-Laux Mar 29, 2024
b6fb9ee
add two additional tests:
Simon-Laux Mar 30, 2024
35fe988
more new tests:
Simon-Laux Mar 31, 2024
ba1e710
add more tests:
Simon-Laux Mar 31, 2024
fc6686c
start with python tests via jsonrpc-python client
Simon-Laux Apr 1, 2024
f0aa143
update autogenerated node constants
Simon-Laux Apr 1, 2024
f9339fc
add test_download_on_demand test
Simon-Laux Apr 1, 2024
1321930
add 2 more tests:
Simon-Laux Apr 2, 2024
2addbca
cargo fmt
Simon-Laux Apr 2, 2024
28a924f
update what I forgot to commit to python test
Simon-Laux Apr 2, 2024
89515fe
run python black formatter
Simon-Laux Apr 2, 2024
33793bf
fix formatting
Simon-Laux Apr 2, 2024
81ad925
more python ruff fixes
Simon-Laux Apr 2, 2024
be08095
more fixes
Simon-Laux Apr 2, 2024
cfa6387
try fix lint once and for all
Simon-Laux Apr 2, 2024
6c65567
fix
Simon-Laux Apr 2, 2024
eb42ead
emit_chatlist_item_changed event on reaction update
Simon-Laux Apr 4, 2024
7d3201d
add test for reaction updates chatlistitem
Simon-Laux Apr 4, 2024
10a0a2c
add test_block_contact_request
Simon-Laux Apr 4, 2024
eb30768
add hint to `get_backup` that you need to start io afterwards
Simon-Laux Apr 8, 2024
3fa8619
fix multidevice tests
Simon-Laux Apr 8, 2024
728c80d
rename module to `chatlist_events`
Simon-Laux Apr 8, 2024
57c0b12
cargo fmt
Simon-Laux Apr 8, 2024
f23a817
fix python lint
Simon-Laux Apr 8, 2024
d808db1
Update deltachat-jsonrpc/src/api.rs
Simon-Laux Apr 8, 2024
c06809f
fix vscode autocomplete in a better way
Simon-Laux Apr 9, 2024
d40aa73
use Account.wait_for_incoming_msg_event
Simon-Laux Apr 9, 2024
851a622
don't use raw _rpc for methods for methods that don't need it
Simon-Laux Apr 9, 2024
07941e4
fix securejoin chatlistevents test
Simon-Laux Apr 11, 2024
221dda6
apply suggestion
Simon-Laux Apr 15, 2024
c7cac04
remove "order" from function naming
Simon-Laux Apr 15, 2024
c6f7b0c
wait for both events
Simon-Laux Apr 15, 2024
4226f26
rm unused function I added in this pr `get_chats_with_contact`
Simon-Laux Apr 15, 2024
5b0012e
Update src/mimeparser.rs
Simon-Laux Apr 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion deltachat-ffi/deltachat.h
Original file line number Diff line number Diff line change
Expand Up @@ -6213,7 +6213,24 @@ void dc_event_unref(dc_event_t* event);
* This event is only emitted by the account manager
*/

#define DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE 2200
#define DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE 2200

/**
* Inform that set of chats or the order of the chats in the chatlist has changed.
*
* Sometimes this is emitted together with `DC_EVENT_CHATLIST_ITEM_CHANGED`.
*/

#define DC_EVENT_CHATLIST_CHANGED 2300

/**
* Inform 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_CHATLIST_ITEM_CHANGED 2301

/**
* @}
Expand Down
12 changes: 11 additions & 1 deletion deltachat-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@ pub unsafe extern "C" fn dc_event_get_id(event: *mut dc_event_t) -> libc::c_int
EventType::WebxdcStatusUpdate { .. } => 2120,
EventType::WebxdcInstanceDeleted { .. } => 2121,
EventType::AccountsBackgroundFetchDone => 2200,
EventType::ChatlistChanged => 2300,
EventType::ChatlistItemChanged { .. } => 2301,
}
}

Expand Down Expand Up @@ -593,6 +595,7 @@ pub unsafe extern "C" fn dc_event_get_data1_int(event: *mut dc_event_t) -> libc:
| EventType::IncomingMsgBunch { .. }
| EventType::ErrorSelfNotInGroup(_)
| EventType::AccountsBackgroundFetchDone => 0,
EventType::ChatlistChanged => 0,
EventType::MsgsChanged { chat_id, .. }
| EventType::ReactionsChanged { chat_id, .. }
| EventType::IncomingMsg { chat_id, .. }
Expand All @@ -617,6 +620,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::ChatlistItemChanged { chat_id } => {
chat_id.unwrap_or_default().to_u32() as libc::c_int
}
}
}

Expand Down Expand Up @@ -653,6 +659,8 @@ pub unsafe extern "C" fn dc_event_get_data2_int(event: *mut dc_event_t) -> libc:
| EventType::IncomingMsgBunch { .. }
| EventType::SelfavatarChanged
| EventType::AccountsBackgroundFetchDone
| EventType::ChatlistChanged
| EventType::ChatlistItemChanged { .. }
| EventType::ConfigSynced { .. } => 0,
EventType::ChatModified(_) => 0,
EventType::MsgsChanged { msg_id, .. }
Expand Down Expand Up @@ -717,7 +725,9 @@ pub unsafe extern "C" fn dc_event_get_data2_str(event: *mut dc_event_t) -> *mut
| EventType::WebxdcInstanceDeleted { .. }
| EventType::AccountsBackgroundFetchDone
| EventType::ChatEphemeralTimerModified { .. }
| EventType::IncomingMsgBunch { .. } => ptr::null_mut(),
| EventType::IncomingMsgBunch { .. }
| EventType::ChatlistItemChanged { .. }
| EventType::ChatlistChanged => ptr::null_mut(),
EventType::ConfigureProgress { comment, .. } => {
if let Some(comment) = comment {
comment.to_c_string().unwrap_or_default().into_raw()
Expand Down
3 changes: 3 additions & 0 deletions deltachat-jsonrpc/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,9 @@ impl CommandApi {
/// the current device.
///
/// Can be cancelled by stopping the ongoing process.
///
/// Do not forget to call start_io on the account after a successful import,
/// otherwise it will not connect to the email server.
async fn get_backup(&self, account_id: u32, qr_text: String) -> Result<()> {
let ctx = self.get_context(account_id).await?;
let qr = qr::check_qr(&ctx, &qr_text).await?;
Expand Down
13 changes: 13 additions & 0 deletions deltachat-jsonrpc/src/api/types/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,15 @@ pub enum EventType {
///
/// This event is only emitted by the account manager
AccountsBackgroundFetchDone,
/// Inform that set of chats or the order of the chats in the chatlist has changed.
///
/// Sometimes this is emitted together with `UIChatlistItemChanged`.
ChatlistChanged,

/// Inform 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")]
ChatlistItemChanged { chat_id: Option<u32> },
}

impl From<CoreEventType> for EventType {
Expand Down Expand Up @@ -357,6 +366,10 @@ impl From<CoreEventType> for EventType {
msg_id: msg_id.to_u32(),
},
CoreEventType::AccountsBackgroundFetchDone => AccountsBackgroundFetchDone,
CoreEventType::ChatlistItemChanged { chat_id } => ChatlistItemChanged {
chat_id: chat_id.map(|id| id.to_u32()),
},
CoreEventType::ChatlistChanged => ChatlistChanged,
}
}
}
4 changes: 4 additions & 0 deletions deltachat-rpc-client/src/deltachat_rpc_client/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ def wait_for_event(self) -> AttrDict:
"""Wait until the next event and return it."""
return AttrDict(self._rpc.wait_for_event(self.id))

def clear_all_events(self):
"""Removes all queued-up events for a given account. Useful for tests."""
self._rpc.clear_all_events(self.id)

def remove(self) -> None:
"""Remove the account."""
self._rpc.remove_account(self.id)
Expand Down
2 changes: 2 additions & 0 deletions deltachat-rpc-client/src/deltachat_rpc_client/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class EventType(str, Enum):
SELFAVATAR_CHANGED = "SelfavatarChanged"
WEBXDC_STATUS_UPDATE = "WebxdcStatusUpdate"
WEBXDC_INSTANCE_DELETED = "WebxdcInstanceDeleted"
CHATLIST_CHANGED = "ChatlistChanged"
CHATLIST_ITEM_CHANGED = "ChatlistItemChanged"


class ChatId(IntEnum):
Expand Down
11 changes: 10 additions & 1 deletion deltachat-rpc-client/src/deltachat_rpc_client/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import subprocess
import sys
from queue import Queue
from queue import Empty, Queue
from threading import Event, Thread
from typing import Any, Iterator, Optional

Expand Down Expand Up @@ -188,5 +188,14 @@ def wait_for_event(self, account_id: int) -> Optional[dict]:
queue = self.get_queue(account_id)
return queue.get()

def clear_all_events(self, account_id: int):
"""Removes all queued-up events for a given account. Useful for tests."""
queue = self.get_queue(account_id)
try:
while True:
queue.get_nowait()
except Empty:
pass

def __getattr__(self, attr: str):
return RpcMethod(self, attr)
218 changes: 218 additions & 0 deletions deltachat-rpc-client/tests/test_chatlist_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
from __future__ import annotations

import base64
import os
from typing import TYPE_CHECKING

from deltachat_rpc_client import Account, EventType, const

if TYPE_CHECKING:
from deltachat_rpc_client.pytestplugin import ACFactory


def wait_for_chatlist_and_specific_item(account, chat_id):
first_event = ""
while True:
event = account.wait_for_event()
if event.kind == EventType.CHATLIST_CHANGED:
first_event = "change"
break
if event.kind == EventType.CHATLIST_ITEM_CHANGED and event.chat_id == chat_id:
first_event = "item_change"
break
while True:
event = account.wait_for_event()
if event.kind == EventType.CHATLIST_CHANGED and first_event == "item_change":
break
if event.kind == EventType.CHATLIST_ITEM_CHANGED and event.chat_id == chat_id and first_event == "change":
break


def wait_for_chatlist_specific_item(account, chat_id):
while True:
event = account.wait_for_event()
if event.kind == EventType.CHATLIST_ITEM_CHANGED and event.chat_id == chat_id:
break


def wait_for_chatlist(account):
while True:
event = account.wait_for_event()
if event.kind == EventType.CHATLIST_CHANGED:
break


def test_delivery_status(acfactory: ACFactory) -> None:
"""
Test change status on chatlistitem when status changes (delivered, read)
"""
alice, bob = acfactory.get_online_accounts(2)

bob_addr = bob.get_config("addr")
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
alice_chat_bob = alice_contact_bob.create_chat()

alice.clear_all_events()
bob.stop_io()
alice.stop_io()
alice_chat_bob.send_text("hi")
wait_for_chatlist_and_specific_item(alice, chat_id=alice_chat_bob.id)

alice.clear_all_events()
alice.start_io()
wait_for_chatlist_specific_item(alice, chat_id=alice_chat_bob.id)

bob.clear_all_events()
bob.start_io()

event = bob.wait_for_incoming_msg_event()
msg = bob.get_message_by_id(event.msg_id)
msg.get_snapshot().chat.accept()
msg.mark_seen()

chat_item = alice._rpc.get_chatlist_items_by_entries(alice.id, [alice_chat_bob.id])[str(alice_chat_bob.id)]
assert chat_item["summaryStatus"] == const.MessageState.OUT_DELIVERED

alice.clear_all_events()

while True:
event = alice.wait_for_event()
if event.kind == EventType.MSG_READ:
break

wait_for_chatlist_specific_item(alice, chat_id=alice_chat_bob.id)
chat_item = alice._rpc.get_chatlist_items_by_entries(alice.id, [alice_chat_bob.id])[str(alice_chat_bob.id)]
assert chat_item["summaryStatus"] == const.MessageState.OUT_MDN_RCVD


def test_delivery_status_failed(acfactory: ACFactory) -> None:
"""
Test change status on chatlistitem when status changes failed
"""
(alice,) = acfactory.get_online_accounts(1)

invalid_contact = alice.create_contact("[email protected]", "invalid address")
invalid_chat = alice.get_chat_by_id(alice._rpc.create_chat_by_contact_id(alice.id, invalid_contact.id))

alice.clear_all_events()

failing_message = invalid_chat.send_text("test")

wait_for_chatlist_and_specific_item(alice, invalid_chat.id)

assert failing_message.get_snapshot().state == const.MessageState.OUT_PENDING

while True:
event = alice.wait_for_event()
if event.kind == EventType.MSG_FAILED:
break

wait_for_chatlist_specific_item(alice, invalid_chat.id)

assert failing_message.get_snapshot().state == const.MessageState.OUT_FAILED


def test_download_on_demand(acfactory: ACFactory) -> None:
"""
Test if download on demand emits chatlist update events.
This is only needed for last message in chat, but finding that out is too expensive, so it's always emitted
"""
alice, bob = acfactory.get_online_accounts(2)

bob_addr = bob.get_config("addr")
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
alice_chat_bob = alice_contact_bob.create_chat()
alice_chat_bob.send_text("hi")

alice.set_config("download_limit", "1")

event = bob.wait_for_incoming_msg_event()
msg = bob.get_message_by_id(event.msg_id)
chat_id = msg.get_snapshot().chat_id
msg.get_snapshot().chat.accept()
bob.get_chat_by_id(chat_id).send_message(
"Hello World, this message is bigger than 5 bytes",
html=base64.b64encode(os.urandom(300000)).decode("utf-8"),
)

msg_id = alice.wait_for_incoming_msg_event().msg_id

assert alice.get_message_by_id(msg_id).get_snapshot().download_state == const.DownloadState.AVAILABLE

alice.clear_all_events()
chat_id = alice.get_message_by_id(msg_id).get_snapshot().chat_id
alice._rpc.download_full_message(alice.id, msg_id)

wait_for_chatlist_specific_item(alice, chat_id)


def get_multi_account_test_setup(acfactory: ACFactory) -> [Account, Account, Account]:
alice, bob = acfactory.get_online_accounts(2)

bob_addr = bob.get_config("addr")
alice_contact_bob = alice.create_contact(bob_addr, "Bob")
alice_chat_bob = alice_contact_bob.create_chat()
alice_chat_bob.send_text("hi")

bob.wait_for_incoming_msg_event()

alice_second_device: Account = acfactory.get_unconfigured_account()

alice._rpc.provide_backup.future(alice.id)
backup_code = alice._rpc.get_backup_qr(alice.id)
alice_second_device._rpc.get_backup(alice_second_device.id, backup_code)
alice_second_device.start_io()
alice.clear_all_events()
alice_second_device.clear_all_events()
bob.clear_all_events()
return [alice, alice_second_device, bob, alice_chat_bob]


def test_imap_sync_seen_msgs(acfactory: ACFactory) -> None:
"""
Test that chatlist changed events are emitted for the second device
when the message is marked as read on the first device
"""
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acfactory)

alice_chat_bob.send_text("hello")

event = bob.wait_for_incoming_msg_event()
msg = bob.get_message_by_id(event.msg_id)
bob_chat_id = msg.get_snapshot().chat_id
msg.get_snapshot().chat.accept()

alice.clear_all_events()
alice_second_device.clear_all_events()
bob.get_chat_by_id(bob_chat_id).send_text("hello")

# make sure alice_second_device already received the message
alice_second_device.wait_for_incoming_msg_event()

event = alice.wait_for_incoming_msg_event()
msg = alice.get_message_by_id(event.msg_id)
alice_second_device.clear_all_events()
msg.mark_seen()

wait_for_chatlist_specific_item(bob, bob_chat_id)
wait_for_chatlist_specific_item(alice, alice_chat_bob.id)


def test_multidevice_sync_chat(acfactory: ACFactory) -> None:
"""
Test multidevice sync: syncing chat visibility and muting across multiple devices
"""
alice, alice_second_device, bob, alice_chat_bob = get_multi_account_test_setup(acfactory)

alice_chat_bob.archive()
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
assert alice_second_device.get_chat_by_id(alice_chat_bob.id).get_basic_snapshot().archived

alice_second_device.clear_all_events()
alice_chat_bob.pin()
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)

alice_second_device.clear_all_events()
alice_chat_bob.mute()
wait_for_chatlist_specific_item(alice_second_device, alice_chat_bob.id)
assert alice_second_device.get_chat_by_id(alice_chat_bob.id).get_basic_snapshot().is_muted
2 changes: 2 additions & 0 deletions node/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ module.exports = {
DC_DOWNLOAD_IN_PROGRESS: 1000,
DC_DOWNLOAD_UNDECIPHERABLE: 30,
DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE: 2200,
DC_EVENT_CHATLIST_CHANGED: 2300,
DC_EVENT_CHATLIST_ITEM_CHANGED: 2301,
DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED: 2021,
DC_EVENT_CHAT_MODIFIED: 2020,
DC_EVENT_CONFIGURE_PROGRESS: 2041,
Expand Down
4 changes: 3 additions & 1 deletion node/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,7 @@ module.exports = {
2111: 'DC_EVENT_CONFIG_SYNCED',
2120: 'DC_EVENT_WEBXDC_STATUS_UPDATE',
2121: 'DC_EVENT_WEBXDC_INSTANCE_DELETED',
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE'
2200: 'DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE',
2300: 'DC_EVENT_CHATLIST_CHANGED',
2301: 'DC_EVENT_CHATLIST_ITEM_CHANGED'
}
Loading