Compare commits

..

1 Commits

Author SHA1 Message Date
Floris Bruynooghe
5e21399813 Create an integration test in rust
This is an attempt at a first integration test in rust, using two
accounts which send real email to each other.
2019-11-03 20:54:32 +01:00
46 changed files with 1908 additions and 2369 deletions

View File

@@ -15,7 +15,7 @@ restore-workspace: &restore-workspace
restore-cache: &restore-cache
restore_cache:
keys:
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- repo-source-{{ .Branch }}-{{ .Revision }}
commands:
@@ -44,7 +44,7 @@ jobs:
command: cargo generate-lockfile
- restore_cache:
keys:
- cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
- run: rustup install $(cat rust-toolchain)
- run: rustup default $(cat rust-toolchain)
- run: rustup component add --toolchain $(cat rust-toolchain) rustfmt
@@ -60,7 +60,7 @@ jobs:
paths:
- crate
- save_cache:
key: cargo-v3-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
key: cargo-v2-{{ checksum "rust-toolchain" }}-{{ checksum "Cargo.toml" }}-{{ checksum "Cargo.lock" }}-{{ arch }}
paths:
- "~/.cargo"
- "~/.rustup"
@@ -121,7 +121,7 @@ jobs:
steps:
- checkout
- run: bash ci_scripts/run-doxygen.sh
- run: mkdir -p workspace/c-docs
- run: mkdir -p workspace/c-docs
- run: cp -av deltachat-ffi/{html,xml} workspace/c-docs/
- persist_to_workspace:
root: workspace
@@ -189,7 +189,7 @@ workflows:
- upload_docs_wheels:
requires:
- build_test_docs_wheel
- build_doxygen
- build_doxygen
- rustfmt:
requires:
- cargo_fetch

View File

@@ -1,30 +0,0 @@
name: CI
on:
pull_request:
push:
env:
RUSTFLAGS: -Dwarnings
jobs:
build:
name: 3.7 python tests against core
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
override: true
components: rustfmt
- name: Setup python
uses: actions/setup-python@v1
with:
python-version: 3.x
architecture: x64
- run: bash ci_scripts/run-python.sh

View File

@@ -1,6 +1,6 @@
# Changelog
## 1.0.0-beta.7
## untagged 1.0.0-beta.7
- fix location-streaming #782

