diff --git a/python/fail_test.py b/python/fail_test.py new file mode 100644 index 000000000..5d1535fdf --- /dev/null +++ b/python/fail_test.py @@ -0,0 +1,7 @@ +from __future__ import print_function +from deltachat import capi +from deltachat.capi import ffi, lib + +if __name__ == "__main__": + ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) + lib.dc_context_shutdown(ctx) diff --git a/python/src/deltachat/account.py b/python/src/deltachat/account.py index d699aff6d..4e1e28447 100644 --- a/python/src/deltachat/account.py +++ b/python/src/deltachat/account.py @@ -35,7 +35,7 @@ class Account(object): :param debug: turn on debug logging for events. """ self._dc_context = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, as_dc_charpointer(os_name)), + lib.dc_context_new(ffi.NULL, as_dc_charpointer(os_name)), _destroy_dc_context, ) self._evlogger = EventLogger(self, logid, debug) @@ -384,8 +384,6 @@ class Account(object): def _export(self, path, imex_cmd): with ImexTracker(self) as imex_tracker: lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL) - if not self._threads.is_started(): - lib.dc_perform_imap_jobs(self._dc_context) return imex_tracker.wait_finish() def import_self_keys(self, path): @@ -406,8 +404,6 @@ class Account(object): def _import(self, path, imex_cmd): with ImexTracker(self) as imex_tracker: lib.dc_imex(self._dc_context, imex_cmd, as_dc_charpointer(path), ffi.NULL) - if not self._threads.is_started(): - lib.dc_perform_imap_jobs(self._dc_context) imex_tracker.wait_finish() def initiate_key_transfer(self): @@ -485,7 +481,7 @@ class Account(object): ev = self._evlogger.get_matching("DC_EVENT_INCOMING_MSG") return self.get_message_by_id(ev[2]) - def start_threads(self, mvbox=False, sentbox=False): + def start_threads(self): """ start IMAP/SMTP threads (and configure account if it hasn't happened). :raises: ValueError if 'addr' or 'mail_pw' are not configured. @@ -493,7 +489,7 @@ class Account(object): """ if not self.is_configured(): self.configure() - self._threads.start(mvbox=mvbox, sentbox=sentbox) + self._threads.start() def stop_threads(self, wait=True): """ stop IMAP/SMTP threads. """ @@ -575,14 +571,7 @@ class IOThreads: def start(self, imap=True, smtp=True, mvbox=False, sentbox=False): assert not self.is_started() - if imap: - self._start_one_thread("inbox", self.imap_thread_run) - if mvbox: - self._start_one_thread("mvbox", self.mvbox_thread_run) - if sentbox: - self._start_one_thread("sentbox", self.sentbox_thread_run) - if smtp: - self._start_one_thread("smtp", self.smtp_thread_run) + self._start_one_thread("deltachat", self.dc_thread_run) def _start_one_thread(self, name, func): self._name2thread[name] = t = threading.Thread(target=func, name=name) @@ -590,58 +579,17 @@ class IOThreads: t.start() def stop(self, wait=False): - self._thread_quitflag = True - - # Workaround for a race condition. Make sure that thread is - # not in between checking for quitflag and entering idle. - time.sleep(0.5) - - lib.dc_interrupt_imap_idle(self._dc_context) - lib.dc_interrupt_smtp_idle(self._dc_context) - lib.dc_interrupt_mvbox_idle(self._dc_context) - lib.dc_interrupt_sentbox_idle(self._dc_context) + lib.dc_context_shutdown(self._dc_context) if wait: for name, thread in self._name2thread.items(): thread.join() - def imap_thread_run(self): - self._log_event("py-bindings-info", 0, "INBOX THREAD START") - while not self._thread_quitflag: - lib.dc_perform_imap_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_imap_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_imap_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "INBOX THREAD FINISHED") - - def mvbox_thread_run(self): - self._log_event("py-bindings-info", 0, "MVBOX THREAD START") - while not self._thread_quitflag: - lib.dc_perform_mvbox_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_mvbox_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_mvbox_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "MVBOX THREAD FINISHED") - - def sentbox_thread_run(self): - self._log_event("py-bindings-info", 0, "SENTBOX THREAD START") - while not self._thread_quitflag: - lib.dc_perform_sentbox_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_sentbox_fetch(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_sentbox_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "SENTBOX THREAD FINISHED") - - def smtp_thread_run(self): - self._log_event("py-bindings-info", 0, "SMTP THREAD START") - while not self._thread_quitflag: - lib.dc_perform_smtp_jobs(self._dc_context) - if not self._thread_quitflag: - lib.dc_perform_smtp_idle(self._dc_context) - self._log_event("py-bindings-info", 0, "SMTP THREAD FINISHED") + def dc_thread_run(self): + self._log_event("py-bindings-info", 0, "DC THREAD START") + + lib.dc_context_run(self._dc_context, lib.py_dc_callback) + self._log_event("py-bindings-info", 0, "DC THREAD FINISHED") def _destroy_dc_context(dc_context, dc_context_unref=lib.dc_context_unref): # destructor for dc_context diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 71c8322d3..1b868e191 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -178,6 +178,7 @@ class TestOfflineChat: assert d["draft"] == "" if chat.get_draft() is None else chat.get_draft() def test_group_chat_creation_with_translation(self, ac1): + ac1.start_threads() ac1.set_stock_translation(const.DC_STR_NEWGROUPDRAFT, "xyz %1$s") ac1._evlogger.consume_events() with pytest.raises(ValueError): @@ -197,6 +198,7 @@ class TestOfflineChat: assert not chat.is_promoted() msg = chat.get_draft() assert msg.text == "xyz title1" + ac1.stop_threads() @pytest.mark.parametrize("verified", [True, False]) def test_group_chat_qr(self, acfactory, ac1, verified): diff --git a/python/tests/test_lowlevel.py b/python/tests/test_lowlevel.py index bf3b1f085..3f64946c7 100644 --- a/python/tests/test_lowlevel.py +++ b/python/tests/test_lowlevel.py @@ -1,29 +1,64 @@ from __future__ import print_function +import threading from deltachat import capi, cutil, const, set_context_callback, clear_context_callback from deltachat.capi import ffi from deltachat.capi import lib +from deltachat.account import EventLogger + + +class EventThread(threading.Thread): + def __init__(self, dc_context): + self.dc_context = dc_context + super(EventThread, self).__init__() + self.setDaemon(1) + + def run(self): + lib.dc_context_run(self.dc_context)#, lib.py_dc_callback) + + def stop(self): + lib.dc_context_shutdown(self.dc_context) def test_empty_context(): - ctx = capi.lib.dc_context_new(capi.ffi.NULL, capi.ffi.NULL, capi.ffi.NULL) + ctx = capi.lib.dc_context_new(capi.ffi.NULL, capi.ffi.NULL) capi.lib.dc_close(ctx) def test_callback_None2int(): - ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback, ffi.NULL, ffi.NULL) + ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) set_context_callback(ctx, lambda *args: None) capi.lib.dc_close(ctx) clear_context_callback(ctx) +def test_start_stop_event_thread_basic(): + print("1") + ctx = capi.lib.dc_context_new(ffi.NULL, ffi.NULL) + print("2") + ev_thread = EventThread(ctx) + print("3 -- starting event thread") + ev_thread.start() + print("4 -- stopping event thread") + ev_thread.stop() def test_dc_close_events(tmpdir): - from deltachat.account import Account + ctx = ffi.gc( + capi.lib.dc_context_new(ffi.NULL, ffi.NULL), + lib.dc_context_unref, + ) + evlog = EventLogger(ctx) + evlog.set_timeout(5) + set_context_callback( + ctx, + lambda ctx, evt_name, data1, data2: evlog(evt_name, data1, data2) + ) + ev_thread = EventThread(ctx) + ev_thread.start() + p = tmpdir.join("hello.db") - ac1 = Account(p.strpath) - ac1.shutdown() + lib.dc_open(ctx, p.strpath.encode("ascii"), ffi.NULL) + capi.lib.dc_close(ctx) def find(info_string): - evlog = ac1._evlogger while 1: ev = evlog.get_matching("DC_EVENT_INFO", check_error=False) data2 = ev[2] @@ -37,11 +72,12 @@ def test_dc_close_events(tmpdir): find("disconnecting mvbox-thread") find("disconnecting SMTP") find("Database closed") + ev_thread.stop() def test_wrong_db(tmpdir): dc_context = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) p = tmpdir.join("hello.db") @@ -53,7 +89,7 @@ def test_wrong_db(tmpdir): def test_empty_blobdir(tmpdir): # Apparently some client code expects this to be the same as passing NULL. ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) db_fname = tmpdir.join("hello.db") @@ -95,7 +131,7 @@ def test_get_special_message_id_returns_empty_message(acfactory): def test_provider_info_none(): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) assert lib.dc_provider_new_from_email(ctx, cutil.as_dc_charpointer("email@unexistent.no")) == ffi.NULL @@ -103,7 +139,7 @@ def test_provider_info_none(): def test_get_info_closed(): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) info = cutil.from_dc_charpointer(lib.dc_get_info(ctx)) @@ -113,7 +149,7 @@ def test_get_info_closed(): def test_get_info_open(tmpdir): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) db_fname = tmpdir.join("test.db") @@ -125,7 +161,7 @@ def test_get_info_open(tmpdir): def test_is_open_closed(): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) assert lib.dc_is_open(ctx) == 0 @@ -133,7 +169,7 @@ def test_is_open_closed(): def test_is_open_actually_open(tmpdir): ctx = ffi.gc( - lib.dc_context_new(lib.py_dc_callback, ffi.NULL, ffi.NULL), + lib.dc_context_new(ffi.NULL, ffi.NULL), lib.dc_context_unref, ) db_fname = tmpdir.join("test.db")