fix python lifecycles so that termination works

This commit is contained in:
holger krekel
2020-05-23 10:17:56 +02:00
parent 05f79c1c01
commit e55dc2213a
10 changed files with 102 additions and 115 deletions

View File

@@ -63,6 +63,10 @@ def run_cmdline(argv=None, account_plugins=None):
log = events.FFIEventLogger(ac, "bot") log = events.FFIEventLogger(ac, "bot")
ac.add_account_plugin(log) ac.add_account_plugin(log)
for plugin in account_plugins or []:
print("adding plugin", plugin)
ac.add_account_plugin(plugin)
if not ac.is_configured(): if not ac.is_configured():
assert args.email and args.password, ( assert args.email and args.password, (
"you must specify --email and --password once to configure this database/account" "you must specify --email and --password once to configure this database/account"
@@ -72,12 +76,11 @@ def run_cmdline(argv=None, account_plugins=None):
ac.set_config("mvbox_move", "0") ac.set_config("mvbox_move", "0")
ac.set_config("mvbox_watch", "0") ac.set_config("mvbox_watch", "0")
ac.set_config("sentbox_watch", "0") ac.set_config("sentbox_watch", "0")
ac.configure()
for plugin in account_plugins or []: ac.wait_configure_finish()
ac.add_account_plugin(plugin)
# start IO threads and configure if neccessary # start IO threads and configure if neccessary
ac.start() ac.start_io()
print("{}: waiting for message".format(ac.get_config("addr"))) print("{}: waiting for message".format(ac.get_config("addr")))

View File

@@ -1,7 +1,6 @@
""" Account class implementation. """ """ Account class implementation. """
from __future__ import print_function from __future__ import print_function
import atexit
from contextlib import contextmanager from contextlib import contextmanager
from email.utils import parseaddr from email.utils import parseaddr
from threading import Event from threading import Event
@@ -48,7 +47,7 @@ class Account(object):
self._dc_context = ffi.gc( self._dc_context = ffi.gc(
lib.dc_context_new(as_dc_charpointer(os_name), db_path, ffi.NULL), lib.dc_context_new(as_dc_charpointer(os_name), db_path, ffi.NULL),
_destroy_dc_context, lib.dc_context_unref,
) )
if self._dc_context == ffi.NULL: if self._dc_context == ffi.NULL:
raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path)) raise ValueError("Could not dc_context_new: {} {}".format(os_name, db_path))
@@ -58,7 +57,6 @@ class Account(object):
self._shutdown_event = Event() self._shutdown_event = Event()
self._event_thread = EventThread(self) self._event_thread = EventThread(self)
self._configkeys = self.get_config("sys.config_keys").split() self._configkeys = self.get_config("sys.config_keys").split()
atexit.register(self.shutdown)
hook.dc_account_init(account=self) hook.dc_account_init(account=self)
def disable_logging(self): def disable_logging(self):
@@ -69,8 +67,8 @@ class Account(object):
""" re-enable logging. """ """ re-enable logging. """
self._logging = True self._logging = True
# def __del__(self): def __del__(self):
# self.shutdown() self.shutdown()
def log(self, msg): def log(self, msg):
if self._logging: if self._logging:
@@ -241,7 +239,7 @@ class Account(object):
:returns: True if deletion succeeded (contact was deleted) :returns: True if deletion succeeded (contact was deleted)
""" """
contact_id = contact.id contact_id = contact.id
assert contact._dc_context == self._dc_context assert contact.account == self
assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL assert contact_id > const.DC_CHAT_ID_LAST_SPECIAL
return bool(lib.dc_delete_contact(self._dc_context, contact_id)) return bool(lib.dc_delete_contact(self._dc_context, contact_id))
@@ -289,7 +287,7 @@ class Account(object):
:returns: a :class:`deltachat.chat.Chat` object. :returns: a :class:`deltachat.chat.Chat` object.
""" """
if hasattr(contact, "id"): if hasattr(contact, "id"):
if contact._dc_context != self._dc_context: if contact.account != self:
raise ValueError("Contact belongs to a different Account") raise ValueError("Contact belongs to a different Account")
contact_id = contact.id contact_id = contact.id
else: else:
@@ -309,7 +307,7 @@ class Account(object):
:returns: a :class:`deltachat.chat.Chat` object. :returns: a :class:`deltachat.chat.Chat` object.
""" """
if hasattr(message, "id"): if hasattr(message, "id"):
if self._dc_context != message._dc_context: if message.account != self:
raise ValueError("Message belongs to a different Account") raise ValueError("Message belongs to a different Account")
msg_id = message.id msg_id = message.id
else: else:
@@ -557,13 +555,14 @@ class Account(object):
""" Stop ongoing securejoin, configuration or other core jobs. """ """ Stop ongoing securejoin, configuration or other core jobs. """
lib.dc_stop_ongoing_process(self._dc_context) lib.dc_stop_ongoing_process(self._dc_context)
def start(self): def start_io(self):
""" start this account's IO scheduling (Rust-core async scheduler) """ start this account's IO scheduling (Rust-core async scheduler)
If this account is not configured but "addr" and "mail_pw" config If this account is not configured an Exception is raised.
values are set, dc_configure() will be called. You need to call account.configure() and account.wait_configure_finish()
before.
You may call `wait_shutdown` or `shutdown` after the You may call `stop_scheduler`, `wait_shutdown` or `shutdown` after the
account is started. account is started.
:raises MissingCredentials: if `addr` and `mail_pw` values are not set. :raises MissingCredentials: if `addr` and `mail_pw` values are not set.
@@ -572,8 +571,7 @@ class Account(object):
:returns: None (account is configured and with io-scheduling running) :returns: None (account is configured and with io-scheduling running)
""" """
if not self.is_configured(): if not self.is_configured():
self.configure() raise ValueError("account not configured, cannot start io")
self.wait_configure_finish()
lib.dc_start_io(self._dc_context) lib.dc_start_io(self._dc_context)
def configure(self): def configure(self):
@@ -601,13 +599,13 @@ class Account(object):
""" wait until shutdown of this account has completed. """ """ wait until shutdown of this account has completed. """
self._shutdown_event.wait() self._shutdown_event.wait()
def stop_scheduler(self): def stop_io(self):
""" stop core scheduler if it is running. """ """ stop core IO scheduler if it is running. """
self.log("stop_ongoing") self.log("stop_ongoing")
self.stop_ongoing() self.stop_ongoing()
if bool(lib.dc_is_io_running(self._dc_context)): if bool(lib.dc_is_io_running(self._dc_context)):
self.log("context_shutdown (stop core scheduler)") self.log("dc_stop_io (stop core IO scheduler)")
lib.dc_stop_io(self._dc_context) lib.dc_stop_io(self._dc_context)
else: else:
self.log("stop_scheduler called on non-running context") self.log("stop_scheduler called on non-running context")
@@ -615,13 +613,12 @@ class Account(object):
def shutdown(self): def shutdown(self):
""" shutdown and destroy account (stop callback thread, close and remove """ shutdown and destroy account (stop callback thread, close and remove
underlying dc_context).""" underlying dc_context)."""
dc_context = self._dc_context if self._dc_context is None:
if dc_context is None:
return return
self.stop_scheduler() self.stop_io()
self.log("remove dc_context") self.log("remove dc_context references")
# the dc_context_unref triggers get_next_event to return ffi.NULL # the dc_context_unref triggers get_next_event to return ffi.NULL
# which in turns makes the event thread finish execution # which in turns makes the event thread finish execution
self._dc_context = None self._dc_context = None
@@ -629,18 +626,13 @@ class Account(object):
self.log("wait for event thread to finish") self.log("wait for event thread to finish")
self._event_thread.wait() self._event_thread.wait()
atexit.unregister(self.shutdown)
self._shutdown_event.set() self._shutdown_event.set()
hook = hookspec.Global._get_plugin_manager().hook hook = hookspec.Global._get_plugin_manager().hook
hook.dc_account_after_shutdown(account=self, dc_context=dc_context) hook.dc_account_after_shutdown(account=self)
self.log("shutdown finished") self.log("shutdown finished")
def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref):
# destructor for dc_context
dc_context_unref(dc_context)
class ScannedQRCode: class ScannedQRCode:
def __init__(self, dc_lot): def __init__(self, dc_lot):
self._dc_lot = dc_lot self._dc_lot = dc_lot

View File

@@ -19,12 +19,11 @@ class Chat(object):
def __init__(self, account, id): def __init__(self, account, id):
self.account = account self.account = account
self._dc_context = account._dc_context
self.id = id self.id = id
def __eq__(self, other): def __eq__(self, other):
return self.id == getattr(other, "id", None) and \ return self.id == getattr(other, "id", None) and \
self._dc_context == getattr(other, "_dc_context", None) self.account._dc_context == other.account._dc_context
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
@@ -35,7 +34,7 @@ class Chat(object):
@property @property
def _dc_chat(self): def _dc_chat(self):
return ffi.gc( return ffi.gc(
lib.dc_get_chat(self._dc_context, self.id), lib.dc_get_chat(self.account._dc_context, self.id),
lib.dc_chat_unref lib.dc_chat_unref
) )
@@ -47,7 +46,7 @@ class Chat(object):
- does not delete messages on server - does not delete messages on server
- the chat or contact is not blocked, new message will arrive - the chat or contact is not blocked, new message will arrive
""" """
lib.dc_delete_chat(self._dc_context, self.id) lib.dc_delete_chat(self.account._dc_context, self.id)
# ------ chat status/metadata API ------------------------------ # ------ chat status/metadata API ------------------------------
@@ -105,7 +104,7 @@ class Chat(object):
:returns: None :returns: None
""" """
name = as_dc_charpointer(name) name = as_dc_charpointer(name)
return lib.dc_set_chat_name(self._dc_context, self.id, name) return lib.dc_set_chat_name(self.account._dc_context, self.id, name)
def mute(self, duration=None): def mute(self, duration=None):
""" mutes the chat """ mutes the chat
@@ -117,7 +116,7 @@ class Chat(object):
mute_duration = -1 mute_duration = -1
else: else:
mute_duration = duration mute_duration = duration
ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, mute_duration) ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, mute_duration)
if not bool(ret): if not bool(ret):
raise ValueError("Call to dc_set_chat_mute_duration failed") raise ValueError("Call to dc_set_chat_mute_duration failed")
@@ -126,7 +125,7 @@ class Chat(object):
:returns: None :returns: None
""" """
ret = lib.dc_set_chat_mute_duration(self._dc_context, self.id, 0) ret = lib.dc_set_chat_mute_duration(self.account._dc_context, self.id, 0)
if not bool(ret): if not bool(ret):
raise ValueError("Failed to unmute chat") raise ValueError("Failed to unmute chat")
@@ -152,7 +151,7 @@ class Chat(object):
in a second channel (typically used by mobiles with QRcode-show + scan UX) in a second channel (typically used by mobiles with QRcode-show + scan UX)
where account.join_with_qrcode(qr) needs to be called. where account.join_with_qrcode(qr) needs to be called.
""" """
res = lib.dc_get_securejoin_qr(self._dc_context, self.id) res = lib.dc_get_securejoin_qr(self.account._dc_context, self.id)
return from_dc_charpointer(res) return from_dc_charpointer(res)
# ------ chat messaging API ------------------------------ # ------ chat messaging API ------------------------------
@@ -174,7 +173,7 @@ class Chat(object):
assert msg.id != 0 assert msg.id != 0
# get a fresh copy of dc_msg, the core needs it # get a fresh copy of dc_msg, the core needs it
msg = Message.from_db(self.account, msg.id) msg = Message.from_db(self.account, msg.id)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
if sent_id == 0: if sent_id == 0:
raise ValueError("message could not be sent") raise ValueError("message could not be sent")
# modify message in place to avoid bad state for the caller # modify message in place to avoid bad state for the caller
@@ -189,7 +188,7 @@ class Chat(object):
:returns: the resulting :class:`deltachat.message.Message` instance :returns: the resulting :class:`deltachat.message.Message` instance
""" """
msg = as_dc_charpointer(text) msg = as_dc_charpointer(text)
msg_id = lib.dc_send_text_msg(self._dc_context, self.id, msg) msg_id = lib.dc_send_text_msg(self.account._dc_context, self.id, msg)
if msg_id == 0: if msg_id == 0:
raise ValueError("message could not be send, does chat exist?") raise ValueError("message could not be send, does chat exist?")
return Message.from_db(self.account, msg_id) return Message.from_db(self.account, msg_id)
@@ -204,7 +203,7 @@ class Chat(object):
""" """
msg = Message.new_empty(self.account, view_type="file") msg = Message.new_empty(self.account, view_type="file")
msg.set_file(path, mime_type) msg.set_file(path, mime_type)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
if sent_id == 0: if sent_id == 0:
raise ValueError("message could not be sent") raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id) return Message.from_db(self.account, sent_id)
@@ -219,7 +218,7 @@ class Chat(object):
mime_type = mimetypes.guess_type(path)[0] mime_type = mimetypes.guess_type(path)[0]
msg = Message.new_empty(self.account, view_type="image") msg = Message.new_empty(self.account, view_type="image")
msg.set_file(path, mime_type) msg.set_file(path, mime_type)
sent_id = lib.dc_send_msg(self._dc_context, self.id, msg._dc_msg) sent_id = lib.dc_send_msg(self.account._dc_context, self.id, msg._dc_msg)
if sent_id == 0: if sent_id == 0:
raise ValueError("message could not be sent") raise ValueError("message could not be sent")
return Message.from_db(self.account, sent_id) return Message.from_db(self.account, sent_id)
@@ -230,7 +229,7 @@ class Chat(object):
:param msg: the message to be prepared. :param msg: the message to be prepared.
:returns: :class:`deltachat.message.Message` instance. :returns: :class:`deltachat.message.Message` instance.
""" """
msg_id = lib.dc_prepare_msg(self._dc_context, self.id, msg._dc_msg) msg_id = lib.dc_prepare_msg(self.account._dc_context, self.id, msg._dc_msg)
if msg_id == 0: if msg_id == 0:
raise ValueError("message could not be prepared") raise ValueError("message could not be prepared")
# invalidate passed in message which is not safe to use anymore # invalidate passed in message which is not safe to use anymore
@@ -266,7 +265,7 @@ class Chat(object):
msg = Message.from_db(self.account, message.id) msg = Message.from_db(self.account, message.id)
# pass 0 as chat-id because core-docs say it's ok when out-preparing # pass 0 as chat-id because core-docs say it's ok when out-preparing
sent_id = lib.dc_send_msg(self._dc_context, 0, msg._dc_msg) sent_id = lib.dc_send_msg(self.account._dc_context, 0, msg._dc_msg)
if sent_id == 0: if sent_id == 0:
raise ValueError("message could not be sent") raise ValueError("message could not be sent")
assert sent_id == msg.id assert sent_id == msg.id
@@ -280,9 +279,9 @@ class Chat(object):
:returns: None :returns: None
""" """
if message is None: if message is None:
lib.dc_set_draft(self._dc_context, self.id, ffi.NULL) lib.dc_set_draft(self.account._dc_context, self.id, ffi.NULL)
else: else:
lib.dc_set_draft(self._dc_context, self.id, message._dc_msg) lib.dc_set_draft(self.account._dc_context, self.id, message._dc_msg)
def get_draft(self): def get_draft(self):
""" get draft message for this chat. """ get draft message for this chat.
@@ -290,7 +289,7 @@ class Chat(object):
:param message: a :class:`Message` instance :param message: a :class:`Message` instance
:returns: Message object or None (if no draft available) :returns: Message object or None (if no draft available)
""" """
x = lib.dc_get_draft(self._dc_context, self.id) x = lib.dc_get_draft(self.account._dc_context, self.id)
if x == ffi.NULL: if x == ffi.NULL:
return None return None
dc_msg = ffi.gc(x, lib.dc_msg_unref) dc_msg = ffi.gc(x, lib.dc_msg_unref)
@@ -302,7 +301,7 @@ class Chat(object):
:returns: list of :class:`deltachat.message.Message` objects for this chat. :returns: list of :class:`deltachat.message.Message` objects for this chat.
""" """
dc_array = ffi.gc( dc_array = ffi.gc(
lib.dc_get_chat_msgs(self._dc_context, self.id, 0, 0), lib.dc_get_chat_msgs(self.account._dc_context, self.id, 0, 0),
lib.dc_array_unref lib.dc_array_unref
) )
return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x))) return list(iter_array(dc_array, lambda x: Message.from_db(self.account, x)))
@@ -312,18 +311,18 @@ class Chat(object):
: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.account._dc_context, 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.account._dc_context, self.id)
def get_summary(self): def get_summary(self):
""" return dictionary with summary information. """ """ return dictionary with summary information. """
dc_res = lib.dc_chat_get_info_json(self._dc_context, self.id) dc_res = lib.dc_chat_get_info_json(self.account._dc_context, self.id)
s = from_dc_charpointer(dc_res) s = from_dc_charpointer(dc_res)
return json.loads(s) return json.loads(s)
@@ -336,7 +335,7 @@ class Chat(object):
:raises ValueError: if contact could not be added :raises ValueError: if contact could not be added
:returns: None :returns: None
""" """
ret = lib.dc_add_contact_to_chat(self._dc_context, self.id, contact.id) ret = lib.dc_add_contact_to_chat(self.account._dc_context, self.id, contact.id)
if ret != 1: if ret != 1:
raise ValueError("could not add contact {!r} to chat".format(contact)) raise ValueError("could not add contact {!r} to chat".format(contact))
@@ -347,7 +346,7 @@ class Chat(object):
:raises ValueError: if contact could not be removed :raises ValueError: if contact could not be removed
:returns: None :returns: None
""" """
ret = lib.dc_remove_contact_from_chat(self._dc_context, self.id, contact.id) ret = lib.dc_remove_contact_from_chat(self.account._dc_context, self.id, contact.id)
if ret != 1: if ret != 1:
raise ValueError("could not remove contact {!r} from chat".format(contact)) raise ValueError("could not remove contact {!r} from chat".format(contact))
@@ -359,7 +358,7 @@ class Chat(object):
""" """
from .contact import Contact from .contact import Contact
dc_array = ffi.gc( dc_array = ffi.gc(
lib.dc_get_chat_contacts(self._dc_context, self.id), lib.dc_get_chat_contacts(self.account._dc_context, self.id),
lib.dc_array_unref lib.dc_array_unref
) )
return list(iter_array( return list(iter_array(
@@ -378,7 +377,7 @@ class Chat(object):
""" """
assert os.path.exists(img_path), img_path assert os.path.exists(img_path), img_path
p = as_dc_charpointer(img_path) p = as_dc_charpointer(img_path)
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, p) res = lib.dc_set_chat_profile_image(self.account._dc_context, self.id, p)
if res != 1: if res != 1:
raise ValueError("Setting Profile Image {!r} failed".format(p)) raise ValueError("Setting Profile Image {!r} failed".format(p))
@@ -391,7 +390,7 @@ class Chat(object):
:raises ValueError: if profile image could not be reset :raises ValueError: if profile image could not be reset
:returns: None :returns: None
""" """
res = lib.dc_set_chat_profile_image(self._dc_context, self.id, ffi.NULL) res = lib.dc_set_chat_profile_image(self.account._dc_context, self.id, ffi.NULL)
if res != 1: if res != 1:
raise ValueError("Removing Profile Image failed") raise ValueError("Removing Profile Image failed")
@@ -421,7 +420,7 @@ class Chat(object):
"""return True if this chat has location-sending enabled currently. """return True if this chat has location-sending enabled currently.
:returns: True if location sending is enabled. :returns: True if location sending is enabled.
""" """
return lib.dc_is_sending_locations_to_chat(self._dc_context, self.id) return lib.dc_is_sending_locations_to_chat(self.account._dc_context, self.id)
def is_archived(self): def is_archived(self):
"""return True if this chat is archived. """return True if this chat is archived.
@@ -434,7 +433,7 @@ class Chat(object):
all subsequent messages will carry a location with them. all subsequent messages will carry a location with them.
""" """
lib.dc_send_locations_to_chat(self._dc_context, self.id, seconds) lib.dc_send_locations_to_chat(self.account._dc_context, self.id, seconds)
def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None): def get_locations(self, contact=None, timestamp_from=None, timestamp_to=None):
"""return list of locations for the given contact in the given timespan. """return list of locations for the given contact in the given timespan.
@@ -458,7 +457,7 @@ class Chat(object):
else: else:
contact_id = contact.id contact_id = contact.id
dc_array = lib.dc_get_locations(self._dc_context, self.id, contact_id, time_from, time_to) dc_array = lib.dc_get_locations(self.account._dc_context, self.id, contact_id, time_from, time_to)
return [ return [
Location( Location(
latitude=lib.dc_array_get_latitude(dc_array, i), latitude=lib.dc_array_get_latitude(dc_array, i),

View File

@@ -12,22 +12,21 @@ class Contact(object):
""" """
def __init__(self, account, id): def __init__(self, account, id):
self.account = account self.account = account
self._dc_context = account._dc_context
self.id = id self.id = id
def __eq__(self, other): def __eq__(self, other):
return self._dc_context == other._dc_context and self.id == other.id return self.account._dc_context == other.account._dc_context and self.id == other.id
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
def __repr__(self): def __repr__(self):
return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self._dc_context) return "<Contact id={} addr={} dc_context={}>".format(self.id, self.addr, self.account._dc_context)
@property @property
def _dc_contact(self): def _dc_contact(self):
return ffi.gc( return ffi.gc(
lib.dc_get_contact(self._dc_context, self.id), lib.dc_get_contact(self.account._dc_context, self.id),
lib.dc_contact_unref lib.dc_contact_unref
) )

View File

@@ -133,8 +133,8 @@ class EventThread(threading.Thread):
""" """
def __init__(self, account): def __init__(self, account):
self.account = account self.account = account
self._dc_context = account._dc_context
super(EventThread, self).__init__(name="events") super(EventThread, self).__init__(name="events")
self.setDaemon(True)
self.start() self.start()
@contextmanager @contextmanager
@@ -157,7 +157,7 @@ class EventThread(threading.Thread):
def _inner_run(self): def _inner_run(self):
event_emitter = ffi.gc( event_emitter = ffi.gc(
lib.dc_get_event_emitter(self._dc_context), lib.dc_get_event_emitter(self.account._dc_context),
lib.dc_event_emitter_unref, lib.dc_event_emitter_unref,
) )
while 1: while 1:
@@ -176,11 +176,15 @@ class EventThread(threading.Thread):
lib.dc_event_unref(event) lib.dc_event_unref(event)
ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2) ffi_event = FFIEvent(name=evt_name, data1=data1, data2=data2)
try:
self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event) self.account._pm.hook.ac_process_ffi_event(account=self, ffi_event=ffi_event)
for name, kwargs in self._map_ffi_event(ffi_event): for name, kwargs in self._map_ffi_event(ffi_event):
# self.account.log("calling hook name={} kwargs={}".format(name, kwargs)) self.account.log("calling hook name={} kwargs={}".format(name, kwargs))
hook = getattr(self.account._pm.hook, name) hook = getattr(self.account._pm.hook, name)
hook(**kwargs) hook(**kwargs)
except Exception:
if self.account._dc_context is not None:
raise
def _map_ffi_event(self, ffi_event): def _map_ffi_event(self, ffi_event):
name = ffi_event.name name = ffi_event.name

View File

@@ -89,5 +89,5 @@ class Global:
""" called when `Account::__init__()` function starts executing. """ """ called when `Account::__init__()` function starts executing. """
@global_hookspec @global_hookspec
def dc_account_after_shutdown(self, account, dc_context): def dc_account_after_shutdown(self, account):
""" Called after the account has been shutdown. """ """ Called after the account has been shutdown. """

View File

@@ -16,8 +16,7 @@ class Message(object):
""" """
def __init__(self, account, dc_msg): def __init__(self, account, dc_msg):
self.account = account self.account = account
self._dc_context = account._dc_context assert isinstance(self.account._dc_context, ffi.CData)
assert isinstance(self._dc_context, ffi.CData)
assert isinstance(dc_msg, ffi.CData) assert isinstance(dc_msg, ffi.CData)
assert dc_msg != ffi.NULL assert dc_msg != ffi.NULL
self._dc_msg = dc_msg self._dc_msg = dc_msg
@@ -58,7 +57,7 @@ class Message(object):
""" """
self.account.create_chat_by_message(self) self.account.create_chat_by_message(self)
self._dc_msg = ffi.gc( self._dc_msg = ffi.gc(
lib.dc_get_msg(self._dc_context, self.id), lib.dc_get_msg(self.account._dc_context, self.id),
lib.dc_msg_unref lib.dc_msg_unref
) )
@@ -118,12 +117,12 @@ class Message(object):
The text is multiline and may contain eg. the raw text of the message. The text is multiline and may contain eg. the raw text of the message.
""" """
return from_dc_charpointer(lib.dc_get_msg_info(self._dc_context, self.id)) return from_dc_charpointer(lib.dc_get_msg_info(self.account._dc_context, self.id))
def continue_key_transfer(self, setup_code): def continue_key_transfer(self, setup_code):
""" extract key and use it as primary key for this account. """ """ extract key and use it as primary key for this account. """
res = lib.dc_continue_key_transfer( res = lib.dc_continue_key_transfer(
self._dc_context, self.account._dc_context,
self.id, self.id,
as_dc_charpointer(setup_code) as_dc_charpointer(setup_code)
) )
@@ -158,7 +157,7 @@ class Message(object):
:returns: email-mime message object (with headers only, no body). :returns: email-mime message object (with headers only, no body).
""" """
import email.parser import email.parser
mime_headers = lib.dc_get_mime_headers(self._dc_context, self.id) mime_headers = lib.dc_get_mime_headers(self.account._dc_context, self.id)
if mime_headers: if mime_headers:
s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref)) s = ffi.string(ffi.gc(mime_headers, lib.dc_str_unref))
if isinstance(s, bytes): if isinstance(s, bytes):
@@ -201,7 +200,7 @@ class Message(object):
else: else:
# load message from db to get a fresh/current state # load message from db to get a fresh/current state
dc_msg = ffi.gc( dc_msg = ffi.gc(
lib.dc_get_msg(self._dc_context, self.id), lib.dc_get_msg(self.account._dc_context, self.id),
lib.dc_msg_unref lib.dc_msg_unref
) )
return lib.dc_msg_get_state(dc_msg) return lib.dc_msg_get_state(dc_msg)

View File

@@ -103,7 +103,6 @@ def pytest_report_header(config, startdir):
t = tempfile.mktemp() t = tempfile.mktemp()
m = MonkeyPatch() m = MonkeyPatch()
try: try:
m.setattr(sys.stdout, "write", lambda x: len(x))
ac = Account(t) ac = Account(t)
info = ac.get_info() info = ac.get_info()
ac.shutdown() ac.shutdown()
@@ -310,16 +309,16 @@ def acfactory(pytestconfig, tmpdir, request, session_liveconfig, data):
ac1 = self.get_online_configuring_account( ac1 = self.get_online_configuring_account(
pre_generated_key=pre_generated_key, mvbox=mvbox, move=move) pre_generated_key=pre_generated_key, mvbox=mvbox, move=move)
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
return ac1 return ac1
def get_two_online_accounts(self, move=False, quiet=False): def get_two_online_accounts(self, move=False, quiet=False):
ac1 = self.get_online_configuring_account(move=True, quiet=quiet) ac1 = self.get_online_configuring_account(move=True, quiet=quiet)
ac2 = self.get_online_configuring_account(quiet=quiet) ac2 = self.get_online_configuring_account(quiet=quiet)
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
return ac1, ac2 return ac1, ac2
def clone_online_account(self, account, pre_generated_key=True): def clone_online_account(self, account, pre_generated_key=True):
@@ -394,6 +393,7 @@ class BotProcess:
break break
line = line.strip() line = line.strip()
self.stdout_queue.put(line) self.stdout_queue.put(line)
print("bot-stdout: ", line)
finally: finally:
self.stdout_queue.put(None) self.stdout_queue.put(None)

View File

@@ -533,9 +533,9 @@ class TestOnlineAccount:
# rsa key gen can be slow especially on CI, adjust timeout # rsa key gen can be slow especially on CI, adjust timeout
ac1._evtracker.set_timeout(120) ac1._evtracker.set_timeout(120)
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
chat = self.get_chat(ac1, ac2, both_created=True) chat = self.get_chat(ac1, ac2, both_created=True)
lp.sec("ac1: send unencrypted message to ac2") lp.sec("ac1: send unencrypted message to ac2")
@@ -591,11 +591,11 @@ class TestOnlineAccount:
ac1_clone = acfactory.clone_online_account(ac1) ac1_clone = acfactory.clone_online_account(ac1)
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
ac1_clone.wait_configure_finish() ac1_clone.wait_configure_finish()
ac1_clone.start() ac1_clone.start_io()
chat = self.get_chat(ac1, ac2) chat = self.get_chat(ac1, ac2)
@@ -700,11 +700,11 @@ class TestOnlineAccount:
lp.sec("ac2: waiting for configuration") lp.sec("ac2: waiting for configuration")
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
lp.sec("ac1: waiting for configuration") lp.sec("ac1: waiting for configuration")
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
lp.sec("ac1: send message and wait for ac2 to receive it") lp.sec("ac1: send message and wait for ac2 to receive it")
chat = self.get_chat(ac1, ac2) chat = self.get_chat(ac1, ac2)
@@ -717,9 +717,9 @@ class TestOnlineAccount:
ac1 = acfactory.get_online_configuring_account() ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True) ac2 = acfactory.get_online_configuring_account(mvbox=True, move=True)
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
chat = self.get_chat(ac1, ac2) chat = self.get_chat(ac1, ac2)
chat.send_text("message1") chat.send_text("message1")
ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED") ev = ac2._evtracker.get_matching("DC_EVENT_INCOMING_MSG|DC_EVENT_MSGS_CHANGED")
@@ -731,9 +731,9 @@ class TestOnlineAccount:
ac1.set_config("bcc_self", "1") ac1.set_config("bcc_self", "1")
ac2 = acfactory.get_online_configuring_account() ac2 = acfactory.get_online_configuring_account()
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
chat = self.get_chat(ac1, ac2) chat = self.get_chat(ac1, ac2)
chat.send_text("message1") chat.send_text("message1")
@@ -1162,9 +1162,9 @@ class TestOnlineAccount:
ac1 = acfactory.get_online_configuring_account() ac1 = acfactory.get_online_configuring_account()
ac2 = acfactory.clone_online_account(ac1) ac2 = acfactory.clone_online_account(ac1)
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
lp.sec("trigger ac setup message and return setupcode") lp.sec("trigger ac setup message and return setupcode")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
@@ -1188,9 +1188,9 @@ class TestOnlineAccount:
ac2 = acfactory.clone_online_account(ac1) ac2 = acfactory.clone_online_account(ac1)
ac2._evtracker.set_timeout(30) ac2._evtracker.set_timeout(30)
ac2.wait_configure_finish() ac2.wait_configure_finish()
ac2.start() ac2.start_io()
ac1.wait_configure_finish() ac1.wait_configure_finish()
ac1.start() ac1.start_io()
lp.sec("trigger ac setup message but ignore") lp.sec("trigger ac setup message but ignore")
assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"] assert ac1.get_info()["fingerprint"] != ac2.get_info()["fingerprint"]
@@ -1502,7 +1502,7 @@ class TestGroupStressTests:
accounts = [acfactory.get_online_configuring_account() for i in range(5)] accounts = [acfactory.get_online_configuring_account() for i in range(5)]
for acc in accounts: for acc in accounts:
acc.wait_configure_finish() acc.wait_configure_finish()
acc.start() acc.start_io()
ac1 = accounts.pop() ac1 = accounts.pop()
lp.sec("ac1: setting up contacts with 4 other members") lp.sec("ac1: setting up contacts with 4 other members")
@@ -1610,7 +1610,7 @@ class TestGroupStressTests:
accounts = [acfactory.get_online_configuring_account() for i in range(3)] accounts = [acfactory.get_online_configuring_account() for i in range(3)]
for acc in accounts: for acc in accounts:
acc.wait_configure_finish() acc.wait_configure_finish()
acc.start() acc.start_io()
ac1 = accounts.pop() ac1 = accounts.pop()
lp.sec("ac1: setting up contacts with 2 other members") lp.sec("ac1: setting up contacts with 2 other members")

