Compare commits

..

32 Commits

Author SHA1 Message Date
Alexander Krotov
035a414c8f Use the first subkey for encryption instead of the primary key 2019-11-10 07:16:50 +03:00
B. Petersen
37ecfa6b67 fix gm2local offset calculations 2019-11-09 20:26:00 +01:00
B. Petersen
99ba2fb358 let dc_timestamp_to_str() print the local time, not UTC times. the function is used for outputs directly shown to the user, eg. dc_get_msg_info() 2019-11-09 20:26:00 +01:00
B. Petersen
85ebde29dc do not resort chatlist on draft changes
resorting the chatlist on changing drafts has some ux issues.
eg. when the chatlist is visible together with the input field,
if may come to flickering resorting during input
or to a resorting just when the user leave the chat
as this might trigger set_draft().

but also on mobiles, the resorting is visible and a bit unexpected.
also it is unclear what happens when a chat with a draft is entered
and left without modifications.

the solution proposed here is to ignore draft on sorting
while still showing them in the chatlist
if they're newer as the last message.

a possible disadvantage is
that the date for the chat with a draft does not follow the ordering
(the ordering is by the last message),
however, the date is not shown as a "primary sort" criterion or so,
so it might be that this is completely okay.
also, of course, it affects only draft :)
2019-11-09 19:32:13 +01:00
björn petersen
0876f45503 Merge pull request #810 from deltachat/sync-config-param
restore config-param if configure() fails
2019-11-08 19:36:43 +01:00
B. Petersen
2fae6890c2 the new MsgId type comes with a formatter that makes special formatting in the repl tool superfluous 2019-11-07 13:19:38 +01:00
B. Petersen
34f9961857 restore config-param if configure() fails 2019-11-06 22:59:14 +01:00
B. Petersen
515f0c5089 correct ffi return value of dc_add_device_msg() 2019-11-06 13:33:30 +01:00
B. Petersen
5a11551b4d block sending to chats that do not support sending (normally, this should already be avoided in the ui) 2019-11-06 13:33:30 +01:00
B. Petersen
49bf99588b show chat-profile-image in repl tool 2019-11-06 13:33:30 +01:00
B. Petersen
231110fb61 add profile-icon for device-chat 2019-11-06 13:33:30 +01:00
B. Petersen
4c30bf80ce target comments of @flub 2019-11-06 13:33:30 +01:00
B. Petersen
f8afefa2c1 get contact- and chat-info for device-messages 2019-11-06 13:33:30 +01:00
B. Petersen
89bb2d0ffe add devicemsg to repl tool 2019-11-06 13:33:30 +01:00
B. Petersen
b5d5d98645 implement add_device_msg() 2019-11-06 13:33:30 +01:00
B. Petersen
89f394ab86 create separate function for preparing a blob 2019-11-06 13:33:30 +01:00
B. Petersen
cbaa4e03b3 basic devicetalk implementation 2019-11-06 13:33:30 +01:00
B. Petersen
50539465b9 prototype a device-chat 2019-11-06 13:33:30 +01:00
B. Petersen
be08bcb22b rename DC_CONTACT_ID_DEVICE to DC_CONTACT_ID_INFO to be in-line with dc_msg_is_info() 2019-11-06 13:33:30 +01:00
holger krekel
dcd92a894e fix export: write backup_time to the destination not the source sql file
and perform slightly cleaner teardown in python
2019-11-06 13:26:32 +01:00
holger krekel
6336eeb568 better error on has_backup() failing 2019-11-06 13:26:32 +01:00
holger krekel
6b18cbda1f refine dc_copy along the lines @flub did for blobstore 2019-11-06 13:26:32 +01:00
Alexander Krotov
cf023ea557 sql: remove unnecessary ? in prepare() and prepare2() 2019-11-06 13:26:03 +01:00
Alexander Krotov
51a804a80f location: use "bool" for "independent" argument 2019-11-06 14:17:59 +03:00
B. Petersen
1a33b1c574 bump version 2019-11-05 17:04:44 +01:00
B. Petersen
67e2e4d415 target comment of @hpk42 2019-11-04 13:20:35 +01:00
B. Petersen
8c2efa707a name special contact-ids where easily possible
the point of this pr is to get an overview
how and where DC_CONTACT_ID_DEVICE is used,
to prepare introducing a device-"chat".

