mirror of
https://github.com/deltachat/deltachat-core.git
synced 2025-10-04 18:29:19 +02:00
better modularize functionality and use it from tests
This commit is contained in:
parent
1ff06c071b
commit
55b05be6af
4 changed files with 152 additions and 121 deletions
|
@ -1,8 +1,9 @@
|
||||||
from deltachat import capi
|
from deltachat import capi
|
||||||
|
from deltachat.capi import ffi
|
||||||
|
from deltachat.account import Account
|
||||||
|
|
||||||
|
|
||||||
_DC_CALLBACK_MAP = {}
|
_DC_CALLBACK_MAP = {}
|
||||||
_DC_EVENTNAME_MAP = {}
|
|
||||||
|
|
||||||
|
|
||||||
@capi.ffi.def_extern()
|
@capi.ffi.def_extern()
|
||||||
|
@ -13,14 +14,35 @@ def py_dc_callback(ctx, evt, data1, data2):
|
||||||
looks up the correct event handler for the given context.
|
looks up the correct event handler for the given context.
|
||||||
"""
|
"""
|
||||||
callback = _DC_CALLBACK_MAP.get(ctx, lambda *a: 0)
|
callback = _DC_CALLBACK_MAP.get(ctx, lambda *a: 0)
|
||||||
|
# the following code relates to the deltachat/_build.py's helper
|
||||||
|
# function which provides us signature info of an event call
|
||||||
|
event_sig_types = capi.lib.dc_get_event_signature_types(evt)
|
||||||
|
if data1 and event_sig_types & 1:
|
||||||
|
data1 = ffi.string(ffi.cast('char*', data1))
|
||||||
|
if data2 and event_sig_types & 2:
|
||||||
|
data2 = ffi.string(ffi.cast('char*', data2))
|
||||||
|
evt_name = get_dc_event_name(evt)
|
||||||
try:
|
try:
|
||||||
ret = callback(ctx, evt, data1, data2)
|
ret = callback(ctx, evt_name, data1, data2)
|
||||||
|
if event_sig_types & 4:
|
||||||
|
return ffi.cast('uintptr_t', ret)
|
||||||
|
elif event_sig_types & 8:
|
||||||
|
return ffi.cast('int', ret)
|
||||||
except:
|
except:
|
||||||
|
raise
|
||||||
ret = 0
|
ret = 0
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def get_dc_event_name(integer):
|
def set_context_callback(dc_context, func):
|
||||||
|
_DC_CALLBACK_MAP[dc_context] = func
|
||||||
|
|
||||||
|
|
||||||
|
def clear_context_callback(dc_context):
|
||||||
|
_DC_CALLBACK_MAP.pop(dc_context, None)
|
||||||
|
|
||||||
|
|
||||||
|
def get_dc_event_name(integer, _DC_EVENTNAME_MAP={}):
|
||||||
if not _DC_EVENTNAME_MAP:
|
if not _DC_EVENTNAME_MAP:
|
||||||
for name, val in vars(capi.lib).items():
|
for name, val in vars(capi.lib).items():
|
||||||
if name.startswith("DC_EVENT_"):
|
if name.startswith("DC_EVENT_"):
|
||||||
|
|
106
python/src/deltachat/account.py
Normal file
106
python/src/deltachat/account.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
from . import capi
|
||||||
|
import deltachat
|
||||||
|
from .capi import ffi
|
||||||
|
|
||||||
|
|
||||||
|
class Account:
|
||||||
|
def __init__(self, db_path, logcallback=None):
|
||||||
|
self.dc_context = ctx = capi.lib.dc_context_new(
|
||||||
|
capi.lib.py_dc_callback,
|
||||||
|
capi.ffi.NULL, capi.ffi.NULL)
|
||||||
|
capi.lib.dc_open(ctx, db_path, capi.ffi.NULL)
|
||||||
|
self._logcallback = logcallback
|
||||||
|
|
||||||
|
def set_config(self, **kwargs):
|
||||||
|
for name, value in kwargs.items():
|
||||||
|
capi.lib.dc_set_config(self.dc_context, name, value)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
deltachat.set_context_callback(self.dc_context, self.process_event)
|
||||||
|
capi.lib.dc_configure(self.dc_context)
|
||||||
|
self._threads = IOThreads(self.dc_context)
|
||||||
|
self._threads.start()
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
deltachat.clear_context_callback(self.dc_context)
|
||||||
|
self._threads.stop(wait=False)
|
||||||
|
# XXX actually we'd like to wait but the smtp/imap
|
||||||
|
# interrupt idle calls do not seem to release the
|
||||||
|
# blocking call to smtp|imap idle. This means we
|
||||||
|
# also can't now close the database because the
|
||||||
|
# threads might still need it
|
||||||
|
# capi.lib.dc_close(self.dc_context)
|
||||||
|
|
||||||
|
def process_event(self, ctx, evt_name, data1, data2):
|
||||||
|
assert ctx == self.dc_context
|
||||||
|
if self._logcallback is not None:
|
||||||
|
self._logcallback((evt_name, data1, data2))
|
||||||
|
callname = evt_name[3:].lower()
|
||||||
|
method = getattr(self, callname, None)
|
||||||
|
if method is not None:
|
||||||
|
return method(data1, data2) or 0
|
||||||
|
# print ("dropping event: no handler for", evt_name)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def read_url(self, url):
|
||||||
|
try:
|
||||||
|
r = requests.get(url)
|
||||||
|
except requests.ConnectionError:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return r.content
|
||||||
|
|
||||||
|
def event_http_get(self, data1, data2):
|
||||||
|
url = data1.decode("utf-8")
|
||||||
|
content = self.read_url(url)
|
||||||
|
s = content.encode("utf-8")
|
||||||
|
# we need to return a fresh pointer that the core owns
|
||||||
|
return capi.lib.dupstring_helper(s)
|
||||||
|
|
||||||
|
def event_is_offline(self, data1, data2):
|
||||||
|
return 0 # always online
|
||||||
|
|
||||||
|
|
||||||
|
class IOThreads:
|
||||||
|
def __init__(self, dc_context):
|
||||||
|
self.dc_context = dc_context
|
||||||
|
self._thread_quitflag = False
|
||||||
|
self._name2thread = {}
|
||||||
|
|
||||||
|
def start(self, imap=True, smtp=True):
|
||||||
|
assert not self._name2thread
|
||||||
|
if imap:
|
||||||
|
self._start_one_thread("imap", self.imap_thread_run)
|
||||||
|
if smtp:
|
||||||
|
self._start_one_thread("smtp", self.smtp_thread_run)
|
||||||
|
|
||||||
|
def _start_one_thread(self, name, func):
|
||||||
|
self._name2thread[name] = t = threading.Thread(target=func, name=name)
|
||||||
|
t.setDaemon(1)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
def stop(self, wait=False):
|
||||||
|
self._thread_quitflag = True
|
||||||
|
# XXX interrupting does not quite work yet, the threads keep idling
|
||||||
|
print("interrupting smtp and idle")
|
||||||
|
capi.lib.dc_interrupt_imap_idle(self.dc_context)
|
||||||
|
capi.lib.dc_interrupt_smtp_idle(self.dc_context)
|
||||||
|
if wait:
|
||||||
|
for name, thread in self._name2thread.items():
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
def imap_thread_run(self):
|
||||||
|
print ("starting imap thread")
|
||||||
|
while not self._thread_quitflag:
|
||||||
|
capi.lib.dc_perform_imap_jobs(self.dc_context)
|
||||||
|
capi.lib.dc_perform_imap_fetch(self.dc_context)
|
||||||
|
capi.lib.dc_perform_imap_idle(self.dc_context)
|
||||||
|
|
||||||
|
def smtp_thread_run(self):
|
||||||
|
print ("starting smtp thread")
|
||||||
|
while not self._thread_quitflag:
|
||||||
|
capi.lib.dc_perform_smtp_jobs(self.dc_context)
|
||||||
|
capi.lib.dc_perform_smtp_idle(self.dc_context)
|
|
@ -1,20 +1,6 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import deltachat
|
import deltachat
|
||||||
from deltachat import capi
|
|
||||||
import threading
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def register_dc_callback(monkeypatch):
|
|
||||||
"""Register a callback for a given context.
|
|
||||||
|
|
||||||
This is a function-scoped fixture and the function will be
|
|
||||||
unregisterd automatically on fixture teardown.
|
|
||||||
"""
|
|
||||||
def register_dc_callback(ctx, func):
|
|
||||||
monkeypatch.setitem(deltachat._DC_CALLBACK_MAP, ctx, func)
|
|
||||||
return register_dc_callback
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
|
@ -32,39 +18,6 @@ def userpassword(pytestconfig):
|
||||||
pytest.skip("specify a test account with --user and --password options")
|
pytest.skip("specify a test account with --user and --password options")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def imap_thread(context, quitflag):
|
|
||||||
print ("starting imap thread")
|
|
||||||
while not quitflag.is_set():
|
|
||||||
capi.lib.dc_perform_imap_jobs(context)
|
|
||||||
capi.lib.dc_perform_imap_fetch(context)
|
|
||||||
capi.lib.dc_perform_imap_idle(context)
|
|
||||||
|
|
||||||
|
|
||||||
def smtp_thread(context, quitflag):
|
|
||||||
print ("starting smtp thread")
|
|
||||||
while not quitflag.is_set():
|
|
||||||
capi.lib.dc_perform_smtp_jobs(context)
|
|
||||||
capi.lib.dc_perform_smtp_idle(context)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def dc_context():
|
def tmp_db_path(tmpdir):
|
||||||
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback,
|
return tmpdir.join("test.db").strpath
|
||||||
capi.ffi.NULL, capi.ffi.NULL)
|
|
||||||
yield ctx
|
|
||||||
capi.lib.dc_close(ctx)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def dc_threads(dc_context):
|
|
||||||
quitflag = threading.Event()
|
|
||||||
t1 = threading.Thread(target=imap_thread, name="imap", args=[dc_context, quitflag])
|
|
||||||
t1.setDaemon(1)
|
|
||||||
t1.start()
|
|
||||||
t2 = threading.Thread(target=smtp_thread, name="smtp", args=[dc_context, quitflag])
|
|
||||||
t2.setDaemon(1)
|
|
||||||
t2.start()
|
|
||||||
yield
|
|
||||||
quitflag.set()
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import deltachat
|
import deltachat
|
||||||
import re
|
import re
|
||||||
import requests
|
from deltachat import capi
|
||||||
from deltachat import capi, get_dc_event_name
|
|
||||||
from deltachat.capi import ffi
|
from deltachat.capi import ffi
|
||||||
import queue
|
import queue
|
||||||
|
|
||||||
|
@ -16,70 +15,21 @@ def test_event_defines():
|
||||||
assert capi.lib.DC_EVENT_INFO == 100
|
assert capi.lib.DC_EVENT_INFO == 100
|
||||||
|
|
||||||
|
|
||||||
def test_cb(register_dc_callback):
|
class TestLive:
|
||||||
def cb(ctx, evt, data1, data2):
|
def test_basic_configure_login_ok(self, request, tmp_db_path, userpassword):
|
||||||
return 0
|
q = queue.Queue()
|
||||||
ctx = capi.lib.dc_context_new(capi.lib.py_dc_callback,
|
dc = deltachat.Account(tmp_db_path, logcallback=q.put)
|
||||||
capi.ffi.NULL, capi.ffi.NULL)
|
dc.set_config(addr=userpassword[0], mail_pw=userpassword[1])
|
||||||
register_dc_callback(ctx, cb)
|
dc.start()
|
||||||
capi.lib.dc_close(ctx)
|
request.addfinalizer(dc.shutdown)
|
||||||
assert deltachat._DC_CALLBACK_MAP[ctx] is cb
|
imap_ok = smtp_ok = False
|
||||||
|
while not imap_ok or not smtp_ok:
|
||||||
|
evt_name, data1, data2 = q.get(timeout=5.0)
|
||||||
def test_basic_events(dc_context, dc_threads, register_dc_callback, tmpdir, userpassword):
|
print(evt_name, data1, data2)
|
||||||
q = queue.Queue()
|
if evt_name == "DC_EVENT_ERROR":
|
||||||
def cb(dc_context, evt, data1, data2):
|
assert 0
|
||||||
# the following code relates to the deltachat/_build.py's helper
|
if evt_name == "DC_EVENT_INFO":
|
||||||
# function which provides us signature info of an event call
|
if re.match("imap-login.*ok.", data2.lower()):
|
||||||
event_sig_types = capi.lib.dc_get_event_signature_types(evt)
|
imap_ok = True
|
||||||
if data1 and event_sig_types & 1:
|
if re.match("smtp-login.*ok.", data2.lower()):
|
||||||
data1 = ffi.string(ffi.cast('char*', data1))
|
smtp_ok = True
|
||||||
if data2 and event_sig_types & 2:
|
|
||||||
data2 = ffi.string(ffi.cast('char*', data2))
|
|
||||||
evt_name = get_dc_event_name(evt)
|
|
||||||
print (evt_name, data1, data2)
|
|
||||||
if evt_name == "DC_EVENT_HTTP_GET":
|
|
||||||
content = read_url(data1)
|
|
||||||
s = content.encode("utf-8")
|
|
||||||
# we need to return a pointer that the core owns
|
|
||||||
dupped = capi.lib.dupstring_helper(s)
|
|
||||||
return ffi.cast('uintptr_t', dupped)
|
|
||||||
elif evt_name == "DC_EVENT_IS_OFFLINE":
|
|
||||||
return 0
|
|
||||||
elif event_sig_types & (4|8): # returning string or int means it's a sync event
|
|
||||||
print ("dropping sync event: no handler for", evt_name)
|
|
||||||
return 0
|
|
||||||
# async event
|
|
||||||
q.put((evt_name, data1, data2))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
register_dc_callback(dc_context, cb)
|
|
||||||
|
|
||||||
dbfile = tmpdir.join("test.db")
|
|
||||||
capi.lib.dc_open(dc_context, dbfile.strpath, capi.ffi.NULL)
|
|
||||||
capi.lib.dc_set_config(dc_context, "addr", userpassword[0])
|
|
||||||
capi.lib.dc_set_config(dc_context, "mail_pw", userpassword[1])
|
|
||||||
capi.lib.dc_configure(dc_context)
|
|
||||||
|
|
||||||
imap_ok = smtp_ok = False
|
|
||||||
while not imap_ok or not smtp_ok:
|
|
||||||
evt_name, data1, data2 = q.get(timeout=5.0)
|
|
||||||
if evt_name == "DC_EVENT_ERROR":
|
|
||||||
assert 0
|
|
||||||
if evt_name == "DC_EVENT_INFO":
|
|
||||||
if re.match("imap-login.*ok.", data2.lower()):
|
|
||||||
imap_ok = True
|
|
||||||
if re.match("smtp-login.*ok.", data2.lower()):
|
|
||||||
smtp_ok = True
|
|
||||||
assert 0
|
|
||||||
# assert capi.lib.dc_imap_is_connected(dc_context)
|
|
||||||
# assert capi.lib.dc_smtp_is_connected(dc_context)
|
|
||||||
|
|
||||||
|
|
||||||
def read_url(url):
|
|
||||||
try:
|
|
||||||
r = requests.get(url)
|
|
||||||
except requests.ConnectionError:
|
|
||||||
return ''
|
|
||||||
else:
|
|
||||||
return r.content
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue