From da2bcd0c5a15c36aadc5140f2fdf4465dcffdf1d Mon Sep 17 00:00:00 2001 From: holger krekel Date: Fri, 14 Sep 2018 21:43:52 +0200 Subject: [PATCH] introduce message.get_state() API which provides is_in_delivered()|is_out_delivered|... methods. --- python/src/deltachat/__init__.py | 2 +- python/src/deltachat/_build.py | 2 +- python/src/deltachat/chatting.py | 74 +++++++++++++++++++++++++++++++- python/tests/test_account.py | 34 +++++++++------ 4 files changed, 96 insertions(+), 16 deletions(-) diff --git a/python/src/deltachat/__init__.py b/python/src/deltachat/__init__.py index a139cd80..b4593bb8 100644 --- a/python/src/deltachat/__init__.py +++ b/python/src/deltachat/__init__.py @@ -2,7 +2,7 @@ from deltachat import capi from deltachat.capi import ffi from deltachat.account import Account # noqa -__version__ = "0.5.dev0" +__version__ = "0.5.dev1" _DC_CALLBACK_MAP = {} diff --git a/python/src/deltachat/_build.py b/python/src/deltachat/_build.py index 8ae04970..94dd6a3d 100644 --- a/python/src/deltachat/_build.py +++ b/python/src/deltachat/_build.py @@ -12,7 +12,7 @@ deltah = joinpath(dirname(dirname(dirname(here))), "src", "deltachat.h") def read_event_defines(): - rex = re.compile(r'#define\s+(?:DC_EVENT_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+\s+([x\d]+).*') + rex = re.compile(r'#define\s+(?:DC_EVENT_|DC_STATE_|DC_CONTACT_ID_|DC_GCL|DC_CHAT)\S+\s+([x\d]+).*') return filter(rex.match, open(deltah)) diff --git a/python/src/deltachat/chatting.py b/python/src/deltachat/chatting.py index 3e5afbcd..63360a6b 100644 --- a/python/src/deltachat/chatting.py +++ b/python/src/deltachat/chatting.py @@ -1,4 +1,4 @@ -""" Chatting related objects: Contact, Chat, Message. """ +""" chatting related objects: Contact, Chat, Message. """ from . import capi from .cutil import convert_to_bytes_utf8, ffi_unicode, iter_array_and_unref @@ -114,10 +114,22 @@ class Message(object): def dc_msg_t(self): return capi.lib.dc_get_msg(self.dc_context, self.id) + def _refresh(self): + if hasattr(self, "_property_cache"): + lib.dc_msg_unref(self.dc_msg_t) + self._property_cache.clear() + def __del__(self): if lib is not None and hasattr(self, "_property_cache"): lib.dc_msg_unref(self.dc_msg_t) + def get_state(self): + """ get the message in/out state. + + :returns: :class:`MessageState` + """ + return MessageState(self) + @property_with_doc def text(self): """unicode representation. """ @@ -131,3 +143,63 @@ class Message(object): """ chat_id = capi.lib.dc_msg_get_chat_id(self.dc_msg_t) return Chat(self.dc_context, chat_id) + + +@attr.s +class MessageState(object): + """ Current Message In/Out state, updated on each call of is_* methods. + """ + message = attr.ib(validator=v.instance_of(Message)) + + @property + def _msgstate(self): + self.message._refresh() + return lib.dc_msg_get_state(self.message.dc_msg_t) + + def is_in_fresh(self): + """ return True if Message is incoming fresh message (un-noticed). + + Fresh messages are not noticed nor seen and are typically + shown in notifications. + """ + return self._msgstate == lib.DC_STATE_IN_FRESH + + def is_in_noticed(self): + """Return True if Message is incoming and noticed. + + Eg. chat opened but message not yet read - noticed messages + are not counted as unread but were not marked as read nor resulted in MDNs. + """ + return self._msgstate == lib.DC_STATE_IN_NOTICED + + def is_in_seen(self): + """Return True if Message is incoming, noticed and has been seen. + + Eg. chat opened but message not yet read - noticed messages + are not counted as unread but were not marked as read nor resulted in MDNs. + """ + return self._msgstate == lib.DC_STATE_IN_SEEN + + def is_out_pending(self): + """Return True if Message is outgoing, but is pending (no single checkmark). + """ + return self._msgstate == lib.DC_STATE_OUT_PENDING + + def is_out_failed(self): + """Return True if Message is unrecoverably failed. + """ + return self._msgstate == lib.DC_STATE_OUT_FAILED + + def is_out_delivered(self): + """Return True if Message was successfully delivered to the server (one checkmark). + + Note, that already delivered messages may get into the state is_out_failed(). + """ + return self._msgstate == lib.DC_STATE_OUT_DELIVERED + + def is_out_mdn_received(self): + """Return True if message was marked as read by the recipient(s) (two checkmarks; + this requires goodwill on the receiver's side). If a sent message changes to this + state, you'll receive the event DC_EVENT_MSG_READ. + """ + return self._msgstate == lib.DC_STATE_OUT_MDN_RCVD diff --git a/python/tests/test_account.py b/python/tests/test_account.py index a148da44..d6575e50 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -56,6 +56,15 @@ class TestOfflineAccount: chat = ac1.create_chat_by_contact(contact1) msg = chat.send_text_message("msg1") assert msg + msg_state = msg.get_state() + assert not msg_state.is_in_fresh() + assert not msg_state.is_in_noticed() + assert not msg_state.is_in_seen() + # XXX the following line should work but doesn't: + # assert msg_state.is_out_pending() + assert not msg_state.is_out_failed() + assert not msg_state.is_out_delivered() + assert not msg_state.is_out_mdn_received() class TestOnlineAccount: @@ -103,26 +112,27 @@ class TestOnlineAccount: self.wait_successful_IMAP_SMTP_connection(ac2) self.wait_configuration_progress(ac1, 1000) self.wait_configuration_progress(ac2, 1000) - msg = chat.send_text_message("msg1") + msg_out = chat.send_text_message("message1") ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED") evt_name, data1, data2 = ev assert data1 == chat.id - assert data2 == msg.id + assert data2 == msg_out.id + assert msg_out.get_state().is_out_delivered() # wait for other account to receive ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") - assert ev[2] == msg.id - msg2 = ac2.get_message_by_id(msg.id) - assert msg2.text == "msg1" + assert ev[2] == msg_out.id + msg_in = ac2.get_message_by_id(msg_out.id) + assert msg_in.text == "message1" # check the message arrived in contact-requets/deaddrop - chat2 = msg2.chat - assert msg2 in chat2.get_messages() + chat2 = msg_in.chat + assert msg_in in chat2.get_messages() assert chat2.is_deaddrop() assert chat2.count_fresh_messages() == 0 # create new chat with contact and verify it's proper - chat2b = ac2.create_chat_by_message(msg2) + chat2b = ac2.create_chat_by_message(msg_in) assert not chat2b.is_deaddrop() assert chat2b.count_fresh_messages() == 1 @@ -131,8 +141,6 @@ class TestOnlineAccount: assert chat2b.count_fresh_messages() == 0 # mark messages as seen and check ac1 sees the MDN - ac2.mark_seen_messages([msg2]) - while 1: - ev = ac1._evlogger.get_matching("DC_EVENT_INFO") - if "Marking message" in ev[2]: - break + ac2.mark_seen_messages([msg_in]) + ac1._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") + assert msg_out.get_state().is_out_mdn_received()