960
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.0.0-beta.7"
version = "1.0.0-beta.6"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL"
@@ -15,13 +15,12 @@ hex = "0.3.2"
sha2 = "0.8.0"
rand = "0.6.5"
smallvec = "0.6.9"
reqwest = { version = "0.9.15", default-features = false, features = ["rustls-tls"] }
reqwest = "0.9.15"
num-derive = "0.2.5"
num-traits = "0.2.6"
lettre = { git = "https://github.com/deltachat/lettre", branch = "feat/rustls" }
async-imap = "0.1"
async-tls = "0.6"
async-std = { version = "1.0", features = ["unstable"] }
native-tls = "0.2.3"
lettre = { git = "https://github.com/deltachat/lettre", branch = "master" }
imap = { git = "https://github.com/deltachat/rust-imap", branch = "master" }
base64 = "0.10"
charset = "0.1"
percent-encoding = "2.0"
@@ -50,10 +49,6 @@ bitflags = "1.1.0"
jetscii = "0.4.4"
debug_stub_derive = "0.3.0"
sanitize-filename = "0.2.1"
stop-token = { version = "0.1.1", features = ["unstable"] }
rustls = "0.16.0"
webpki-roots = "0.18.0"
webpki = "0.21.0"
[dev-dependencies]
tempfile = "3.0"
@@ -79,6 +74,6 @@ path = "examples/repl/main.rs"
[features]
default = ["nightly", "ringbuf"]
vendored = []
vendored = ["native-tls/vendored", "reqwest/default-tls-vendored"]
nightly = ["pgp/nightly"]
ringbuf = ["pgp/ringbuf"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@@ -5,6 +5,16 @@ RUN echo /usr/local/lib64 > /etc/ld.so.conf.d/local.conf && \
echo /usr/local/lib >> /etc/ld.so.conf.d/local.conf
ENV PKG_CONFIG_PATH /usr/local/lib64/pkgconfig:/usr/local/lib/pkgconfig
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1
# Install a recent Perl, needed to install OpenSSL
ADD deps/build_perl.sh /builder/build_perl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
@@ -13,12 +23,3 @@ RUN mkdir tmp1 && cd tmp1 && bash /builder/build_perl.sh && cd .. && rm -r tmp1
ADD deps/build_openssl.sh /builder/build_openssl.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_openssl.sh && cd .. && rm -r tmp1
ENV PIP_DISABLE_PIP_VERSION_CHECK 1
# Install python tools (auditwheels,tox, ...)
ADD deps/build_python.sh /builder/build_python.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_python.sh && cd .. && rm -r tmp1
# Install Rust nightly
ADD deps/build_rust.sh /builder/build_rust.sh
RUN mkdir tmp1 && cd tmp1 && bash /builder/build_rust.sh && cd .. && rm -r tmp1

View File

@@ -1,11 +1,11 @@
#!/bin/bash
PERL_VERSION=5.30.0
# PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
PERL_VERSION=5.28.0
PERL_SHA256=7e929f64d4cb0e9d1159d4a59fc89394e27fa1f7004d0836ca0d514685406ea8
curl -O https://www.cpan.org/src/5.0/perl-${PERL_VERSION}.tar.gz
# echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar -xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
echo "${PERL_SHA256} perl-${PERL_VERSION}.tar.gz" | sha256sum -c -
tar xzf perl-${PERL_VERSION}.tar.gz
cd perl-${PERL_VERSION}
./Configure -de
make

View File

@@ -1,8 +1,11 @@
#!/bin/bash
set -e -x
set -e -x
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-09-12 -y
# Install Rust
curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly-2019-07-10 -y
export PATH=/root/.cargo/bin:$PATH
rustc --version
# remove some 300-400 MB that we don't need for automated builds
rm -rf /root/.rustup/toolchains/nightly-2019-07-10-x86_64-unknown-linux-gnu/share/

View File

@@ -13,6 +13,7 @@ export TOXWORKDIR=.docker-tox
export PATH=/root/.cargo/bin:$PATH
cargo build --release -p deltachat_ffi
# cargo test --all --all-features
# Statically link against libdeltachat.a.
export DCC_RS_DEV=$(pwd)
@@ -21,27 +22,36 @@ export DCC_RS_DEV=$(pwd)
# needed by tox below.
export PATH=$PATH:/opt/python/cp35-cp35m/bin
export PYTHONDONTWRITEBYTECODE=1
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
#tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
#tox --workdir "$TOXWORKDIR" -e auditwheels
pushd /bin
ln -s /opt/python/cp27-cp27m/bin/python2.7
ln -s /opt/python/cp36-cp36m/bin/python3.6
ln -s /opt/python/cp37-cp37m/bin/python3.7
popd
if [ -n "$TESTS" ]; then
pushd python
# prepare a clean tox run
rm -rf tests/__pycache__
rm -rf src/deltachat/__pycache__
export PYTHONDONTWRITEBYTECODE=1
# run tox. The circle-ci project env-var-setting DCC_PY_LIVECONFIG
# allows running of "liveconfig" tests but for speed reasons
# we run them only for the highest python version we support
# we split out qr-tests run to minimize likelyness of flaky tests
# (some qr tests are pretty heavy in terms of send/received
# messages and rust's imap code likely has concurrency problems)
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "not qr"
tox --workdir "$TOXWORKDIR" -e py37 -- --reruns 3 -k "qr"
unset DCC_PY_LIVECONFIG
tox --workdir "$TOXWORKDIR" -p4 -e lint,py35,py36,doc
tox --workdir "$TOXWORKDIR" -e auditwheels
popd
fi
# if [ -n "$DOCS" ]; then
# echo -----------------------
# echo generating python docs

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.0.0-beta.7"
version = "1.0.0-beta.6"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"

View File

@@ -501,9 +501,15 @@ char* dc_get_oauth2_url (dc_context_t* context, const char*
* To interrupt a configuration prematurely, use dc_stop_ongoing_process();
* this is not needed if #DC_EVENT_CONFIGURE_PROGRESS reports success.
*
* If #DC_EVENT_CONFIGURE_PROGRESS reports failure,
* the core continues to use the last working configuration
* and parameters as `addr`, `mail_pw` etc. are set to that.
* On a successfull configuration,
* the core makes a copy of the parameters mentioned above:
* the original parameters as are never modified by the core.
*
* UI-implementors should keep this in mind -
* eg. if the UI wants to prefill a configure-edit-dialog with these parameters,
* the UI should reset them if the user cancels the dialog
* after a configure-attempts has failed.
* Otherwise the parameters may not reflect the current configuation.
*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
@@ -1092,27 +1098,6 @@ uint32_t dc_send_text_msg (dc_context_t* context, uint32_t ch
void dc_set_draft (dc_context_t* context, uint32_t chat_id, dc_msg_t* msg);
/**
* Add a message to the device-chat.
* Device-messages usually contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* Device-messages may be added from the core,
* however, with this function, this can be done from the ui as well.
* If needed, the device-chat is created before.
*
* Sends the event #DC_EVENT_MSGS_CHANGED on success.
* To check, if a given chat is a device-chat, see dc_chat_is_device_talk()
*
* @memberof dc_context_t
* @param context The context as created by dc_context_new().
* @param msg Message to be added to the device-chat.
* The message appears to the user as an incoming message.
* @return The ID of the added message.
*/
uint32_t dc_add_device_msg (dc_context_t* context, dc_msg_t* msg);
/**
* Get draft for a chat, if any.
* See dc_set_draft() for more details about drafts.
@@ -2730,39 +2715,6 @@ int dc_chat_is_unpromoted (const dc_chat_t* chat);
int dc_chat_is_self_talk (const dc_chat_t* chat);
/**
* Check if a chat is a device-talk.
* Device-talks contain update information
* and some hints that are added during the program runs, multi-device etc.
*
* From the ui view, device-talks are not very special,
* the user can delete and forward messages, archive the chat, set notifications etc.
*
* Messages may be added from the core to the device chat,
* so the chat just pops up as usual.
* However, if needed the ui can also add messages using dc_add_device_msg()
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return 1=chat is device-talk, 0=chat is no device-talk
*/
int dc_chat_is_device_talk (const dc_chat_t* chat);
/**
* Check if messages can be sent to a give chat.
* This is not true eg. for the deaddrop or for the device-talk, cmp. dc_chat_is_device_talk().
*
* Calling dc_send_msg() for these chats will fail
* and the ui may decide to hide input controls therefore.
*
* @memberof dc_chat_t
* @param chat The chat object.
* @return 1=chat is writable, 0=chat is not writable
*/
int dc_chat_can_send (const dc_chat_t* chat);
/**
* Check if a chat is verified. Verified chats contain only verified members
* and encryption is alwasy enabled. Verified chats are created using
@@ -3418,8 +3370,7 @@ void dc_msg_latefiling_mediasize (dc_msg_t* msg, int width, int hei
#define DC_CONTACT_ID_SELF 1
#define DC_CONTACT_ID_INFO 2 // centered messages as "member added", used in all chats
#define DC_CONTACT_ID_DEVICE 5 // messages "update info" in the device-chat
#define DC_CONTACT_ID_DEVICE 2
#define DC_CONTACT_ID_LAST_SPECIAL 9
@@ -4467,8 +4418,7 @@ void dc_array_add_id (dc_array_t*, uint32_t); // depreca
#define DC_STR_MSGLOCATIONDISABLED 65
#define DC_STR_LOCATION 66
#define DC_STR_STICKER 67
#define DC_STR_DEVICE_MESSAGES 68
#define DC_STR_COUNT 68
#define DC_STR_COUNT 67
/*
* @}

View File

@@ -811,23 +811,6 @@ pub unsafe extern "C" fn dc_set_draft(
.unwrap_or(())
}
#[no_mangle]
pub unsafe extern "C" fn dc_add_device_msg(context: *mut dc_context_t, msg: *mut dc_msg_t) -> u32 {
if context.is_null() || msg.is_null() {
eprintln!("ignoring careless call to dc_add_device_msg()");
return 0;
}
let ffi_context = &mut *context;
let ffi_msg = &mut *msg;
ffi_context
.with_inner(|ctx| {
chat::add_device_msg(ctx, &mut ffi_msg.message)
.unwrap_or_log_default(ctx, "Failed to add device message")
})
.map(|msg_id| msg_id.to_u32())
.unwrap_or(0)
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_draft(context: *mut dc_context_t, chat_id: u32) -> *mut dc_msg_t {
if context.is_null() {
@@ -2281,26 +2264,6 @@ pub unsafe extern "C" fn dc_chat_is_self_talk(chat: *mut dc_chat_t) -> libc::c_i
ffi_chat.chat.is_self_talk() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_is_device_talk(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
eprintln!("ignoring careless call to dc_chat_is_device_talk()");
return 0;
}
let ffi_chat = &*chat;
ffi_chat.chat.is_device_talk() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {
eprintln!("ignoring careless call to dc_chat_can_send()");
return 0;
}
let ffi_chat = &*chat;
ffi_chat.chat.can_send() as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_chat_is_verified(chat: *mut dc_chat_t) -> libc::c_int {
if chat.is_null() {

View File

@@ -192,7 +192,7 @@ unsafe fn log_msg(context: &Context, prefix: impl AsRef<str>, msg: &Message) {
let msgtext = msg.get_text();
info!(
context,
"{}{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
"{}#{}{}{}: {} (Contact#{}): {} {}{}{}{}{} [{}]",
prefix.as_ref(),
msg.get_id(),
if msg.get_showpadlock() { "🔒" } else { "" },
@@ -240,7 +240,7 @@ unsafe fn log_msglist(context: &Context, msglist: &Vec<MsgId>) -> Result<(), Err
lines_out += 1
}
let msg = Message::load_from_db(context, msg_id)?;
log_msg(context, "", &msg);
log_msg(context, "Msg", &msg);
}
}
if lines_out > 0 {
@@ -353,7 +353,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
configure\n\
connect\n\
disconnect\n\
interrupt\n\
maybenetwork\n\
housekeeping\n\
help imex (Import/Export)\n\
@@ -379,7 +378,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
sendimage <file> [<text>]\n\
sendfile <file> [<text>]\n\
draft [<text>]\n\
devicemsg <text>\n\
listmedia\n\
archive <chat-id>\n\
unarchive <chat-id>\n\
@@ -426,12 +424,12 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
if msg.is_setupmessage() {
let setupcodebegin = msg.get_setupcodebegin(context);
println!(
"The setup code for setup message {} starts with: {}",
"The setup code for setup message Msg#{} starts with: {}",
msg_id,
setupcodebegin.unwrap_or_default(),
);
} else {
bail!("{} is no setup message.", msg_id,);
bail!("Msg#{} is no setup message.", msg_id,);
}
}
"continue-key-transfer" => {
@@ -495,9 +493,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
"info" => {
println!("{:#?}", context.get_info());
}
"interrupt" => {
interrupt_imap_idle(context);
}
"maybenetwork" => {
maybe_network(context);
}
@@ -522,12 +517,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
for i in (0..cnt).rev() {
let chat = Chat::load_from_db(context, chatlist.get_chat_id(i))?;
let temp_name = chat.get_name();
info!(
context,
"{}#{}: {} [{} fresh]",
chat_prefix(&chat),
chat.get_id(),
chat.get_name(),
temp_name,
chat::get_fresh_msg_cnt(context, chat.get_id()),
);
let lot = chatlist.get_summary(context, i, Some(&chat));
@@ -586,33 +582,25 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let msglist = chat::get_chat_msgs(context, sel_chat.get_id(), 0x1, None);
let members = chat::get_chat_contacts(context, sel_chat.id);
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
let temp2 = if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
let contact = Contact::get_by_id(context, members[0])?;
contact.get_addr().to_string()
} else {
format!("{} member(s)", members.len())
};
let temp_name = sel_chat.get_name();
info!(
context,
"{}#{}: {} [{}]{}{}",
"{}#{}: {} [{}]{}",
chat_prefix(sel_chat),
sel_chat.get_id(),
sel_chat.get_name(),
subtitle,
temp_name,
temp2,
if sel_chat.is_sending_locations() {
"📍"
} else {
""
},
match sel_chat.get_profile_image(context) {
Some(icon) => match icon.to_str() {
Some(icon) => format!(" Icon: {}", icon),
_ => " Icon: Err".to_string(),
},
_ => "".to_string(),
},
);
log_msglist(context, &msglist)?;
if let Some(draft) = chat::get_draft(context, sel_chat.get_id())? {
@@ -726,7 +714,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
let marker = location.marker.as_ref().unwrap_or(&default_marker);
info!(
context,
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} Msg#{} {}",
location.location_id,
dc_timestamp_to_str(location.timestamp),
location.latitude,
@@ -830,15 +818,6 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("Draft deleted.");
}
}
"devicemsg" => {
ensure!(
!arg1.is_empty(),
"Please specify text to add as device message."
);
let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some(arg1.to_string()));
chat::add_device_msg(context, &mut msg)?;
}
"listmedia" => {
ensure!(sel_chat.is_some(), "No chat selected.");
@@ -852,9 +831,9 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
println!("{} images or videos: ", images.len());
for (i, data) in images.iter().enumerate() {
if 0 == i {
print!("{}", data);
print!("Msg#{}", data);
} else {
print!(", {}", data);
print!(", Msg#{}", data);
}
}
print!("\n");

View File

@@ -153,14 +153,6 @@ class Account(object):
self.check_is_configured()
return from_dc_charpointer(lib.dc_get_info(self._dc_context))
def get_latest_backupfile(self, backupdir):
""" return the latest backup file in a given directory.
"""
res = lib.dc_imex_has_backup(self._dc_context, as_dc_charpointer(backupdir))
if res == ffi.NULL:
return None
return from_dc_charpointer(res)
def get_blobdir(self):
""" return the directory for files.
@@ -485,9 +477,8 @@ class Account(object):
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
if self._threads.is_started():
self.stop_ongoing()
self._threads.stop(wait=wait)
self.stop_ongoing()
self._threads.stop(wait=wait)
def shutdown(self, wait=True):
""" stop threads and close and remove underlying dc_context and callbacks. """

View File

@@ -47,8 +47,7 @@ DC_STATE_OUT_FAILED = 24
DC_STATE_OUT_DELIVERED = 26
DC_STATE_OUT_MDN_RCVD = 28
DC_CONTACT_ID_SELF = 1
DC_CONTACT_ID_INFO = 2
DC_CONTACT_ID_DEVICE = 5
DC_CONTACT_ID_DEVICE = 2
DC_CONTACT_ID_LAST_SPECIAL = 9
DC_MSG_TEXT = 10
DC_MSG_IMAGE = 20

View File

@@ -2,7 +2,6 @@ from __future__ import print_function
import pytest
import os
import queue
import time
from deltachat import const, Account
from deltachat.message import Message
from datetime import datetime, timedelta
@@ -642,29 +641,18 @@ class TestOnlineAccount:
assert os.path.exists(msg_in.filename)
assert os.stat(msg_in.filename).st_size == os.stat(path).st_size
def test_import_export_online_all(self, acfactory, tmpdir, lp):
def test_import_export_online_all(self, acfactory, tmpdir):
ac1 = acfactory.get_online_configuring_account()
wait_configuration_progress(ac1, 1000)
lp.sec("create some chat content")
contact1 = ac1.create_contact("some1@hello.com", name="some1")
chat = ac1.create_chat_by_contact(contact1)
chat.send_text("msg1")
backupdir = tmpdir.mkdir("backup")
lp.sec("export all to {}".format(backupdir))
path = ac1.export_all(backupdir.strpath)
assert os.path.exists(path)
t = time.time()
lp.sec("get fresh empty account")
ac2 = acfactory.get_unconfigured_account()
lp.sec("get latest backup file")
path2 = ac2.get_latest_backupfile(backupdir.strpath)
assert path2 == path
lp.sec("import backup and check it's proper")
ac2.import_all(path)
contacts = ac2.get_contacts(query="some1")
assert len(contacts) == 1
@@ -675,17 +663,6 @@ class TestOnlineAccount:
assert len(messages) == 1
assert messages[0].text == "msg1"
# wait until a second passed since last backup
# because get_latest_backupfile() shall return the latest backup
# from a UI it's unlikely anyone manages to export two
# backups in one second.
time.sleep(max(0, 1 - (time.time() - t)))
lp.sec("Second-time export all to {}".format(backupdir))
path2 = ac1.export_all(backupdir.strpath)
assert os.path.exists(path2)
assert path2 != path
assert ac2.get_latest_backupfile(backupdir.strpath) == path2
def test_ac_setup_message(self, acfactory, lp):
# note that the receiving account needs to be configured and running
# before ther setup message is send. DC does not read old messages
@@ -895,7 +872,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "cannot login" in ev1[2].lower()
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_user(self, acfactory):
@@ -904,7 +881,7 @@ class TestOnlineConfigureFails:
ac1.start_threads()
wait_configuration_progress(ac1, 500)
ev1 = ac1._evlogger.get_matching("DC_EVENT_ERROR_NETWORK")
assert "cannot login" in ev1[2].lower()
assert "authentication failed" in ev1[2].lower()
wait_configuration_progress(ac1, 0, 0)
def test_invalid_domain(self, acfactory):

View File

@@ -1 +1 @@
nightly-2019-11-06
nightly-2019-08-13

View File

@@ -97,8 +97,6 @@ impl Chat {
if chat.param.exists(Param::Selftalk) {
chat.name = context.stock_str(StockMessage::SelfMsg).into();
} else if chat.param.exists(Param::Devicetalk) {
chat.name = context.stock_str(StockMessage::DeviceMessages).into();
}
}
}
@@ -111,14 +109,6 @@ impl Chat {
self.param.exists(Param::Selftalk)
}
pub fn is_device_talk(&self) -> bool {
self.param.exists(Param::Devicetalk)
}
pub fn can_send(&self) -> bool {
self.id > DC_CHAT_ID_LAST_SPECIAL && !self.is_device_talk()
}
pub fn update_param(&mut self, context: &Context) -> Result<(), Error> {
sql::execute(
context,
@@ -268,7 +258,7 @@ impl Chat {
}
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
&& !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF)
&& !is_contact_in_chat(context, self.id, 1 as u32)
{
emit_event!(
context,
@@ -424,7 +414,7 @@ impl Chat {
&context.sql,
"INSERT INTO locations \
(timestamp,from_id,chat_id, latitude,longitude,independent)\
VALUES (?,?,?, ?,?,1);", // 1=DC_CONTACT_ID_SELF
VALUES (?,?,?, ?,?,1);",
params![
timestamp,
DC_CONTACT_ID_SELF,
@@ -456,7 +446,7 @@ impl Chat {
params![
new_rfc724_mid,
self.id as i32,
DC_CONTACT_ID_SELF,
1i32,
to_id as i32,
timestamp,
msg.type_0,
@@ -592,12 +582,6 @@ pub fn set_blocking(context: &Context, chat_id: u32, new_blocking: Blocked) -> b
.is_ok()
}
fn copy_device_icon_to_blobs(context: &Context) -> Result<String, Error> {
let icon = include_bytes!("../assets/icon-device.png");
let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?;
Ok(blob.as_name().to_string())
}
pub fn create_or_lookup_by_contact_id(
context: &Context,
contact_id: u32,
@@ -621,14 +605,7 @@ pub fn create_or_lookup_by_contact_id(
"INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')",
100,
chat_name,
match contact_id {
DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk
DC_CONTACT_ID_DEVICE => {
let icon = copy_device_icon_to_blobs(context)?;
format!("D=1\ni={}", icon) // D = Param::Devicetalk, i = Param::ProfileImage
},
_ => "".to_string()
},
if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" },
create_blocked as u8,
contact.get_addr(),
),
@@ -700,7 +677,8 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
}
}
fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset();
if msg.type_0 == Viewtype::Text {
// the caller should check if the message text is empty
} else if msgtype_has_file(msg.type_0) {
@@ -736,16 +714,10 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
} else {
bail!("Cannot send messages of type #{}.", msg.type_0);
}
Ok(())
}
fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset();
prepare_msg_blob(context, msg)?;
unarchive(context, chat_id)?;
let mut chat = Chat::load_from_db(context, chat_id)?;
ensure!(chat.can_send(), "cannot send to chat #{}", chat_id);
// The OutPreparing state is set by dc_prepare_msg() before it
// calls this function and the message is left in the OutPreparing
@@ -942,7 +914,7 @@ fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<()
VALUES (?,?,?, ?,?,?,?,?);",
params![
chat_id as i32,
DC_CONTACT_ID_SELF,
1,
time(),
msg.type_0,
MessageState::OutDraft,
@@ -1021,8 +993,8 @@ pub fn get_chat_msgs(
" ON m.chat_id=chats.id",
" LEFT JOIN contacts",
" ON m.from_id=contacts.id",
" WHERE m.from_id!=1", // 1=DC_CONTACT_ID_SELF
" AND m.from_id!=2", // 2=DC_CONTACT_ID_INFO
" WHERE m.from_id!=1",
" AND m.from_id!=2",
" AND m.hidden=0",
" AND chats.blocked=2",
" AND contacts.blocked=0",
@@ -1378,7 +1350,7 @@ pub fn create_group_chat(
let chat_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid);
if chat_id != 0 {
if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF) {
if add_to_chat_contacts_table(context, chat_id, 1) {
let mut draft_msg = Message::new(Viewtype::Text);
draft_msg.set_text(Some(draft_txt));
set_draft_raw(context, chat_id, &mut draft_msg);
@@ -1585,7 +1557,7 @@ pub fn remove_contact_from_chat(
/* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */
if let Ok(chat) = Chat::load_from_db(context, chat_id) {
if real_group_exists(context, chat_id) {
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) {
if !is_contact_in_chat(context, chat_id, 1 as u32) {
emit_event!(
context,
Event::ErrorSelfNotInGroup(
@@ -1681,7 +1653,7 @@ pub fn set_chat_name(
if real_group_exists(context, chat_id) {
if chat.name == new_name.as_ref() {
success = true;
} else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) {
} else if !is_contact_in_chat(context, chat_id, 1) {
emit_event!(
context,
Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into())
@@ -1926,46 +1898,15 @@ pub fn get_chat_id_by_grpid(context: &Context, grpid: impl AsRef<str>) -> (u32,
.unwrap_or((0, false, Blocked::Not))
}
pub fn add_device_msg(context: &Context, msg: &mut Message) -> Result<MsgId, Error> {
let (chat_id, _blocked) =
create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?;
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
prepare_msg_blob(context, msg)?;
unarchive(context, chat_id)?;
context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \
VALUES (?,?,?, ?,?,?, ?,?,?);",
params![
chat_id,
DC_CONTACT_ID_DEVICE,
DC_CONTACT_ID_SELF,
dc_create_smeared_timestamp(context),
msg.type_0,
MessageState::InFresh,
msg.text.as_ref().map_or("", String::as_str),
msg.param.to_string(),
rfc724_mid,
],
)?;
let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid);
let msg_id = MsgId::new(row_id);
context.call_cb(Event::IncomingMsg { chat_id, msg_id });
Ok(msg_id)
}
pub fn add_info_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
pub fn add_device_msg(context: &Context, chat_id: u32, text: impl AsRef<str>) {
let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device");
if context.sql.execute(
"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,rfc724_mid) VALUES (?,?,?, ?,?,?, ?,?);",
params![
chat_id as i32,
DC_CONTACT_ID_INFO,
DC_CONTACT_ID_INFO,
2,
2,
dc_create_smeared_timestamp(context),
Viewtype::Text,
MessageState::InNoticed,

View File

@@ -126,7 +126,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?)",
@@ -149,7 +149,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.archived=1",
@@ -175,7 +175,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.name LIKE ?",
@@ -198,7 +198,7 @@ impl Chatlist {
" SELECT MAX(timestamp)",
" FROM msgs",
" WHERE chat_id=c.id",
" AND hidden=0)",
" AND (hidden=0 OR (hidden=1 AND state=19)))",
" WHERE c.id>9",
" AND c.blocked=0",
" AND c.archived=0",
@@ -294,8 +294,8 @@ impl Chatlist {
let lastmsg_id = self.ids[index].1;
let mut lastcontact = None;
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != DC_CONTACT_ID_SELF
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != 1
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
@@ -306,16 +306,6 @@ impl Chatlist {
None
};
if let Ok(draft) = get_draft(context, chat.id) {
if draft.is_some()
&& (lastmsg.is_none()
|| draft.as_ref().unwrap().timestamp_sort
> lastmsg.as_ref().unwrap().timestamp_sort)
{
lastmsg = draft;
}
}
if chat.id == DC_CHAT_ID_ARCHIVED_LINK {
ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED

View File

@@ -421,16 +421,6 @@ pub fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &Context) {
);
}
*/
// remember the entered parameters on success
// and restore to last-entered on failure.
// this way, the parameters visible to the ui are always in-sync with the current configuration.
if success {
LoginParam::from_database(context, "").save_to_database(context, "configured_raw_");
} else {
LoginParam::from_database(context, "configured_raw_").save_to_database(context, "");
}
context.free_ongoing();
progress!(context, if success { 1000 } else { 0 });
}
@@ -541,26 +531,21 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
param.send_user, param.send_server, param.send_port, param.server_flags
);
info!(context, "Trying: {}", inf);
match context
if context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
Ok(()) => {
info!(context, "success: {}", inf);
Some(true)
}
Err(err) => {
if context.shall_stop_ongoing() {
Some(false)
} else {
warn!(context, "could not connect: {}", err);
None
}
}
info!(context, "success: {}", inf);
return Some(true);
}
if context.shall_stop_ongoing() {
return Some(false);
}
info!(context, "could not connect: {}", inf);
None
}
/*******************************************************************************
@@ -569,7 +554,7 @@ fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<bool> {
pub fn dc_connect_to_configured_imap(context: &Context, imap: &Imap) -> libc::c_int {
let mut ret_connected = 0;
if async_std::task::block_on(async move { imap.is_connected().await }) {
if imap.is_connected() {
ret_connected = 1
} else if !context.sql.get_raw_config_bool(context, "configured") {
warn!(context, "Not configured, cannot connect.",);

View File

@@ -130,8 +130,7 @@ const DC_MAX_GET_INFO_LEN: usize = 100000;
pub const DC_CONTACT_ID_UNDEFINED: u32 = 0;
pub const DC_CONTACT_ID_SELF: u32 = 1;
pub const DC_CONTACT_ID_INFO: u32 = 2;
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
pub const DC_CONTACT_ID_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;

View File

@@ -153,16 +153,7 @@ impl Contact {
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
} else if contact_id == DC_CONTACT_ID_DEVICE {
let contact = Contact {
id: contact_id,
name: context.stock_str(StockMessage::DeviceMessages).into(),
authname: "".into(),
addr: "device@localhost".into(),
blocked: false,
origin: Origin::Unknown,
};
return Ok(contact);
}
@@ -273,7 +264,7 @@ impl Contact {
.unwrap_or_default();
if addr_normalized == addr_self {
return DC_CONTACT_ID_SELF;
return 1;
}
context.sql.query_get_value(
@@ -310,7 +301,7 @@ impl Contact {
.unwrap_or_default();
if addr == addr_self {
return Ok((DC_CONTACT_ID_SELF, sth_modified));
return Ok((1, sth_modified));
}
if !may_be_valid_addr(&addr) {

View File

@@ -391,7 +391,7 @@ unsafe fn add_parts(
} else {
MessageState::InFresh
};
*to_id = DC_CONTACT_ID_SELF;
*to_id = 1;
// handshake messages must be processed _before_ chats are created
// (eg. contacs may be marked as verified)
if mime_parser.lookup_field("Secure-Join").is_some() {
@@ -551,9 +551,8 @@ unsafe fn add_parts(
if to_ids.is_empty() && 0 != to_self {
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
// maybe an Autocrypt Setup Messag
let (id, bl) =
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
.unwrap_or_default();
let (id, bl) = chat::create_or_lookup_by_contact_id(context, 1, Blocked::Not)
.unwrap_or_default();
*chat_id = id;
chat_id_blocked = bl;
@@ -850,7 +849,7 @@ fn save_locations(
if mime_parser.message_kml.is_some() {
let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, true).unwrap_or_default();
location::save(context, chat_id, from_id, locations, 1).unwrap_or_default();
if 0 != newest_location_id && 0 == hidden {
if location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok() {
location_id_written = true;
@@ -865,8 +864,7 @@ fn save_locations(
if contact.get_addr().to_lowercase() == addr.to_lowercase() {
let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
let newest_location_id =
location::save(context, chat_id, from_id, locations, false)
.unwrap_or_default();
location::save(context, chat_id, from_id, locations, 0).unwrap_or_default();
if newest_location_id != 0 && hidden == 0 && !location_id_written {
if let Err(err) = location::set_msg_location_id(
context,
@@ -1352,8 +1350,8 @@ unsafe fn create_or_lookup_adhoc_group(
if !member_ids.contains(&from_id) {
member_ids.push(from_id);
}
if !member_ids.contains(&DC_CONTACT_ID_SELF) {
member_ids.push(DC_CONTACT_ID_SELF);
if !member_ids.contains(&1) {
member_ids.push(1);
}
if member_ids.len() < 3 {
// too few contacts given
@@ -1473,7 +1471,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
.sql
.query_map(
format!(
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1",
member_ids_str
),
params![],
@@ -1527,7 +1525,7 @@ fn search_chat_ids_by_contact_ids(
WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN({})) \
AND c.type=120 \
AND cc.contact_id!=1 \
ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
ORDER BY cc.chat_id, cc.contact_id;",
contact_ids_str
),
params![],

View File

@@ -154,15 +154,13 @@ pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
******************************************************************************/
pub fn dc_timestamp_to_str(wanted: i64) -> String {
let ts = Local.timestamp(wanted, 0);
let ts = chrono::Utc.timestamp(wanted, 0);
ts.format("%Y.%m.%d %H:%M:%S").to_string()
}
pub(crate) fn dc_gm2local_offset() -> i64 {
/* returns the offset that must be _added_ to an UTC/GMT-time to create the localtime.
the function may return negative values. */
let lt = Local::now();
lt.offset().local_minus_utc() as i64
((lt.offset().local_minus_utc() / (60 * 60)) * 100) as i64
}
/* timesmearing */
@@ -429,55 +427,21 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
pub(crate) fn dc_copy_file(
context: &Context,
src_path: impl AsRef<std::path::Path>,
dest_path: impl AsRef<std::path::Path>,
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
) -> bool {
let src_abs = dc_get_abs_path(context, &src_path);
let mut src_file = match fs::File::open(&src_abs) {
Ok(file) => file,
Err(err) => {
warn!(
context,
"failed to open for read '{}': {}",
src_abs.display(),
err
);
return false;
}
};
let dest_abs = dc_get_abs_path(context, &dest_path);
let mut dest_file = match fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&dest_abs)
{
Ok(file) => file,
Err(err) => {
warn!(
context,
"failed to open for write '{}': {}",
dest_abs.display(),
err
);
return false;
}
};
match std::io::copy(&mut src_file, &mut dest_file) {
let src_abs = dc_get_abs_path(context, &src);
let dest_abs = dc_get_abs_path(context, &dest);
match fs::copy(&src_abs, &dest_abs) {
Ok(_) => true,
Err(err) => {
error!(
context,
"Cannot copy \"{}\" to \"{}\": {}",
src_abs.display(),
dest_abs.display(),
src.as_ref().display(),
dest.as_ref().display(),
err
);
{
// Attempt to remove the failed file, swallow errors resulting from that.
fs::remove_file(dest_abs).ok();
}
false
}
}
@@ -1319,9 +1283,6 @@ mod tests {
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
// attempting to copy a second time should fail
assert!(!dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();

View File

@@ -498,7 +498,7 @@ fn decrypt_if_autocrypt_message(
public_keyring_for_validate: &Keyring,
ret_valid_signatures: &mut HashSet<String>,
ret_gossip_headers: *mut *mut mailimf_fields,
) -> Result<bool> {
) -> Result<(bool)> {
/* The returned bool is true if we detected an Autocrypt-encrypted
message and successfully decrypted it. Decryption then modifies the
passed in mime structure in place. The returned bool is false

File diff suppressed because it is too large Load Diff

View File

@@ -1,294 +0,0 @@
use async_imap::{
error::{Error as ImapError, Result as ImapResult},
extensions::idle::Handle as ImapIdleHandle,
types::{Capabilities, Fetch, Mailbox, Name},
Client as ImapClient, Session as ImapSession,
};
use async_std::net::{self, TcpStream};
use async_std::prelude::*;
use async_std::sync::Arc;
use async_tls::client::TlsStream;
use crate::login_param::{dc_build_tls_config, CertificateChecks};
const DCC_IMAP_DEBUG: &str = "DCC_IMAP_DEBUG";
#[derive(Debug)]
pub(crate) enum Client {
Secure(ImapClient<TlsStream<TcpStream>>),
Insecure(ImapClient<TcpStream>),
}
#[derive(Debug)]
pub(crate) enum Session {
Secure(ImapSession<TlsStream<TcpStream>>),
Insecure(ImapSession<TcpStream>),
}
#[derive(Debug)]
pub(crate) enum IdleHandle {
Secure(ImapIdleHandle<TlsStream<TcpStream>>),
Insecure(ImapIdleHandle<TcpStream>),
}
impl Client {
pub async fn connect_secure<A: net::ToSocketAddrs, S: AsRef<str>>(
addr: A,
domain: S,
certificate_checks: CertificateChecks,
) -> ImapResult<Self> {
let stream = TcpStream::connect(addr).await?;
let tls_config = dc_build_tls_config(certificate_checks);
let tls_connector: async_tls::TlsConnector = Arc::new(tls_config).into();
let tls_stream = tls_connector.connect(domain.as_ref(), stream)?.await?;
let mut client = ImapClient::new(tls_stream);
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
let _greeting = client
.read_response()
.await
.expect("failed to read greeting");
Ok(Client::Secure(client))
}
pub async fn connect_insecure<A: net::ToSocketAddrs>(addr: A) -> ImapResult<Self> {
let stream = TcpStream::connect(addr).await?;
let mut client = ImapClient::new(stream);
if std::env::var(DCC_IMAP_DEBUG).is_ok() {
client.debug = true;
}
let _greeting = client
.read_response()
.await
.expect("failed to read greeting");
Ok(Client::Insecure(client))
}
pub async fn secure<S: AsRef<str>>(
self,
domain: S,
_certificate_checks: CertificateChecks,
) -> ImapResult<Client> {
match self {
Client::Insecure(client) => {
let tls = async_tls::TlsConnector::new();
let client_sec = client.secure(domain, &tls).await?;
Ok(Client::Secure(client_sec))
}
// Nothing to do
Client::Secure(_) => Ok(self),
}
}
pub async fn authenticate<A: async_imap::Authenticator, S: AsRef<str>>(
self,
auth_type: S,
authenticator: &A,
) -> Result<Session, (ImapError, Client)> {
match self {
Client::Secure(i) => match i.authenticate(auth_type, authenticator).await {
Ok(session) => Ok(Session::Secure(session)),
Err((err, c)) => Err((err, Client::Secure(c))),
},
Client::Insecure(i) => match i.authenticate(auth_type, authenticator).await {
Ok(session) => Ok(Session::Insecure(session)),
Err((err, c)) => Err((err, Client::Insecure(c))),
},
}
}
pub async fn login<U: AsRef<str>, P: AsRef<str>>(
self,
username: U,
password: P,
) -> Result<Session, (ImapError, Client)> {
match self {
Client::Secure(i) => match i.login(username, password).await {
Ok(session) => Ok(Session::Secure(session)),
Err((err, c)) => Err((err, Client::Secure(c))),
},
Client::Insecure(i) => match i.login(username, password).await {
Ok(session) => Ok(Session::Insecure(session)),
Err((err, c)) => Err((err, Client::Insecure(c))),
},
}
}
}
impl Session {
pub async fn capabilities(&mut self) -> ImapResult<Capabilities> {
let res = match self {
Session::Secure(i) => i.capabilities().await?,
Session::Insecure(i) => i.capabilities().await?,
};
Ok(res)
}
pub async fn list(
&mut self,
reference_name: Option<&str>,
mailbox_pattern: Option<&str>,
) -> ImapResult<Vec<Name>> {
let res = match self {
Session::Secure(i) => {
i.list(reference_name, mailbox_pattern)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.list(reference_name, mailbox_pattern)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn create<S: AsRef<str>>(&mut self, mailbox_name: S) -> ImapResult<()> {
match self {
Session::Secure(i) => i.create(mailbox_name).await?,
Session::Insecure(i) => i.create(mailbox_name).await?,
}
Ok(())
}
pub async fn subscribe<S: AsRef<str>>(&mut self, mailbox: S) -> ImapResult<()> {
match self {
Session::Secure(i) => i.subscribe(mailbox).await?,
Session::Insecure(i) => i.subscribe(mailbox).await?,
}
Ok(())
}
pub async fn close(&mut self) -> ImapResult<()> {
match self {
Session::Secure(i) => i.close().await?,
Session::Insecure(i) => i.close().await?,
}
Ok(())
}
pub async fn select<S: AsRef<str>>(&mut self, mailbox_name: S) -> ImapResult<Mailbox> {
let mbox = match self {
Session::Secure(i) => i.select(mailbox_name).await?,
Session::Insecure(i) => i.select(mailbox_name).await?,
};
Ok(mbox)
}
pub async fn fetch<S1, S2>(&mut self, sequence_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.fetch(sequence_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.fetch(sequence_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn uid_fetch<S1, S2>(&mut self, uid_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.uid_fetch(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.uid_fetch(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub fn idle(self) -> IdleHandle {
match self {
Session::Secure(i) => {
let h = i.idle();
IdleHandle::Secure(h)
}
Session::Insecure(i) => {
let h = i.idle();
IdleHandle::Insecure(h)
}
}
}
pub async fn uid_store<S1, S2>(&mut self, uid_set: S1, query: S2) -> ImapResult<Vec<Fetch>>
where
S1: AsRef<str>,
S2: AsRef<str>,
{
let res = match self {
Session::Secure(i) => {
i.uid_store(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
Session::Insecure(i) => {
i.uid_store(uid_set, query)
.await?
.collect::<ImapResult<_>>()
.await?
}
};
Ok(res)
}
pub async fn uid_mv<S1: AsRef<str>, S2: AsRef<str>>(
&mut self,
uid_set: S1,
mailbox_name: S2,
) -> ImapResult<()> {
match self {
Session::Secure(i) => i.uid_mv(uid_set, mailbox_name).await?,
Session::Insecure(i) => i.uid_mv(uid_set, mailbox_name).await?,
}
Ok(())
}
pub async fn uid_copy<S1: AsRef<str>, S2: AsRef<str>>(
&mut self,
uid_set: S1,
mailbox_name: S2,
) -> ImapResult<()> {
match self {
Session::Secure(i) => i.uid_copy(uid_set, mailbox_name).await?,
Session::Insecure(i) => i.uid_copy(uid_set, mailbox_name).await?,
}
Ok(())
}
}

View File

@@ -1,5 +1,5 @@
use core::cmp::{max, min};
use std::path::Path;
use std::path::{Path, PathBuf};
use num_traits::FromPrimitive;
use rand::{thread_rng, Rng};
@@ -75,7 +75,7 @@ pub fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<Path>>)
job_add(context, Action::ImexImap, 0, param, 0);
}
/// Returns the filename of the backup found (otherwise an error)
/// Returns the filename of the backup if found, nullptr otherwise.
pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name)?;
@@ -90,15 +90,13 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
if name.starts_with("delta-chat") && name.ends_with(".bak") {
let sql = Sql::new();
if sql.open(context, &path, true) {
let curr_backup_time = sql
.get_raw_config_int(context, "backup_time")
.unwrap_or_default();
let curr_backup_time =
sql.get_raw_config_int(context, "backup_time")
.unwrap_or_default() as u64;
if curr_backup_time > newest_backup_time {
newest_backup_path = Some(path);
newest_backup_time = curr_backup_time;
}
info!(context, "backup_time of {} is {}", name, curr_backup_time);
sql.close(&context);
}
}
}
@@ -107,7 +105,7 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<Strin
}
match newest_backup_path {
Some(path) => Ok(path.to_string_lossy().into_owned()),
None => bail!("no backup found in {}", dir_name.display()),
None => bail!("no backup found"),
}
}
@@ -485,13 +483,10 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
let now = time();
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
let dest_path_string = dest_path_filename.to_string_lossy().to_string();
sql::housekeeping(context);
sql::try_execute(context, &context.sql, "VACUUM;").ok();
// we close the database during the copy of the dbfile
context.sql.close(context);
info!(
context,
@@ -501,40 +496,38 @@ fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
);
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
context.sql.open(&context, &context.get_dbfile(), false);
if !copied {
let s = dest_path_filename.to_string_lossy().to_string();
bail!(
"could not copy file from '{}' to '{}'",
context.get_dbfile().display(),
dest_path_string
s
);
}
let dest_sql = Sql::new();
ensure!(
dest_sql.open(context, &dest_path_filename, false),
"could not open exported database {}",
dest_path_string
);
let res = match add_files_to_export(context, &dest_sql) {
match add_files_to_export(context, &dest_path_filename) {
Err(err) => {
dc_delete_file(context, &dest_path_filename);
error!(context, "backup failed: {}", err);
Err(err)
}
Ok(()) => {
dest_sql.set_raw_config_int(context, "backup_time", now as i32)?;
context
.sql
.set_raw_config_int(context, "backup_time", now as i32)?;
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
Ok(())
}
};
dest_sql.close(context);
res
}
}
fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
fn add_files_to_export(context: &Context, dest_path_filename: &PathBuf) -> Result<()> {
// add all files as blobs to the database copy (this does not require
// the source to be locked, neigher the destination as it is used only here)
let sql = Sql::new();
ensure!(
sql.open(context, &dest_path_filename, false),
"could not open db"
);
if !sql.table_exists("backup_blobs") {
sql::execute(
context,

View File

@@ -135,7 +135,8 @@ impl Job {
if !context.smtp.lock().unwrap().is_connected() {
let loginparam = LoginParam::from_database(context, "configured_");
let connected = context.smtp.lock().unwrap().connect(context, &loginparam);
if connected.is_err() {
if !connected {
self.try_again_later(3, None);
return;
}
@@ -172,10 +173,10 @@ impl Job {
// its ok/error response processing. Note that if a message
// was sent we need to mark it in the database ASAP as we
// otherwise might send it twice.
let mut smtp = context.smtp.lock().unwrap();
match smtp.send(context, recipients_list, body, self.job_id) {
let mut sock = context.smtp.lock().unwrap();
match sock.send(context, recipients_list, body) {
Err(err) => {
smtp.disconnect();
sock.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
}
@@ -245,10 +246,10 @@ impl Job {
&dest_folder,
&mut dest_uid,
) {
ImapActionResult::RetryLater => {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapActionResult::Success => {
ImapResult::Success => {
message::update_server_uid(
context,
&msg.rfc724_mid,
@@ -256,7 +257,7 @@ impl Job {
dest_uid,
);
}
ImapActionResult::Failed | ImapActionResult::AlreadyDone => {}
ImapResult::Failed | ImapResult::AlreadyDone => {}
}
}
}
@@ -280,7 +281,7 @@ impl Job {
let mid = msg.rfc724_mid;
let server_folder = msg.server_folder.as_ref().unwrap();
let res = inbox.delete_msg(context, &mid, server_folder, &mut msg.server_uid);
if res == ImapActionResult::RetryLater {
if res == ImapResult::RetryLater {
self.try_again_later(-1i32, None);
return;
}
@@ -313,11 +314,11 @@ impl Job {
if let Ok(msg) = Message::load_from_db(context, MsgId::new(self.foreign_id)) {
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_seen(context, folder, msg.server_uid) {
ImapActionResult::RetryLater => {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
ImapActionResult::AlreadyDone => {}
ImapActionResult::Success | ImapActionResult::Failed => {
ImapResult::AlreadyDone => {}
ImapResult::Success | ImapResult::Failed => {
// XXX the message might just have been moved
// we want to send out an MDN anyway
// The job will not be retried so locally
@@ -343,7 +344,7 @@ impl Job {
.to_string();
let uid = self.param.get_int(Param::ServerUid).unwrap_or_default() as u32;
let inbox = context.inbox.read().unwrap();
if inbox.set_seen(context, &folder, uid) == ImapActionResult::RetryLater {
if inbox.set_seen(context, &folder, uid) == ImapResult::RetryLater {
self.try_again_later(3i32, None);
return;
}
@@ -361,7 +362,7 @@ impl Job {
.get_raw_config(context, "configured_mvbox_folder");
if let Some(dest_folder) = dest_folder {
let mut dest_uid = 0;
if ImapActionResult::RetryLater
if ImapResult::RetryLater
== inbox.mv(context, &folder, uid, &dest_folder, &mut dest_uid)
{
self.try_again_later(3, None);
@@ -504,6 +505,8 @@ pub fn perform_smtp_jobs(context: &Context) {
}
}
pub fn perform_smtp_fetch(_context: &Context) {}
pub fn perform_smtp_idle(context: &Context) {
info!(context, "SMTP-idle started...",);
{
@@ -1022,9 +1025,8 @@ pub fn interrupt_smtp_idle(context: &Context) {
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, "Interrupting INBOX-IDLE...",);
info!(context, "Interrupting IMAP-IDLE...",);
*context.perform_inbox_jobs_needed.write().unwrap() = true;
context.inbox.read().unwrap().interrupt_idle();
}

View File

@@ -15,7 +15,7 @@ pub struct JobThread {
#[derive(Clone, Debug, Default)]
pub struct JobState {
idle: bool,
jobs_needed: bool,
jobs_needed: i32,
suspended: bool,
using_handle: bool,
}
@@ -58,7 +58,7 @@ impl JobThread {
pub fn interrupt_idle(&self, context: &Context) {
{
self.state.0.lock().unwrap().jobs_needed = true;
self.state.0.lock().unwrap().jobs_needed = 1;
}
info!(context, "Interrupting {}-IDLE...", self.name);
@@ -107,17 +107,12 @@ impl JobThread {
}
fn connect_to_imap(&self, context: &Context) -> bool {
if async_std::task::block_on(async move { self.imap.is_connected().await }) {
if self.imap.is_connected() {
return true;
}
let watch_folder_name = match context.sql.get_raw_config(context, self.folder_config_name) {
Some(name) => name,
None => {
return false;
}
};
let ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
let mut ret_connected = dc_connect_to_configured_imap(context, &self.imap) != 0;
if ret_connected {
if context
.sql
@@ -128,7 +123,12 @@ impl JobThread {
self.imap.configure_folders(context, 0x1);
}
self.imap.set_watch_folder(watch_folder_name);
if let Some(mvbox_name) = context.sql.get_raw_config(context, self.folder_config_name) {
self.imap.set_watch_folder(mvbox_name);
} else {
self.imap.disconnect(context);
ret_connected = false;
}
}
ret_connected
@@ -139,13 +139,13 @@ impl JobThread {
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if state.jobs_needed {
if 0 != state.jobs_needed {
info!(
context,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
state.jobs_needed = false;
state.jobs_needed = 0;
return;
}
@@ -170,18 +170,10 @@ impl JobThread {
}
}
if self.connect_to_imap(context) {
info!(context, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
} else {
// It's probably wrong that the thread even runs
// but let's call fake_idle and tell it to not try network at all.
// (once we move to rust-managed threads this problem goes away)
info!(context, "{}-IDLE not connected, fake-idling", self.name);
async_std::task::block_on(async move { self.imap.fake_idle(context, false).await });
info!(context, "{}-IDLE fake-idling finished", self.name);
}
self.connect_to_imap(context);
info!(context, "{}-IDLE started...", self.name,);
self.imap.idle(context);
info!(context, "{}-IDLE ended.", self.name);
self.state.0.lock().unwrap().using_handle = false;
}

View File

@@ -29,44 +29,44 @@ impl From<SignedSecretKey> for Key {
}
}
impl std::convert::TryFrom<Key> for SignedSecretKey {
impl std::convert::TryInto<SignedSecretKey> for Key {
type Error = ();
fn try_from(value: Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<SignedSecretKey, Self::Error> {
match self {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedSecretKey {
impl<'a> std::convert::TryInto<&'a SignedSecretKey> for &'a Key {
type Error = ();
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<&'a SignedSecretKey, Self::Error> {
match self {
Key::Public(_) => Err(()),
Key::Secret(key) => Ok(key),
}
}
}
impl std::convert::TryFrom<Key> for SignedPublicKey {
impl std::convert::TryInto<SignedPublicKey> for Key {
type Error = ();
fn try_from(value: Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<SignedPublicKey, Self::Error> {
match self {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}
}
}
impl<'a> std::convert::TryFrom<&'a Key> for &'a SignedPublicKey {
impl<'a> std::convert::TryInto<&'a SignedPublicKey> for &'a Key {
type Error = ();
fn try_from(value: &'a Key) -> Result<Self, Self::Error> {
match value {
fn try_into(self) -> Result<&'a SignedPublicKey, Self::Error> {
match self {
Key::Public(key) => Ok(key),
Key::Secret(_) => Err(()),
}

View File

@@ -40,7 +40,6 @@ pub mod contact;
pub mod context;
mod e2ee;
mod imap;
mod imap_client;
pub mod imex;
pub mod job;
mod job_thread;

View File

@@ -190,7 +190,13 @@ impl Kml {
}
}
// location streaming
/// Starts streaming locations to a chat.
///
/// # Parameters
///
/// * `context` - The [Context].
/// * `chat_id` - The ID of the chat to send locations to.
/// * `seconds` - The duration for which to stream the location.
pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
let now = time();
if !(seconds < 0 || chat_id <= DC_CHAT_ID_LAST_SPECIAL) {
@@ -219,7 +225,7 @@ pub fn send_locations_to_chat(context: &Context, chat_id: u32, seconds: i64) {
} else if 0 == seconds && is_sending_locations_before {
let stock_str =
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_info_msg(context, chat_id, stock_str);
chat::add_device_msg(context, chat_id, stock_str);
}
context.call_cb(Event::ChatModified(chat_id));
if 0 != seconds {
@@ -275,7 +281,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b
accuracy,
time(),
chat_id,
DC_CONTACT_ID_SELF,
1,
]
) {
warn!(context, "failed to store location {:?}", err);
@@ -492,7 +498,7 @@ pub fn save(
chat_id: u32,
contact_id: u32,
locations: &[Location],
independent: bool,
independent: i32,
) -> Result<u32, Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
context.sql.prepare2(
@@ -507,7 +513,7 @@ pub fn save(
for location in locations {
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if independent || !exists {
if 0 != independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
@@ -651,7 +657,7 @@ pub fn job_do_DC_JOB_MAYBE_SEND_LOC_ENDED(context: &Context, job: &mut Job) {
params![chat_id as i32],
).is_ok() {
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
chat::add_info_msg(context, chat_id, stock_str);
chat::add_device_msg(context, chat_id, stock_str);
context.call_cb(Event::ChatModified(chat_id));
}
}

View File

@@ -3,10 +3,6 @@ use std::fmt;
use crate::context::Context;
use crate::error::Error;
use async_std::sync::Arc;
use rustls;
use webpki;
use webpki_roots;
#[derive(Copy, Clone, Debug, Display, FromPrimitive)]
#[repr(i32)]
@@ -255,49 +251,27 @@ fn get_readable_flags(flags: i32) -> String {
res
}
pub struct NoCertificateVerification {}
impl rustls::ServerCertVerifier for NoCertificateVerification {
fn verify_server_cert(
&self,
_roots: &rustls::RootCertStore,
_presented_certs: &[rustls::Certificate],
_dns_name: webpki::DNSNameRef<'_>,
_ocsp: &[u8],
) -> Result<rustls::ServerCertVerified, rustls::TLSError> {
Ok(rustls::ServerCertVerified::assertion())
}
}
pub fn dc_build_tls_config(certificate_checks: CertificateChecks) -> rustls::ClientConfig {
let mut config = rustls::ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
pub fn dc_build_tls(
certificate_checks: CertificateChecks,
) -> Result<native_tls::TlsConnector, native_tls::Error> {
let mut tls_builder = native_tls::TlsConnector::builder();
match certificate_checks {
CertificateChecks::Strict => {}
CertificateChecks::Automatic => {
// Same as AcceptInvalidCertificates for now.
// TODO: use provider database when it becomes available
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
}
CertificateChecks::AcceptInvalidCertificates => {
// TODO: only accept invalid certs
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true)
}
CertificateChecks::Strict => &mut tls_builder,
CertificateChecks::AcceptInvalidHostnames => {
// TODO: only accept invalid hostnames
config
.dangerous()
.set_certificate_verifier(Arc::new(NoCertificateVerification {}));
tls_builder.danger_accept_invalid_hostnames(true)
}
CertificateChecks::AcceptInvalidCertificates => tls_builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true),
}
config
.build()
}
#[cfg(test)]

View File

@@ -476,8 +476,8 @@ impl Message {
pub fn is_info(&self) -> bool {
let cmd = self.param.get_cmd();
self.from_id == DC_CONTACT_ID_INFO as libc::c_uint
|| self.to_id == DC_CONTACT_ID_INFO as libc::c_uint
self.from_id == DC_CONTACT_ID_DEVICE as libc::c_uint
|| self.to_id == DC_CONTACT_ID_DEVICE as libc::c_uint
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
}
@@ -714,7 +714,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
ret += "\n";
}
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
if msg.from_id == 2 || msg.to_id == 2 {
// device-internal message, no further details needed
return ret;
}

View File

@@ -76,8 +76,6 @@ pub enum Param {
ProfileImage = b'i',
// For Chats
Selftalk = b'K',
// For Chats
Devicetalk = b'D',
// For QR
Auth = b's',
// For QR

View File

@@ -5,7 +5,7 @@ use std::io::Cursor;
use pgp::armor::BlockType;
use pgp::composed::{
Deserializable, KeyType as PgpKeyType, Message, SecretKeyParamsBuilder, SignedPublicKey,
SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder,
SignedSecretKey, SubkeyParamsBuilder,
};
use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
@@ -97,29 +97,18 @@ pub fn create_keypair(addr: impl AsRef<str>) -> Option<(Key, Key)> {
Some((Key::Public(public_key), Key::Secret(private_key)))
}
/// Select subkey of the public key to use for encryption.
///
/// Currently the first subkey is selected.
fn select_pk_for_encryption(key: &SignedPublicKey) -> Option<&SignedPublicSubKey> {
key.public_subkeys.iter().find(|_k|
// TODO: check if it is an encryption subkey
true)
}
pub fn pk_encrypt(
plain: &[u8],
public_keys_for_encryption: &Keyring,
private_key_for_signing: Option<&Key>,
) -> Result<String, Error> {
let lit_msg = Message::new_literal_bytes("", plain);
let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption
let pkeys: Vec<&SignedPublicKey> = public_keys_for_encryption
.keys()
.iter()
.filter_map(|key| {
key.as_ref()
.try_into()
.ok()
.and_then(select_pk_for_encryption)
let k: &Key = &key;
k.try_into().ok()
})
.collect();

View File

@@ -150,7 +150,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop)
.unwrap_or_default();
chat::add_info_msg(
chat::add_device_msg(
context,
id,
format!("{} verified.", peerstate.addr.unwrap_or_default()),

View File

@@ -638,7 +638,7 @@ fn secure_connection_established(context: &Context, contact_chat_id: u32) {
"?"
};
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
chat::add_info_msg(context, contact_chat_id, msg);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
@@ -654,7 +654,7 @@ fn could_not_establish_secure_connection(context: &Context, contact_chat_id: u32
},
);
chat::add_info_msg(context, contact_chat_id, &msg);
chat::add_device_msg(context, contact_chat_id, &msg);
error!(context, "{} ({})", &msg, details);
}
@@ -735,7 +735,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<
};
let msg = context.stock_string_repl_str(StockMessage::ContactSetupChanged, peeraddr);
chat::add_info_msg(context, contact_chat_id, msg);
chat::add_device_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
}

View File

@@ -5,7 +5,7 @@ use crate::constants::*;
use crate::context::Context;
use crate::error::Error;
use crate::events::Event;
use crate::login_param::{dc_build_tls_config, LoginParam};
use crate::login_param::{dc_build_tls, LoginParam};
use crate::oauth2::*;
#[derive(DebugStub)]
@@ -44,41 +44,43 @@ impl Smtp {
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<(), Error> {
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
if self.is_connected() {
warn!(context, "SMTP already connected.");
return Ok(());
return true;
}
if lp.send_server.is_empty() || lp.send_port == 0 {
context.call_cb(Event::ErrorNetwork("SMTP bad parameters.".into()));
bail!("SMTP Bad parameters");
}
self.from = match EmailAddress::new(lp.addr.clone()) {
Ok(addr) => Some(addr),
Err(err) => {
bail!("invalid login address {}: {}", lp.addr, err);
}
self.from = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
Some(addr)
} else {
None
};
if self.from.is_none() {
// TODO: print error
return false;
}
let domain = &lp.send_server;
let port = lp.send_port as u16;
let tls_config = dc_build_tls_config(lp.smtp_certificate_checks);
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls_config);
let tls = dc_build_tls(lp.smtp_certificate_checks).unwrap();
let tls_parameters = ClientTlsParameters::new(domain.to_string(), tls);
let (creds, mechanism) = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
// oauth2
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
ensure!(
access_token.is_some(),
"could not get oaut2_access token addr={}",
addr
);
if access_token.is_none() {
return false;
}
let user = &lp.send_user;
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
@@ -123,27 +125,27 @@ impl Smtp {
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
return Ok(());
return true;
}
Err(err) => {
bail!("SMTP: failed to connect {:?}", err);
warn!(context, "SMTP: failed to connect {:?}", err);
}
}
}
Err(err) => {
bail!("SMTP: failed to setup connection {:?}", err);
warn!(context, "SMTP: failed to setup connection {:?}", err);
}
}
false
}
/// SMTP-Send a prepared mail to recipients.
/// on successful send out Ok() is returned.
/// returns boolean whether send was successful.
pub fn send<'a>(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
message: Vec<u8>,
job_id: u32,
) -> Result<(), Error> {
let message_len = message.len();
@@ -154,15 +156,12 @@ impl Smtp {
.join(",");
if let Some(ref mut transport) = self.transport {
let envelope = match Envelope::new(self.from.clone(), recipients) {
Ok(env) => env,
Err(err) => {
bail!("{}", err);
}
};
let envelope = Envelope::new(self.from.clone(), recipients);
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
let envelope = envelope.unwrap();
let mail = SendableEmail::new(
envelope,
format!("{}", job_id), // only used for internal logging
"mail-id".into(), // TODO: random id
message,
);

View File

@@ -92,7 +92,8 @@ impl Sql {
self.start_stmt(sql.to_string());
self.with_conn(|conn| {
let stmt = conn.prepare(sql)?;
g(stmt, conn)
let res = g(stmt, conn)?;
Ok(res)
})
}
@@ -105,7 +106,8 @@ impl Sql {
let stmt1 = conn.prepare(sql1)?;
let stmt2 = conn.prepare(sql2)?;
g(stmt1, stmt2, conn)
let res = g(stmt1, stmt2, conn)?;
Ok(res)
})
}
@@ -383,8 +385,8 @@ fn open(
)?;
sql.execute(
"INSERT INTO contacts (id,name,origin) VALUES \
(1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \
(4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \
(1,'self',262144), (2,'device',262144), (3,'rsvd',262144), \
(4,'rsvd',262144), (5,'rsvd',262144), (6,'rsvd',262144), \
(7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);",
params![],
)?;

View File

@@ -110,8 +110,6 @@ pub enum StockMessage {
Location = 66,
#[strum(props(fallback = "Sticker"))]
Sticker = 67,
#[strum(props(fallback = "Device Messages"))]
DeviceMessages = 68,
}
impl StockMessage {

View File

@@ -0,0 +1 @@
xcLYBF297/EBCADEgI6RhCnbfNnKqlPzHNAmCVJ65KniO0reqaR94CFxcMRcO3k5XZDAo0u1KbqobehYm39ggtoqtmattjgG/AbR+Ex90CjbT1PbIi4Efkt91deMzt4G/S/POtqnIt8nbPSohMRkTOBKI0y7DSGneZTx+uj/GuE+aWbM2ubENsW283onnUKqDUOtxqQoBT3vWocsS6iViZCjqhqqQPX4OVK0gaQDLFCm63NC5VnazSvJkmDFFhG2bN8ncZ3wVibai7r3scF+I+hCnR4ll+/Q02WdpCSW0V26DXUQiCadTri6jDAi3JAGCnsCUiXR9780yAms+idWjPAXv2l9PfvSYkITABEBAAEACACopQS20szxuOdaTnCaN+JUoq+NFW7P4L9S9hlcht1s9LExz0EtAKZZDkzNgLDYGOvOEDZz6BnBiqX49GiFZgucbROI7vwBrMV1TpJb/OBhcQP7rxdSvD0qB4Lc6srGlXYsozXCN1BPkJgr+QsnJuuz+fm556Hk5KT7r+tZ/wAVEMNLIz8ta06MS7DGMQCh8kSRXldQznSAV2etG7OmSeTka/DISA9y1lW4MWAlvaa435sXqB1zQJPXQiRG6WVhZ9MwX/nZEOGutdeA8O4D4wU520UipJNtSXbm+CQeVTzse7+ZDvpwkOLSXfp0BDOO+Hv1/bcByzH2JwhVQSZfzRXxBADO6w8xeVlxjVHkXS76sRvVLdmHy7Jo3b9DojaUgrQvB1uOlmHZ4WwiHi2xRkaJoY9AAa1Ndf4g9y2BvKpHmUxfVvRjXRQuuLLkjjU2RudxDsdDw4rVytjQafDFgyqeYUBlx0XN79HG4ATBbkI+A0hlIaP4Ja/RuIQGGI+DA+LtjQQA8xz+KuTKmKmEUb+PPQMww/ucVcC4bV66Rqz/GUqjdvmsPH5UCW7NdmrSnqJ/7n2WOK4OuZ9Wx3lmCeZ4r3FlbGT7sauojKeCsnJTbXZdMJ+4FHUI6dF0EjOpsHfGrKkWuGmPJoMA/N6Lx4wMyO/UjyZ/HYrq4h+B40TjNzKq9h8D/AxMUgg5n57fUZj31sQE4RGODGEtTFGFtTuC2Ih0Goc1DdG9vsx2nDtM7HI2P4RjHKolhTmbCqnbmoi+frOZoHskWpRzESQVQJAmCz3h4gDPcLsvK7K5KJ17XXR1GA1mWgEpP4H1s4D/Ke4ucwFFztIQrTIF93ptqqipZCbJlqH8Q33NETxib2JAZXhhbXBsZS5jb20+wsCJBBABCAAzAhkBBQJdvfAxAhsDBAsJCAcGFQgJCgsCAxYCARYhBMCyk5nNgMGX89zC8GVj9ZoUEAYDAAoJEGVj9ZoUEAYDWBoIAJhfY396iAauf0X+WBBTjEEJR9svmbxIIaGah3GSlYpAVXK44mLjKeJIdDzxFJ7nhfs+wEkSpU0NMnQsifdtZd+bjEN1hQxJq3WEoeyPEEp/KFOw312zt1ucysDao59oji4LLkZhKPjBz7v8e/DaWKty45Cv2t2/3+g5IWHChmyzbd0hhGpuQ6osR4iNV9xatLYWncJMUgow1YLgtAV2XBu/5B0bZA9oqHw9JX0oMWmBjHvU2ngsreUbQTcA10S2ExzfFjE9WeArTv7suVQLmcteBxLqjbFZ3UqpLraJmNejdRp0SE+OJAtiZKhq2PYPm6Wl+i2VLdScjkiqnsLjx9XHwtgEXb3v8QEIAMbfa0AldcdhCUX1ma7eZ2bA7zYLI2RbKNvBePk4Dnig/MVJBe2PUVBbo78TCxnExLZl7P7+faelKxWR5IVyy70NLglYlpL22Q/Ul3GBuUjhqCpolpkvBfqjrFa8L1Oo7g8vkrkFLPK9Ul1MVlAQ6mt/oWWObWWiO1FfHw4NiHMvGnnkRQSEg2qbmNzXvi4YIt1RhBCg1hdUJP9l1k8avRFpO9rcF3x3MM7ZuzED7zR7j0qExyguu76zYRsI/q3COy5Gw+kwl0hpN2nQSUqS0g6xZ7zxbB09ygXUS4IkZHN2tCBMgEzQh8axIQ+3ogaDC7RDESTi24+T7JXIjFY9FaEAEQEAAQAH/jWqUpHDyg2cdNkpFmim4XZL+AE4bjuFkfgDNHbkFpucrbk7JFtfwkyR/hTwuZ0hiQfDZ3nECPp1SrQOY4FTYgFJDjQ9cJyF+jsYXimmHO663htbj9AUbWOeSUI6k/babisw5kIBUIjMZ+5/TAddGTUbAt2Z2pGDfshNh97N7hVOlXe8N/4lEgTL4IMMc3Ub31t4XIzO1I5weonu7Hhj96arwe+Zo/O6BmO3+LuTuDSlh5kFmjVnN2AqwdHq3OGWhGoDv568tUsdnHTv9ps2OLa8JJqE/5/5gowdxR+K7ufjnMTIAtMKBWWC781urXUjN0kLBWjIHfXQa3FHV85TacEEAPXdSWe9Ua+P9mNkdbrxMPz6xeZ451WMnQ3Qx9is/7Ij7wCZtpgXl5Wq6bGlJhc7lNjNOCl/RR282pWNIVG4Z6h84hIZhiAUWrjiEK4hxxCe907CAp8MF2YANOX10EZAW5IQ4VhMAqtvqAZlkpo6z4UTbe3lyIG/RV4gAMYaMRUZBADPEjVz4F0FemnXFzzu9ssHnJMdFz+n+9sE6HKO63Jm6RXg0hE5UAdtMt8FoUipbZeoiqDcNWQ+JQU/7YyruLIMd86pLT7an7ojENrP70XJGDlvHVUcsoV5FtvBl/dSv5keYLOy7Jk4qER6cad06j/Yf4jNNOtEAAND9XUwrZNNyQP/eBmSmVA0RdqA6yw3WQvOejC+bEuUBxUjiGtFobG2Ch1E0qj+RJRtsg/kWN014+eWZbQHwOQ2LkIfFOlhZjXCE+QlAel/bm9rDw5kVWM/cm4sOqjxxWnebaxCNaAV1i/wuVO02Wg4stylUSGuIjZDtVX4gvO266TLQi6d1mreNg47C8LAdgQYAQgAIAUCXb3wMQIbDBYhBMCyk5nNgMGX89zC8GVj9ZoUEAYDAAoJEGVj9ZoUEAYD41wH/jIQgva+k3vmGtfYDR5tB/IdEpc6MjGJxo2NwOkBKYJfaigyK3dmZ1DY8ZfkYMfQ9s5d4cW3Lel4t7nRH5Vh5FiaIWlDuxfGVTMLNpOzlXswgHlwckrfJucVWk3/hLT/xStsSjC+SwKSC6+ejmHIqkSqbTztwVCABg63otzREV4NspEsSrO0+SUD+n2mpFFeo4ULjPXEtlJzrmoJNdByDBEODiMFUyw0voMXN13ZqFv46HVtmembBxc8tJXtHX8rvC2ODiyygI3y3HENJPYR+CBGY/v8K8sg35i7PidUEsK/V3NJRTU0WkI+NS+4b80xE5KxizQMTDNPiSuTOlb7gO4=

View File

@@ -0,0 +1 @@
xsBNBF297/EBCADEgI6RhCnbfNnKqlPzHNAmCVJ65KniO0reqaR94CFxcMRcO3k5XZDAo0u1KbqobehYm39ggtoqtmattjgG/AbR+Ex90CjbT1PbIi4Efkt91deMzt4G/S/POtqnIt8nbPSohMRkTOBKI0y7DSGneZTx+uj/GuE+aWbM2ubENsW283onnUKqDUOtxqQoBT3vWocsS6iViZCjqhqqQPX4OVK0gaQDLFCm63NC5VnazSvJkmDFFhG2bN8ncZ3wVibai7r3scF+I+hCnR4ll+/Q02WdpCSW0V26DXUQiCadTri6jDAi3JAGCnsCUiXR9780yAms+idWjPAXv2l9PfvSYkITABEBAAHNETxib2JAZXhhbXBsZS5jb20+wsCJBBABCAAzAhkBBQJdvfAxAhsDBAsJCAcGFQgJCgsCAxYCARYhBMCyk5nNgMGX89zC8GVj9ZoUEAYDAAoJEGVj9ZoUEAYDWBoIAJhfY396iAauf0X+WBBTjEEJR9svmbxIIaGah3GSlYpAVXK44mLjKeJIdDzxFJ7nhfs+wEkSpU0NMnQsifdtZd+bjEN1hQxJq3WEoeyPEEp/KFOw312zt1ucysDao59oji4LLkZhKPjBz7v8e/DaWKty45Cv2t2/3+g5IWHChmyzbd0hhGpuQ6osR4iNV9xatLYWncJMUgow1YLgtAV2XBu/5B0bZA9oqHw9JX0oMWmBjHvU2ngsreUbQTcA10S2ExzfFjE9WeArTv7suVQLmcteBxLqjbFZ3UqpLraJmNejdRp0SE+OJAtiZKhq2PYPm6Wl+i2VLdScjkiqnsLjx9XOwE0EXb3v8QEIAMbfa0AldcdhCUX1ma7eZ2bA7zYLI2RbKNvBePk4Dnig/MVJBe2PUVBbo78TCxnExLZl7P7+faelKxWR5IVyy70NLglYlpL22Q/Ul3GBuUjhqCpolpkvBfqjrFa8L1Oo7g8vkrkFLPK9Ul1MVlAQ6mt/oWWObWWiO1FfHw4NiHMvGnnkRQSEg2qbmNzXvi4YIt1RhBCg1hdUJP9l1k8avRFpO9rcF3x3MM7ZuzED7zR7j0qExyguu76zYRsI/q3COy5Gw+kwl0hpN2nQSUqS0g6xZ7zxbB09ygXUS4IkZHN2tCBMgEzQh8axIQ+3ogaDC7RDESTi24+T7JXIjFY9FaEAEQEAAcLAdgQYAQgAIAUCXb3wMQIbDBYhBMCyk5nNgMGX89zC8GVj9ZoUEAYDAAoJEGVj9ZoUEAYD41wH/jIQgva+k3vmGtfYDR5tB/IdEpc6MjGJxo2NwOkBKYJfaigyK3dmZ1DY8ZfkYMfQ9s5d4cW3Lel4t7nRH5Vh5FiaIWlDuxfGVTMLNpOzlXswgHlwckrfJucVWk3/hLT/xStsSjC+SwKSC6+ejmHIqkSqbTztwVCABg63otzREV4NspEsSrO0+SUD+n2mpFFeo4ULjPXEtlJzrmoJNdByDBEODiMFUyw0voMXN13ZqFv46HVtmembBxc8tJXtHX8rvC2ODiyygI3y3HENJPYR+CBGY/v8K8sg35i7PidUEsK/V3NJRTU0WkI+NS+4b80xE5KxizQMTDNPiSuTOlb7gO4=

476
tests/location.rs Normal file
View File

@@ -0,0 +1,476 @@
//! Integration tests for location streaming.
use std::collections::{HashMap, VecDeque};
use std::mem::discriminant;
use std::path::Path;
use std::sync::{atomic, Arc, Condvar, Mutex};
use std::thread;
use itertools::Itertools;
use libc::uintptr_t;
use serde::Deserialize;
use tempfile;
use deltachat::chat;
use deltachat::config::Config;
use deltachat::contact::Contact;
use deltachat::context::Context;
use deltachat::job;
use deltachat::location;
use deltachat::Event;
/// Credentials for a test account.
///
/// This is populated by the JSON returned from the account provider's
/// API.
#[derive(Debug, Deserialize)]
struct AccountCredentials {
email: String,
password: String,
}
impl AccountCredentials {
/// Creates a new online account.
///
/// Invoke the API of the account provider to create a new
/// temporary account.
fn new(provider_url: &str) -> AccountCredentials {
let (post_url, token) = provider_url.splitn(2, '#').next_tuple().unwrap();
let mut data: HashMap<&str, u64> = HashMap::new();
data.insert("token_create_user", token.parse().unwrap());
let client = reqwest::Client::new();
let mut response = client.post(post_url).json(&data).send().unwrap();
assert!(
response.status().is_success(),
format!("Failed to create new tmpuser: {}", response.status())
);
response.json().unwrap()
}
}
#[derive(Debug)]
struct EventsItem {
acc_name: String,
when: std::time::Duration,
event: Event,
}
#[derive(Debug)]
struct EventsQueue {
name: String,
events: Mutex<VecDeque<EventsItem>>,
cond: Condvar,
}
impl EventsQueue {
fn new(name: &str) -> EventsQueue {
EventsQueue {
name: name.to_string(),
events: Mutex::new(VecDeque::new()),
cond: Condvar::new(),
}
}
fn push(&self, evt: EventsItem) {
let mut queue = self.events.lock().unwrap();
queue.push_back(evt);
self.cond.notify_all();
}
fn wait_for(&self, event: Event, data: bool) -> Result<(), ()> {
println!(
"==> [{}] Waiting for: {:?} match-data={}",
self.name, event, data
);
let mut queue = self.events.lock().unwrap();
let start_time = std::time::Instant::now();
loop {
while let Some(item) = queue.pop_front() {
let hit = match data {
true => event == item.event,
false => discriminant(&event) == discriminant(&item.event),
};
self.log_event(&item);
if hit {
println!(
"<== [{}] Found {:?} match-data={} in {:?}",
self.name,
event,
data,
start_time.elapsed()
);
return Ok(());
}
}
if start_time.elapsed().as_secs() > 25 {
println!(
"=!= [{}] Timed out waiting for {:?} match-data={}",
self.name, event, data
);
return Err(());
}
queue = self.cond.wait(queue).unwrap();
}
}
fn clear(&self) {
let mut queue = self.events.lock().unwrap();
while let Some(item) = queue.pop_front() {
self.log_event(&item);
}
}
fn log_event(&self, item: &EventsItem) {
match &item.event {
Event::Info(msg) => println!("I [{} {:?}]: {}", item.acc_name, item.when, msg),
Event::Warning(msg) => println!("W [{} {:?}]: {}", item.acc_name, item.when, msg),
Event::Error(msg) => println!("E [{} {:?}]: {}", item.acc_name, item.when, msg),
_ => println!("Evt [{} {:?}]: {:?}", item.acc_name, item.when, item.event),
}
}
fn clear_log_events(&self) {
let mut queue = self.events.lock().unwrap();
for item in queue.iter() {
self.log_event(item)
}
queue.retain(|item| match item.event {
Event::Info(_) | Event::Warning(_) | Event::Error(_) => false,
_ => true,
});
}
}
/// A Configured DeltaChat account.
#[derive(Debug)]
struct Account {
name: String,
creds: AccountCredentials,
ctx: Arc<Context>,
events: Arc<EventsQueue>,
running: Arc<atomic::AtomicBool>,
imap_handle: Option<thread::JoinHandle<()>>,
mvbox_handle: Option<thread::JoinHandle<()>>,
sentbox_handle: Option<thread::JoinHandle<()>>,
smtp_handle: Option<thread::JoinHandle<()>>,
}
impl Account {
fn new(name: &str, dir: &Path, keys: KeyPair, start: std::time::Instant) -> Account {
// Create events queue and callback.
let events = Arc::new(EventsQueue::new(name));
let events_cb = Arc::clone(&events);
let name_cb = name.to_string();
let cb = move |_ctx: &Context, evt: Event| -> uintptr_t {
events_cb.push(EventsItem {
acc_name: name_cb.clone(),
when: start.elapsed(),
event: evt,
});
0
};
// Create and configure the context.
let dbfile = dir.join(format!("{}.db", name));
let creds = AccountCredentials::new(&Account::liveconfig_url());
println!("Account credentials for {}: {:#?}", name, creds);
let ctx = Arc::new(Context::new(Box::new(cb), "TestClient".into(), dbfile).unwrap());
ctx.set_config(Config::Addr, Some(&creds.email)).unwrap();
ctx.set_config(Config::MailPw, Some(&creds.password))
.unwrap();
keys.save_as_self(&ctx);
deltachat::configure::configure(&ctx);
// Start the threads.
let running = Arc::new(atomic::AtomicBool::new(true));
let imap_handle = Self::start_imap(name, Arc::clone(&ctx), Arc::clone(&running));
let mvbox_handle = Self::start_mvbox(name, Arc::clone(&ctx), Arc::clone(&running));
let sentbox_handle = Self::start_sentbox(name, Arc::clone(&ctx), Arc::clone(&running));
let smtp_handle = Self::start_smtp(name, Arc::clone(&ctx), Arc::clone(&running));
events.clear_log_events();
Account {
name: name.to_string(),
creds,
ctx,
events,
running,
imap_handle: Some(imap_handle),
mvbox_handle: Some(mvbox_handle),
sentbox_handle: Some(sentbox_handle),
smtp_handle: Some(smtp_handle),
}
}
/// Find the liveconfig URL.
///
/// Prefers the `DCC_TMPACCOUNT_PROVIDER`, will also use the
/// `DCC_PY_LIVECONFIG` environment variable and finally fall back
/// to finding a file named `liveconfig` and starting with
/// `#:provider:https://`.
fn liveconfig_url() -> String {
if let Some(url) = std::env::var("DCC_TMPACCOUNT_PROVIDER").ok() {
return url;
}
if let Some(url) = std::env::var("DCC_PY_LIVECONFIG").ok() {
return url;
}
let mut dir = Some(Path::new(".").canonicalize().unwrap());
loop {
let cfg_fname = match dir {
Some(path) => {
dir = path.parent().map(|p| p.to_path_buf());
path.join("liveconfig")
}
None => break,
};
if cfg_fname.is_file() {
let raw_data = std::fs::read(&cfg_fname).unwrap();
let data = String::from_utf8(raw_data).unwrap();
for line in data.lines() {
if line.starts_with("#:provider:https://") {
let (_, url) = line.split_at(11);
return url.to_string();
}
}
panic!("No provider URL in {}", cfg_fname.display());
}
}
panic!("Found no liveconfig");
}
fn start_imap(
name: &str,
ctx: Arc<Context>,
running: Arc<atomic::AtomicBool>,
) -> thread::JoinHandle<()> {
thread::Builder::new()
.name(format!("{}-imap", name))
.spawn(move || {
while running.load(atomic::Ordering::Relaxed) {
job::perform_imap_jobs(&ctx);
job::perform_imap_fetch(&ctx);
if !running.load(atomic::Ordering::Relaxed) {
break;
}
job::perform_imap_idle(&ctx);
}
})
.unwrap()
}
fn start_mvbox(
name: &str,
ctx: Arc<Context>,
running: Arc<atomic::AtomicBool>,
) -> thread::JoinHandle<()> {
thread::Builder::new()
.name(format!("{}-mvbox", name))
.spawn(move || {
while running.load(atomic::Ordering::Relaxed) {
job::perform_mvbox_jobs(&ctx);
job::perform_mvbox_fetch(&ctx);
if !running.load(atomic::Ordering::Relaxed) {
break;
}
job::perform_mvbox_idle(&ctx);
}
})
.unwrap()
}
fn start_sentbox(
name: &str,
ctx: Arc<Context>,
running: Arc<atomic::AtomicBool>,
) -> thread::JoinHandle<()> {
thread::Builder::new()
.name(format!("{}-sentbox", name))
.spawn(move || {
while running.load(atomic::Ordering::Relaxed) {
job::perform_sentbox_jobs(&ctx);
job::perform_sentbox_fetch(&ctx);
if !running.load(atomic::Ordering::Relaxed) {
break;
}
job::perform_sentbox_idle(&ctx);
}
})
.unwrap()
}
fn start_smtp(
name: &str,
ctx: Arc<Context>,
running: Arc<atomic::AtomicBool>,
) -> thread::JoinHandle<()> {
thread::Builder::new()
.name(format!("{}-smtp", name))
.spawn(move || {
while running.load(atomic::Ordering::Relaxed) {
job::perform_smtp_jobs(&ctx);
job::perform_smtp_fetch(&ctx);
if !running.load(atomic::Ordering::Relaxed) {
break;
}
job::perform_smtp_idle(&ctx);
}
})
.unwrap()
}
/// Goes through the events queue and prints all log events.
///
/// Each processed event is removed from the queue.
fn process_log_events(&self) {}
}
impl Drop for Account {
fn drop(&mut self) {
println!("Terminating Account {}", self.name);
self.running.store(false, atomic::Ordering::Relaxed);
job::interrupt_imap_idle(&self.ctx);
job::interrupt_mvbox_idle(&self.ctx);
self.imap_handle.take().unwrap().join().unwrap();
self.mvbox_handle.take().unwrap().join().unwrap();
self.events.clear();
println!("Account {} Terminated", self.name);
}
}
/// Helper struct to handle account key pairs.
struct KeyPair {
public: deltachat::key::Key,
private: deltachat::key::Key,
}
impl KeyPair {
/// Create a new [KeyPair].
///
/// # Example
///
/// ```
/// let alice_keys = KeyPair::new(
/// include_str!("../test-data/key/public.asc"),
/// include_str!("../test-data/key/private.asc"),
/// );
/// ```
fn new(public_data: &str, private_data: &str) -> KeyPair {
let public =
deltachat::key::Key::from_base64(public_data, deltachat::constants::KeyType::Public)
.unwrap();
let private =
deltachat::key::Key::from_base64(private_data, deltachat::constants::KeyType::Private)
.unwrap();
KeyPair { public, private }
}
/// Saves a key into the context as the default key of the self address.
///
/// [Config::Addr] must already be set.
fn save_as_self(&self, ctx: &Context) {
let addr = ctx.get_config(Config::Addr).unwrap();
let ok = deltachat::key::dc_key_save_self_keypair(
&ctx,
&self.public,
&self.private,
&addr,
true,
&ctx.sql,
);
assert_eq!(ok, true);
}
}
#[test]
fn test_location_streaming() {
// Create accounts
let start = std::time::Instant::now();
let tmpdir = tempfile::tempdir().unwrap();
let alice_keys = KeyPair::new(
include_str!("../test-data/key/public.asc"),
include_str!("../test-data/key/private.asc"),
);
let alice = Account::new("alice", tmpdir.path(), alice_keys, start);
let bob_keys = KeyPair::new(
include_str!("../test-data/key/public2.asc"),
include_str!("../test-data/key/private2.asc"),
);
let bob = Account::new("bob", tmpdir.path(), bob_keys, start);
alice
.events
.wait_for(Event::ConfigureProgress(1000), true)
.unwrap();
bob.events
.wait_for(Event::ConfigureProgress(1000), true)
.unwrap();
// Create contacts and chats.
let contact_bob = Contact::create(&alice.ctx, "Bob", &bob.creds.email).unwrap();
let contact_alice = Contact::create(&bob.ctx, "Alice", &bob.creds.email).unwrap();
let alice_to_bob = deltachat::chat::create_by_contact_id(&alice.ctx, contact_bob).unwrap();
let bob_to_alice = deltachat::chat::create_by_contact_id(&bob.ctx, contact_alice).unwrap();
alice.events.clear();
bob.events.clear();
println!("### Starting location streaming from Alice to Bob");
assert!(!location::is_sending_locations_to_chat(
&alice.ctx,
alice_to_bob
));
assert!(!location::is_sending_locations_to_chat(
&bob.ctx,
bob_to_alice
));
location::send_locations_to_chat(&alice.ctx, alice_to_bob, 100);
assert!(location::is_sending_locations_to_chat(
&alice.ctx,
alice_to_bob
));
alice
.events
.wait_for(Event::SmtpMessageSent(Default::default()), false)
.unwrap();
assert_eq!(location::set(&alice.ctx, 1.0, 1.0, 1.0), true);
alice
.events
.wait_for(Event::LocationChanged(Default::default()), false)
.unwrap();
assert_eq!(location::set(&alice.ctx, 1.1, 1.1, 1.0), true);
chat::send_text_msg(&alice.ctx, alice_to_bob, "ping".to_string()).unwrap();
alice
.events
.wait_for(Event::SmtpMessageSent(Default::default()), false)
.unwrap();
println!("### Looking for location messages received by Bob");
// First message is the "enabled-location-streaming" command.
bob.events
.wait_for(
Event::MsgsChanged {
chat_id: Default::default(),
msg_id: Default::default(),
},
false,
)
.unwrap();
// Core emits location changed before the incoming message. Sadly
// the the ordering requirement is brittle.
bob.events
.wait_for(Event::LocationChanged(Default::default()), false)
.unwrap();
// Next message is the "ping" one which should contain a location.
bob.events
.wait_for(
Event::MsgsChanged {
chat_id: Default::default(),
msg_id: Default::default(),
},
false,
)
.unwrap();
let positions = location::get_range(&bob.ctx, bob_to_alice, contact_alice, 0, 0);
println!("pos len: {}", positions.len());
println!("{:#?}", positions);
assert!(false, "THE END");
}