mirror of
https://github.com/chatmail/core.git
synced 2026-05-09 01:46:30 +03:00
Merge pull request #3381 from deltachat/adb/apply-black-and-isort
apply isort and black formatters, add format checking to CI
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# content of echo_and_quit.py
|
# content of echo_and_quit.py
|
||||||
|
|
||||||
from deltachat import account_hookimpl, run_cmdline
|
from deltachat import account_hookimpl, run_cmdline
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
# content of group_tracking.py
|
# content of group_tracking.py
|
||||||
|
|
||||||
from deltachat import account_hookimpl, run_cmdline
|
from deltachat import account_hookimpl, run_cmdline
|
||||||
@@ -33,15 +32,21 @@ class GroupTrackingPlugin:
|
|||||||
|
|
||||||
@account_hookimpl
|
@account_hookimpl
|
||||||
def ac_member_added(self, chat, contact, actor, message):
|
def ac_member_added(self, chat, contact, actor, message):
|
||||||
print("ac_member_added {} to chat {} from {}".format(
|
print(
|
||||||
contact.addr, chat.id, actor or message.get_sender_contact().addr))
|
"ac_member_added {} to chat {} from {}".format(
|
||||||
|
contact.addr, chat.id, actor or message.get_sender_contact().addr
|
||||||
|
)
|
||||||
|
)
|
||||||
for member in chat.get_contacts():
|
for member in chat.get_contacts():
|
||||||
print("chat member: {}".format(member.addr))
|
print("chat member: {}".format(member.addr))
|
||||||
|
|
||||||
@account_hookimpl
|
@account_hookimpl
|
||||||
def ac_member_removed(self, chat, contact, actor, message):
|
def ac_member_removed(self, chat, contact, actor, message):
|
||||||
print("ac_member_removed {} from chat {} by {}".format(
|
print(
|
||||||
contact.addr, chat.id, actor or message.get_sender_contact().addr))
|
"ac_member_removed {} from chat {} by {}".format(
|
||||||
|
contact.addr, chat.id, actor or message.get_sender_contact().addr
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
|
|
||||||
import pytest
|
|
||||||
import py
|
|
||||||
import echo_and_quit
|
import echo_and_quit
|
||||||
import group_tracking
|
import group_tracking
|
||||||
|
import py
|
||||||
|
import pytest
|
||||||
|
|
||||||
from deltachat.events import FFIEventLogger
|
from deltachat.events import FFIEventLogger
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope="session")
|
||||||
def datadir():
|
def datadir():
|
||||||
"""The py.path.local object of the test-data/ directory."""
|
"""The py.path.local object of the test-data/ directory."""
|
||||||
for path in reversed(py.path.local(__file__).parts()):
|
for path in reversed(py.path.local(__file__).parts()):
|
||||||
datadir = path.join('test-data')
|
datadir = path.join("test-data")
|
||||||
if datadir.isdir():
|
if datadir.isdir():
|
||||||
return datadir
|
return datadir
|
||||||
else:
|
else:
|
||||||
pytest.skip('test-data directory not found')
|
pytest.skip("test-data directory not found")
|
||||||
|
|
||||||
|
|
||||||
def test_echo_quit_plugin(acfactory, lp):
|
def test_echo_quit_plugin(acfactory, lp):
|
||||||
@@ -22,7 +22,7 @@ def test_echo_quit_plugin(acfactory, lp):
|
|||||||
botproc = acfactory.run_bot_process(echo_and_quit)
|
botproc = acfactory.run_bot_process(echo_and_quit)
|
||||||
|
|
||||||
lp.sec("creating a temp account to contact the bot")
|
lp.sec("creating a temp account to contact the bot")
|
||||||
ac1, = acfactory.get_online_accounts(1)
|
(ac1,) = acfactory.get_online_accounts(1)
|
||||||
|
|
||||||
lp.sec("sending a message to the bot")
|
lp.sec("sending a message to the bot")
|
||||||
bot_contact = ac1.create_contact(botproc.addr)
|
bot_contact = ac1.create_contact(botproc.addr)
|
||||||
@@ -44,9 +44,11 @@ def test_group_tracking_plugin(acfactory, lp):
|
|||||||
|
|
||||||
ac1, ac2 = acfactory.get_online_accounts(2)
|
ac1, ac2 = acfactory.get_online_accounts(2)
|
||||||
|
|
||||||
botproc.fnmatch_lines("""
|
botproc.fnmatch_lines(
|
||||||
|
"""
|
||||||
*ac_configure_completed*
|
*ac_configure_completed*
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
ac1.add_account_plugin(FFIEventLogger(ac1))
|
ac1.add_account_plugin(FFIEventLogger(ac1))
|
||||||
ac2.add_account_plugin(FFIEventLogger(ac2))
|
ac2.add_account_plugin(FFIEventLogger(ac2))
|
||||||
|
|
||||||
@@ -56,9 +58,11 @@ def test_group_tracking_plugin(acfactory, lp):
|
|||||||
ch.add_contact(bot_contact)
|
ch.add_contact(bot_contact)
|
||||||
ch.send_text("hello")
|
ch.send_text("hello")
|
||||||
|
|
||||||
botproc.fnmatch_lines("""
|
botproc.fnmatch_lines(
|
||||||
|
"""
|
||||||
*ac_chat_modified*bot test group*
|
*ac_chat_modified*bot test group*
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
lp.sec("adding third member {}".format(ac2.get_config("addr")))
|
lp.sec("adding third member {}".format(ac2.get_config("addr")))
|
||||||
contact3 = ac1.create_contact(ac2.get_config("addr"))
|
contact3 = ac1.create_contact(ac2.get_config("addr"))
|
||||||
@@ -68,12 +72,20 @@ def test_group_tracking_plugin(acfactory, lp):
|
|||||||
assert "hello" in reply.text
|
assert "hello" in reply.text
|
||||||
|
|
||||||
lp.sec("now looking at what the bot received")
|
lp.sec("now looking at what the bot received")
|
||||||
botproc.fnmatch_lines("""
|
botproc.fnmatch_lines(
|
||||||
|
"""
|
||||||
*ac_member_added {}*from*{}*
|
*ac_member_added {}*from*{}*
|
||||||
""".format(contact3.addr, ac1.get_config("addr")))
|
""".format(
|
||||||
|
contact3.addr, ac1.get_config("addr")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
lp.sec("contact successfully added, now removing")
|
lp.sec("contact successfully added, now removing")
|
||||||
ch.remove_contact(contact3)
|
ch.remove_contact(contact3)
|
||||||
botproc.fnmatch_lines("""
|
botproc.fnmatch_lines(
|
||||||
|
"""
|
||||||
*ac_member_removed {}*from*{}*
|
*ac_member_removed {}*from*{}*
|
||||||
""".format(contact3.addr, ac1.get_config("addr")))
|
""".format(
|
||||||
|
contact3.addr, ac1.get_config("addr")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ build-backend = "setuptools.build_meta"
|
|||||||
root = ".."
|
root = ".."
|
||||||
tag_regex = '^(?P<prefix>py-)?(?P<version>[^\+]+)(?P<suffix>.*)?$'
|
tag_regex = '^(?P<prefix>py-)?(?P<version>[^\+]+)(?P<suffix>.*)?$'
|
||||||
git_describe_command = "git describe --dirty --tags --long --match py-*.*"
|
git_describe_command = "git describe --dirty --tags --long --match py-*.*"
|
||||||
|
|
||||||
|
[tool.black]
|
||||||
|
line-length = 120
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from . import capi, const, hookspec # noqa
|
from pkg_resources import DistributionNotFound, get_distribution
|
||||||
from .capi import ffi # noqa
|
|
||||||
from .account import Account, get_core_info # noqa
|
from . import capi, const, events, hookspec # noqa
|
||||||
from .message import Message # noqa
|
from .account import Account, get_core_info # noqa
|
||||||
from .contact import Contact # noqa
|
from .capi import ffi # noqa
|
||||||
from .chat import Chat # noqa
|
from .chat import Chat # noqa
|
||||||
from .hookspec import account_hookimpl, global_hookimpl # noqa
|
from .contact import Contact # noqa
|
||||||
from . import events
|
from .hookspec import account_hookimpl, global_hookimpl # noqa
|
||||||
|
from .message import Message # noqa
|
||||||
|
|
||||||
from pkg_resources import get_distribution, DistributionNotFound
|
|
||||||
try:
|
try:
|
||||||
__version__ = get_distribution(__name__).version
|
__version__ = get_distribution(__name__).version
|
||||||
except DistributionNotFound:
|
except DistributionNotFound:
|
||||||
@@ -46,6 +46,7 @@ def run_cmdline(argv=None, account_plugins=None):
|
|||||||
"""Run a simple default command line app, registering the specified
|
"""Run a simple default command line app, registering the specified
|
||||||
account plugins."""
|
account plugins."""
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
if argv is None:
|
if argv is None:
|
||||||
argv = sys.argv
|
argv = sys.argv
|
||||||
|
|
||||||
@@ -69,9 +70,9 @@ def run_cmdline(argv=None, account_plugins=None):
|
|||||||
ac.add_account_plugin(plugin)
|
ac.add_account_plugin(plugin)
|
||||||
|
|
||||||
if not ac.is_configured():
|
if not ac.is_configured():
|
||||||
assert args.email and args.password, (
|
assert (
|
||||||
"you must specify --email and --password once to configure this database/account"
|
args.email and args.password
|
||||||
)
|
), "you must specify --email and --password once to configure this database/account"
|
||||||
ac.set_config("addr", args.email)
|
ac.set_config("addr", args.email)
|
||||||
ac.set_config("mail_pw", args.password)
|
ac.set_config("mail_pw", args.password)
|
||||||
ac.set_config("mvbox_move", "0")
|
ac.set_config("mvbox_move", "0")
|
||||||
|
|||||||
@@ -20,30 +20,33 @@ def local_build_flags(projdir, target):
|
|||||||
:param target: The rust build target, `debug` or `release`.
|
:param target: The rust build target, `debug` or `release`.
|
||||||
"""
|
"""
|
||||||
flags = {}
|
flags = {}
|
||||||
if platform.system() == 'Darwin':
|
if platform.system() == "Darwin":
|
||||||
flags['libraries'] = ['resolv', 'dl']
|
flags["libraries"] = ["resolv", "dl"]
|
||||||
flags['extra_link_args'] = [
|
flags["extra_link_args"] = [
|
||||||
'-framework', 'CoreFoundation',
|
"-framework",
|
||||||
'-framework', 'CoreServices',
|
"CoreFoundation",
|
||||||
'-framework', 'Security',
|
"-framework",
|
||||||
|
"CoreServices",
|
||||||
|
"-framework",
|
||||||
|
"Security",
|
||||||
]
|
]
|
||||||
elif platform.system() == 'Linux':
|
elif platform.system() == "Linux":
|
||||||
flags['libraries'] = ['rt', 'dl', 'm']
|
flags["libraries"] = ["rt", "dl", "m"]
|
||||||
flags['extra_link_args'] = []
|
flags["extra_link_args"] = []
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
|
raise NotImplementedError("Compilation not supported yet on Windows, can you help?")
|
||||||
target_dir = os.environ.get("CARGO_TARGET_DIR")
|
target_dir = os.environ.get("CARGO_TARGET_DIR")
|
||||||
if target_dir is None:
|
if target_dir is None:
|
||||||
target_dir = os.path.join(projdir, 'target')
|
target_dir = os.path.join(projdir, "target")
|
||||||
flags['extra_objects'] = [os.path.join(target_dir, target, 'libdeltachat.a')]
|
flags["extra_objects"] = [os.path.join(target_dir, target, "libdeltachat.a")]
|
||||||
assert os.path.exists(flags['extra_objects'][0]), flags['extra_objects']
|
assert os.path.exists(flags["extra_objects"][0]), flags["extra_objects"]
|
||||||
flags['include_dirs'] = [os.path.join(projdir, 'deltachat-ffi')]
|
flags["include_dirs"] = [os.path.join(projdir, "deltachat-ffi")]
|
||||||
return flags
|
return flags
|
||||||
|
|
||||||
|
|
||||||
def system_build_flags():
|
def system_build_flags():
|
||||||
"""Construct build flags for building against an installed libdeltachat."""
|
"""Construct build flags for building against an installed libdeltachat."""
|
||||||
return pkgconfig.parse('deltachat')
|
return pkgconfig.parse("deltachat")
|
||||||
|
|
||||||
|
|
||||||
def extract_functions(flags):
|
def extract_functions(flags):
|
||||||
@@ -61,11 +64,13 @@ def extract_functions(flags):
|
|||||||
src_name = os.path.join(tmpdir, "include.h")
|
src_name = os.path.join(tmpdir, "include.h")
|
||||||
dst_name = os.path.join(tmpdir, "expanded.h")
|
dst_name = os.path.join(tmpdir, "expanded.h")
|
||||||
with open(src_name, "w") as src_fp:
|
with open(src_name, "w") as src_fp:
|
||||||
src_fp.write('#include <deltachat.h>')
|
src_fp.write("#include <deltachat.h>")
|
||||||
cc.preprocess(source=src_name,
|
cc.preprocess(
|
||||||
|
source=src_name,
|
||||||
output_file=dst_name,
|
output_file=dst_name,
|
||||||
include_dirs=flags['include_dirs'],
|
include_dirs=flags["include_dirs"],
|
||||||
macros=[('PY_CFFI', '1')])
|
macros=[("PY_CFFI", "1")],
|
||||||
|
)
|
||||||
with open(dst_name, "r") as dst_fp:
|
with open(dst_name, "r") as dst_fp:
|
||||||
return dst_fp.read()
|
return dst_fp.read()
|
||||||
finally:
|
finally:
|
||||||
@@ -87,7 +92,9 @@ def find_header(flags):
|
|||||||
obj_name = os.path.join(tmpdir, "where.o")
|
obj_name = os.path.join(tmpdir, "where.o")
|
||||||
dst_name = os.path.join(tmpdir, "where")
|
dst_name = os.path.join(tmpdir, "where")
|
||||||
with open(src_name, "w") as src_fp:
|
with open(src_name, "w") as src_fp:
|
||||||
src_fp.write(textwrap.dedent("""
|
src_fp.write(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <deltachat.h>
|
#include <deltachat.h>
|
||||||
|
|
||||||
@@ -95,18 +102,20 @@ def find_header(flags):
|
|||||||
printf("%s", _dc_header_file_location());
|
printf("%s", _dc_header_file_location());
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
"""))
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
cwd = os.getcwd()
|
cwd = os.getcwd()
|
||||||
try:
|
try:
|
||||||
os.chdir(tmpdir)
|
os.chdir(tmpdir)
|
||||||
cc.compile(sources=["where.c"],
|
cc.compile(
|
||||||
include_dirs=flags['include_dirs'],
|
sources=["where.c"],
|
||||||
macros=[("PY_CFFI_INC", "1")])
|
include_dirs=flags["include_dirs"],
|
||||||
|
macros=[("PY_CFFI_INC", "1")],
|
||||||
|
)
|
||||||
finally:
|
finally:
|
||||||
os.chdir(cwd)
|
os.chdir(cwd)
|
||||||
cc.link_executable(objects=[obj_name],
|
cc.link_executable(objects=[obj_name], output_progname="where", output_dir=tmpdir)
|
||||||
output_progname="where",
|
|
||||||
output_dir=tmpdir)
|
|
||||||
return subprocess.check_output(dst_name)
|
return subprocess.check_output(dst_name)
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(tmpdir)
|
shutil.rmtree(tmpdir)
|
||||||
@@ -123,7 +132,8 @@ def extract_defines(flags):
|
|||||||
cdef() method.
|
cdef() method.
|
||||||
"""
|
"""
|
||||||
header = find_header(flags)
|
header = find_header(flags)
|
||||||
defines_re = re.compile(r"""
|
defines_re = re.compile(
|
||||||
|
r"""
|
||||||
\#define\s+ # The start of a define.
|
\#define\s+ # The start of a define.
|
||||||
( # Begin capturing group which captures the define name.
|
( # Begin capturing group which captures the define name.
|
||||||
(?: # A nested group which is not captured, this allows us
|
(?: # A nested group which is not captured, this allows us
|
||||||
@@ -151,26 +161,28 @@ def extract_defines(flags):
|
|||||||
) # Close the capturing group, this contains
|
) # Close the capturing group, this contains
|
||||||
# the entire name e.g. DC_MSG_TEXT.
|
# the entire name e.g. DC_MSG_TEXT.
|
||||||
\s+\S+ # Ensure there is whitespace followed by a value.
|
\s+\S+ # Ensure there is whitespace followed by a value.
|
||||||
""", re.VERBOSE)
|
""",
|
||||||
|
re.VERBOSE,
|
||||||
|
)
|
||||||
defines = []
|
defines = []
|
||||||
with open(header) as fp:
|
with open(header) as fp:
|
||||||
for line in fp:
|
for line in fp:
|
||||||
match = defines_re.match(line)
|
match = defines_re.match(line)
|
||||||
if match:
|
if match:
|
||||||
defines.append(match.group(1))
|
defines.append(match.group(1))
|
||||||
return '\n'.join('#define {} ...'.format(d) for d in defines)
|
return "\n".join("#define {} ...".format(d) for d in defines)
|
||||||
|
|
||||||
|
|
||||||
def ffibuilder():
|
def ffibuilder():
|
||||||
projdir = os.environ.get('DCC_RS_DEV')
|
projdir = os.environ.get("DCC_RS_DEV")
|
||||||
if projdir:
|
if projdir:
|
||||||
target = os.environ.get('DCC_RS_TARGET', 'release')
|
target = os.environ.get("DCC_RS_TARGET", "release")
|
||||||
flags = local_build_flags(projdir, target)
|
flags = local_build_flags(projdir, target)
|
||||||
else:
|
else:
|
||||||
flags = system_build_flags()
|
flags = system_build_flags()
|
||||||
builder = cffi.FFI()
|
builder = cffi.FFI()
|
||||||
builder.set_source(
|
builder.set_source(
|
||||||
'deltachat.capi',
|
"deltachat.capi",
|
||||||
"""
|
"""
|
||||||
#include <deltachat.h>
|
#include <deltachat.h>
|
||||||
int dc_event_has_string_data(int e)
|
int dc_event_has_string_data(int e)
|
||||||
@@ -180,11 +192,13 @@ def ffibuilder():
|
|||||||
""",
|
""",
|
||||||
**flags,
|
**flags,
|
||||||
)
|
)
|
||||||
builder.cdef("""
|
builder.cdef(
|
||||||
|
"""
|
||||||
typedef int... time_t;
|
typedef int... time_t;
|
||||||
void free(void *ptr);
|
void free(void *ptr);
|
||||||
extern int dc_event_has_string_data(int);
|
extern int dc_event_has_string_data(int);
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
function_defs = extract_functions(flags)
|
function_defs = extract_functions(flags)
|
||||||
defines = extract_defines(flags)
|
defines = extract_defines(flags)
|
||||||
builder.cdef(function_defs)
|
builder.cdef(function_defs)
|
||||||
@@ -192,8 +206,9 @@ def ffibuilder():
|
|||||||
return builder
|
return builder
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
import os.path
|
import os.path
|
||||||
pkgdir = os.path.join(os.path.dirname(__file__), '..')
|
|
||||||
|
pkgdir = os.path.join(os.path.dirname(__file__), "..")
|
||||||
builder = ffibuilder()
|
builder = ffibuilder()
|
||||||
builder.compile(tmpdir=pkgdir, verbose=True)
|
builder.compile(tmpdir=pkgdir, verbose=True)
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
""" Account class implementation. """
|
""" Account class implementation. """
|
||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
from array import array
|
||||||
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
|
||||||
import os
|
from typing import Any, Dict, Generator, List, Optional, Union
|
||||||
from array import array
|
|
||||||
from . import const
|
from . import const, hookspec
|
||||||
from .capi import ffi, lib
|
from .capi import ffi, lib
|
||||||
from .cutil import as_dc_charpointer, from_dc_charpointer, from_optional_dc_charpointer, iter_array, DCLot
|
|
||||||
from .chat import Chat
|
from .chat import Chat
|
||||||
from .message import Message
|
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
from .tracker import ImexTracker, ConfigureTracker
|
from .cutil import (
|
||||||
from . import hookspec
|
DCLot,
|
||||||
|
as_dc_charpointer,
|
||||||
|
from_dc_charpointer,
|
||||||
|
from_optional_dc_charpointer,
|
||||||
|
iter_array,
|
||||||
|
)
|
||||||
from .events import EventThread
|
from .events import EventThread
|
||||||
from typing import Union, Any, Dict, Optional, List, Generator
|
from .message import Message
|
||||||
|
from .tracker import ConfigureTracker, ImexTracker
|
||||||
|
|
||||||
|
|
||||||
class MissingCredentials(ValueError):
|
class MissingCredentials(ValueError):
|
||||||
@@ -28,10 +35,12 @@ def get_core_info():
|
|||||||
|
|
||||||
with NamedTemporaryFile() as path:
|
with NamedTemporaryFile() as path:
|
||||||
path.close()
|
path.close()
|
||||||
return get_dc_info_as_dict(ffi.gc(
|
return get_dc_info_as_dict(
|
||||||
|
ffi.gc(
|
||||||
lib.dc_context_new(as_dc_charpointer(""), as_dc_charpointer(path.name), ffi.NULL),
|
lib.dc_context_new(as_dc_charpointer(""), as_dc_charpointer(path.name), ffi.NULL),
|
||||||
lib.dc_context_unref,
|
lib.dc_context_unref,
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_dc_info_as_dict(dc_context):
|
def get_dc_info_as_dict(dc_context):
|
||||||
@@ -50,6 +59,7 @@ class Account(object):
|
|||||||
by the underlying deltachat core library. All public Account methods are
|
by the underlying deltachat core library. All public Account methods are
|
||||||
meant to be memory-safe and return memory-safe objects.
|
meant to be memory-safe and return memory-safe objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
MissingCredentials = MissingCredentials
|
MissingCredentials = MissingCredentials
|
||||||
|
|
||||||
def __init__(self, db_path, os_name=None, logging=True) -> None:
|
def __init__(self, db_path, os_name=None, logging=True) -> None:
|
||||||
@@ -102,8 +112,7 @@ class Account(object):
|
|||||||
|
|
||||||
def _check_config_key(self, name: str) -> None:
|
def _check_config_key(self, name: str) -> None:
|
||||||
if name not in self._configkeys:
|
if name not in self._configkeys:
|
||||||
raise KeyError("{!r} not a valid config key, existing keys: {!r}".format(
|
raise KeyError("{!r} not a valid config key, existing keys: {!r}".format(name, self._configkeys))
|
||||||
name, self._configkeys))
|
|
||||||
|
|
||||||
def get_info(self) -> Dict[str, str]:
|
def get_info(self) -> Dict[str, str]:
|
||||||
"""return dictionary of built config parameters."""
|
"""return dictionary of built config parameters."""
|
||||||
@@ -175,10 +184,12 @@ class Account(object):
|
|||||||
|
|
||||||
In other words, you don't need this.
|
In other words, you don't need this.
|
||||||
"""
|
"""
|
||||||
res = lib.dc_preconfigure_keypair(self._dc_context,
|
res = lib.dc_preconfigure_keypair(
|
||||||
|
self._dc_context,
|
||||||
as_dc_charpointer(addr),
|
as_dc_charpointer(addr),
|
||||||
as_dc_charpointer(public),
|
as_dc_charpointer(public),
|
||||||
as_dc_charpointer(secret))
|
as_dc_charpointer(secret),
|
||||||
|
)
|
||||||
if res == 0:
|
if res == 0:
|
||||||
raise Exception("Failed to set key")
|
raise Exception("Failed to set key")
|
||||||
|
|
||||||
@@ -224,8 +235,7 @@ class Account(object):
|
|||||||
raise ValueError("need to configure first")
|
raise ValueError("need to configure first")
|
||||||
|
|
||||||
def get_latest_backupfile(self, backupdir) -> Optional[str]:
|
def get_latest_backupfile(self, backupdir) -> Optional[str]:
|
||||||
""" return the latest backup file in a given directory.
|
"""return the latest backup file in a given directory."""
|
||||||
"""
|
|
||||||
res = lib.dc_imex_has_backup(self._dc_context, as_dc_charpointer(backupdir))
|
res = lib.dc_imex_has_backup(self._dc_context, as_dc_charpointer(backupdir))
|
||||||
return from_optional_dc_charpointer(res)
|
return from_optional_dc_charpointer(res)
|
||||||
|
|
||||||
@@ -318,10 +328,7 @@ class Account(object):
|
|||||||
|
|
||||||
:returns: list of :class:`deltachat.contact.Contact` objects.
|
:returns: list of :class:`deltachat.contact.Contact` objects.
|
||||||
"""
|
"""
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(lib.dc_get_blocked_contacts(self._dc_context), lib.dc_array_unref)
|
||||||
lib.dc_get_blocked_contacts(self._dc_context),
|
|
||||||
lib.dc_array_unref
|
|
||||||
)
|
|
||||||
return list(iter_array(dc_array, lambda x: Contact(self, x)))
|
return list(iter_array(dc_array, lambda x: Contact(self, x)))
|
||||||
|
|
||||||
def get_contacts(
|
def get_contacts(
|
||||||
@@ -344,18 +351,12 @@ class Account(object):
|
|||||||
flags |= const.DC_GCL_VERIFIED_ONLY
|
flags |= const.DC_GCL_VERIFIED_ONLY
|
||||||
if with_self:
|
if with_self:
|
||||||
flags |= const.DC_GCL_ADD_SELF
|
flags |= const.DC_GCL_ADD_SELF
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(lib.dc_get_contacts(self._dc_context, flags, query), lib.dc_array_unref)
|
||||||
lib.dc_get_contacts(self._dc_context, flags, query),
|
|
||||||
lib.dc_array_unref
|
|
||||||
)
|
|
||||||
return list(iter_array(dc_array, lambda x: Contact(self, x)))
|
return list(iter_array(dc_array, lambda x: Contact(self, x)))
|
||||||
|
|
||||||
def get_fresh_messages(self) -> Generator[Message, None, None]:
|
def get_fresh_messages(self) -> Generator[Message, None, None]:
|
||||||
"""yield all fresh messages from all chats."""
|
"""yield all fresh messages from all chats."""
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(lib.dc_get_fresh_msgs(self._dc_context), lib.dc_array_unref)
|
||||||
lib.dc_get_fresh_msgs(self._dc_context),
|
|
||||||
lib.dc_array_unref
|
|
||||||
)
|
|
||||||
yield from iter_array(dc_array, lambda x: Message.from_db(self, x))
|
yield from iter_array(dc_array, lambda x: Message.from_db(self, x))
|
||||||
|
|
||||||
def create_chat(self, obj) -> Chat:
|
def create_chat(self, obj) -> Chat:
|
||||||
@@ -389,10 +390,7 @@ class Account(object):
|
|||||||
|
|
||||||
:returns: a list of :class:`deltachat.chat.Chat` objects.
|
:returns: a list of :class:`deltachat.chat.Chat` objects.
|
||||||
"""
|
"""
|
||||||
dc_chatlist = ffi.gc(
|
dc_chatlist = ffi.gc(lib.dc_get_chatlist(self._dc_context, 0, ffi.NULL, 0), lib.dc_chatlist_unref)
|
||||||
lib.dc_get_chatlist(self._dc_context, 0, ffi.NULL, 0),
|
|
||||||
lib.dc_chatlist_unref
|
|
||||||
)
|
|
||||||
|
|
||||||
assert dc_chatlist != ffi.NULL
|
assert dc_chatlist != ffi.NULL
|
||||||
chatlist = []
|
chatlist = []
|
||||||
@@ -528,10 +526,7 @@ class Account(object):
|
|||||||
|
|
||||||
def check_qr(self, qr):
|
def check_qr(self, qr):
|
||||||
"""check qr code and return :class:`ScannedQRCode` instance representing the result"""
|
"""check qr code and return :class:`ScannedQRCode` instance representing the result"""
|
||||||
res = ffi.gc(
|
res = ffi.gc(lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)), lib.dc_lot_unref)
|
||||||
lib.dc_check_qr(self._dc_context, as_dc_charpointer(qr)),
|
|
||||||
lib.dc_lot_unref
|
|
||||||
)
|
|
||||||
lot = DCLot(res)
|
lot = DCLot(res)
|
||||||
if lot.state() == const.DC_QR_ERROR:
|
if lot.state() == const.DC_QR_ERROR:
|
||||||
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
|
raise ValueError("invalid or unknown QR code: {}".format(lot.text1()))
|
||||||
@@ -566,9 +561,7 @@ class Account(object):
|
|||||||
raise ValueError("could not join group")
|
raise ValueError("could not join group")
|
||||||
return Chat(self, chat_id)
|
return Chat(self, chat_id)
|
||||||
|
|
||||||
def set_location(
|
def set_location(self, latitude: float = 0.0, longitude: float = 0.0, accuracy: float = 0.0) -> None:
|
||||||
self, latitude: float = 0.0, longitude: float = 0.0, accuracy: float = 0.0
|
|
||||||
) -> None:
|
|
||||||
"""set a new location. It effects all chats where we currently
|
"""set a new location. It effects all chats where we currently
|
||||||
have enabled location streaming.
|
have enabled location streaming.
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,22 @@
|
|||||||
""" Chat and Location related API. """
|
""" Chat and Location related API. """
|
||||||
|
|
||||||
import mimetypes
|
|
||||||
import calendar
|
import calendar
|
||||||
import json
|
import json
|
||||||
from datetime import datetime, timezone
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
from .cutil import as_dc_charpointer, from_dc_charpointer, from_optional_dc_charpointer, iter_array
|
from datetime import datetime, timezone
|
||||||
from .capi import lib, ffi
|
|
||||||
from . import const
|
|
||||||
from .message import Message
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from . import const
|
||||||
|
from .capi import ffi, lib
|
||||||
|
from .cutil import (
|
||||||
|
as_dc_charpointer,
|
||||||
|
from_dc_charpointer,
|
||||||
|
from_optional_dc_charpointer,
|
||||||
|
iter_array,
|
||||||
|
)
|
||||||
|
from .message import Message
|
||||||
|
|
||||||
|
|
||||||
class Chat(object):
|
class Chat(object):
|
||||||
"""Chat object which manages members and through which you can send and retrieve messages.
|
"""Chat object which manages members and through which you can send and retrieve messages.
|
||||||
@@ -20,13 +26,13 @@ class Chat(object):
|
|||||||
|
|
||||||
def __init__(self, account, id) -> None:
|
def __init__(self, account, id) -> None:
|
||||||
from .account import Account
|
from .account import Account
|
||||||
|
|
||||||
assert isinstance(account, Account), repr(account)
|
assert isinstance(account, Account), repr(account)
|
||||||
self.account = account
|
self.account = account
|
||||||
self.id = id
|
self.id = id
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
return self.id == getattr(other, "id", None) and \
|
return self.id == getattr(other, "id", None) and self.account._dc_context == other.account._dc_context
|
||||||
self.account._dc_context == other.account._dc_context
|
|
||||||
|
|
||||||
def __ne__(self, other) -> bool:
|
def __ne__(self, other) -> bool:
|
||||||
return not (self == other)
|
return not (self == other)
|
||||||
@@ -36,10 +42,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.account._dc_context, self.id), lib.dc_chat_unref)
|
||||||
lib.dc_get_chat(self.account._dc_context, self.id),
|
|
||||||
lib.dc_chat_unref
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete(self) -> None:
|
def delete(self) -> None:
|
||||||
"""Delete this chat and all its messages.
|
"""Delete this chat and all its messages.
|
||||||
@@ -343,7 +346,7 @@ class Chat(object):
|
|||||||
"""
|
"""
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(
|
||||||
lib.dc_get_chat_msgs(self.account._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)))
|
||||||
|
|
||||||
@@ -399,19 +402,18 @@ class Chat(object):
|
|||||||
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
|
:returns: list of :class:`deltachat.contact.Contact` objects for this chat
|
||||||
"""
|
"""
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
|
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(
|
||||||
lib.dc_get_chat_contacts(self.account._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(
|
|
||||||
dc_array, lambda id: Contact(self.account, id))
|
|
||||||
)
|
)
|
||||||
|
return list(iter_array(dc_array, lambda id: Contact(self.account, id)))
|
||||||
|
|
||||||
def num_contacts(self):
|
def num_contacts(self):
|
||||||
"""return number of contacts in this chat."""
|
"""return number of contacts in this chat."""
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(
|
||||||
lib.dc_get_chat_contacts(self.account._dc_context, self.id),
|
lib.dc_get_chat_contacts(self.account._dc_context, self.id),
|
||||||
lib.dc_array_unref
|
lib.dc_array_unref,
|
||||||
)
|
)
|
||||||
return lib.dc_array_get_cnt(dc_array)
|
return lib.dc_array_get_cnt(dc_array)
|
||||||
|
|
||||||
@@ -513,10 +515,7 @@ class Chat(object):
|
|||||||
latitude=lib.dc_array_get_latitude(dc_array, i),
|
latitude=lib.dc_array_get_latitude(dc_array, i),
|
||||||
longitude=lib.dc_array_get_longitude(dc_array, i),
|
longitude=lib.dc_array_get_longitude(dc_array, i),
|
||||||
accuracy=lib.dc_array_get_accuracy(dc_array, i),
|
accuracy=lib.dc_array_get_accuracy(dc_array, i),
|
||||||
timestamp=datetime.fromtimestamp(
|
timestamp=datetime.fromtimestamp(lib.dc_array_get_timestamp(dc_array, i), timezone.utc),
|
||||||
lib.dc_array_get_timestamp(dc_array, i),
|
|
||||||
timezone.utc
|
|
||||||
),
|
|
||||||
marker=from_optional_dc_charpointer(lib.dc_array_get_marker(dc_array, i)),
|
marker=from_optional_dc_charpointer(lib.dc_array_get_marker(dc_array, i)),
|
||||||
)
|
)
|
||||||
for i in range(lib.dc_array_get_cnt(dc_array))
|
for i in range(lib.dc_array_get_cnt(dc_array))
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ class Contact(object):
|
|||||||
|
|
||||||
You obtain instances of it through :class:`deltachat.account.Account`.
|
You obtain instances of it through :class:`deltachat.account.Account`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, account, id):
|
def __init__(self, account, id):
|
||||||
from .account import Account
|
from .account import Account
|
||||||
|
|
||||||
assert isinstance(account, Account), repr(account)
|
assert isinstance(account, Account), repr(account)
|
||||||
self.account = account
|
self.account = account
|
||||||
self.id = id
|
self.id = id
|
||||||
@@ -31,10 +33,7 @@ class Contact(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _dc_contact(self):
|
def _dc_contact(self):
|
||||||
return ffi.gc(
|
return ffi.gc(lib.dc_get_contact(self.account._dc_context, self.id), lib.dc_contact_unref)
|
||||||
lib.dc_get_contact(self.account._dc_context, self.id),
|
|
||||||
lib.dc_contact_unref
|
|
||||||
)
|
|
||||||
|
|
||||||
@props.with_doc
|
@props.with_doc
|
||||||
def addr(self) -> str:
|
def addr(self) -> str:
|
||||||
@@ -52,9 +51,7 @@ class Contact(object):
|
|||||||
@props.with_doc
|
@props.with_doc
|
||||||
def last_seen(self) -> date:
|
def last_seen(self) -> date:
|
||||||
"""Last seen timestamp."""
|
"""Last seen timestamp."""
|
||||||
return datetime.fromtimestamp(
|
return datetime.fromtimestamp(lib.dc_contact_get_last_seen(self._dc_contact), timezone.utc)
|
||||||
lib.dc_contact_get_last_seen(self._dc_contact), timezone.utc
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_blocked(self):
|
def is_blocked(self):
|
||||||
"""Return True if the contact is blocked."""
|
"""Return True if the contact is blocked."""
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
from .capi import lib
|
|
||||||
from .capi import ffi
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional, TypeVar, Generator, Callable
|
from typing import Callable, Generator, Optional, TypeVar
|
||||||
|
|
||||||
T = TypeVar('T')
|
from .capi import ffi, lib
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
def as_dc_charpointer(obj):
|
def as_dc_charpointer(obj):
|
||||||
|
|||||||
@@ -3,18 +3,27 @@ Internal Python-level IMAP handling used by the testplugin
|
|||||||
and for cleaning up inbox/mvbox for each test function run.
|
and for cleaning up inbox/mvbox for each test function run.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import io
|
|
||||||
import ssl
|
|
||||||
import pathlib
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from imap_tools import MailBox, MailBoxTls, errors, AND, Header, MailMessageFlags, MailMessage
|
|
||||||
import imaplib
|
import imaplib
|
||||||
from deltachat import const, Account
|
import io
|
||||||
|
import pathlib
|
||||||
|
import ssl
|
||||||
|
from contextlib import contextmanager
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
from imap_tools import (
|
||||||
|
AND,
|
||||||
|
Header,
|
||||||
|
MailBox,
|
||||||
|
MailBoxTls,
|
||||||
|
MailMessage,
|
||||||
|
MailMessageFlags,
|
||||||
|
errors,
|
||||||
|
)
|
||||||
|
|
||||||
FLAGS = b'FLAGS'
|
from deltachat import Account, const
|
||||||
FETCH = b'FETCH'
|
|
||||||
|
FLAGS = b"FLAGS"
|
||||||
|
FETCH = b"FETCH"
|
||||||
ALL = "1:*"
|
ALL = "1:*"
|
||||||
|
|
||||||
|
|
||||||
@@ -88,7 +97,7 @@ class DirectImap:
|
|||||||
to make sure the messages are really gone and not
|
to make sure the messages are really gone and not
|
||||||
just flagged as deleted.
|
just flagged as deleted.
|
||||||
"""
|
"""
|
||||||
self.conn.client.uid('STORE', uid_list, '+FLAGS', r'(\Deleted)')
|
self.conn.client.uid("STORE", uid_list, "+FLAGS", r"(\Deleted)")
|
||||||
if expunge:
|
if expunge:
|
||||||
self.conn.expunge()
|
self.conn.expunge()
|
||||||
|
|
||||||
@@ -141,7 +150,13 @@ class DirectImap:
|
|||||||
fn = path.joinpath(str(msg.uid))
|
fn = path.joinpath(str(msg.uid))
|
||||||
fn.write_bytes(body)
|
fn.write_bytes(body)
|
||||||
log("Message", msg.uid, fn)
|
log("Message", msg.uid, fn)
|
||||||
log("Message", msg.uid, msg.flags, "Message-Id:", msg.obj.get("Message-Id"))
|
log(
|
||||||
|
"Message",
|
||||||
|
msg.uid,
|
||||||
|
msg.flags,
|
||||||
|
"Message-Id:",
|
||||||
|
msg.obj.get("Message-Id"),
|
||||||
|
)
|
||||||
|
|
||||||
if empty_folders:
|
if empty_folders:
|
||||||
log("--------- EMPTY FOLDERS:", empty_folders)
|
log("--------- EMPTY FOLDERS:", empty_folders)
|
||||||
@@ -163,11 +178,11 @@ class DirectImap:
|
|||||||
"""
|
"""
|
||||||
if msg.startswith("\n"):
|
if msg.startswith("\n"):
|
||||||
msg = msg[1:]
|
msg = msg[1:]
|
||||||
msg = '\n'.join([s.lstrip() for s in msg.splitlines()])
|
msg = "\n".join([s.lstrip() for s in msg.splitlines()])
|
||||||
self.conn.append(bytes(msg, encoding='ascii'), folder)
|
self.conn.append(bytes(msg, encoding="ascii"), folder)
|
||||||
|
|
||||||
def get_uid_by_message_id(self, message_id) -> str:
|
def get_uid_by_message_id(self, message_id) -> str:
|
||||||
msgs = [msg.uid for msg in self.conn.fetch(AND(header=Header('MESSAGE-ID', message_id)))]
|
msgs = [msg.uid for msg in self.conn.fetch(AND(header=Header("MESSAGE-ID", message_id)))]
|
||||||
if len(msgs) == 0:
|
if len(msgs) == 0:
|
||||||
raise Exception("Did not find message " + message_id + ", maybe you forgot to select the correct folder?")
|
raise Exception("Did not find message " + message_id + ", maybe you forgot to select the correct folder?")
|
||||||
return msgs[0]
|
return msgs[0]
|
||||||
@@ -192,18 +207,17 @@ class IdleManager:
|
|||||||
def wait_for_new_message(self, timeout=None) -> bytes:
|
def wait_for_new_message(self, timeout=None) -> bytes:
|
||||||
while 1:
|
while 1:
|
||||||
for item in self.check(timeout=timeout):
|
for item in self.check(timeout=timeout):
|
||||||
if b'EXISTS' in item or b'RECENT' in item:
|
if b"EXISTS" in item or b"RECENT" in item:
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def wait_for_seen(self, timeout=None) -> int:
|
def wait_for_seen(self, timeout=None) -> int:
|
||||||
""" Return first message with SEEN flag from a running idle-stream.
|
"""Return first message with SEEN flag from a running idle-stream."""
|
||||||
"""
|
|
||||||
while 1:
|
while 1:
|
||||||
for item in self.check(timeout=timeout):
|
for item in self.check(timeout=timeout):
|
||||||
if FETCH in item:
|
if FETCH in item:
|
||||||
self.log(str(item))
|
self.log(str(item))
|
||||||
if FLAGS in item and rb'\Seen' in item:
|
if FLAGS in item and rb"\Seen" in item:
|
||||||
return int(item.split(b' ')[1])
|
return int(item.split(b" ")[1])
|
||||||
|
|
||||||
def done(self):
|
def done(self):
|
||||||
"""send idle-done to server if we are currently in idle mode."""
|
"""send idle-done to server if we are currently in idle mode."""
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import threading
|
|
||||||
import sys
|
|
||||||
import traceback
|
|
||||||
import time
|
|
||||||
import io
|
import io
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
from queue import Queue, Empty
|
import re
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from queue import Empty, Queue
|
||||||
|
|
||||||
import deltachat
|
import deltachat
|
||||||
from .hookspec import account_hookimpl
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from .capi import ffi, lib
|
from .capi import ffi, lib
|
||||||
from .message import map_system_message
|
|
||||||
from .cutil import from_optional_dc_charpointer
|
from .cutil import from_optional_dc_charpointer
|
||||||
|
from .hookspec import account_hookimpl
|
||||||
|
from .message import map_system_message
|
||||||
|
|
||||||
|
|
||||||
class FFIEvent:
|
class FFIEvent:
|
||||||
@@ -29,6 +30,7 @@ class FFIEventLogger:
|
|||||||
"""If you register an instance of this logger with an Account
|
"""If you register an instance of this logger with an Account
|
||||||
you'll get all ffi-events printed.
|
you'll get all ffi-events printed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# to prevent garbled logging
|
# to prevent garbled logging
|
||||||
_loglock = threading.RLock()
|
_loglock = threading.RLock()
|
||||||
|
|
||||||
@@ -56,9 +58,9 @@ class FFIEventLogger:
|
|||||||
s = "{:2.2f} [{}] {}".format(elapsed, locname, message)
|
s = "{:2.2f} [{}] {}".format(elapsed, locname, message)
|
||||||
|
|
||||||
if os.name == "posix":
|
if os.name == "posix":
|
||||||
WARN = '\033[93m'
|
WARN = "\033[93m"
|
||||||
ERROR = '\033[91m'
|
ERROR = "\033[91m"
|
||||||
ENDC = '\033[0m'
|
ENDC = "\033[0m"
|
||||||
if message.startswith("DC_EVENT_WARNING"):
|
if message.startswith("DC_EVENT_WARNING"):
|
||||||
s = WARN + s + ENDC
|
s = WARN + s + ENDC
|
||||||
if message.startswith("DC_EVENT_ERROR"):
|
if message.startswith("DC_EVENT_ERROR"):
|
||||||
@@ -195,6 +197,7 @@ class EventThread(threading.Thread):
|
|||||||
|
|
||||||
With each Account init this callback thread is started.
|
With each Account init this callback thread is started.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, account) -> None:
|
def __init__(self, account) -> None:
|
||||||
self.account = account
|
self.account = account
|
||||||
super(EventThread, self).__init__(name="events")
|
super(EventThread, self).__init__(name="events")
|
||||||
@@ -259,8 +262,7 @@ class EventThread(threading.Thread):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logfile = io.StringIO()
|
logfile = io.StringIO()
|
||||||
traceback.print_exception(*sys.exc_info(), file=logfile)
|
traceback.print_exception(*sys.exc_info(), file=logfile)
|
||||||
self.account.log("{}\nException {}\nTraceback:\n{}"
|
self.account.log("{}\nException {}\nTraceback:\n{}".format(info, ex, logfile.getvalue()))
|
||||||
.format(info, ex, logfile.getvalue()))
|
|
||||||
|
|
||||||
def _map_ffi_event(self, ffi_event: FFIEvent):
|
def _map_ffi_event(self, ffi_event: FFIEvent):
|
||||||
name = ffi_event.name
|
name = ffi_event.name
|
||||||
@@ -282,7 +284,10 @@ class EventThread(threading.Thread):
|
|||||||
yield res
|
yield res
|
||||||
yield "ac_outgoing_message", dict(message=msg)
|
yield "ac_outgoing_message", dict(message=msg)
|
||||||
elif msg.is_in_fresh():
|
elif msg.is_in_fresh():
|
||||||
yield map_system_message(msg) or ("ac_incoming_message", dict(message=msg))
|
yield map_system_message(msg) or (
|
||||||
|
"ac_incoming_message",
|
||||||
|
dict(message=msg),
|
||||||
|
)
|
||||||
elif name == "DC_EVENT_MSG_DELIVERED":
|
elif name == "DC_EVENT_MSG_DELIVERED":
|
||||||
msg = account.get_message_by_id(ffi_event.data2)
|
msg = account.get_message_by_id(ffi_event.data2)
|
||||||
yield "ac_message_delivered", dict(message=msg)
|
yield "ac_message_delivered", dict(message=msg)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
|
||||||
|
|
||||||
account_spec_name = "deltachat-account"
|
account_spec_name = "deltachat-account"
|
||||||
account_hookspec = pluggy.HookspecMarker(account_spec_name)
|
account_hookspec = pluggy.HookspecMarker(account_spec_name)
|
||||||
account_hookimpl = pluggy.HookimplMarker(account_spec_name)
|
account_hookimpl = pluggy.HookimplMarker(account_spec_name)
|
||||||
@@ -19,6 +18,7 @@ class PerAccount:
|
|||||||
Hooks are generally not allowed to block/last long as this
|
Hooks are generally not allowed to block/last long as this
|
||||||
blocks overall event processing on the python side.
|
blocks overall event processing on the python side.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _make_plugin_manager(cls):
|
def _make_plugin_manager(cls):
|
||||||
pm = pluggy.PluginManager(account_spec_name)
|
pm = pluggy.PluginManager(account_spec_name)
|
||||||
@@ -89,6 +89,7 @@ class Global:
|
|||||||
plugin manager instance.
|
plugin manager instance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_plugin_manager = None
|
_plugin_manager = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from . import props
|
|
||||||
from .cutil import from_dc_charpointer, from_optional_dc_charpointer, as_dc_charpointer
|
|
||||||
from .capi import lib, ffi
|
|
||||||
from . import const
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from . import const, props
|
||||||
|
from .capi import ffi, lib
|
||||||
|
from .cutil import as_dc_charpointer, from_dc_charpointer, from_optional_dc_charpointer
|
||||||
|
|
||||||
|
|
||||||
class Message(object):
|
class Message(object):
|
||||||
"""Message object.
|
"""Message object.
|
||||||
@@ -16,6 +16,7 @@ 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:`deltachat.chat.Chat`.
|
:class:`deltachat.chat.Chat`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, account, dc_msg):
|
def __init__(self, account, dc_msg):
|
||||||
self.account = account
|
self.account = account
|
||||||
assert isinstance(self.account._dc_context, ffi.CData)
|
assert isinstance(self.account._dc_context, ffi.CData)
|
||||||
@@ -32,16 +33,20 @@ class Message(object):
|
|||||||
c = self.get_sender_contact()
|
c = self.get_sender_contact()
|
||||||
typ = "outgoing" if self.is_outgoing() else "incoming"
|
typ = "outgoing" if self.is_outgoing() else "incoming"
|
||||||
return "<Message {} sys={} {} id={} sender={}/{} chat={}/{}>".format(
|
return "<Message {} sys={} {} id={} sender={}/{} chat={}/{}>".format(
|
||||||
typ, self.is_system_message(), repr(self.text[:10]),
|
typ,
|
||||||
self.id, c.id, c.addr, self.chat.id, self.chat.get_name())
|
self.is_system_message(),
|
||||||
|
repr(self.text[:10]),
|
||||||
|
self.id,
|
||||||
|
c.id,
|
||||||
|
c.addr,
|
||||||
|
self.chat.id,
|
||||||
|
self.chat.get_name(),
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_db(cls, account, id):
|
def from_db(cls, account, id):
|
||||||
assert id > 0
|
assert id > 0
|
||||||
return cls(account, ffi.gc(
|
return cls(account, ffi.gc(lib.dc_get_msg(account._dc_context, id), lib.dc_msg_unref))
|
||||||
lib.dc_get_msg(account._dc_context, id),
|
|
||||||
lib.dc_msg_unref
|
|
||||||
))
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def new_empty(cls, account, view_type):
|
def new_empty(cls, account, view_type):
|
||||||
@@ -54,10 +59,10 @@ class Message(object):
|
|||||||
view_type_code = view_type
|
view_type_code = view_type
|
||||||
else:
|
else:
|
||||||
view_type_code = get_viewtype_code_from_name(view_type)
|
view_type_code = get_viewtype_code_from_name(view_type)
|
||||||
return Message(account, ffi.gc(
|
return Message(
|
||||||
lib.dc_msg_new(account._dc_context, view_type_code),
|
account,
|
||||||
lib.dc_msg_unref
|
ffi.gc(lib.dc_msg_new(account._dc_context, view_type_code), lib.dc_msg_unref),
|
||||||
))
|
)
|
||||||
|
|
||||||
def create_chat(self):
|
def create_chat(self):
|
||||||
"""create or get an existing chat (group) object for this message.
|
"""create or get an existing chat (group) object for this message.
|
||||||
@@ -87,8 +92,7 @@ class Message(object):
|
|||||||
@props.with_doc
|
@props.with_doc
|
||||||
def html(self) -> str:
|
def html(self) -> str:
|
||||||
"""html text of this messages (might be empty if not an html message)."""
|
"""html text of this messages (might be empty if not an html message)."""
|
||||||
return from_optional_dc_charpointer(
|
return from_optional_dc_charpointer(lib.dc_get_msg_html(self.account._dc_context, self.id)) or ""
|
||||||
lib.dc_get_msg_html(self.account._dc_context, self.id)) or ""
|
|
||||||
|
|
||||||
def has_html(self):
|
def has_html(self):
|
||||||
"""return True if this message has an html part, False otherwise."""
|
"""return True if this message has an html part, False otherwise."""
|
||||||
@@ -157,11 +161,7 @@ class Message(object):
|
|||||||
|
|
||||||
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.account._dc_context, self.id, as_dc_charpointer(setup_code))
|
||||||
self.account._dc_context,
|
|
||||||
self.id,
|
|
||||||
as_dc_charpointer(setup_code)
|
|
||||||
)
|
|
||||||
if res == 0:
|
if res == 0:
|
||||||
raise ValueError("could not decrypt")
|
raise ValueError("could not decrypt")
|
||||||
|
|
||||||
@@ -238,6 +238,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.account._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))
|
||||||
@@ -257,6 +258,7 @@ class Message(object):
|
|||||||
:returns: :class:`deltachat.chat.Chat` object
|
:returns: :class:`deltachat.chat.Chat` object
|
||||||
"""
|
"""
|
||||||
from .chat import Chat
|
from .chat import Chat
|
||||||
|
|
||||||
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
|
chat_id = lib.dc_msg_get_chat_id(self._dc_msg)
|
||||||
return Chat(self.account, chat_id)
|
return Chat(self.account, chat_id)
|
||||||
|
|
||||||
@@ -266,13 +268,11 @@ class Message(object):
|
|||||||
|
|
||||||
Usually used to impersonate someone else.
|
Usually used to impersonate someone else.
|
||||||
"""
|
"""
|
||||||
return from_optional_dc_charpointer(
|
return from_optional_dc_charpointer(lib.dc_msg_get_override_sender_name(self._dc_msg))
|
||||||
lib.dc_msg_get_override_sender_name(self._dc_msg))
|
|
||||||
|
|
||||||
def set_override_sender_name(self, name):
|
def set_override_sender_name(self, name):
|
||||||
"""set different sender name for a message."""
|
"""set different sender name for a message."""
|
||||||
lib.dc_msg_set_override_sender_name(
|
lib.dc_msg_set_override_sender_name(self._dc_msg, as_dc_charpointer(name))
|
||||||
self._dc_msg, as_dc_charpointer(name))
|
|
||||||
|
|
||||||
def get_sender_chat(self):
|
def get_sender_chat(self):
|
||||||
"""return the 1:1 chat with the sender of this message.
|
"""return the 1:1 chat with the sender of this message.
|
||||||
@@ -287,6 +287,7 @@ class Message(object):
|
|||||||
:returns: :class:`deltachat.chat.Contact` instance
|
:returns: :class:`deltachat.chat.Contact` instance
|
||||||
"""
|
"""
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
|
|
||||||
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
contact_id = lib.dc_msg_get_from_id(self._dc_msg)
|
||||||
return Contact(self.account, contact_id)
|
return Contact(self.account, contact_id)
|
||||||
|
|
||||||
@@ -299,10 +300,7 @@ class Message(object):
|
|||||||
dc_msg = self._dc_msg
|
dc_msg = self._dc_msg
|
||||||
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.account._dc_context, self.id), lib.dc_msg_unref)
|
||||||
lib.dc_get_msg(self.account._dc_context, self.id),
|
|
||||||
lib.dc_msg_unref
|
|
||||||
)
|
|
||||||
return lib.dc_msg_get_state(dc_msg)
|
return lib.dc_msg_get_state(dc_msg)
|
||||||
|
|
||||||
def is_in_fresh(self):
|
def is_in_fresh(self):
|
||||||
@@ -332,23 +330,23 @@ class Message(object):
|
|||||||
def is_outgoing(self):
|
def is_outgoing(self):
|
||||||
"""Return True if Message is outgoing."""
|
"""Return True if Message is outgoing."""
|
||||||
return self._msgstate in (
|
return self._msgstate in (
|
||||||
const.DC_STATE_OUT_PREPARING, const.DC_STATE_OUT_PENDING,
|
const.DC_STATE_OUT_PREPARING,
|
||||||
const.DC_STATE_OUT_FAILED, const.DC_STATE_OUT_MDN_RCVD,
|
const.DC_STATE_OUT_PENDING,
|
||||||
const.DC_STATE_OUT_DELIVERED)
|
const.DC_STATE_OUT_FAILED,
|
||||||
|
const.DC_STATE_OUT_MDN_RCVD,
|
||||||
|
const.DC_STATE_OUT_DELIVERED,
|
||||||
|
)
|
||||||
|
|
||||||
def is_out_preparing(self):
|
def is_out_preparing(self):
|
||||||
"""Return True if Message is outgoing, but its file is being prepared.
|
"""Return True if Message is outgoing, but its file is being prepared."""
|
||||||
"""
|
|
||||||
return self._msgstate == const.DC_STATE_OUT_PREPARING
|
return self._msgstate == const.DC_STATE_OUT_PREPARING
|
||||||
|
|
||||||
def is_out_pending(self):
|
def is_out_pending(self):
|
||||||
"""Return True if Message is outgoing, but is pending (no single checkmark).
|
"""Return True if Message is outgoing, but is pending (no single checkmark)."""
|
||||||
"""
|
|
||||||
return self._msgstate == const.DC_STATE_OUT_PENDING
|
return self._msgstate == const.DC_STATE_OUT_PENDING
|
||||||
|
|
||||||
def is_out_failed(self):
|
def is_out_failed(self):
|
||||||
"""Return True if Message is unrecoverably failed.
|
"""Return True if Message is unrecoverably failed."""
|
||||||
"""
|
|
||||||
return self._msgstate == const.DC_STATE_OUT_FAILED
|
return self._msgstate == const.DC_STATE_OUT_FAILED
|
||||||
|
|
||||||
def is_out_delivered(self):
|
def is_out_delivered(self):
|
||||||
@@ -410,13 +408,13 @@ class Message(object):
|
|||||||
# some code for handling DC_MSG_* view types
|
# some code for handling DC_MSG_* view types
|
||||||
|
|
||||||
_view_type_mapping = {
|
_view_type_mapping = {
|
||||||
'text': const.DC_MSG_TEXT,
|
"text": const.DC_MSG_TEXT,
|
||||||
'image': const.DC_MSG_IMAGE,
|
"image": const.DC_MSG_IMAGE,
|
||||||
'gif': const.DC_MSG_GIF,
|
"gif": const.DC_MSG_GIF,
|
||||||
'audio': const.DC_MSG_AUDIO,
|
"audio": const.DC_MSG_AUDIO,
|
||||||
'video': const.DC_MSG_VIDEO,
|
"video": const.DC_MSG_VIDEO,
|
||||||
'file': const.DC_MSG_FILE,
|
"file": const.DC_MSG_FILE,
|
||||||
'sticker': const.DC_MSG_STICKER,
|
"sticker": const.DC_MSG_STICKER,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -424,14 +422,16 @@ def get_viewtype_code_from_name(view_type_name):
|
|||||||
code = _view_type_mapping.get(view_type_name)
|
code = _view_type_mapping.get(view_type_name)
|
||||||
if code is not None:
|
if code is not None:
|
||||||
return code
|
return code
|
||||||
raise ValueError("message typecode not found for {!r}, "
|
raise ValueError(
|
||||||
"available {!r}".format(view_type_name, list(_view_type_mapping.keys())))
|
"message typecode not found for {!r}, " "available {!r}".format(view_type_name, list(_view_type_mapping.keys()))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# some helper code for turning system messages into hook events
|
# some helper code for turning system messages into hook events
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
def map_system_message(msg):
|
def map_system_message(msg):
|
||||||
if msg.is_system_message():
|
if msg.is_system_message():
|
||||||
res = parse_system_add_remove(msg.text)
|
res = parse_system_add_remove(msg.text)
|
||||||
@@ -448,7 +448,7 @@ def map_system_message(msg):
|
|||||||
|
|
||||||
|
|
||||||
def extract_addr(text):
|
def extract_addr(text):
|
||||||
m = re.match(r'.*\((.+@.+)\)', text)
|
m = re.match(r".*\((.+@.+)\)", text)
|
||||||
if m:
|
if m:
|
||||||
text = m.group(1)
|
text = m.group(1)
|
||||||
text = text.rstrip(".")
|
text = text.rstrip(".")
|
||||||
@@ -467,7 +467,7 @@ def parse_system_add_remove(text):
|
|||||||
# Group left by some one (tmp1@x.org).
|
# Group left by some one (tmp1@x.org).
|
||||||
# Group left by tmp1@x.org.
|
# Group left by tmp1@x.org.
|
||||||
text = text.lower()
|
text = text.lower()
|
||||||
m = re.match(r'member (.+) (removed|added) by (.+)', text)
|
m = re.match(r"member (.+) (removed|added) by (.+)", text)
|
||||||
if m:
|
if m:
|
||||||
affected, action, actor = m.groups()
|
affected, action, actor = m.groups()
|
||||||
return action, extract_addr(affected), extract_addr(actor)
|
return action, extract_addr(affected), extract_addr(actor)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ def with_doc(f):
|
|||||||
# https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py
|
# https://github.com/devpi/devpi/blob/master/common/devpi_common/types.py
|
||||||
def cached(f):
|
def cached(f):
|
||||||
"""returns a cached property that is calculated by function f"""
|
"""returns a cached property that is calculated by function f"""
|
||||||
|
|
||||||
def get(self):
|
def get(self):
|
||||||
try:
|
try:
|
||||||
return self._property_cache[f]
|
return self._property_cache[f]
|
||||||
|
|||||||
@@ -26,14 +26,12 @@ class Provider(object):
|
|||||||
@property
|
@property
|
||||||
def overview_page(self) -> str:
|
def overview_page(self) -> str:
|
||||||
"""URL to the overview page of the provider on providers.delta.chat."""
|
"""URL to the overview page of the provider on providers.delta.chat."""
|
||||||
return from_dc_charpointer(
|
return from_dc_charpointer(lib.dc_provider_get_overview_page(self._provider))
|
||||||
lib.dc_provider_get_overview_page(self._provider))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_before_login_hints(self) -> str:
|
def get_before_login_hints(self) -> str:
|
||||||
"""Should be shown to the user on login."""
|
"""Should be shown to the user on login."""
|
||||||
return from_dc_charpointer(
|
return from_dc_charpointer(lib.dc_provider_get_before_login_hint(self._provider))
|
||||||
lib.dc_provider_get_before_login_hint(self._provider))
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self) -> int:
|
def status(self) -> int:
|
||||||
|
|||||||
@@ -1,56 +1,62 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import io
|
|
||||||
import subprocess
|
|
||||||
import queue
|
|
||||||
import threading
|
|
||||||
import fnmatch
|
import fnmatch
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
import pathlib
|
||||||
|
import queue
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
import weakref
|
import weakref
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from typing import List, Callable
|
from typing import Callable, List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
import pathlib
|
|
||||||
|
|
||||||
from . import Account, const, account_hookimpl, get_core_info
|
|
||||||
from .events import FFIEventLogger, FFIEventTracker
|
|
||||||
from _pytest._code import Source
|
from _pytest._code import Source
|
||||||
|
|
||||||
import deltachat
|
import deltachat
|
||||||
|
|
||||||
|
from . import Account, account_hookimpl, const, get_core_info
|
||||||
|
from .events import FFIEventLogger, FFIEventTracker
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup("deltachat testplugin options")
|
group = parser.getgroup("deltachat testplugin options")
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--liveconfig", action="store", default=None,
|
"--liveconfig",
|
||||||
help="a file with >=2 lines where each line "
|
action="store",
|
||||||
"contains NAME=VALUE config settings for one account"
|
default=None,
|
||||||
|
help="a file with >=2 lines where each line " "contains NAME=VALUE config settings for one account",
|
||||||
)
|
)
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--ignored", action="store_true",
|
"--ignored",
|
||||||
|
action="store_true",
|
||||||
help="Also run tests marked with the ignored marker",
|
help="Also run tests marked with the ignored marker",
|
||||||
)
|
)
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--strict-tls", action="store_true",
|
"--strict-tls",
|
||||||
|
action="store_true",
|
||||||
help="Never accept invalid TLS certificates for test accounts",
|
help="Never accept invalid TLS certificates for test accounts",
|
||||||
)
|
)
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--extra-info", action="store_true",
|
"--extra-info",
|
||||||
help="show more info on failures (imap server state, config)"
|
action="store_true",
|
||||||
|
help="show more info on failures (imap server state, config)",
|
||||||
)
|
)
|
||||||
group.addoption(
|
group.addoption(
|
||||||
"--debug-setup", action="store_true",
|
"--debug-setup",
|
||||||
help="show events during configure and start io phases of online accounts"
|
action="store_true",
|
||||||
|
help="show events during configure and start io phases of online accounts",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
cfg = config.getoption('--liveconfig')
|
cfg = config.getoption("--liveconfig")
|
||||||
if not cfg:
|
if not cfg:
|
||||||
cfg = os.getenv('DCC_NEW_TMP_EMAIL')
|
cfg = os.getenv("DCC_NEW_TMP_EMAIL")
|
||||||
if cfg:
|
if cfg:
|
||||||
config.option.liveconfig = cfg
|
config.option.liveconfig = cfg
|
||||||
|
|
||||||
@@ -113,19 +119,21 @@ def pytest_configure(config):
|
|||||||
|
|
||||||
def pytest_report_header(config, startdir):
|
def pytest_report_header(config, startdir):
|
||||||
info = get_core_info()
|
info = get_core_info()
|
||||||
summary = ['Deltachat core={} sqlite={} journal_mode={}'.format(
|
summary = [
|
||||||
info['deltachat_core_version'],
|
"Deltachat core={} sqlite={} journal_mode={}".format(
|
||||||
info['sqlite_version'],
|
info["deltachat_core_version"],
|
||||||
info['journal_mode'],
|
info["sqlite_version"],
|
||||||
)]
|
info["journal_mode"],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
cfg = config.option.liveconfig
|
cfg = config.option.liveconfig
|
||||||
if cfg:
|
if cfg:
|
||||||
if "?" in cfg:
|
if "?" in cfg:
|
||||||
url, token = cfg.split("?", 1)
|
url, token = cfg.split("?", 1)
|
||||||
summary.append('Liveconfig provider: {}?<token ommitted>'.format(url))
|
summary.append("Liveconfig provider: {}?<token ommitted>".format(url))
|
||||||
else:
|
else:
|
||||||
summary.append('Liveconfig file: {}'.format(cfg))
|
summary.append("Liveconfig file: {}".format(cfg))
|
||||||
return summary
|
return summary
|
||||||
|
|
||||||
|
|
||||||
@@ -135,8 +143,8 @@ def testprocess(request):
|
|||||||
|
|
||||||
|
|
||||||
class TestProcess:
|
class TestProcess:
|
||||||
""" A pytest session-scoped instance to help with managing "live" account configurations.
|
"""A pytest session-scoped instance to help with managing "live" account configurations."""
|
||||||
"""
|
|
||||||
def __init__(self, pytestconfig):
|
def __init__(self, pytestconfig):
|
||||||
self.pytestconfig = pytestconfig
|
self.pytestconfig = pytestconfig
|
||||||
self._addr2files = {}
|
self._addr2files = {}
|
||||||
@@ -154,7 +162,7 @@ class TestProcess:
|
|||||||
|
|
||||||
if not liveconfig_opt.startswith("http"):
|
if not liveconfig_opt.startswith("http"):
|
||||||
for line in open(liveconfig_opt):
|
for line in open(liveconfig_opt):
|
||||||
if line.strip() and not line.strip().startswith('#'):
|
if line.strip() and not line.strip().startswith("#"):
|
||||||
d = {}
|
d = {}
|
||||||
for part in line.split():
|
for part in line.split():
|
||||||
name, value = part.split("=")
|
name, value = part.split("=")
|
||||||
@@ -170,8 +178,7 @@ class TestProcess:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
res = requests.post(liveconfig_opt)
|
res = requests.post(liveconfig_opt)
|
||||||
if res.status_code != 200:
|
if res.status_code != 200:
|
||||||
pytest.fail("newtmpuser count={} code={}: '{}'".format(
|
pytest.fail("newtmpuser count={} code={}: '{}'".format(index, res.status_code, res.text))
|
||||||
index, res.status_code, res.text))
|
|
||||||
d = res.json()
|
d = res.json()
|
||||||
config = dict(addr=d["email"], mail_pw=d["password"])
|
config = dict(addr=d["email"], mail_pw=d["password"])
|
||||||
print("newtmpuser {}: addr={}".format(index, config["addr"]))
|
print("newtmpuser {}: addr={}".format(index, config["addr"]))
|
||||||
@@ -230,10 +237,13 @@ def data(request):
|
|||||||
# because we are run from a dev-setup with pytest direct,
|
# because we are run from a dev-setup with pytest direct,
|
||||||
# through tox, and then maybe also from deltachat-binding
|
# through tox, and then maybe also from deltachat-binding
|
||||||
# users like "deltabot".
|
# users like "deltabot".
|
||||||
self.paths = [os.path.normpath(x) for x in [
|
self.paths = [
|
||||||
|
os.path.normpath(x)
|
||||||
|
for x in [
|
||||||
os.path.join(os.path.dirname(request.fspath.strpath), "data"),
|
os.path.join(os.path.dirname(request.fspath.strpath), "data"),
|
||||||
os.path.join(os.path.dirname(__file__), "..", "..", "..", "test-data")
|
os.path.join(os.path.dirname(__file__), "..", "..", "..", "test-data"),
|
||||||
]]
|
]
|
||||||
|
]
|
||||||
|
|
||||||
def get_path(self, bn):
|
def get_path(self, bn):
|
||||||
"""return path of file or None if it doesn't exist."""
|
"""return path of file or None if it doesn't exist."""
|
||||||
@@ -257,6 +267,7 @@ class ACSetup:
|
|||||||
and io & imap initialization phases. From tests, use the higher level
|
and io & imap initialization phases. From tests, use the higher level
|
||||||
public ACFactory methods instead of its private helper class.
|
public ACFactory methods instead of its private helper class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
CONFIGURING = "CONFIGURING"
|
CONFIGURING = "CONFIGURING"
|
||||||
CONFIGURED = "CONFIGURED"
|
CONFIGURED = "CONFIGURED"
|
||||||
IDLEREADY = "IDLEREADY"
|
IDLEREADY = "IDLEREADY"
|
||||||
@@ -279,6 +290,7 @@ class ACSetup:
|
|||||||
|
|
||||||
def start_configure(self, account, reconfigure=False):
|
def start_configure(self, account, reconfigure=False):
|
||||||
"""add an account and start its configure process."""
|
"""add an account and start its configure process."""
|
||||||
|
|
||||||
class PendingTracker:
|
class PendingTracker:
|
||||||
@account_hookimpl
|
@account_hookimpl
|
||||||
def ac_configure_completed(this, success):
|
def ac_configure_completed(this, success):
|
||||||
@@ -375,8 +387,7 @@ class ACFactory:
|
|||||||
self._finalizers = []
|
self._finalizers = []
|
||||||
self._accounts = []
|
self._accounts = []
|
||||||
self._acsetup = ACSetup(testprocess, self.init_time)
|
self._acsetup = ACSetup(testprocess, self.init_time)
|
||||||
self._preconfigured_keys = ["alice", "bob", "charlie",
|
self._preconfigured_keys = ["alice", "bob", "charlie", "dom", "elena", "fiona"]
|
||||||
"dom", "elena", "fiona"]
|
|
||||||
self.set_logging_default(False)
|
self.set_logging_default(False)
|
||||||
request.addfinalizer(self.finalize)
|
request.addfinalizer(self.finalize)
|
||||||
|
|
||||||
@@ -461,11 +472,16 @@ class ACFactory:
|
|||||||
ac = self.get_unconfigured_account()
|
ac = self.get_unconfigured_account()
|
||||||
acname = ac._logid
|
acname = ac._logid
|
||||||
addr = "{}@offline.org".format(acname)
|
addr = "{}@offline.org".format(acname)
|
||||||
ac.update_config(dict(
|
ac.update_config(
|
||||||
addr=addr, displayname=acname, mail_pw="123",
|
dict(
|
||||||
configured_addr=addr, configured_mail_pw="123",
|
addr=addr,
|
||||||
|
displayname=acname,
|
||||||
|
mail_pw="123",
|
||||||
|
configured_addr=addr,
|
||||||
|
configured_mail_pw="123",
|
||||||
configured="1",
|
configured="1",
|
||||||
))
|
)
|
||||||
|
)
|
||||||
self._preconfigure_key(ac, addr)
|
self._preconfigure_key(ac, addr)
|
||||||
self._acsetup.init_logging(ac)
|
self._acsetup.init_logging(ac)
|
||||||
return ac
|
return ac
|
||||||
@@ -531,8 +547,10 @@ class ACFactory:
|
|||||||
sys.executable,
|
sys.executable,
|
||||||
"-u",
|
"-u",
|
||||||
fn,
|
fn,
|
||||||
"--email", bot_cfg["addr"],
|
"--email",
|
||||||
"--password", bot_cfg["mail_pw"],
|
bot_cfg["addr"],
|
||||||
|
"--password",
|
||||||
|
bot_cfg["mail_pw"],
|
||||||
bot_ac.db_path,
|
bot_ac.db_path,
|
||||||
]
|
]
|
||||||
if ffi:
|
if ffi:
|
||||||
@@ -545,7 +563,7 @@ class ACFactory:
|
|||||||
stderr=subprocess.STDOUT, # combine stdout/stderr in one stream
|
stderr=subprocess.STDOUT, # combine stdout/stderr in one stream
|
||||||
bufsize=0, # line buffering
|
bufsize=0, # line buffering
|
||||||
close_fds=True, # close all FDs other than 0/1/2
|
close_fds=True, # close all FDs other than 0/1/2
|
||||||
universal_newlines=True # give back text
|
universal_newlines=True, # give back text
|
||||||
)
|
)
|
||||||
bot = BotProcess(popen, addr=bot_cfg["addr"])
|
bot = BotProcess(popen, addr=bot_cfg["addr"])
|
||||||
self._finalizers.append(bot.kill)
|
self._finalizers.append(bot.kill)
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Event
|
from threading import Event
|
||||||
|
|
||||||
from .hookspec import account_hookimpl, Global
|
from .hookspec import Global, account_hookimpl
|
||||||
|
|
||||||
|
|
||||||
class ImexFailed(RuntimeError):
|
class ImexFailed(RuntimeError):
|
||||||
@@ -24,8 +23,9 @@ class ImexTracker:
|
|||||||
while True:
|
while True:
|
||||||
ev = self._imex_events.get(timeout=progress_timeout)
|
ev = self._imex_events.get(timeout=progress_timeout)
|
||||||
if isinstance(ev, int) and ev >= target_progress:
|
if isinstance(ev, int) and ev >= target_progress:
|
||||||
assert ev <= progress_upper_limit, \
|
assert ev <= progress_upper_limit, (
|
||||||
str(ev) + " exceeded upper progress limit " + str(progress_upper_limit)
|
str(ev) + " exceeded upper progress limit " + str(progress_upper_limit)
|
||||||
|
)
|
||||||
return ev
|
return ev
|
||||||
if ev == 0:
|
if ev == 0:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
assert len(sys.argv) == 2
|
assert len(sys.argv) == 2
|
||||||
workspacedir = sys.argv[1]
|
workspacedir = sys.argv[1]
|
||||||
@@ -13,5 +11,13 @@ if __name__ == "__main__":
|
|||||||
if relpath.startswith("deltachat"):
|
if relpath.startswith("deltachat"):
|
||||||
p = os.path.join(workspacedir, relpath)
|
p = os.path.join(workspacedir, relpath)
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
["auditwheel", "repair", p, "-w", workspacedir,
|
[
|
||||||
"--plat", "manylinux2014_" + arch])
|
"auditwheel",
|
||||||
|
"repair",
|
||||||
|
p,
|
||||||
|
"-w",
|
||||||
|
workspacedir,
|
||||||
|
"--plat",
|
||||||
|
"manylinux2014_" + arch,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
assert len(sys.argv) == 2
|
assert len(sys.argv) == 2
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
import pytest
|
|
||||||
import os
|
import os
|
||||||
from queue import Queue, Empty
|
import threading
|
||||||
|
import time
|
||||||
|
from queue import Empty, Queue
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
import deltachat
|
import deltachat
|
||||||
|
|
||||||
@@ -63,9 +63,7 @@ def test_db_busy_error(acfactory, tmpdir):
|
|||||||
elif report_type == ReportType.message_echo:
|
elif report_type == ReportType.message_echo:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
raise ValueError("{} unknown report type {}, args={}".format(
|
raise ValueError("{} unknown report type {}, args={}".format(addr, report_type, report_args))
|
||||||
addr, report_type, report_args
|
|
||||||
))
|
|
||||||
alive_count -= 1
|
alive_count -= 1
|
||||||
replier.log("shutting down")
|
replier.log("shutting down")
|
||||||
replier.account.shutdown()
|
replier.account.shutdown()
|
||||||
@@ -88,10 +86,7 @@ class AutoReplier:
|
|||||||
self.current_sent = 0
|
self.current_sent = 0
|
||||||
self.addr = self.account.get_self_contact().addr
|
self.addr = self.account.get_self_contact().addr
|
||||||
|
|
||||||
self._thread = threading.Thread(
|
self._thread = threading.Thread(name="Stats{}".format(self.account), target=self.thread_stats)
|
||||||
name="Stats{}".format(self.account),
|
|
||||||
target=self.thread_stats
|
|
||||||
)
|
|
||||||
self._thread.setDaemon(True)
|
self._thread.setDaemon(True)
|
||||||
self._thread.start()
|
self._thread.start()
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@@ -277,7 +278,7 @@ def test_fetch_existing_msgs_group_and_single(acfactory, lp):
|
|||||||
assert len(chats) == 4 # two newly created chats + self-chat + device-chat
|
assert len(chats) == 4 # two newly created chats + self-chat + device-chat
|
||||||
group_chat = [c for c in chats if c.get_name() == "group name"][0]
|
group_chat = [c for c in chats if c.get_name() == "group name"][0]
|
||||||
assert group_chat.is_group()
|
assert group_chat.is_group()
|
||||||
private_chat, = [c for c in chats if c.get_name() == ac1_ac2_chat.get_name()]
|
(private_chat,) = [c for c in chats if c.get_name() == ac1_ac2_chat.get_name()]
|
||||||
assert not private_chat.is_group()
|
assert not private_chat.is_group()
|
||||||
|
|
||||||
group_messages = group_chat.get_messages()
|
group_messages = group_chat.get_messages()
|
||||||
@@ -378,7 +379,7 @@ def test_ephemeral_timer(acfactory, lp):
|
|||||||
lp.sec("ac1: check that ephemeral timer is set for chat")
|
lp.sec("ac1: check that ephemeral timer is set for chat")
|
||||||
assert chat1.get_ephemeral_timer() == 60
|
assert chat1.get_ephemeral_timer() == 60
|
||||||
chat1_summary = chat1.get_summary()
|
chat1_summary = chat1.get_summary()
|
||||||
assert chat1_summary["ephemeral_timer"] == {'Enabled': {'duration': 60}}
|
assert chat1_summary["ephemeral_timer"] == {"Enabled": {"duration": 60}}
|
||||||
|
|
||||||
lp.sec("ac2: receive system message about ephemeral timer modification")
|
lp.sec("ac2: receive system message about ephemeral timer modification")
|
||||||
ac2._evtracker.get_matching("DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED")
|
ac2._evtracker.get_matching("DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED")
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import queue
|
import queue
|
||||||
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from imap_tools import AND, U
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from imap_tools import AND, U
|
||||||
|
|
||||||
from deltachat import const
|
from deltachat import const
|
||||||
from deltachat.hookspec import account_hookimpl
|
from deltachat.hookspec import account_hookimpl
|
||||||
from deltachat.message import Message
|
from deltachat.message import Message
|
||||||
@@ -78,7 +79,7 @@ def test_export_import_self_keys(acfactory, tmpdir, lp):
|
|||||||
assert len(export_files) == 2
|
assert len(export_files) == 2
|
||||||
for x in export_files:
|
for x in export_files:
|
||||||
assert x.startswith(dir.strpath)
|
assert x.startswith(dir.strpath)
|
||||||
key_id, = ac1._evtracker.get_info_regex_groups(r".*xporting.*KeyId\((.*)\).*")
|
(key_id,) = ac1._evtracker.get_info_regex_groups(r".*xporting.*KeyId\((.*)\).*")
|
||||||
ac1._evtracker.consume_events()
|
ac1._evtracker.consume_events()
|
||||||
|
|
||||||
lp.sec("exported keys (private and public)")
|
lp.sec("exported keys (private and public)")
|
||||||
@@ -86,8 +87,7 @@ def test_export_import_self_keys(acfactory, tmpdir, lp):
|
|||||||
lp.indent(dir.strpath + os.sep + name)
|
lp.indent(dir.strpath + os.sep + name)
|
||||||
lp.sec("importing into existing account")
|
lp.sec("importing into existing account")
|
||||||
ac2.import_self_keys(dir.strpath)
|
ac2.import_self_keys(dir.strpath)
|
||||||
key_id2, = ac2._evtracker.get_info_regex_groups(
|
(key_id2,) = ac2._evtracker.get_info_regex_groups(r".*stored.*KeyId\((.*)\).*", check_error=False)
|
||||||
r".*stored.*KeyId\((.*)\).*", check_error=False)
|
|
||||||
assert key_id2 == key_id
|
assert key_id2 == key_id
|
||||||
|
|
||||||
|
|
||||||
@@ -689,7 +689,7 @@ def test_gossip_encryption_preference(acfactory, lp):
|
|||||||
msg = ac1._evtracker.wait_next_incoming_message()
|
msg = ac1._evtracker.wait_next_incoming_message()
|
||||||
assert msg.text == "first message"
|
assert msg.text == "first message"
|
||||||
assert not msg.is_encrypted()
|
assert not msg.is_encrypted()
|
||||||
res = "End-to-end encryption preferred:\n{}".format(ac2.get_config('addr'))
|
res = "End-to-end encryption preferred:\n{}".format(ac2.get_config("addr"))
|
||||||
assert msg.chat.get_encryption_info() == res
|
assert msg.chat.get_encryption_info() == res
|
||||||
lp.sec("ac2 learns that ac3 prefers encryption")
|
lp.sec("ac2 learns that ac3 prefers encryption")
|
||||||
ac2.create_chat(ac3)
|
ac2.create_chat(ac3)
|
||||||
@@ -701,7 +701,7 @@ def test_gossip_encryption_preference(acfactory, lp):
|
|||||||
lp.sec("ac3 does not know that ac1 prefers encryption")
|
lp.sec("ac3 does not know that ac1 prefers encryption")
|
||||||
ac1.create_chat(ac3)
|
ac1.create_chat(ac3)
|
||||||
chat = ac3.create_chat(ac1)
|
chat = ac3.create_chat(ac1)
|
||||||
res = "No encryption:\n{}".format(ac1.get_config('addr'))
|
res = "No encryption:\n{}".format(ac1.get_config("addr"))
|
||||||
assert chat.get_encryption_info() == res
|
assert chat.get_encryption_info() == res
|
||||||
msg = chat.send_text("not encrypted")
|
msg = chat.send_text("not encrypted")
|
||||||
msg = ac1._evtracker.wait_next_incoming_message()
|
msg = ac1._evtracker.wait_next_incoming_message()
|
||||||
@@ -766,7 +766,7 @@ def test_send_first_message_as_long_unicode_with_cr(acfactory, lp):
|
|||||||
|
|
||||||
def test_no_draft_if_cant_send(acfactory):
|
def test_no_draft_if_cant_send(acfactory):
|
||||||
"""Tests that no quote can be set if the user can't send to this chat"""
|
"""Tests that no quote can be set if the user can't send to this chat"""
|
||||||
ac1, = acfactory.get_online_accounts(1)
|
(ac1,) = acfactory.get_online_accounts(1)
|
||||||
device_chat = ac1.get_device_chat()
|
device_chat = ac1.get_device_chat()
|
||||||
msg = Message.new_empty(ac1, "text")
|
msg = Message.new_empty(ac1, "text")
|
||||||
device_chat.set_draft(msg)
|
device_chat.set_draft(msg)
|
||||||
@@ -795,7 +795,9 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
acfactory.bring_accounts_online()
|
acfactory.bring_accounts_online()
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
|
|
||||||
ac1.direct_imap.append("Drafts", """
|
ac1.direct_imap.append(
|
||||||
|
"Drafts",
|
||||||
|
"""
|
||||||
From: ac1 <{}>
|
From: ac1 <{}>
|
||||||
Subject: subj
|
Subject: subj
|
||||||
To: alice@example.org
|
To: alice@example.org
|
||||||
@@ -803,8 +805,13 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
Content-Type: text/plain; charset=utf-8
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
|
||||||
message in Drafts that is moved to Sent later
|
message in Drafts that is moved to Sent later
|
||||||
""".format(ac1.get_config("configured_addr")))
|
""".format(
|
||||||
ac1.direct_imap.append("Sent", """
|
ac1.get_config("configured_addr")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ac1.direct_imap.append(
|
||||||
|
"Sent",
|
||||||
|
"""
|
||||||
From: ac1 <{}>
|
From: ac1 <{}>
|
||||||
Subject: subj
|
Subject: subj
|
||||||
To: alice@example.org
|
To: alice@example.org
|
||||||
@@ -812,8 +819,13 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
Content-Type: text/plain; charset=utf-8
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
|
||||||
message in Sent
|
message in Sent
|
||||||
""".format(ac1.get_config("configured_addr")))
|
""".format(
|
||||||
ac1.direct_imap.append("Spam", """
|
ac1.get_config("configured_addr")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ac1.direct_imap.append(
|
||||||
|
"Spam",
|
||||||
|
"""
|
||||||
From: unknown.address@junk.org
|
From: unknown.address@junk.org
|
||||||
Subject: subj
|
Subject: subj
|
||||||
To: {}
|
To: {}
|
||||||
@@ -821,8 +833,13 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
Content-Type: text/plain; charset=utf-8
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
|
||||||
Unknown message in Spam
|
Unknown message in Spam
|
||||||
""".format(ac1.get_config("configured_addr")))
|
""".format(
|
||||||
ac1.direct_imap.append("Junk", """
|
ac1.get_config("configured_addr")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ac1.direct_imap.append(
|
||||||
|
"Junk",
|
||||||
|
"""
|
||||||
From: unknown.address@junk.org
|
From: unknown.address@junk.org
|
||||||
Subject: subj
|
Subject: subj
|
||||||
To: {}
|
To: {}
|
||||||
@@ -830,7 +847,10 @@ def test_dont_show_emails(acfactory, lp):
|
|||||||
Content-Type: text/plain; charset=utf-8
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
|
||||||
Unknown message in Junk
|
Unknown message in Junk
|
||||||
""".format(ac1.get_config("configured_addr")))
|
""".format(
|
||||||
|
ac1.get_config("configured_addr")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
ac1.set_config("scan_all_folders_debounce_secs", "0")
|
||||||
lp.sec("All prepared, now let DC find the message")
|
lp.sec("All prepared, now let DC find the message")
|
||||||
@@ -1154,7 +1174,7 @@ def test_send_and_receive_image(acfactory, lp, data):
|
|||||||
|
|
||||||
|
|
||||||
def test_import_export_online_all(acfactory, tmpdir, data, lp):
|
def test_import_export_online_all(acfactory, tmpdir, data, lp):
|
||||||
ac1, = acfactory.get_online_accounts(1)
|
(ac1,) = acfactory.get_online_accounts(1)
|
||||||
|
|
||||||
lp.sec("create some chat content")
|
lp.sec("create some chat content")
|
||||||
chat1 = ac1.create_contact("some1@example.org", name="some1").create_chat()
|
chat1 = ac1.create_contact("some1@example.org", name="some1").create_chat()
|
||||||
@@ -1387,8 +1407,7 @@ def test_add_remove_member_remote_events(acfactory, lp):
|
|||||||
ev = in_list.get()
|
ev = in_list.get()
|
||||||
assert ev.action == "chat-modified"
|
assert ev.action == "chat-modified"
|
||||||
assert chat.is_promoted()
|
assert chat.is_promoted()
|
||||||
assert sorted(x.addr for x in chat.get_contacts()) == \
|
assert sorted(x.addr for x in chat.get_contacts()) == sorted(x.addr for x in ev.chat.get_contacts())
|
||||||
sorted(x.addr for x in ev.chat.get_contacts())
|
|
||||||
|
|
||||||
lp.sec("ac1: add address2")
|
lp.sec("ac1: add address2")
|
||||||
# note that if the above create_chat() would not
|
# note that if the above create_chat() would not
|
||||||
@@ -1530,8 +1549,10 @@ def test_connectivity(acfactory, lp):
|
|||||||
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTING)
|
ac1._evtracker.wait_for_connectivity(const.DC_CONNECTIVITY_CONNECTING)
|
||||||
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_CONNECTING, const.DC_CONNECTIVITY_CONNECTED)
|
ac1._evtracker.wait_for_connectivity_change(const.DC_CONNECTIVITY_CONNECTING, const.DC_CONNECTIVITY_CONNECTED)
|
||||||
|
|
||||||
lp.sec("Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, " +
|
lp.sec(
|
||||||
"all messages are fetched")
|
"Test that after calling start_io(), maybe_network() and waiting for `all_work_done()`, "
|
||||||
|
+ "all messages are fetched"
|
||||||
|
)
|
||||||
|
|
||||||
ac1.direct_imap.select_config_folder("inbox")
|
ac1.direct_imap.select_config_folder("inbox")
|
||||||
with ac1.direct_imap.idle() as idle1:
|
with ac1.direct_imap.idle() as idle1:
|
||||||
@@ -1594,10 +1615,12 @@ def test_fetch_deleted_msg(acfactory, lp):
|
|||||||
|
|
||||||
See https://github.com/deltachat/deltachat-core-rust/issues/2429.
|
See https://github.com/deltachat/deltachat-core-rust/issues/2429.
|
||||||
"""
|
"""
|
||||||
ac1, = acfactory.get_online_accounts(1)
|
(ac1,) = acfactory.get_online_accounts(1)
|
||||||
ac1.stop_io()
|
ac1.stop_io()
|
||||||
|
|
||||||
ac1.direct_imap.append("INBOX", """
|
ac1.direct_imap.append(
|
||||||
|
"INBOX",
|
||||||
|
"""
|
||||||
From: alice <alice@example.org>
|
From: alice <alice@example.org>
|
||||||
Subject: subj
|
Subject: subj
|
||||||
To: bob@example.com
|
To: bob@example.com
|
||||||
@@ -1606,7 +1629,8 @@ def test_fetch_deleted_msg(acfactory, lp):
|
|||||||
Content-Type: text/plain; charset=utf-8
|
Content-Type: text/plain; charset=utf-8
|
||||||
|
|
||||||
Deleted message
|
Deleted message
|
||||||
""")
|
""",
|
||||||
|
)
|
||||||
ac1.direct_imap.delete("1:*", expunge=False)
|
ac1.direct_imap.delete("1:*", expunge=False)
|
||||||
ac1.start_io()
|
ac1.start_io()
|
||||||
|
|
||||||
@@ -1888,11 +1912,26 @@ def test_group_quote(acfactory, lp):
|
|||||||
assert received_reply.quote.id == out_msg.id
|
assert received_reply.quote.id == out_msg.id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("folder,move,expected_destination,", [
|
@pytest.mark.parametrize(
|
||||||
("xyz", False, "xyz"), # Test that emails are recognized in a random folder but not moved
|
"folder,move,expected_destination,",
|
||||||
("xyz", True, "DeltaChat"), # ...emails are found in a random folder and moved to DeltaChat
|
[
|
||||||
("Spam", False, "INBOX"), # ...emails are moved from the spam folder to the Inbox
|
(
|
||||||
])
|
"xyz",
|
||||||
|
False,
|
||||||
|
"xyz",
|
||||||
|
), # Test that emails are recognized in a random folder but not moved
|
||||||
|
(
|
||||||
|
"xyz",
|
||||||
|
True,
|
||||||
|
"DeltaChat",
|
||||||
|
), # ...emails are found in a random folder and moved to DeltaChat
|
||||||
|
(
|
||||||
|
"Spam",
|
||||||
|
False,
|
||||||
|
"INBOX",
|
||||||
|
), # ...emails are moved from the spam folder to the Inbox
|
||||||
|
],
|
||||||
|
)
|
||||||
# Testrun.org does not support the CREATE-SPECIAL-USE capability, which means that we can't create a folder with
|
# Testrun.org does not support the CREATE-SPECIAL-USE capability, which means that we can't create a folder with
|
||||||
# the "\Junk" flag (see https://tools.ietf.org/html/rfc6154). So, we can't test spam folder detection by flag.
|
# the "\Junk" flag (see https://tools.ietf.org/html/rfc6154). So, we can't test spam folder detection by flag.
|
||||||
def test_scan_folders(acfactory, lp, folder, move, expected_destination):
|
def test_scan_folders(acfactory, lp, folder, move, expected_destination):
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ from __future__ import print_function
|
|||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
|
from filecmp import cmp
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from filecmp import cmp
|
|
||||||
|
|
||||||
|
|
||||||
def wait_msg_delivered(account, msg_list):
|
def wait_msg_delivered(account, msg_list):
|
||||||
@@ -38,7 +38,7 @@ class TestOnlineInCreation:
|
|||||||
|
|
||||||
lp.sec("Creating in-creation file outside of blobdir")
|
lp.sec("Creating in-creation file outside of blobdir")
|
||||||
assert tmpdir.strpath != ac1.get_blobdir()
|
assert tmpdir.strpath != ac1.get_blobdir()
|
||||||
src = tmpdir.join('file.txt').ensure(file=1)
|
src = tmpdir.join("file.txt").ensure(file=1)
|
||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
chat.prepare_message_file(src.strpath)
|
chat.prepare_message_file(src.strpath)
|
||||||
|
|
||||||
@@ -48,11 +48,11 @@ class TestOnlineInCreation:
|
|||||||
|
|
||||||
lp.sec("Creating file outside of blobdir")
|
lp.sec("Creating file outside of blobdir")
|
||||||
assert tmpdir.strpath != ac1.get_blobdir()
|
assert tmpdir.strpath != ac1.get_blobdir()
|
||||||
src = tmpdir.join('file.txt')
|
src = tmpdir.join("file.txt")
|
||||||
src.write("hello there\n")
|
src.write("hello there\n")
|
||||||
chat.send_file(src.strpath)
|
chat.send_file(src.strpath)
|
||||||
|
|
||||||
blob_src = os.path.join(ac1.get_blobdir(), 'file.txt')
|
blob_src = os.path.join(ac1.get_blobdir(), "file.txt")
|
||||||
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
|
assert os.path.exists(blob_src), "file.txt not copied to blobdir"
|
||||||
|
|
||||||
def test_forward_increation(self, acfactory, data, lp):
|
def test_forward_increation(self, acfactory, data, lp):
|
||||||
@@ -63,7 +63,7 @@ class TestOnlineInCreation:
|
|||||||
|
|
||||||
lp.sec("create a message with a file in creation")
|
lp.sec("create a message with a file in creation")
|
||||||
orig = data.get_path("d.png")
|
orig = data.get_path("d.png")
|
||||||
path = os.path.join(ac1.get_blobdir(), 'd.png')
|
path = os.path.join(ac1.get_blobdir(), "d.png")
|
||||||
with open(path, "x") as fp:
|
with open(path, "x") as fp:
|
||||||
fp.write("preparing")
|
fp.write("preparing")
|
||||||
prepared_original = chat.prepare_message_file(path)
|
prepared_original = chat.prepare_message_file(path)
|
||||||
@@ -94,10 +94,7 @@ class TestOnlineInCreation:
|
|||||||
assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered()
|
assert fwd_msg.is_out_pending() or fwd_msg.is_out_delivered()
|
||||||
|
|
||||||
lp.sec("wait for both messages to be delivered to SMTP")
|
lp.sec("wait for both messages to be delivered to SMTP")
|
||||||
wait_msg_delivered(ac1, [
|
wait_msg_delivered(ac1, [(chat2.id, forwarded_id), (chat.id, prepared_original.id)])
|
||||||
(chat2.id, forwarded_id),
|
|
||||||
(chat.id, prepared_original.id)
|
|
||||||
])
|
|
||||||
|
|
||||||
lp.sec("wait1 for original or forwarded messages to arrive")
|
lp.sec("wait1 for original or forwarded messages to arrive")
|
||||||
received_original = ac2._evtracker.wait_next_incoming_message()
|
received_original = ac2._evtracker.wait_next_incoming_message()
|
||||||
|
|||||||
@@ -1,32 +1,50 @@
|
|||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import pytest
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from deltachat import const, Account
|
|
||||||
from deltachat.message import Message
|
|
||||||
from deltachat.hookspec import account_hookimpl
|
|
||||||
from deltachat.capi import ffi, lib
|
|
||||||
from deltachat.cutil import iter_array
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
@pytest.mark.parametrize("msgtext,res", [
|
from deltachat import Account, const
|
||||||
("Member Me (tmp1@x.org) removed by tmp2@x.org.",
|
from deltachat.capi import ffi, lib
|
||||||
("removed", "tmp1@x.org", "tmp2@x.org")),
|
from deltachat.cutil import iter_array
|
||||||
("Member With space (tmp1@x.org) removed by tmp2@x.org.",
|
from deltachat.hookspec import account_hookimpl
|
||||||
("removed", "tmp1@x.org", "tmp2@x.org")),
|
from deltachat.message import Message
|
||||||
("Member With space (tmp1@x.org) removed by Another member (tmp2@x.org).",
|
|
||||||
("removed", "tmp1@x.org", "tmp2@x.org")),
|
|
||||||
("Member With space (tmp1@x.org) removed by me",
|
@pytest.mark.parametrize(
|
||||||
("removed", "tmp1@x.org", "me")),
|
"msgtext,res",
|
||||||
("Group left by some one (tmp1@x.org).",
|
[
|
||||||
("removed", "tmp1@x.org", "tmp1@x.org")),
|
(
|
||||||
("Group left by tmp1@x.org.",
|
"Member Me (tmp1@x.org) removed by tmp2@x.org.",
|
||||||
("removed", "tmp1@x.org", "tmp1@x.org")),
|
("removed", "tmp1@x.org", "tmp2@x.org"),
|
||||||
("Member tmp1@x.org added by tmp2@x.org.", ("added", "tmp1@x.org", "tmp2@x.org")),
|
),
|
||||||
|
(
|
||||||
|
"Member With space (tmp1@x.org) removed by tmp2@x.org.",
|
||||||
|
("removed", "tmp1@x.org", "tmp2@x.org"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Member With space (tmp1@x.org) removed by Another member (tmp2@x.org).",
|
||||||
|
("removed", "tmp1@x.org", "tmp2@x.org"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Member With space (tmp1@x.org) removed by me",
|
||||||
|
("removed", "tmp1@x.org", "me"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Group left by some one (tmp1@x.org).",
|
||||||
|
("removed", "tmp1@x.org", "tmp1@x.org"),
|
||||||
|
),
|
||||||
|
("Group left by tmp1@x.org.", ("removed", "tmp1@x.org", "tmp1@x.org")),
|
||||||
|
(
|
||||||
|
"Member tmp1@x.org added by tmp2@x.org.",
|
||||||
|
("added", "tmp1@x.org", "tmp2@x.org"),
|
||||||
|
),
|
||||||
("Member nothing bla bla", None),
|
("Member nothing bla bla", None),
|
||||||
("Another unknown system message", None),
|
("Another unknown system message", None),
|
||||||
])
|
],
|
||||||
|
)
|
||||||
def test_parse_system_add_remove(msgtext, res):
|
def test_parse_system_add_remove(msgtext, res):
|
||||||
from deltachat.message import parse_system_add_remove
|
from deltachat.message import parse_system_add_remove
|
||||||
|
|
||||||
@@ -424,11 +442,14 @@ class TestOfflineChat:
|
|||||||
assert os.path.exists(msg.filename)
|
assert os.path.exists(msg.filename)
|
||||||
assert msg.filemime == "image/png"
|
assert msg.filemime == "image/png"
|
||||||
|
|
||||||
@pytest.mark.parametrize("typein,typeout", [
|
@pytest.mark.parametrize(
|
||||||
|
"typein,typeout",
|
||||||
|
[
|
||||||
(None, "application/octet-stream"),
|
(None, "application/octet-stream"),
|
||||||
("text/plain", "text/plain"),
|
("text/plain", "text/plain"),
|
||||||
("image/png", "image/png"),
|
("image/png", "image/png"),
|
||||||
])
|
],
|
||||||
|
)
|
||||||
def test_message_file(self, ac1, chat1, data, lp, typein, typeout):
|
def test_message_file(self, ac1, chat1, data, lp, typein, typeout):
|
||||||
lp.sec("sending file")
|
lp.sec("sending file")
|
||||||
fn = data.get_path("r.txt")
|
fn = data.get_path("r.txt")
|
||||||
@@ -629,6 +650,6 @@ class TestOfflineChat:
|
|||||||
lp.sec("check message count of only system messages (without daymarkers)")
|
lp.sec("check message count of only system messages (without daymarkers)")
|
||||||
dc_array = ffi.gc(
|
dc_array = ffi.gc(
|
||||||
lib.dc_get_chat_msgs(ac1._dc_context, chat.id, const.DC_GCM_INFO_ONLY, 0),
|
lib.dc_get_chat_msgs(ac1._dc_context, chat.id, const.DC_GCM_INFO_ONLY, 0),
|
||||||
lib.dc_array_unref
|
lib.dc_array_unref,
|
||||||
)
|
)
|
||||||
assert len(list(iter_array(dc_array, lambda x: x))) == 2
|
assert len(list(iter_array(dc_array, lambda x: x))) == 2
|
||||||
|
|||||||
@@ -1,24 +1,25 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from queue import Queue
|
from queue import Queue
|
||||||
from deltachat import capi, cutil, const
|
|
||||||
from deltachat import register_global_plugin
|
from deltachat import capi, const, cutil, register_global_plugin
|
||||||
|
from deltachat.capi import ffi, lib
|
||||||
from deltachat.hookspec import global_hookimpl
|
from deltachat.hookspec import global_hookimpl
|
||||||
from deltachat.capi import ffi
|
from deltachat.testplugin import (
|
||||||
from deltachat.capi import lib
|
ACSetup,
|
||||||
from deltachat.testplugin import ACSetup, create_dict_from_files_in_path, write_dict_to_dir
|
create_dict_from_files_in_path,
|
||||||
|
write_dict_to_dir,
|
||||||
|
)
|
||||||
|
|
||||||
# from deltachat.account import EventLogger
|
# from deltachat.account import EventLogger
|
||||||
|
|
||||||
|
|
||||||
class TestACSetup:
|
class TestACSetup:
|
||||||
|
|
||||||
def test_cache_writing(self, tmp_path):
|
def test_cache_writing(self, tmp_path):
|
||||||
base = tmp_path.joinpath("hello")
|
base = tmp_path.joinpath("hello")
|
||||||
base.mkdir()
|
base.mkdir()
|
||||||
d1 = base.joinpath("dir1")
|
d1 = base.joinpath("dir1")
|
||||||
d1.mkdir()
|
d1.mkdir()
|
||||||
d1.joinpath("file1").write_bytes(b'content1')
|
d1.joinpath("file1").write_bytes(b"content1")
|
||||||
d2 = d1.joinpath("dir2")
|
d2 = d1.joinpath("dir2")
|
||||||
d2.mkdir()
|
d2.mkdir()
|
||||||
d2.joinpath("file2").write_bytes(b"123")
|
d2.joinpath("file2").write_bytes(b"123")
|
||||||
@@ -103,6 +104,7 @@ def test_dc_close_events(tmpdir, acfactory):
|
|||||||
def dc_account_after_shutdown(self, account):
|
def dc_account_after_shutdown(self, account):
|
||||||
assert account._dc_context is None
|
assert account._dc_context is None
|
||||||
shutdowns.put(account)
|
shutdowns.put(account)
|
||||||
|
|
||||||
register_global_plugin(ShutdownPlugin())
|
register_global_plugin(ShutdownPlugin())
|
||||||
assert hasattr(ac1, "_dc_context")
|
assert hasattr(ac1, "_dc_context")
|
||||||
ac1.shutdown()
|
ac1.shutdown()
|
||||||
@@ -178,8 +180,8 @@ def test_get_info_open(tmpdir):
|
|||||||
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))
|
||||||
assert 'deltachat_core_version' in info
|
assert "deltachat_core_version" in info
|
||||||
assert 'database_dir' in info
|
assert "database_dir" in info
|
||||||
|
|
||||||
|
|
||||||
def test_logged_hook_failure(acfactory):
|
def test_logged_hook_failure(acfactory):
|
||||||
|
|||||||
@@ -36,10 +36,14 @@ skipsdist = True
|
|||||||
skip_install = True
|
skip_install = True
|
||||||
deps =
|
deps =
|
||||||
flake8
|
flake8
|
||||||
|
isort
|
||||||
|
black
|
||||||
# pygments required by rst-lint
|
# pygments required by rst-lint
|
||||||
pygments
|
pygments
|
||||||
restructuredtext_lint
|
restructuredtext_lint
|
||||||
commands =
|
commands =
|
||||||
|
isort --check --profile black src/deltachat examples/ tests/
|
||||||
|
black --check src/deltachat examples/ tests/
|
||||||
flake8 src/deltachat
|
flake8 src/deltachat
|
||||||
flake8 tests/ examples/
|
flake8 tests/ examples/
|
||||||
rst-lint --encoding 'utf-8' README.rst
|
rst-lint --encoding 'utf-8' README.rst
|
||||||
@@ -85,3 +89,4 @@ markers =
|
|||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
|
ignore = E203, E266, E501, W503
|
||||||
Reference in New Issue
Block a user