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

move all unref'in to a specific shadow class that takes care of unref'ing.

bump version
This commit is contained in:
holger krekel 2018-09-14 23:42:37 +02:00
parent da2bcd0c5a
commit 8c1c65b631
6 changed files with 120 additions and 97 deletions

View file

@ -2,7 +2,7 @@ from deltachat import capi
from deltachat.capi import ffi from deltachat.capi import ffi
from deltachat.account import Account # noqa from deltachat.account import Account # noqa
__version__ = "0.5.dev1" __version__ = "0.5.dev2"
_DC_CALLBACK_MAP = {} _DC_CALLBACK_MAP = {}

View file

@ -1,4 +1,4 @@
""" Delta.Chat Account class. """ """ Account class implementation. """
from __future__ import print_function from __future__ import print_function
import threading import threading
@ -9,13 +9,13 @@ try:
from queue import Queue from queue import Queue
except ImportError: except ImportError:
from Queue import Queue from Queue import Queue
import deltachat
from . import capi
from .cutil import convert_to_bytes_utf8, ffi_unicode, iter_array_and_unref
from .capi import ffi, lib
import attr import attr
from attr import validators as v from attr import validators as v
import deltachat
from .capi import ffi, lib
from .cutil import convert_to_bytes_utf8, ffi_unicode, iter_array_and_unref
from .types import DC_Context
from .chatting import Contact, Chat, Message from .chatting import Contact, Chat, Message
@ -23,6 +23,7 @@ class Account(object):
""" An account contains configuration and provides methods """ An account contains configuration and provides methods
for configuration, contact and chat creation and manipulation. for configuration, contact and chat creation and manipulation.
""" """
def __init__(self, db_path, logid=None): def __init__(self, db_path, logid=None):
""" initialize account object. """ initialize account object.
@ -32,19 +33,16 @@ class Account(object):
the default internal logging. the default internal logging.
""" """
self.dc_context = ctx = capi.lib.dc_context_new( self._dc_context = DC_Context(
capi.lib.py_dc_callback, lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL)
capi.ffi.NULL, capi.ffi.NULL) )
if hasattr(db_path, "encode"): if hasattr(db_path, "encode"):
db_path = db_path.encode("utf8") db_path = db_path.encode("utf8")
capi.lib.dc_open(ctx, db_path, capi.ffi.NULL) if not lib.dc_open(self._dc_context.p, db_path, ffi.NULL):
self._evhandler = EventHandler(self.dc_context) raise ValueError("Could not dc_open: {}".format(db_path))
self._evlogger = EventLogger(self.dc_context, logid) self._evhandler = EventHandler(self._dc_context)
self._threads = IOThreads(self.dc_context) self._evlogger = EventLogger(self._dc_context, logid)
self._threads = IOThreads(self._dc_context)
def __del__(self):
if lib is not None:
lib.dc_context_unref(self.dc_context)
def set_config(self, **kwargs): def set_config(self, **kwargs):
""" set configuration values. """ set configuration values.
@ -56,7 +54,7 @@ class Account(object):
for name, value in kwargs.items(): for name, value in kwargs.items():
name = name.encode("utf8") name = name.encode("utf8")
value = value.encode("utf8") value = value.encode("utf8")
capi.lib.dc_set_config(self.dc_context, name, value) lib.dc_set_config(self._dc_context.p, name, value)
def get_config(self, name): def get_config(self, name):
""" return unicode string value. """ return unicode string value.
@ -65,7 +63,7 @@ class Account(object):
:returns: unicode value :returns: unicode value
""" """
name = name.encode("utf8") name = name.encode("utf8")
res = capi.lib.dc_get_config(self.dc_context, name, b'') res = lib.dc_get_config(self._dc_context.p, name, b'')
return ffi_unicode(res) return ffi_unicode(res)
def is_configured(self): def is_configured(self):
@ -73,7 +71,7 @@ class Account(object):
:returns: True if account is configured. :returns: True if account is configured.
""" """
return capi.lib.dc_is_configured(self.dc_context) return lib.dc_is_configured(self._dc_context.p)
def check_is_configured(self): def check_is_configured(self):
""" Raise ValueError if this account is not configured. """ """ Raise ValueError if this account is not configured. """
@ -86,7 +84,7 @@ class Account(object):
:returns: :class:`Contact` :returns: :class:`Contact`
""" """
self.check_is_configured() self.check_is_configured()
return Contact(self.dc_context, capi.lib.DC_CONTACT_ID_SELF) return Contact(self._dc_context, lib.DC_CONTACT_ID_SELF)
def create_contact(self, email, name=ffi.NULL): def create_contact(self, email, name=ffi.NULL):
""" create a (new) Contact. If there already is a Contact """ create a (new) Contact. If there already is a Contact
@ -99,8 +97,8 @@ class Account(object):
""" """
name = convert_to_bytes_utf8(name) name = convert_to_bytes_utf8(name)
email = convert_to_bytes_utf8(email) email = convert_to_bytes_utf8(email)
contact_id = capi.lib.dc_create_contact(self.dc_context, name, email) contact_id = lib.dc_create_contact(self._dc_context.p, name, email)
return Contact(self.dc_context, contact_id) return Contact(self._dc_context, contact_id)
def get_contacts(self, query=ffi.NULL, with_self=False, only_verified=False): def get_contacts(self, query=ffi.NULL, with_self=False, only_verified=False):
""" get a (filtered) list of contacts. """ get a (filtered) list of contacts.
@ -117,8 +115,8 @@ class Account(object):
flags |= lib.DC_GCL_VERIFIED_ONLY flags |= lib.DC_GCL_VERIFIED_ONLY
if with_self: if with_self:
flags |= lib.DC_GCL_ADD_SELF flags |= lib.DC_GCL_ADD_SELF
dc_array_t = lib.dc_get_contacts(self.dc_context, flags, query) dc_array_t = lib.dc_get_contacts(self._dc_context.p, flags, query)
return list(iter_array_and_unref(dc_array_t, lambda x: Contact(self.dc_context, x))) return list(iter_array_and_unref(dc_array_t, lambda x: Contact(self._dc_context.p, x)))
def create_chat_by_contact(self, contact): def create_chat_by_contact(self, contact):
""" create or get an existing 1:1 chat object for the specified contact. """ create or get an existing 1:1 chat object for the specified contact.
@ -128,9 +126,9 @@ class Account(object):
""" """
contact_id = getattr(contact, "id", contact) contact_id = getattr(contact, "id", contact)
assert isinstance(contact_id, int) assert isinstance(contact_id, int)
chat_id = capi.lib.dc_create_chat_by_contact_id( chat_id = lib.dc_create_chat_by_contact_id(
self.dc_context, contact_id) self._dc_context.p, contact_id)
return Chat(self.dc_context, chat_id) return Chat(self._dc_context, chat_id)
def create_chat_by_message(self, message): def create_chat_by_message(self, message):
""" create or get an existing 1:1 chat object for the specified sender """ create or get an existing 1:1 chat object for the specified sender
@ -141,12 +139,12 @@ class Account(object):
""" """
msg_id = getattr(message, "id", message) msg_id = getattr(message, "id", message)
assert isinstance(msg_id, int) assert isinstance(msg_id, int)
chat_id = capi.lib.dc_create_chat_by_msg_id(self.dc_context, msg_id) chat_id = lib.dc_create_chat_by_msg_id(self._dc_context.p, msg_id)
return Chat(self.dc_context, chat_id) return Chat(self._dc_context, chat_id)
def get_message_by_id(self, msg_id): def get_message_by_id(self, msg_id):
""" return Message instance. """ """ return Message instance. """
return Message(self.dc_context, msg_id) return Message(self._dc_context, msg_id)
def mark_seen_messages(self, messages): def mark_seen_messages(self, messages):
""" mark the given set of messages as seen. """ mark the given set of messages as seen.
@ -158,22 +156,22 @@ class Account(object):
msg = getattr(msg, "id", msg) msg = getattr(msg, "id", msg)
arr.append(msg) arr.append(msg)
msg_ids = ffi.cast("uint32_t*", ffi.from_buffer(arr)) msg_ids = ffi.cast("uint32_t*", ffi.from_buffer(arr))
lib.dc_markseen_msgs(self.dc_context, msg_ids, len(messages)) lib.dc_markseen_msgs(self._dc_context.p, msg_ids, len(messages))
def start(self): def start(self):
""" configure this account object, start receiving events, """ configure this account object, start receiving events,
start IMAP/SMTP threads. """ start IMAP/SMTP threads. """
deltachat.set_context_callback(self.dc_context, self._process_event) deltachat.set_context_callback(self._dc_context.p, self._process_event)
capi.lib.dc_configure(self.dc_context) lib.dc_configure(self._dc_context.p)
self._threads.start() self._threads.start()
def shutdown(self): def shutdown(self):
""" shutdown IMAP/SMTP threads and stop receiving events""" """ shutdown IMAP/SMTP threads and stop receiving events"""
deltachat.clear_context_callback(self.dc_context) deltachat.clear_context_callback(self._dc_context.p)
self._threads.stop(wait=True) self._threads.stop(wait=True)
def _process_event(self, ctx, evt_name, data1, data2): def _process_event(self, ctx, evt_name, data1, data2):
assert ctx == self.dc_context assert ctx == self._dc_context.p
self._evlogger(evt_name, data1, data2) self._evlogger(evt_name, data1, data2)
method = getattr(self._evhandler, evt_name.lower(), None) method = getattr(self._evhandler, evt_name.lower(), None)
if method is not None: if method is not None:
@ -183,7 +181,7 @@ class Account(object):
class IOThreads: class IOThreads:
def __init__(self, dc_context): def __init__(self, dc_context):
self.dc_context = dc_context self._dc_context = dc_context
self._thread_quitflag = False self._thread_quitflag = False
self._name2thread = {} self._name2thread = {}
@ -201,8 +199,8 @@ class IOThreads:
def stop(self, wait=False): def stop(self, wait=False):
self._thread_quitflag = True self._thread_quitflag = True
capi.lib.dc_interrupt_imap_idle(self.dc_context) lib.dc_interrupt_imap_idle(self._dc_context.p)
capi.lib.dc_interrupt_smtp_idle(self.dc_context) lib.dc_interrupt_smtp_idle(self._dc_context.p)
if wait: if wait:
for name, thread in self._name2thread.items(): for name, thread in self._name2thread.items():
thread.join() thread.join()
@ -210,20 +208,20 @@ class IOThreads:
def imap_thread_run(self): def imap_thread_run(self):
print ("starting imap thread") print ("starting imap thread")
while not self._thread_quitflag: while not self._thread_quitflag:
capi.lib.dc_perform_imap_jobs(self.dc_context) lib.dc_perform_imap_jobs(self._dc_context.p)
capi.lib.dc_perform_imap_fetch(self.dc_context) lib.dc_perform_imap_fetch(self._dc_context.p)
capi.lib.dc_perform_imap_idle(self.dc_context) lib.dc_perform_imap_idle(self._dc_context.p)
def smtp_thread_run(self): def smtp_thread_run(self):
print ("starting smtp thread") print ("starting smtp thread")
while not self._thread_quitflag: while not self._thread_quitflag:
capi.lib.dc_perform_smtp_jobs(self.dc_context) lib.dc_perform_smtp_jobs(self._dc_context.p)
capi.lib.dc_perform_smtp_idle(self.dc_context) lib.dc_perform_smtp_idle(self._dc_context.p)
@attr.s @attr.s
class EventHandler(object): class EventHandler(object):
dc_context = attr.ib(validator=v.instance_of(ffi.CData)) _dc_context = attr.ib(validator=v.instance_of(DC_Context))
def read_url(self, url): def read_url(self, url):
try: try:
@ -239,7 +237,7 @@ class EventHandler(object):
if not isinstance(content, bytes): if not isinstance(content, bytes):
content = content.encode("utf8") content = content.encode("utf8")
# we need to return a fresh pointer that the core owns # we need to return a fresh pointer that the core owns
return capi.lib.dupstring_helper(content) return lib.dupstring_helper(content)
def dc_event_is_offline(self, data1, data2): def dc_event_is_offline(self, data1, data2):
return 0 # always online return 0 # always online
@ -247,11 +245,11 @@ class EventHandler(object):
class EventLogger: class EventLogger:
def __init__(self, dc_context, logid=None, debug=True): def __init__(self, dc_context, logid=None, debug=True):
self.dc_context = dc_context self._dc_context = dc_context
self._event_queue = Queue() self._event_queue = Queue()
self._debug = debug self._debug = debug
if logid is None: if logid is None:
logid = str(self.dc_context).strip(">").split()[-1] logid = str(self._dc_context.p).strip(">").split()[-1]
self.logid = logid self.logid = logid
self._timeout = None self._timeout = None

