From d66829702f2d72445fddb2a1f5a7bd6b001a509b Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 4 Mar 2020 14:34:26 +0100 Subject: [PATCH] fix #164 add MEMBER_REMOVED event and member_removed plugin python hook --- deltachat-ffi/deltachat.h | 9 ++++++++ deltachat-ffi/src/lib.rs | 4 ++++ python/src/deltachat/account.py | 18 +++++++++++++-- python/src/deltachat/const.py | 1 + python/src/deltachat/hookspec.py | 4 ++++ python/tests/test_account.py | 38 ++++++++++++++++++++++++++++++++ src/chat.rs | 22 +++++++++++------- src/events.rs | 6 +++++ 8 files changed, 92 insertions(+), 10 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 7e2d7c5b9..7a160d693 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -4522,6 +4522,15 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot); */ #define DC_EVENT_MEMBER_ADDED 2063 +/** + * This event is sent for each member that gets removed from a (verified or unverified) chat. + * + * @param data1 (int) chat_id + * @param data2 (int) contact_id + * @return 0 + */ +#define DC_EVENT_MEMBER_REMOVED 2064 + /** * @} */ diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 10cdfaccf..5d68aedd6 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -203,6 +203,10 @@ impl ContextWrapper { | Event::MemberAdded { chat_id, contact_id, + } + | Event::MemberRemoved { + chat_id, + contact_id, } => { ffi_cb( self, diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index 853d13894..78b21686d 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -3,6 +3,7 @@ from __future__ import print_function import atexit from contextlib import contextmanager +import queue from threading import Event import os from array import array @@ -15,7 +16,6 @@ from .message import Message from .contact import Contact from .tracker import ImexTracker from . import hookspec, iothreads -from queue import Queue class MissingCredentials(ValueError): @@ -49,7 +49,7 @@ class Account(object): hook.account_init(account=self, db_path=db_path) self._threads = iothreads.IOThreads(self) - self._hook_event_queue = Queue() + self._hook_event_queue = queue.Queue() self._in_use_iter_events = False self._shutdown_event = Event() @@ -578,6 +578,16 @@ class Account(object): hook = hookspec.Global._get_plugin_manager().hook hook.account_after_shutdown(account=self, dc_context=dc_context) + def _handle_current_events(self): + """ handle all currently queued events and then return. """ + while 1: + try: + event = self._hook_event_queue.get(block=False) + except queue.Empty: + break + else: + event.call_hook() + def iter_events(self, timeout=None): """ yield hook events until shutdown. @@ -614,6 +624,10 @@ class Account(object): chat = self.get_chat_by_id(ffi_event.data1) contact = self.get_contact_by_id(ffi_event.data2) return "member_added", dict(chat=chat, contact=contact) + elif name == "DC_EVENT_MEMBER_REMOVED": + chat = self.get_chat_by_id(ffi_event.data1) + contact = self.get_contact_by_id(ffi_event.data2) + return "member_removed", dict(chat=chat, contact=contact) return None, {} diff --git a/python/src/deltachat/const.py b/python/src/deltachat/const.py index 09229ff75..249be9804 100644 --- a/python/src/deltachat/const.py +++ b/python/src/deltachat/const.py @@ -100,6 +100,7 @@ DC_EVENT_SECUREJOIN_INVITER_PROGRESS = 2060 DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061 DC_EVENT_SECUREJOIN_MEMBER_ADDED = 2062 DC_EVENT_MEMBER_ADDED = 2063 +DC_EVENT_MEMBER_REMOVED = 2064 DC_EVENT_FILE_COPIED = 2055 DC_EVENT_IS_OFFLINE = 2081 DC_EVENT_GET_STRING = 2091 diff --git a/python/src/deltachat/hookspec.py b/python/src/deltachat/hookspec.py index 092dd7232..a74a71237 100644 --- a/python/src/deltachat/hookspec.py +++ b/python/src/deltachat/hookspec.py @@ -56,6 +56,10 @@ class PerAccount: def member_added(self, chat, contact): """ Called for each contact added to a chat. """ + @account_hookspec + def member_removed(self, chat, contact): + """ Called for each contact removed from a chat. """ + class Global: """ global hook specifications using a per-process singleton diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 191178b84..ffecef9af 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -438,20 +438,56 @@ class TestOfflineChat: def test_group_chat_many_members_add_remove(self, ac1, lp): lp.sec("ac1: creating group chat with 10 other members") chat = ac1.create_group_chat(name="title1") + # promote chat + chat.send_text("hello") + assert chat.is_promoted() + + # activate local plugin + in_list = [] + + class InPlugin: + @account_hookimpl + def member_added(self, chat, contact): + in_list.append(("added", chat, contact)) + + @account_hookimpl + def member_removed(self, chat, contact): + in_list.append(("removed", chat, contact)) + + ac1.add_account_plugin(InPlugin()) + + # perform add contact many times contacts = [] for i in range(10): + lp.sec("create contact") contact = ac1.create_contact("some{}@example.org".format(i)) contacts.append(contact) + lp.sec("add contact") chat.add_contact(contact) num_contacts = len(chat.get_contacts()) assert num_contacts == 11 + # perform plugin hooks + ac1._handle_current_events() + + assert len(in_list) == 10 + for in_cmd, in_chat, in_contact in in_list: + assert in_cmd == "added" + assert in_chat == chat + assert in_contact in contacts + lp.sec("ac1: removing two contacts and checking things are right") chat.remove_contact(contacts[9]) chat.remove_contact(contacts[3]) assert len(chat.get_contacts()) == 9 + ac1._handle_current_events() + assert len(in_list) == 12 + assert in_list[-2][0] == "removed" + assert in_list[-2][1] == chat + assert in_list[-2][2] == contacts[9] + class TestOnlineAccount: def get_chat(self, ac1, ac2, both_created=False): @@ -1390,7 +1426,9 @@ class TestGroupStressTests: lp.sec("ac2: removing one contact") to_remove = contacts[-1] + msg.chat.remove_contact(to_remove) + ac2._evtracker.get_matching("DC_EVENT_MEMBER_REMOVED") lp.sec("ac1: receiving system message about contact removal") sysmsg = ac1._evtracker.wait_next_incoming_message() diff --git a/src/chat.rs b/src/chat.rs index 367007279..1d09540b6 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1953,20 +1953,22 @@ pub(crate) fn add_contact_to_chat_ex( msg.param.set_cmd(SystemMessage::MemberAddedToGroup); msg.param.set(Param::Arg, contact.get_addr()); msg.param.set_int(Param::Arg2, from_handshake.into()); + msg.id = send_msg(context, chat_id, &mut msg)?; - context.call_cb(Event::MsgsChanged { - chat_id, - msg_id: msg.id, - }); + // send_msg sends MsgsChanged event + // so we only send an explicit MemberAdded one context.call_cb(Event::MemberAdded { chat_id, contact_id: contact.id, }); + } else { + // send an event for unpromoted groups + // XXX probably not neccessary because ChatModified should suffice + context.call_cb(Event::MsgsChanged { + chat_id, + msg_id: MsgId::new(0), + }); } - context.call_cb(Event::MsgsChanged { - chat_id, - msg_id: MsgId::new(0), - }); context.call_cb(Event::ChatModified(chat_id)); Ok(true) } @@ -2171,6 +2173,10 @@ pub fn remove_contact_from_chat( msg.param.set_cmd(SystemMessage::MemberRemovedFromGroup); msg.param.set(Param::Arg, contact.get_addr()); msg.id = send_msg(context, chat_id, &mut msg)?; + context.call_cb(Event::MemberRemoved { + chat_id, + contact_id: contact.id, + }); context.call_cb(Event::MsgsChanged { chat_id, msg_id: msg.id, diff --git a/src/events.rs b/src/events.rs index 3d1eb140f..540fe3364 100644 --- a/src/events.rs +++ b/src/events.rs @@ -213,4 +213,10 @@ pub enum Event { /// @param data2 (int) contact_id #[strum(props(id = "2063"))] MemberAdded { chat_id: ChatId, contact_id: u32 }, + + /// This event is sent for each contact removed from a chat. + /// @param data1 (int) chat_id + /// @param data2 (int) contact_id + #[strum(props(id = "2064"))] + MemberRemoved { chat_id: ChatId, contact_id: u32 }, }