i did not change the sql statements for now
as this would require some more refactoring
and has the potential to introduce bugs.
2019-11-04 13:20:35 +01:00
B. Petersen
87abc6e4a2 adapt wording 2019-11-03 21:36:40 +01:00
B. Petersen
0ea017c53d add repl command for testing interrupt-idle 2019-11-03 21:36:40 +01:00
B. Petersen
b9c7510b58 use boolean for jobs_needed 2019-11-03 21:36:40 +01:00
holger krekel
01e7caf65a use job_id as mail_id for SendableEmail -- it's only an internal id and job_id is unique enough. 2019-11-03 21:36:01 +01:00
holger krekel
1cfeb730c3 try to fix some smtp todos and do better error logging 2019-11-03 21:36:01 +01:00
33 changed files with 458 additions and 647 deletions

View File

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

6
Cargo.lock generated
View File

@@ -480,7 +480,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.0.0-beta.6"
version = "1.0.0-beta.7"
dependencies = [
"backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)",
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -550,9 +550,9 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.0.0-beta.6"
version = "1.0.0-beta.7"
dependencies = [
"deltachat 1.0.0-beta.6",
"deltachat 1.0.0-beta.7",
"deltachat-provider-database 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"human-panic 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",

View File

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

BIN
assets/icon-device.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

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

View File

@@ -501,15 +501,9 @@ 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.
*
* 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.
* 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.
*
* @memberof dc_context_t
* @param context The context object as created by dc_context_new().
@@ -1098,6 +1092,27 @@ 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.
@@ -2715,6 +2730,39 @@ 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
@@ -3370,7 +3418,8 @@ void dc_msg_latefiling_mediasize (dc_msg_t* msg, int width, int hei
#define DC_CONTACT_ID_SELF 1
#define DC_CONTACT_ID_DEVICE 2
#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_LAST_SPECIAL 9

View File

@@ -811,6 +811,23 @@ 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() {
@@ -2264,6 +2281,26 @@ 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", &msg);
log_msg(context, "", &msg);
}
}
if lines_out > 0 {
@@ -353,6 +353,7 @@ 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\
@@ -378,6 +379,7 @@ 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\
@@ -424,12 +426,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 Msg#{} starts with: {}",
"The setup code for setup message {} starts with: {}",
msg_id,
setupcodebegin.unwrap_or_default(),
);
} else {
bail!("Msg#{} is no setup message.", msg_id,);
bail!("{} is no setup message.", msg_id,);
}
}
"continue-key-transfer" => {
@@ -493,6 +495,9 @@ 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);
}
@@ -517,13 +522,12 @@ 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(),
temp_name,
chat.get_name(),
chat::get_fresh_msg_cnt(context, chat.get_id()),
);
let lot = chatlist.get_summary(context, i, Some(&chat));
@@ -582,25 +586,33 @@ 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 temp2 = if sel_chat.get_type() == Chattype::Single && members.len() >= 1 {
let subtitle = if sel_chat.is_device_talk() {
"device-talk".to_string()
} else 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(),
temp_name,
temp2,
sel_chat.get_name(),
subtitle,
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())? {
@@ -714,7 +726,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#{} Msg#{} {}",
"Loc#{}: {}: lat={} lng={} acc={} Chat#{} Contact#{} {} {}",
location.location_id,
dc_timestamp_to_str(location.timestamp),
location.latitude,
@@ -818,6 +830,15 @@ 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.");
@@ -831,9 +852,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!("Msg#{}", data);
print!("{}", data);
} else {
print!(", Msg#{}", data);
print!(", {}", data);
}
}
print!("\n");

View File

