1
0
Fork 0
mirror of https://github.com/deltachat/deltachat-core.git synced 2025-10-05 02:29:28 +02:00

address #273 -- introduce attr for contact/chat/message/EventHandler

also refine the GC-handling of contact and message pointers.
free-ing dc_context pointers currently does not work because
the threads are keeping a reference and not shutting down.
help on this shutdown issue from @r10s or @flub welcome.
I think fixing the context GC/shutdown issue should not hold
up merging this branch.
This commit is contained in:
holger krekel 2018-09-13 12:12:49 +02:00
parent 64a8d85be5
commit 75f8169072
5 changed files with 94 additions and 36 deletions

View file

@ -11,7 +11,7 @@ def main():
long_description = long_description, long_description = long_description,
author='Delta Chat contributors', author='Delta Chat contributors',
setup_requires=['cffi>=1.0.0'], setup_requires=['cffi>=1.0.0'],
install_requires=['cffi>=1.0.0', 'requests'], install_requires=['cffi>=1.0.0', 'requests', 'attr'],
packages=setuptools.find_packages('src'), packages=setuptools.find_packages('src'),
package_dir={'': 'src'}, package_dir={'': 'src'},
cffi_modules=['src/deltachat/_build.py:ffibuilder'], cffi_modules=['src/deltachat/_build.py:ffibuilder'],

View file

@ -7,14 +7,15 @@ try:
except ImportError: except ImportError:
from Queue import Queue from Queue import Queue
import deltachat
from . import capi from . import capi
from .capi import ffi from .capi import ffi
import deltachat from .myattr import attrib_CData, attrs, attrib_int, cached_property
class EventHandler: @attrs
def __init__(self, dc_context): class EventHandler(object):
self.dc_context = dc_context dc_context = attrib_CData()
def read_url(self, url): def read_url(self, url):
try: try:
@ -75,14 +76,17 @@ class EventLogger:
tname, self.logid, evt_name, data1, data2)) tname, self.logid, evt_name, data1, data2))
class Contact: @attrs
def __init__(self, dc_context, contact_id): class Contact(object):
self.dc_context = dc_context dc_context = attrib_CData()
self.id = contact_id id = attrib_int()
self.dc_contact_t = capi.lib.dc_get_contact(self.dc_context, contact_id)
@cached_property # only get it once because we only free it once
def dc_contact_t(self):
return capi.lib.dc_get_contact(self.dc_context, self.id)
def __del__(self): def __del__(self):
capi.lib.free(self.dc_contact_t) capi.lib.dc_contact_unref(self.dc_contact_t)
@property @property
def addr(self): def addr(self):
@ -101,33 +105,37 @@ class Contact:
return capi.lib.dc_contact_is_verified(self.dc_contact_t) return capi.lib.dc_contact_is_verified(self.dc_contact_t)
class Chat: @attrs
def __init__(self, dc_context, chat_id): class Chat(object):
self.dc_context = dc_context dc_context = attrib_CData()
self.id = chat_id id = attrib_int()
def __eq__(self, other_chat): @cached_property
return ( def dc_chat_t(self):
self.dc_context == getattr(other_chat, "dc_context", None) and return capi.lib.get_chat(self.id)
self.id == getattr(other_chat, "id", None)
)
def __ne__(self, other_chat): def __del__(self):
return not self == other_chat capi.lib.dc_chat_unref(self.dc_chat_t)
def send_text_message(self, msg): def send_text_message(self, msg):
""" return ID of the message in this chat. """ send a text message and return Message instance. """
'msg' should be unicode"""
msg = convert_to_bytes_utf8(msg) msg = convert_to_bytes_utf8(msg)
print ("chat id", self.id) print ("chat id", self.id)
return capi.lib.dc_send_text_msg(self.dc_context, self.id, msg) msg_id = capi.lib.dc_send_text_msg(self.dc_context, self.id, msg)
return Message(self.dc_context, msg_id)
class Message: @attrs
def __init__(self, dc_context, msg_id): class Message(object):
self.dc_context = dc_context dc_context = attrib_CData()
self.id = msg_id id = attrib_int()
self.dc_msg = capi.lib.dc_get_msg(self.dc_context, msg_id)
@cached_property
def dc_msg(self):
return capi.lib.dc_get_msg(self.dc_context, self.id)
def __del__(self):
capi.lib.dc_msg_unref(self.dc_msg_t)
@property @property
def text(self): def text(self):
@ -139,7 +147,7 @@ class Message:
return Chat(self.dc_context, chat_id) return Chat(self.dc_context, chat_id)
class Account: class Account(object):
def __init__(self, db_path, logid=None): def __init__(self, db_path, logid=None):
self.dc_context = ctx = capi.lib.dc_context_new( self.dc_context = ctx = capi.lib.dc_context_new(
capi.lib.py_dc_callback, capi.lib.py_dc_callback,
@ -151,6 +159,15 @@ class Account:
self._evlogger = EventLogger(self.dc_context, logid) self._evlogger = EventLogger(self.dc_context, logid)
self._threads = IOThreads(self.dc_context) self._threads = IOThreads(self.dc_context)
def __del__(self):
# XXX this causes currently a segfault because
# the threads still have a reference to dc_context
#
# capi.lib.dc_context_unref(self.dc_context)
#
# let's for now leak memory instead of causing segfaults
pass
def set_config(self, **kwargs): def set_config(self, **kwargs):
for name, value in kwargs.items(): for name, value in kwargs.items():
name = name.encode("utf8") name = name.encode("utf8")
@ -184,7 +201,7 @@ class Account:
self.dc_context, contact_id) self.dc_context, contact_id)
return Chat(self.dc_context, chat_id) return Chat(self.dc_context, chat_id)
def get_message(self, msg_id): def get_message_by_id(self, msg_id):
return Message(self.dc_context, msg_id) return Message(self.dc_context, msg_id)
def start(self): def start(self):