View file

@ -1,9 +1,9 @@
""" 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 from .cutil import convert_to_bytes_utf8, ffi_unicode, iter_array_and_unref
from .capi import ffi, lib from .capi import lib
from .types import cached_property, property_with_doc from .types import cached_property, property_with_doc
from .types import DC_Context, DC_Contact, DC_Chat, DC_Msg
import attr import attr
from attr import validators as v from attr import validators as v
@ -14,34 +14,30 @@ class Contact(object):
You obtain instances of it through :class:`deltachat.account.Account`. You obtain instances of it through :class:`deltachat.account.Account`.
""" """
dc_context = attr.ib(validator=v.instance_of(ffi.CData)) _dc_context = attr.ib(validator=v.instance_of(DC_Context))
id = attr.ib(validator=v.instance_of(int)) id = attr.ib(validator=v.instance_of(int))
@cached_property # only get it once because we only free it once @cached_property
def dc_contact_t(self): def _dc_contact(self):
return capi.lib.dc_get_contact(self.dc_context, self.id) return DC_Contact(lib.dc_get_contact(self._dc_context.p, self.id))
def __del__(self):
if lib is not None and hasattr(self, "_property_cache"):
lib.dc_contact_unref(self.dc_contact_t)
@property_with_doc @property_with_doc
def addr(self): def addr(self):
""" normalized e-mail address for this account. """ """ normalized e-mail address for this account. """
return ffi_unicode(capi.lib.dc_contact_get_addr(self.dc_contact_t)) return ffi_unicode(lib.dc_contact_get_addr(self._dc_contact.p))
@property_with_doc @property_with_doc
def display_name(self): def display_name(self):
""" display name for this contact. """ """ display name for this contact. """
return ffi_unicode(capi.lib.dc_contact_get_display_name(self.dc_contact_t)) return ffi_unicode(lib.dc_contact_get_display_name(self._dc_contact.p))
def is_blocked(self): def is_blocked(self):
""" Return True if the contact is blocked. """ """ Return True if the contact is blocked. """
return capi.lib.dc_contact_is_blocked(self.dc_contact_t) return lib.dc_contact_is_blocked(self._dc_contact.p)
def is_verified(self): def is_verified(self):
""" Return True if the contact is verified. """ """ Return True if the contact is verified. """
return capi.lib.dc_contact_is_verified(self.dc_contact_t) return lib.dc_contact_is_verified(self._dc_contact.p)
@attr.s @attr.s
@ -50,17 +46,12 @@ class Chat(object):
You obtain instances of it through :class:`deltachat.account.Account`. You obtain instances of it through :class:`deltachat.account.Account`.
""" """
_dc_context = attr.ib(validator=v.instance_of(DC_Context))
dc_context = attr.ib(validator=v.instance_of(ffi.CData))
id = attr.ib(validator=v.instance_of(int)) id = attr.ib(validator=v.instance_of(int))
@cached_property @cached_property
def dc_chat_t(self): def _dc_chat(self):
return capi.lib.dc_get_chat(self.dc_context, self.id) return DC_Chat(lib.dc_get_chat(self._dc_context.p, self.id))
def __del__(self):
if lib is not None and hasattr(self, "_property_cache"):
lib.dc_chat_unref(self.dc_chat_t)
def is_deaddrop(self): def is_deaddrop(self):
""" return true if this chat is a deaddrop chat. """ """ return true if this chat is a deaddrop chat. """
@ -73,31 +64,30 @@ class Chat(object):
:returns: the resulting :class:`Message` instance :returns: the resulting :class:`Message` instance
""" """
msg = convert_to_bytes_utf8(msg) msg = convert_to_bytes_utf8(msg)
print ("chat id", self.id) msg_id = lib.dc_send_text_msg(self._dc_context.p, self.id, msg)
msg_id = capi.lib.dc_send_text_msg(self.dc_context, self.id, msg) return Message(self._dc_context, msg_id)
return Message(self.dc_context, msg_id)
def get_messages(self): def get_messages(self):
""" return list of messages in this chat. """ return list of messages in this chat.
:returns: list of :class:`Message` objects for this chat. :returns: list of :class:`Message` objects for this chat.
""" """
dc_array_t = lib.dc_get_chat_msgs(self.dc_context, self.id, 0, 0) dc_array_t = lib.dc_get_chat_msgs(self._dc_context.p, self.id, 0, 0)
return list(iter_array_and_unref(dc_array_t, lambda x: Message(self.dc_context, x))) return list(iter_array_and_unref(dc_array_t, lambda x: Message(self._dc_context, x)))
def count_fresh_messages(self): def count_fresh_messages(self):
""" return number of fresh messages in this chat. """ return number of fresh messages in this chat.
:returns: number of fresh messages :returns: number of fresh messages
""" """
return lib.dc_get_fresh_msg_cnt(self.dc_context, self.id) return lib.dc_get_fresh_msg_cnt(self._dc_context.p, self.id)
def mark_noticed(self): def mark_noticed(self):
""" mark all messages in this chat as noticed. """ mark all messages in this chat as noticed.
Noticed messages are no longer fresh. Noticed messages are no longer fresh.
""" """
return lib.dc_marknoticed_chat(self.dc_context, self.id) return lib.dc_marknoticed_chat(self._dc_context.p, self.id)
@attr.s @attr.s
@ -107,21 +97,18 @@ class Message(object):
You obtain instances of it through :class:`deltachat.account.Account` or You obtain instances of it through :class:`deltachat.account.Account` or
:class:`Chat`. :class:`Chat`.
""" """
dc_context = attr.ib(validator=v.instance_of(ffi.CData)) _dc_context = attr.ib(validator=v.instance_of(DC_Context))
id = attr.ib(validator=v.instance_of(int)) id = attr.ib(validator=v.instance_of(int))
@cached_property @cached_property
def dc_msg_t(self): def _dc_msg(self):
return capi.lib.dc_get_msg(self.dc_context, self.id) return DC_Msg(lib.dc_get_msg(self._dc_context.p, self.id))
def _refresh(self): def _refresh(self):
if hasattr(self, "_property_cache"): try:
lib.dc_msg_unref(self.dc_msg_t) del self._dc_msg
self._property_cache.clear() except KeyError:
pass
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): def get_state(self):
""" get the message in/out state. """ get the message in/out state.
@ -133,7 +120,7 @@ class Message(object):
@property_with_doc @property_with_doc
def text(self): def text(self):
"""unicode representation. """ """unicode representation. """
return ffi_unicode(capi.lib.dc_msg_get_text(self.dc_msg_t)) return ffi_unicode(lib.dc_msg_get_text(self._dc_msg.p))
@property @property
def chat(self): def chat(self):
@ -141,8 +128,8 @@ class Message(object):
:returns: :class:`Chat` object :returns: :class:`Chat` object
""" """
chat_id = capi.lib.dc_msg_get_chat_id(self.dc_msg_t) chat_id = lib.dc_msg_get_chat_id(self._dc_msg.p)
return Chat(self.dc_context, chat_id) return Chat(self._dc_context, chat_id)
@attr.s @attr.s
@ -154,7 +141,7 @@ class MessageState(object):
@property @property
def _msgstate(self): def _msgstate(self):
self.message._refresh() self.message._refresh()
return lib.dc_msg_get_state(self.message.dc_msg_t) return lib.dc_msg_get_state(self.message._dc_msg.p)
def is_in_fresh(self): def is_in_fresh(self):
""" return True if Message is incoming fresh message (un-noticed). """ return True if Message is incoming fresh message (un-noticed).

View file

@ -1,9 +1,36 @@
from .capi import lib
def property_with_doc(f): def property_with_doc(f):
return property(f, None, None, f.__doc__) return property(f, None, None, f.__doc__)
class _UnrefStruct(object):
def __init__(self, c_obj):
self.p = c_obj
def __del__(self):
obj = self.__dict__.pop("p", None)
if lib is not None and obj is not None:
self._unref(obj)
class DC_Context(_UnrefStruct):
_unref = lib.dc_context_unref
class DC_Contact(_UnrefStruct):
_unref = lib.dc_contact_unref
class DC_Chat(_UnrefStruct):
_unref = lib.dc_chat_unref
class DC_Msg(_UnrefStruct):
_unref = lib.dc_msg_unref
# copied over unmodified from # copied over unmodified from
# https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py # https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py
@ -22,4 +49,9 @@ def cached_property(f):
def set(self, val): def set(self, val):
propcache = self.__dict__.setdefault("_property_cache", {}) propcache = self.__dict__.setdefault("_property_cache", {})
propcache[f] = val propcache[f] = val
return property(get, set)
def fdel(self):
propcache = self.__dict__.setdefault("_property_cache", {})
del propcache[f]
return property(get, set, fdel)

View file

@ -48,7 +48,6 @@ class TestOfflineAccount:
assert chat2.id == chat.id assert chat2.id == chat.id
assert chat == chat2 assert chat == chat2
assert not (chat != chat2) assert not (chat != chat2)
assert chat.dc_chat_t
def test_message(self, acfactory): def test_message(self, acfactory):
ac1 = acfactory.get_offline_account() ac1 = acfactory.get_offline_account()
@ -77,14 +76,14 @@ class TestOnlineAccount:
imap_ok = True imap_ok = True
if evt_name == "DC_EVENT_SMTP_CONNECTED": if evt_name == "DC_EVENT_SMTP_CONNECTED":
smtp_ok = True smtp_ok = True
print("** IMAP and SMTP logins successful", account.dc_context) print("** IMAP and SMTP logins successful", account)
def wait_configuration_progress(self, account, target): def wait_configuration_progress(self, account, target):
while 1: while 1:
evt_name, data1, data2 = \ evt_name, data1, data2 = \
account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS") account._evlogger.get_matching("DC_EVENT_CONFIGURE_PROGRESS")
if data1 >= target: if data1 >= target:
print("** CONFIG PROGRESS {}".format(target), account.dc_context) print("** CONFIG PROGRESS {}".format(target), account)
break break
def test_selfcontact(self, acfactory): def test_selfcontact(self, acfactory):

View file

@ -1,5 +1,6 @@
from __future__ import print_function from __future__ import print_function
from deltachat import capi import pytest
from deltachat import capi, Account
def test_empty_context(): def test_empty_context():
@ -7,6 +8,12 @@ def test_empty_context():
capi.lib.dc_close(ctx) capi.lib.dc_close(ctx)
def test_wrong_db(tmpdir):
tmpdir.join("hello.db").write("123")
with pytest.raises(ValueError):
Account(db_path=tmpdir.strpath)
def test_event_defines(): def test_event_defines():
assert capi.lib.DC_EVENT_INFO == 100 assert capi.lib.DC_EVENT_INFO == 100
assert capi.lib.DC_CONTACT_ID_SELF assert capi.lib.DC_CONTACT_ID_SELF