@@ -153,6 +153,14 @@ 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.
@@ -477,8 +485,9 @@ class Account(object):
def stop_threads(self, wait=True):
""" stop IMAP/SMTP threads. """
self.stop_ongoing()
self._threads.stop(wait=wait)
if self._threads.is_started():
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,7 +47,8 @@ DC_STATE_OUT_FAILED = 24
DC_STATE_OUT_DELIVERED = 26
DC_STATE_OUT_MDN_RCVD = 28
DC_CONTACT_ID_SELF = 1
DC_CONTACT_ID_DEVICE = 2
DC_CONTACT_ID_INFO = 2
DC_CONTACT_ID_DEVICE = 5
DC_CONTACT_ID_LAST_SPECIAL = 9
DC_MSG_TEXT = 10
DC_MSG_IMAGE = 20

View File

@@ -2,6 +2,7 @@ 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
@@ -641,18 +642,29 @@ 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):
def test_import_export_online_all(self, acfactory, tmpdir, lp):
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
@@ -663,6 +675,18 @@ class TestOnlineAccount:
assert len(messages) == 1
assert messages[0].text == "msg1"
pytest.xfail("cannot export twice yet, probably due to interrupt_idle failing")
# 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

View File

@@ -97,6 +97,8 @@ 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();
}
}
}
@@ -109,6 +111,14 @@ 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,
@@ -258,7 +268,7 @@ impl Chat {
}
if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup)
&& !is_contact_in_chat(context, self.id, 1 as u32)
&& !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF)
{
emit_event!(
context,
@@ -414,7 +424,7 @@ impl Chat {
&context.sql,
"INSERT INTO locations \
(timestamp,from_id,chat_id, latitude,longitude,independent)\
VALUES (?,?,?, ?,?,1);",
VALUES (?,?,?, ?,?,1);", // 1=DC_CONTACT_ID_SELF
params![
timestamp,
DC_CONTACT_ID_SELF,
@@ -446,7 +456,7 @@ impl Chat {
params![
new_rfc724_mid,
self.id as i32,
1i32,
DC_CONTACT_ID_SELF,
to_id as i32,
timestamp,
msg.type_0,
@@ -582,6 +592,12 @@ 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,
@@ -605,7 +621,14 @@ pub fn create_or_lookup_by_contact_id(
"INSERT INTO chats (type, name, param, blocked, grpid) VALUES({}, '{}', '{}', {}, '{}')",
100,
chat_name,
if contact_id == DC_CONTACT_ID_SELF as u32 { "K=1" } else { "" },
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()
},
create_blocked as u8,
contact.get_addr(),
),
@@ -677,8 +700,7 @@ pub fn msgtype_has_file(msgtype: Viewtype) -> bool {
}
}
fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Result<MsgId, Error> {
msg.id = MsgId::new_unset();
fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> {
if msg.type_0 == Viewtype::Text {
// the caller should check if the message text is empty
} else if msgtype_has_file(msg.type_0) {
@@ -714,10 +736,16 @@ fn prepare_msg_common(context: &Context, chat_id: u32, msg: &mut Message) -> Res
} 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
@@ -914,7 +942,7 @@ fn do_set_draft(context: &Context, chat_id: u32, msg: &mut Message) -> Result<()
VALUES (?,?,?, ?,?,?,?,?);",
params![
chat_id as i32,
1,
DC_CONTACT_ID_SELF,
time(),
msg.type_0,
MessageState::OutDraft,
@@ -993,8 +1021,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",
" AND m.from_id!=2",
" WHERE m.from_id!=1", // 1=DC_CONTACT_ID_SELF
" AND m.from_id!=2", // 2=DC_CONTACT_ID_INFO
" AND m.hidden=0",
" AND chats.blocked=2",
" AND contacts.blocked=0",
@@ -1350,7 +1378,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, 1) {
if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF) {
let mut draft_msg = Message::new(Viewtype::Text);
draft_msg.set_text(Some(draft_txt));
set_draft_raw(context, chat_id, &mut draft_msg);
@@ -1557,7 +1585,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, 1 as u32) {
if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) {
emit_event!(
context,
Event::ErrorSelfNotInGroup(
@@ -1653,7 +1681,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, 1) {
} else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) {
emit_event!(
context,
Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into())
@@ -1898,15 +1926,46 @@ 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, chat_id: u32, text: impl AsRef<str>) {
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>) {
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,
2,
2,
DC_CONTACT_ID_INFO,
DC_CONTACT_ID_INFO,
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 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" 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 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" 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 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" 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 OR (hidden=1 AND state=19)))",
" AND hidden=0)",
" 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 lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != 1
let mut lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) {
if lastmsg.from_id != DC_CONTACT_ID_SELF
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
{
lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok();
@@ -306,6 +306,16 @@ 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,6 +421,16 @@ 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 });
}
@@ -531,21 +541,26 @@ 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);
if context
match context
.smtp
.clone()
.lock()
.unwrap()
.connect(context, &param)
{
info!(context, "success: {}", inf);
return Some(true);
Ok(()) => {
info!(context, "success: {}", inf);
Some(true)
}
Err(err) => {
if context.shall_stop_ongoing() {
Some(false)
} else {
warn!(context, "could not connect: {}", err);
None
}
}
}
if context.shall_stop_ongoing() {
return Some(false);
}
info!(context, "could not connect: {}", inf);
None
}
/*******************************************************************************

View File

@@ -130,7 +130,8 @@ 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_DEVICE: u32 = 2;
pub const DC_CONTACT_ID_INFO: u32 = 2;
pub const DC_CONTACT_ID_DEVICE: u32 = 5;
pub const DC_CONTACT_ID_LAST_SPECIAL: u32 = 9;
pub const DC_CREATE_MVBOX: usize = 1;

View File

@@ -153,7 +153,16 @@ 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);
}
@@ -264,7 +273,7 @@ impl Contact {
.unwrap_or_default();
if addr_normalized == addr_self {
return 1;
return DC_CONTACT_ID_SELF;
}
context.sql.query_get_value(
@@ -301,7 +310,7 @@ impl Contact {
.unwrap_or_default();
if addr == addr_self {
return Ok((1, sth_modified));
return Ok((DC_CONTACT_ID_SELF, sth_modified));
}
if !may_be_valid_addr(&addr) {

View File

@@ -391,7 +391,7 @@ unsafe fn add_parts(
} else {
MessageState::InFresh
};
*to_id = 1;
*to_id = DC_CONTACT_ID_SELF;
// 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,8 +551,9 @@ 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, 1, Blocked::Not)
.unwrap_or_default();
let (id, bl) =
chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not)
.unwrap_or_default();
*chat_id = id;
chat_id_blocked = bl;
@@ -849,7 +850,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, 1).unwrap_or_default();
location::save(context, chat_id, from_id, locations, true).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;
@@ -864,7 +865,8 @@ 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, 0).unwrap_or_default();
location::save(context, chat_id, from_id, locations, false)
.unwrap_or_default();
if newest_location_id != 0 && hidden == 0 && !location_id_written {
if let Err(err) = location::set_msg_location_id(
context,
@@ -1350,8 +1352,8 @@ unsafe fn create_or_lookup_adhoc_group(
if !member_ids.contains(&from_id) {
member_ids.push(from_id);
}
if !member_ids.contains(&1) {
member_ids.push(1);
if !member_ids.contains(&DC_CONTACT_ID_SELF) {
member_ids.push(DC_CONTACT_ID_SELF);
}
if member_ids.len() < 3 {
// too few contacts given
@@ -1471,7 +1473,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",
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
member_ids_str
),
params![],
@@ -1525,7 +1527,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;",
ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
contact_ids_str
),
params![],

View File

@@ -154,13 +154,15 @@ 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 = chrono::Utc.timestamp(wanted, 0);
let ts = Local.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() / (60 * 60)) * 100) as i64
lt.offset().local_minus_utc() as i64
}
/* timesmearing */
@@ -427,21 +429,55 @@ pub(crate) fn dc_delete_file(context: &Context, path: impl AsRef<std::path::Path
pub(crate) fn dc_copy_file(
context: &Context,
src: impl AsRef<std::path::Path>,
dest: impl AsRef<std::path::Path>,
src_path: impl AsRef<std::path::Path>,
dest_path: impl AsRef<std::path::Path>,
) -> bool {
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) {
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) {
Ok(_) => true,
Err(err) => {
error!(
context,
"Cannot copy \"{}\" to \"{}\": {}",
src.as_ref().display(),
dest.as_ref().display(),
src_abs.display(),
dest_abs.display(),
err
);
{
// Attempt to remove the failed file, swallow errors resulting from that.
fs::remove_file(dest_abs).ok();
}
false
}
}
@@ -1283,6 +1319,9 @@ 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

@@ -1,5 +1,5 @@
use core::cmp::{max, min};
use std::path::{Path, PathBuf};
use std::path::Path;
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 if found, nullptr otherwise.
/// Returns the filename of the backup found (otherwise an error)
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,13 +90,15 @@ 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() as u64;
let curr_backup_time = sql
.get_raw_config_int(context, "backup_time")
.unwrap_or_default();
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);
}
}
}
@@ -105,7 +107,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"),
None => bail!("no backup found in {}", dir_name.display()),
}
}
@@ -483,10 +485,13 @@ 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,
@@ -496,38 +501,40 @@ 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(),
s
dest_path_string
);
}
match add_files_to_export(context, &dest_path_filename) {
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) {
Err(err) => {
dc_delete_file(context, &dest_path_filename);
error!(context, "backup failed: {}", err);
Err(err)
}
Ok(()) => {
context
.sql
.set_raw_config_int(context, "backup_time", now as i32)?;
dest_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, dest_path_filename: &PathBuf) -> Result<()> {
fn add_files_to_export(context: &Context, sql: &Sql) -> 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,8 +135,7 @@ 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 {
if connected.is_err() {
self.try_again_later(3, None);
return;
}
@@ -173,10 +172,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 sock = context.smtp.lock().unwrap();
match sock.send(context, recipients_list, body) {
let mut smtp = context.smtp.lock().unwrap();
match smtp.send(context, recipients_list, body, self.job_id) {
Err(err) => {
sock.disconnect();
smtp.disconnect();
warn!(context, "smtp failed: {}", err);
self.try_again_later(-1, Some(err.to_string()));
}
@@ -505,8 +504,6 @@ 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...",);
{
@@ -1025,8 +1022,9 @@ pub fn interrupt_smtp_idle(context: &Context) {
}
pub fn interrupt_imap_idle(context: &Context) {
info!(context, "Interrupting IMAP-IDLE...",);
info!(context, "Interrupting INBOX-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: i32,
jobs_needed: bool,
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 = 1;
self.state.0.lock().unwrap().jobs_needed = true;
}
info!(context, "Interrupting {}-IDLE...", self.name);
@@ -139,13 +139,13 @@ impl JobThread {
let &(ref lock, ref cvar) = &*self.state.clone();
let mut state = lock.lock().unwrap();
if 0 != state.jobs_needed {
if state.jobs_needed {
info!(
context,
"{}-IDLE will not be started as it was interrupted while not ideling.",
self.name,
);
state.jobs_needed = 0;
state.jobs_needed = false;
return;
}

View File

@@ -190,13 +190,7 @@ impl Kml {
}
}
/// 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.
// location streaming
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) {
@@ -225,7 +219,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_device_msg(context, chat_id, stock_str);
chat::add_info_msg(context, chat_id, stock_str);
}
context.call_cb(Event::ChatModified(chat_id));
if 0 != seconds {
@@ -281,7 +275,7 @@ pub fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64) -> b
accuracy,
time(),
chat_id,
1,
DC_CONTACT_ID_SELF,
]
) {
warn!(context, "failed to store location {:?}", err);
@@ -498,7 +492,7 @@ pub fn save(
chat_id: u32,
contact_id: u32,
locations: &[Location],
independent: i32,
independent: bool,
) -> Result<u32, Error> {
ensure!(chat_id > DC_CHAT_ID_LAST_SPECIAL, "Invalid chat id");
context.sql.prepare2(
@@ -513,7 +507,7 @@ pub fn save(
for location in locations {
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
if 0 != independent || !exists {
if independent || !exists {
stmt_insert.execute(params![
location.timestamp,
contact_id as i32,
@@ -657,7 +651,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_device_msg(context, chat_id, stock_str);
chat::add_info_msg(context, chat_id, stock_str);
context.call_cb(Event::ChatModified(chat_id));
}
}

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_DEVICE as libc::c_uint
|| self.to_id == DC_CONTACT_ID_DEVICE as libc::c_uint
self.from_id == DC_CONTACT_ID_INFO as libc::c_uint
|| self.to_id == DC_CONTACT_ID_INFO 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 == 2 || msg.to_id == 2 {
if msg.from_id == DC_CONTACT_ID_INFO || msg.to_id == DC_CONTACT_ID_INFO {
// device-internal message, no further details needed
return ret;
}

View File

@@ -76,6 +76,8 @@ 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,
SignedSecretKey, SubkeyParamsBuilder,
SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder,
};
use pgp::crypto::{HashAlgorithm, SymmetricKeyAlgorithm};
use pgp::types::{CompressionAlgorithm, KeyTrait, SecretKeyTrait, StringToKey};
@@ -97,18 +97,29 @@ 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<&SignedPublicKey> = public_keys_for_encryption
let pkeys: Vec<&SignedPublicSubKey> = public_keys_for_encryption
.keys()
.iter()
.filter_map(|key| {
let k: &Key = &key;
k.try_into().ok()
key.as_ref()
.try_into()
.ok()
.and_then(select_pk_for_encryption)
})
.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_device_msg(
chat::add_info_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_device_msg(context, contact_chat_id, msg);
chat::add_info_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_device_msg(context, contact_chat_id, &msg);
chat::add_info_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_device_msg(context, contact_chat_id, msg);
chat::add_info_msg(context, contact_chat_id, msg);
emit_event!(context, Event::ChatModified(contact_chat_id));
}
}

View File

@@ -44,27 +44,24 @@ impl Smtp {
}
/// Connect using the provided login params
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> bool {
pub fn connect(&mut self, context: &Context, lp: &LoginParam) -> Result<(), Error> {
if self.is_connected() {
warn!(context, "SMTP already connected.");
return true;
return Ok(());
}
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 = if let Ok(addr) = EmailAddress::new(lp.addr.clone()) {
Some(addr)
} else {
None
self.from = match EmailAddress::new(lp.addr.clone()) {
Ok(addr) => Some(addr),
Err(err) => {
bail!("invalid login address {}: {}", lp.addr, err);
}
};
if self.from.is_none() {
// TODO: print error
return false;
}
let domain = &lp.send_server;
let port = lp.send_port as u16;
@@ -76,11 +73,12 @@ impl Smtp {
let addr = &lp.addr;
let send_pw = &lp.send_pw;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
if access_token.is_none() {
return false;
}
ensure!(
access_token.is_some(),
"could not get oaut2_access token addr={}",
addr
);
let user = &lp.send_user;
(
lettre::smtp::authentication::Credentials::new(
user.to_string(),
@@ -125,27 +123,27 @@ impl Smtp {
"SMTP-LOGIN as {} ok",
lp.send_user,
)));
return true;
return Ok(());
}
Err(err) => {
warn!(context, "SMTP: failed to connect {:?}", err);
bail!("SMTP: failed to connect {:?}", err);
}
}
}
Err(err) => {
warn!(context, "SMTP: failed to setup connection {:?}", err);
bail!("SMTP: failed to setup connection {:?}", err);
}
}
false
}
/// SMTP-Send a prepared mail to recipients.
/// returns boolean whether send was successful.
/// on successful send out Ok() is returned.
pub fn send<'a>(
&mut self,
context: &Context,
recipients: Vec<EmailAddress>,
message: Vec<u8>,
job_id: u32,
) -> Result<(), Error> {
let message_len = message.len();
@@ -156,12 +154,15 @@ impl Smtp {
.join(",");
if let Some(ref mut transport) = self.transport {
let envelope = Envelope::new(self.from.clone(), recipients);
ensure!(envelope.is_ok(), "internal smtp-message construction fail");
let envelope = envelope.unwrap();
let envelope = match Envelope::new(self.from.clone(), recipients) {
Ok(env) => env,
Err(err) => {
bail!("{}", err);
}
};
let mail = SendableEmail::new(
envelope,
"mail-id".into(), // TODO: random id
format!("{}", job_id), // only used for internal logging
message,
);

View File

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

View File

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

View File

@@ -1 +0,0 @@
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

@@ -1 +0,0 @@
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=

View File

@@ -1,476 +0,0 @@
//! 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");
}