View File

@@ -36,13 +36,14 @@ def test_wrong_db(tmpdir):
# write an invalid database file # write an invalid database file
p.write("x123" * 10) p.write("x123" * 10)
assert ffi.NULL == lib.dc_context_new(ffi.NULL, ffi.NULL, p.strpath.encode("ascii"), ffi.NULL) assert ffi.NULL == lib.dc_context_new(ffi.NULL, p.strpath.encode("ascii"), ffi.NULL)
def test_empty_blobdir(tmpdir): def test_empty_blobdir(tmpdir):
db_fname = tmpdir.join("hello.db") db_fname = tmpdir.join("hello.db")
# Apparently some client code expects this to be the same as passing NULL. # Apparently some client code expects this to be the same as passing NULL.
ctx = ffi.gc( ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), b""), lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), b""),
lib.dc_context_unref, lib.dc_context_unref,
) )
assert ctx != ffi.NULL assert ctx != ffi.NULL
@@ -86,26 +87,16 @@ def test_get_special_message_id_returns_empty_message(acfactory):
def test_provider_info_none(): def test_provider_info_none():
ctx = ffi.gc( ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_new(ffi.NULL, ffi.NULL, ffi.NULL),
lib.dc_context_unref, lib.dc_context_unref,
) )
assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL
def test_get_info_closed():
ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL),
lib.dc_context_unref,
)
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))
assert 'deltachat_core_version' in info
assert 'database_dir' not in info
def test_get_info_open(tmpdir): def test_get_info_open(tmpdir):
db_fname = tmpdir.join("test.db") db_fname = tmpdir.join("test.db")
ctx = ffi.gc( ctx = ffi.gc(
lib.dc_context_new(ffi.NULL, ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL), lib.dc_context_new(ffi.NULL, db_fname.strpath.encode("ascii"), ffi.NULL),
lib.dc_context_unref, lib.dc_context_unref,
) )
info = cutil.from_dc_charpointer(lib.dc_get_info(ctx)) info = cutil.from_dc_charpointer(lib.dc_get_info(ctx))