View file

@ -0,0 +1,33 @@
from attr import attrs, attrib # noqa
from attr import validators as v
def attrib_int():
return attrib(validator=v.instance_of(int))
def attrib_CData():
from deltachat.capi import ffi
return attrib(validator=v.instance_of(ffi.CData))
# copied over unmodified from
# https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py
# where it's also tested
def cached_property(f):
"""returns a cached property that is calculated by function f"""
def get(self):
try:
return self._property_cache[f]
except AttributeError:
self._property_cache = {}
except KeyError:
pass
x = self._property_cache[f] = f(self)
return x
def set(self, val):
propcache = self.__dict__.setdefault("_property_cache", {})
propcache[f] = val
return property(get, set)

View file

@ -29,6 +29,13 @@ class TestOfflineAccount:
assert chat == chat2 assert chat == chat2
assert not (chat != chat2) assert not (chat != chat2)
def test_message(self, acfactory):
ac1 = acfactory.get_offline_account()
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
msg = chat.send_text_message("msg1")
assert msg
class TestOnlineAccount: class TestOnlineAccount:
def wait_successful_IMAP_SMTP_connection(self, account): def wait_successful_IMAP_SMTP_connection(self, account):
@ -74,14 +81,14 @@ class TestOnlineAccount:
self.wait_successful_IMAP_SMTP_connection(ac2) self.wait_successful_IMAP_SMTP_connection(ac2)
self.wait_configuration_progress(ac1, 1000) self.wait_configuration_progress(ac1, 1000)
self.wait_configuration_progress(ac2, 1000) self.wait_configuration_progress(ac2, 1000)
msg_id = chat.send_text_message("msg1") msg = chat.send_text_message("msg1")
ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED") ev = ac1._evlogger.get_matching("DC_EVENT_MSG_DELIVERED")
evt_name, data1, data2 = ev evt_name, data1, data2 = ev
assert data1 == chat.id assert data1 == chat.id
assert data2 == msg_id assert data2 == msg.id
ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED") ev = ac2._evlogger.get_matching("DC_EVENT_MSGS_CHANGED")
assert ev[2] == msg_id assert ev[2] == msg.id
msg = ac2.get_message(msg_id) msg = ac2.get_message_by_id(msg.id)
assert msg.text == "msg1" assert msg.text == "msg1"
# note that ev[1] aka data1 contains a bogus channel id # note that ev[1] aka data1 contains a bogus channel id
# probably should just not get passed from the core # probably should just not get passed from the core

View file

@ -12,6 +12,7 @@ commands = pytest {posargs:tests}
usedevelop = True usedevelop = True
deps = deps =
pytest pytest
pytest-faulthandler
pdbpp pdbpp
[testenv:lint] [testenv:lint]