mirror of
https://github.com/chatmail/core.git
synced 2026-04-21 15:36:30 +03:00
it compiles
This commit is contained in:
66
src/blob.rs
66
src/blob.rs
@@ -486,9 +486,9 @@ mod tests {
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_create() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create() {
|
||||
let t = dummy_context().await;
|
||||
let blob = BlobObject::create(&t.ctx, "foo", b"hello").unwrap();
|
||||
let fname = t.ctx.get_blobdir().join("foo");
|
||||
let data = fs::read(fname).unwrap();
|
||||
@@ -497,39 +497,39 @@ mod tests {
|
||||
assert_eq!(blob.to_abs_path(), t.ctx.get_blobdir().join("foo"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lowercase_ext() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_lowercase_ext() {
|
||||
let t = dummy_context().await;
|
||||
let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello").unwrap();
|
||||
assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_file_name() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_as_file_name() {
|
||||
let t = dummy_context().await;
|
||||
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
assert_eq!(blob.as_file_name(), "foo.txt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_as_rel_path() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_as_rel_path() {
|
||||
let t = dummy_context().await;
|
||||
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
assert_eq!(blob.as_rel_path(), Path::new("foo.txt"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_suffix() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_suffix() {
|
||||
let t = dummy_context().await;
|
||||
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
assert_eq!(blob.suffix(), Some("txt"));
|
||||
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
|
||||
assert_eq!(blob.suffix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_dup() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_dup() {
|
||||
let t = dummy_context().await;
|
||||
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
|
||||
let foo_path = t.ctx.get_blobdir().join("foo.txt");
|
||||
assert!(foo_path.exists());
|
||||
@@ -546,9 +546,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_double_ext_preserved() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_double_ext_preserved() {
|
||||
let t = dummy_context().await;
|
||||
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
|
||||
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
|
||||
assert!(foo_path.exists());
|
||||
@@ -566,18 +566,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_long_names() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_long_names() {
|
||||
let t = dummy_context().await;
|
||||
let s = "1".repeat(150);
|
||||
let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap();
|
||||
let blobname = blob.as_name().split('/').last().unwrap();
|
||||
assert!(blobname.len() < 128);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_and_copy() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_and_copy() {
|
||||
let t = dummy_context().await;
|
||||
let src = t.dir.path().join("src");
|
||||
fs::write(&src, b"boo").unwrap();
|
||||
let blob = BlobObject::create_and_copy(&t.ctx, &src).unwrap();
|
||||
@@ -591,9 +591,9 @@ mod tests {
|
||||
assert!(!whoops.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_from_path() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_from_path() {
|
||||
let t = dummy_context().await;
|
||||
|
||||
let src_ext = t.dir.path().join("external");
|
||||
fs::write(&src_ext, b"boo").unwrap();
|
||||
@@ -609,9 +609,9 @@ mod tests {
|
||||
let data = fs::read(blob.to_abs_path()).unwrap();
|
||||
assert_eq!(data, b"boo");
|
||||
}
|
||||
#[test]
|
||||
fn test_create_from_name_long() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_from_name_long() {
|
||||
let t = dummy_context().await;
|
||||
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
|
||||
fs::write(&src_ext, b"boo").unwrap();
|
||||
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).unwrap();
|
||||
|
||||
340
src/chat.rs
340
src/chat.rs
File diff suppressed because it is too large
Load Diff
@@ -136,7 +136,7 @@ impl Chatlist {
|
||||
AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2)
|
||||
GROUP BY c.id
|
||||
ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
|
||||
paramsv![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned],
|
||||
process_row,
|
||||
process_rows,
|
||||
).await?
|
||||
@@ -159,7 +159,7 @@ impl Chatlist {
|
||||
AND c.archived=1
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft],
|
||||
paramsv![MessageState::OutDraft],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
@@ -192,7 +192,7 @@ impl Chatlist {
|
||||
AND c.name LIKE ?
|
||||
GROUP BY c.id
|
||||
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft, str_like_cmd],
|
||||
paramsv![MessageState::OutDraft, str_like_cmd],
|
||||
process_row,
|
||||
process_rows,
|
||||
)
|
||||
@@ -221,7 +221,7 @@ impl Chatlist {
|
||||
AND NOT c.archived=?2
|
||||
GROUP BY c.id
|
||||
ORDER BY c.id=?3 DESC, c.archived=?4 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
|
||||
params![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
|
||||
paramsv![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned],
|
||||
process_row,
|
||||
process_rows,
|
||||
).await?;
|
||||
@@ -333,9 +333,15 @@ impl Chatlist {
|
||||
ret.text2 = None;
|
||||
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED
|
||||
{
|
||||
ret.text2 = Some(context.stock_str(StockMessage::NoMessages).to_string());
|
||||
ret.text2 = Some(
|
||||
context
|
||||
.stock_str(StockMessage::NoMessages)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
} else {
|
||||
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context);
|
||||
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context)
|
||||
.await;
|
||||
}
|
||||
|
||||
ret
|
||||
@@ -353,7 +359,7 @@ pub async fn dc_get_archived_cnt(context: &Context) -> u32 {
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
@@ -376,7 +382,7 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
|
||||
" AND c.blocked=2",
|
||||
" ORDER BY m.timestamp DESC, m.id DESC;"
|
||||
),
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -389,7 +395,7 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_try_load() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -410,7 +416,7 @@ mod tests {
|
||||
// drafts are sorted to the top
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some("hello".to_string()));
|
||||
chat_id2.set_draft(&t.ctx, Some(&mut msg));
|
||||
chat_id2.set_draft(&t.ctx, Some(&mut msg)).await;
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.get_chat_id(0), chat_id2);
|
||||
|
||||
@@ -453,8 +459,8 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_search_special_chat_names() {
|
||||
let t = dummy_context();
|
||||
t.ctx.update_device_chats().unwrap();
|
||||
let t = dummy_context().await;
|
||||
t.ctx.update_device_chats().await.unwrap();
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None)
|
||||
.await
|
||||
@@ -467,6 +473,7 @@ mod tests {
|
||||
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None)
|
||||
.await
|
||||
@@ -475,6 +482,7 @@ mod tests {
|
||||
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None)
|
||||
.await
|
||||
@@ -484,14 +492,14 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_get_summary_unwrap() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.set_text(Some("foo:\nbar \r\n test".to_string()));
|
||||
chat_id1.set_draft(&t.ctx, Some(&mut msg));
|
||||
chat_id1.set_draft(&t.ctx, Some(&mut msg)).await;
|
||||
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
let summary = chats.get_summary(&t.ctx, 0, None).await;
|
||||
|
||||
@@ -10,7 +10,6 @@ use crate::dc_tools::*;
|
||||
use crate::job::*;
|
||||
use crate::mimefactory::RECOMMENDED_FILE_SIZE;
|
||||
use crate::stock::StockMessage;
|
||||
use rusqlite::NO_PARAMS;
|
||||
|
||||
/// The available configuration keys.
|
||||
#[derive(
|
||||
@@ -113,7 +112,7 @@ impl Context {
|
||||
|
||||
// Default values
|
||||
match key {
|
||||
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).into_owned()),
|
||||
Config::Selfstatus => Some(self.stock_str(StockMessage::StatusLine).await.into_owned()),
|
||||
_ => key.get_str("default").map(|s| s.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -135,7 +134,7 @@ impl Context {
|
||||
match key {
|
||||
Config::Selfavatar => {
|
||||
self.sql
|
||||
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)
|
||||
.execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![])
|
||||
.await?;
|
||||
self.sql
|
||||
.set_raw_config_bool(self, "attach_selfavatar", true)
|
||||
@@ -167,7 +166,7 @@ impl Context {
|
||||
ret
|
||||
}
|
||||
Config::Selfstatus => {
|
||||
let def = self.stock_str(StockMessage::StatusLine);
|
||||
let def = self.stock_str(StockMessage::StatusLine).await;
|
||||
let val = if value.is_none() || value.unwrap() == def {
|
||||
None
|
||||
} else {
|
||||
@@ -224,7 +223,7 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_selfavatar_outside_blobdir() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let avatar_src = t.dir.path().join("avatar.jpg");
|
||||
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
|
||||
File::create(&avatar_src)
|
||||
@@ -253,7 +252,7 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_selfavatar_in_blobdir() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let avatar_src = t.ctx.get_blobdir().join("avatar.png");
|
||||
let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png");
|
||||
File::create(&avatar_src)
|
||||
|
||||
@@ -33,7 +33,7 @@ macro_rules! progress {
|
||||
impl Context {
|
||||
/// Starts a configuration job.
|
||||
pub async fn configure(&self) {
|
||||
if self.has_ongoing() {
|
||||
if self.has_ongoing().await {
|
||||
warn!(self, "There is already another ongoing process running.",);
|
||||
return;
|
||||
}
|
||||
@@ -57,7 +57,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
progress!(context, 0);
|
||||
return job::Status::Finished(Err(format_err!("Database not opened")));
|
||||
}
|
||||
if !context.alloc_ongoing() {
|
||||
if !context.alloc_ongoing().await {
|
||||
progress!(context, 0);
|
||||
return job::Status::Finished(Err(format_err!("Cannot allocated ongoing process")));
|
||||
}
|
||||
@@ -87,7 +87,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
const STEP_13_AFTER_AUTOCONFIG: u8 = 13;
|
||||
|
||||
let mut step_counter: u8 = 0;
|
||||
while !context.shall_stop_ongoing() {
|
||||
while !context.shall_stop_ongoing().await {
|
||||
step_counter += 1;
|
||||
|
||||
let success = match step_counter {
|
||||
@@ -108,6 +108,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
progress!(context, 10);
|
||||
if let Some(oauth2_addr) =
|
||||
dc_get_oauth2_addr(context, ¶m.addr, ¶m.mail_pw)
|
||||
.await
|
||||
.and_then(|e| e.parse().ok())
|
||||
{
|
||||
info!(context, "Authorized address is {}", oauth2_addr);
|
||||
@@ -444,7 +445,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
|
||||
}
|
||||
}
|
||||
|
||||
context.free_ongoing();
|
||||
context.free_ongoing().await;
|
||||
progress!(context, if success { 1000 } else { 0 });
|
||||
job::Status::Finished(Ok(()))
|
||||
}
|
||||
@@ -574,7 +575,7 @@ async fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<boo
|
||||
info!(context, "success: {}", inf);
|
||||
return Some(true);
|
||||
}
|
||||
if context.shall_stop_ongoing() {
|
||||
if context.shall_stop_ongoing().await {
|
||||
return Some(false);
|
||||
}
|
||||
info!(context, "Could not connect: {}", inf);
|
||||
@@ -623,7 +624,7 @@ async fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<boo
|
||||
Some(true)
|
||||
}
|
||||
Err(err) => {
|
||||
if context.shall_stop_ongoing() {
|
||||
if context.shall_stop_ongoing().await {
|
||||
Some(false)
|
||||
} else {
|
||||
warn!(context, "could not connect: {}", err);
|
||||
@@ -642,7 +643,7 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_no_panic_on_bad_credentials() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
t.ctx
|
||||
.set_config(Config::Addr, Some("probably@unexistant.addr"))
|
||||
.await
|
||||
@@ -654,9 +655,9 @@ mod tests {
|
||||
job_configure_imap(&t.ctx).await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_offline_autoconfig() {
|
||||
let context = dummy_context().ctx;
|
||||
#[async_std::test]
|
||||
async fn test_get_offline_autoconfig() {
|
||||
let context = dummy_context().await.ctx;
|
||||
|
||||
let mut params = LoginParam::new();
|
||||
params.addr = "someone123@example.org".to_string();
|
||||
|
||||
228
src/contact.rs
228
src/contact.rs
@@ -170,7 +170,7 @@ impl Contact {
|
||||
"SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
|
||||
FROM contacts c
|
||||
WHERE c.id=?;",
|
||||
params![contact_id as i32],
|
||||
paramsv![contact_id as i32],
|
||||
|row| {
|
||||
let contact = Self {
|
||||
id: contact_id,
|
||||
@@ -186,13 +186,16 @@ impl Contact {
|
||||
)
|
||||
.await?;
|
||||
if contact_id == DC_CONTACT_ID_SELF {
|
||||
res.name = context.stock_str(StockMessage::SelfMsg).to_string();
|
||||
res.name = context.stock_str(StockMessage::SelfMsg).await.to_string();
|
||||
res.addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
} else if contact_id == DC_CONTACT_ID_DEVICE {
|
||||
res.name = context.stock_str(StockMessage::DeviceMessages).to_string();
|
||||
res.name = context
|
||||
.stock_str(StockMessage::DeviceMessages)
|
||||
.await
|
||||
.to_string();
|
||||
res.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
|
||||
}
|
||||
Ok(res)
|
||||
@@ -251,7 +254,7 @@ impl Contact {
|
||||
},
|
||||
));
|
||||
if blocked {
|
||||
Contact::unblock(context, contact_id);
|
||||
Contact::unblock(context, contact_id).await;
|
||||
}
|
||||
|
||||
Ok(contact_id)
|
||||
@@ -266,7 +269,7 @@ impl Contact {
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;",
|
||||
params![MessageState::InNoticed, id as i32, MessageState::InFresh],
|
||||
paramsv![MessageState::InNoticed, id as i32, MessageState::InFresh],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -301,7 +304,7 @@ impl Contact {
|
||||
context.sql.query_get_value(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
|
||||
params![
|
||||
paramsv![
|
||||
addr_normalized,
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
DC_ORIGIN_MIN_CONTACT_LIST,
|
||||
@@ -348,13 +351,13 @@ impl Contact {
|
||||
);
|
||||
ensure!(origin != Origin::Unknown, "Missing valid origin");
|
||||
|
||||
let addr = addr_normalize(addr.as_ref());
|
||||
let addr = addr_normalize(addr.as_ref()).to_string();
|
||||
let addr_self = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if addr_cmp(addr, addr_self) {
|
||||
if addr_cmp(&addr, addr_self) {
|
||||
return Ok((DC_CONTACT_ID_SELF, sth_modified));
|
||||
}
|
||||
|
||||
@@ -379,7 +382,7 @@ impl Contact {
|
||||
|
||||
if let Ok((id, row_name, row_addr, row_origin, row_authname)) = context.sql.query_row(
|
||||
"SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;",
|
||||
params![addr],
|
||||
paramsv![addr.to_string()],
|
||||
|row| {
|
||||
let row_id = row.get(0)?;
|
||||
let row_name: String = row.get(1)?;
|
||||
@@ -416,30 +419,30 @@ impl Contact {
|
||||
if update_name || update_authname || update_addr || origin > row_origin {
|
||||
let new_name = if update_name {
|
||||
if !name.as_ref().is_empty() {
|
||||
name.as_ref()
|
||||
name.as_ref().to_string()
|
||||
} else {
|
||||
&row_authname
|
||||
row_authname.clone()
|
||||
}
|
||||
} else {
|
||||
&row_name
|
||||
row_name
|
||||
};
|
||||
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
|
||||
params![
|
||||
paramsv![
|
||||
new_name,
|
||||
if update_addr { addr } else { &row_addr },
|
||||
if update_addr { addr.to_string() } else { row_addr },
|
||||
if origin > row_origin {
|
||||
origin
|
||||
} else {
|
||||
row_origin
|
||||
},
|
||||
if update_authname {
|
||||
name.as_ref()
|
||||
name.as_ref().to_string()
|
||||
} else {
|
||||
&row_authname
|
||||
row_authname
|
||||
},
|
||||
row_id
|
||||
],
|
||||
@@ -452,7 +455,7 @@ impl Contact {
|
||||
// This is one of the few duplicated data, however, getting the chat list is easier this way.
|
||||
context.sql.execute(
|
||||
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
params![new_name, Chattype::Single, row_id]
|
||||
paramsv![new_name, Chattype::Single, row_id]
|
||||
).await.ok();
|
||||
}
|
||||
sth_modified = Modifier::Modified;
|
||||
@@ -466,11 +469,11 @@ impl Contact {
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
|
||||
params![
|
||||
name.as_ref(),
|
||||
paramsv![
|
||||
name.as_ref().to_string(),
|
||||
addr,
|
||||
origin,
|
||||
if update_authname { name.as_ref() } else { "" }
|
||||
if update_authname { name.as_ref().to_string() } else { "".to_string() }
|
||||
],
|
||||
)
|
||||
.await
|
||||
@@ -478,10 +481,10 @@ impl Contact {
|
||||
{
|
||||
row_id = context
|
||||
.sql
|
||||
.get_rowid(context, "contacts", "addr", addr)
|
||||
.get_rowid(context, "contacts", "addr", &addr)
|
||||
.await?;
|
||||
sth_modified = Modifier::Created;
|
||||
info!(context, "added contact id={} addr={}", row_id, addr);
|
||||
info!(context, "added contact id={} addr={}", row_id, &addr);
|
||||
} else {
|
||||
error!(context, "Cannot add contact.");
|
||||
}
|
||||
@@ -577,13 +580,13 @@ impl Contact {
|
||||
AND (c.name LIKE ?4 OR c.addr LIKE ?5) \
|
||||
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
|
||||
ORDER BY LOWER(c.name||c.addr),c.id;",
|
||||
params![
|
||||
paramsv![
|
||||
self_addr,
|
||||
DC_CONTACT_ID_LAST_SPECIAL as i32,
|
||||
Origin::IncomingReplyTo,
|
||||
&s3str_like_cmd,
|
||||
&s3str_like_cmd,
|
||||
if flag_verified_only { 0 } else { 1 },
|
||||
s3str_like_cmd,
|
||||
s3str_like_cmd,
|
||||
if flag_verified_only { 0i32 } else { 1i32 },
|
||||
],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| {
|
||||
@@ -604,7 +607,7 @@ impl Contact {
|
||||
if let Some(query) = query {
|
||||
if self_addr.contains(query.as_ref())
|
||||
|| self_name.contains(query.as_ref())
|
||||
|| self_name2.contains(query.as_ref())
|
||||
|| self_name2.await.contains(query.as_ref())
|
||||
{
|
||||
add_self = true;
|
||||
}
|
||||
@@ -616,7 +619,7 @@ impl Contact {
|
||||
|
||||
context.sql.query_map(
|
||||
"SELECT id FROM contacts WHERE addr!=?1 AND id>?2 AND origin>=?3 AND blocked=0 ORDER BY LOWER(name||addr),id;",
|
||||
params![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100],
|
||||
paramsv![self_addr, DC_CONTACT_ID_LAST_SPECIAL as i32, 0x100],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|ids| {
|
||||
for id in ids {
|
||||
@@ -637,10 +640,10 @@ impl Contact {
|
||||
pub async fn get_blocked_cnt(context: &Context) -> usize {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(
|
||||
.query_get_value::<isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default() as usize
|
||||
@@ -652,7 +655,7 @@ impl Contact {
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
|row| row.get::<_, u32>(0),
|
||||
|ids| {
|
||||
ids.collect::<std::result::Result<Vec<_>, _>>()
|
||||
@@ -672,7 +675,7 @@ impl Contact {
|
||||
let mut ret = String::new();
|
||||
|
||||
if let Ok(contact) = Contact::load_from_db(context, contact_id).await {
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr);
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr).await;
|
||||
let loginparam = LoginParam::from_database(context, "configured_").await;
|
||||
|
||||
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await;
|
||||
@@ -684,18 +687,19 @@ impl Contact {
|
||||
.is_some()
|
||||
{
|
||||
let peerstate = peerstate.as_ref().unwrap();
|
||||
let p =
|
||||
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
|
||||
let p = context
|
||||
.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
|
||||
StockMessage::E2ePreferred
|
||||
} else {
|
||||
StockMessage::E2eAvailable
|
||||
});
|
||||
})
|
||||
.await;
|
||||
ret += &p;
|
||||
if self_key.is_none() {
|
||||
e2ee::ensure_secret_key_exists(context).await?;
|
||||
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await;
|
||||
}
|
||||
let p = context.stock_str(StockMessage::FingerPrints);
|
||||
let p = context.stock_str(StockMessage::FingerPrints).await;
|
||||
ret += &format!(" {}:", p);
|
||||
|
||||
let fingerprint_self = self_key
|
||||
@@ -729,9 +733,9 @@ impl Contact {
|
||||
} else if 0 == loginparam.server_flags & DC_LP_IMAP_SOCKET_PLAIN as i32
|
||||
&& 0 == loginparam.server_flags & DC_LP_SMTP_SOCKET_PLAIN as i32
|
||||
{
|
||||
ret += &context.stock_str(StockMessage::EncrTransp);
|
||||
ret += &context.stock_str(StockMessage::EncrTransp).await;
|
||||
} else {
|
||||
ret += &context.stock_str(StockMessage::EncrNone);
|
||||
ret += &context.stock_str(StockMessage::EncrNone).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,7 +757,7 @@ impl Contact {
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
|
||||
params![contact_id as i32],
|
||||
paramsv![contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
@@ -764,7 +768,7 @@ impl Contact {
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;",
|
||||
params![contact_id as i32, contact_id as i32],
|
||||
paramsv![contact_id as i32, contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
@@ -777,7 +781,7 @@ impl Contact {
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM contacts WHERE id=?;",
|
||||
params![contact_id as i32],
|
||||
paramsv![contact_id as i32],
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -815,7 +819,7 @@ impl Contact {
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET param=? WHERE id=?",
|
||||
params![self.param.to_string(), self.id as i32],
|
||||
paramsv![self.param.to_string(), self.id as i32],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -913,17 +917,17 @@ impl Contact {
|
||||
///
|
||||
/// The UI may draw a checkbox or something like that beside verified contacts.
|
||||
///
|
||||
pub fn is_verified(&self, context: &Context) -> VerifiedStatus {
|
||||
self.is_verified_ex(context, None)
|
||||
pub async fn is_verified(&self, context: &Context) -> VerifiedStatus {
|
||||
self.is_verified_ex(context, None).await
|
||||
}
|
||||
|
||||
/// Same as `Contact::is_verified` but allows speeding up things
|
||||
/// by adding the peerstate belonging to the contact.
|
||||
/// If you do not have the peerstate available, it is loaded automatically.
|
||||
pub fn is_verified_ex(
|
||||
pub async fn is_verified_ex(
|
||||
&self,
|
||||
context: &Context,
|
||||
peerstate: Option<&Peerstate>,
|
||||
peerstate: Option<&Peerstate<'_>>,
|
||||
) -> VerifiedStatus {
|
||||
// We're always sort of secured-verified as we could verify the key on this device any time with the key
|
||||
// on this device
|
||||
@@ -937,7 +941,7 @@ impl Contact {
|
||||
}
|
||||
}
|
||||
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr);
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &self.addr).await;
|
||||
if let Some(ps) = peerstate {
|
||||
if ps.verified_key.is_some() {
|
||||
return VerifiedStatus::BidirectVerified;
|
||||
@@ -975,10 +979,10 @@ impl Contact {
|
||||
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(
|
||||
.query_get_value::<isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM contacts WHERE id>?;",
|
||||
params![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default() as usize
|
||||
@@ -993,7 +997,7 @@ impl Contact {
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM contacts WHERE id=?;",
|
||||
params![contact_id as i32],
|
||||
paramsv![contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
@@ -1004,7 +1008,7 @@ impl Contact {
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
|
||||
params![origin, contact_id as i32, origin],
|
||||
paramsv![origin, contact_id as i32, origin],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -1044,7 +1048,7 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE contacts SET blocked=? WHERE id=?;",
|
||||
params![new_blocking as i32, contact_id as i32],
|
||||
paramsv![new_blocking as i32, contact_id as i32],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -1056,9 +1060,9 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
|
||||
// this would result in recreating the same group...)
|
||||
if context.sql.execute(
|
||||
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);",
|
||||
params![new_blocking, 100, contact_id as i32],
|
||||
paramsv![new_blocking, 100, contact_id as i32],
|
||||
).await.is_ok() {
|
||||
Contact::mark_noticed(context, contact_id);
|
||||
Contact::mark_noticed(context, contact_id).await;
|
||||
context.call_cb(Event::ContactsChanged(None));
|
||||
}
|
||||
}
|
||||
@@ -1240,25 +1244,33 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_contacts() {
|
||||
let context = dummy_context();
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap();
|
||||
#[async_std::test]
|
||||
async fn test_get_contacts() {
|
||||
let context = dummy_context().await;
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("some2"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
|
||||
let id = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap();
|
||||
let id = Contact::create(&context.ctx, "bob", "bob@mail.de")
|
||||
.await
|
||||
.unwrap();
|
||||
assert_ne!(id, 0);
|
||||
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("bob")).unwrap();
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("bob"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contacts.len(), 1);
|
||||
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("alice")).unwrap();
|
||||
let contacts = Contact::get_all(&context.ctx, 0, Some("alice"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contacts.len(), 0);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_is_self_addr() -> Result<()> {
|
||||
let t = test_context(None);
|
||||
let t = test_context(None).await;
|
||||
assert!(t.ctx.is_self_addr("me@me.org").await.is_err());
|
||||
|
||||
let addr = configure_alice_keypair(&t.ctx).await;
|
||||
@@ -1268,10 +1280,10 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_or_lookup() {
|
||||
#[async_std::test]
|
||||
async fn test_add_or_lookup() {
|
||||
// add some contacts, this also tests add_address_book()
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let book = concat!(
|
||||
" Name one \n one@eins.org \n",
|
||||
"Name two\ntwo@deux.net\n",
|
||||
@@ -1279,15 +1291,16 @@ mod tests {
|
||||
"\nthree@drei.sam\n",
|
||||
"Name two\ntwo@deux.net\n" // should not be added again
|
||||
);
|
||||
assert_eq!(Contact::add_address_book(&t.ctx, book).unwrap(), 3);
|
||||
assert_eq!(Contact::add_address_book(&t.ctx, book).await.unwrap(), 3);
|
||||
|
||||
// check first added contact, this does not modify because of lower origin
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(&t.ctx, "bla foo", "one@eins.org", Origin::IncomingUnknownTo)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::None);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_id(), contact_id);
|
||||
assert_eq!(contact.get_name(), "Name one");
|
||||
assert_eq!(contact.get_display_name(), "Name one");
|
||||
@@ -1301,10 +1314,11 @@ mod tests {
|
||||
" one@eins.org ",
|
||||
Origin::ManuallyCreated,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contact_id, contact_id_test);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_name(), "Real one");
|
||||
assert_eq!(contact.get_addr(), "one@eins.org");
|
||||
assert!(!contact.is_blocked());
|
||||
@@ -1312,10 +1326,11 @@ mod tests {
|
||||
// check third added contact (contact without name)
|
||||
let (contact_id, sth_modified) =
|
||||
Contact::add_or_lookup(&t.ctx, "", "three@drei.sam", Origin::IncomingUnknownTo)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::None);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_name(), "");
|
||||
assert_eq!(contact.get_display_name(), "three@drei.sam");
|
||||
assert_eq!(contact.get_addr(), "three@drei.sam");
|
||||
@@ -1328,10 +1343,11 @@ mod tests {
|
||||
"three@drei.sam",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contact_id, contact_id_test);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_name_n_addr(), "m. serious (three@drei.sam)");
|
||||
assert!(!contact.is_blocked());
|
||||
|
||||
@@ -1342,25 +1358,31 @@ mod tests {
|
||||
"three@drei.sam",
|
||||
Origin::ManuallyCreated,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contact_id, contact_id_test);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "m. serious");
|
||||
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
|
||||
assert!(!contact.is_blocked());
|
||||
|
||||
// check SELF
|
||||
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, DC_CONTACT_ID_SELF)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(DC_CONTACT_ID_SELF, 1);
|
||||
assert_eq!(contact.get_name(), t.ctx.stock_str(StockMessage::SelfMsg));
|
||||
assert_eq!(
|
||||
contact.get_name(),
|
||||
t.ctx.stock_str(StockMessage::SelfMsg).await
|
||||
);
|
||||
assert_eq!(contact.get_addr(), ""); // we're not configured
|
||||
assert!(!contact.is_blocked());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_authnames() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_remote_authnames() {
|
||||
let t = dummy_context().await;
|
||||
|
||||
// incoming mail `From: bob1 <bob@example.org>` - this should init authname and name
|
||||
let (contact_id, sth_modified) = Contact::add_or_lookup(
|
||||
@@ -1369,10 +1391,11 @@ mod tests {
|
||||
"bob@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::Created);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob1");
|
||||
assert_eq!(contact.get_name(), "bob1");
|
||||
assert_eq!(contact.get_display_name(), "bob1");
|
||||
@@ -1384,18 +1407,21 @@ mod tests {
|
||||
"bob@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob2");
|
||||
assert_eq!(contact.get_name(), "bob2");
|
||||
assert_eq!(contact.get_display_name(), "bob2");
|
||||
|
||||
// manually edit name to "bob3" - authname should be still be "bob2" a given in `From:` above
|
||||
let contact_id = Contact::create(&t.ctx, "bob3", "bob@example.org").unwrap();
|
||||
let contact_id = Contact::create(&t.ctx, "bob3", "bob@example.org")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob2");
|
||||
assert_eq!(contact.get_name(), "bob3");
|
||||
assert_eq!(contact.get_display_name(), "bob3");
|
||||
@@ -1407,23 +1433,26 @@ mod tests {
|
||||
"bob@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "bob4");
|
||||
assert_eq!(contact.get_name(), "bob3");
|
||||
assert_eq!(contact.get_display_name(), "bob3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_authnames_create_empty() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_remote_authnames_create_empty() {
|
||||
let t = dummy_context().await;
|
||||
|
||||
// manually create "claire@example.org" without a given name
|
||||
let contact_id = Contact::create(&t.ctx, "", "claire@example.org").unwrap();
|
||||
let contact_id = Contact::create(&t.ctx, "", "claire@example.org")
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "");
|
||||
assert_eq!(contact.get_name(), "");
|
||||
assert_eq!(contact.get_display_name(), "claire@example.org");
|
||||
@@ -1435,10 +1464,11 @@ mod tests {
|
||||
"claire@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contact_id, contact_id_same);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "claire1");
|
||||
assert_eq!(contact.get_name(), "claire1");
|
||||
assert_eq!(contact.get_display_name(), "claire1");
|
||||
@@ -1450,22 +1480,25 @@ mod tests {
|
||||
"claire@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(contact_id, contact_id_same);
|
||||
assert_eq!(sth_modified, Modifier::Modified);
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "claire2");
|
||||
assert_eq!(contact.get_name(), "claire2");
|
||||
assert_eq!(contact.get_display_name(), "claire2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remote_authnames_edit_empty() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_remote_authnames_edit_empty() {
|
||||
let t = dummy_context().await;
|
||||
|
||||
// manually create "dave@example.org"
|
||||
let contact_id = Contact::create(&t.ctx, "dave1", "dave@example.org").unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact_id = Contact::create(&t.ctx, "dave1", "dave@example.org")
|
||||
.await
|
||||
.unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "");
|
||||
assert_eq!(contact.get_name(), "dave1");
|
||||
assert_eq!(contact.get_display_name(), "dave1");
|
||||
@@ -1477,15 +1510,18 @@ mod tests {
|
||||
"dave@example.org",
|
||||
Origin::IncomingUnknownFrom,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "dave2");
|
||||
assert_eq!(contact.get_name(), "dave1");
|
||||
assert_eq!(contact.get_display_name(), "dave1");
|
||||
|
||||
// manually clear the name
|
||||
Contact::create(&t.ctx, "", "dave@example.org").unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap();
|
||||
Contact::create(&t.ctx, "", "dave@example.org")
|
||||
.await
|
||||
.unwrap();
|
||||
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
|
||||
assert_eq!(contact.get_authname(), "dave2");
|
||||
assert_eq!(contact.get_name(), "dave2");
|
||||
assert_eq!(contact.get_display_name(), "dave2");
|
||||
|
||||
108
src/context.rs
108
src/context.rs
@@ -3,7 +3,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::OsString;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use async_std::sync::{Arc, Mutex, RwLock};
|
||||
|
||||
use crate::chat::*;
|
||||
use crate::config::Config;
|
||||
@@ -156,14 +158,14 @@ impl Context {
|
||||
* Ongoing process allocation/free/check
|
||||
******************************************************************************/
|
||||
|
||||
pub fn alloc_ongoing(&self) -> bool {
|
||||
if self.has_ongoing() {
|
||||
pub async fn alloc_ongoing(&self) -> bool {
|
||||
if self.has_ongoing().await {
|
||||
warn!(self, "There is already another ongoing process running.",);
|
||||
|
||||
false
|
||||
} else {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
let mut s = s_a.write().await;
|
||||
|
||||
s.ongoing_running = true;
|
||||
s.shall_stop_ongoing = false;
|
||||
@@ -172,25 +174,25 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_ongoing(&self) {
|
||||
pub async fn free_ongoing(&self) {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
let mut s = s_a.write().await;
|
||||
|
||||
s.ongoing_running = false;
|
||||
s.shall_stop_ongoing = true;
|
||||
}
|
||||
|
||||
pub fn has_ongoing(&self) -> bool {
|
||||
pub async fn has_ongoing(&self) -> bool {
|
||||
let s_a = self.running_state.clone();
|
||||
let s = s_a.read().unwrap();
|
||||
let s = s_a.read().await;
|
||||
|
||||
s.ongoing_running || !s.shall_stop_ongoing
|
||||
}
|
||||
|
||||
/// Signal an ongoing process to stop.
|
||||
pub fn stop_ongoing(&self) {
|
||||
pub async fn stop_ongoing(&self) {
|
||||
let s_a = self.running_state.clone();
|
||||
let mut s = s_a.write().unwrap();
|
||||
let mut s = s_a.write().await;
|
||||
|
||||
if s.ongoing_running && !s.shall_stop_ongoing {
|
||||
info!(self, "Signaling the ongoing process to stop ASAP.",);
|
||||
@@ -200,12 +202,8 @@ impl Context {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn shall_stop_ongoing(&self) -> bool {
|
||||
self.running_state
|
||||
.clone()
|
||||
.read()
|
||||
.unwrap()
|
||||
.shall_stop_ongoing
|
||||
pub async fn shall_stop_ongoing(&self) -> bool {
|
||||
self.running_state.clone().read().await.shall_stop_ongoing
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
@@ -218,8 +216,8 @@ impl Context {
|
||||
let l2 = LoginParam::from_database(self, "configured_").await;
|
||||
let displayname = self.get_config(Config::Displayname).await;
|
||||
let chats = get_chat_cnt(self).await as usize;
|
||||
let real_msgs = message::get_real_msg_cnt(self) as usize;
|
||||
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize;
|
||||
let real_msgs = message::get_real_msg_cnt(self).await as usize;
|
||||
let deaddrop_msgs = message::get_deaddrop_msg_cnt(self).await as usize;
|
||||
let contacts = Contact::get_real_cnt(self).await as usize;
|
||||
let is_configured = self.get_config_int(Config::Configured).await;
|
||||
let dbversion = self
|
||||
@@ -234,16 +232,12 @@ impl Context {
|
||||
|
||||
let prv_key_cnt: Option<isize> = self
|
||||
.sql
|
||||
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS)
|
||||
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", paramsv![])
|
||||
.await;
|
||||
|
||||
let pub_key_cnt: Option<isize> = self
|
||||
.sql
|
||||
.query_get_value(
|
||||
self,
|
||||
"SELECT COUNT(*) FROM acpeerstates;",
|
||||
rusqlite::NO_PARAMS,
|
||||
)
|
||||
.query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![])
|
||||
.await;
|
||||
|
||||
let fingerprint_str =
|
||||
@@ -316,7 +310,7 @@ impl Context {
|
||||
}
|
||||
|
||||
pub async fn get_fresh_msgs(&self) -> Vec<MsgId> {
|
||||
let show_deaddrop = 0;
|
||||
let show_deaddrop: i32 = 0;
|
||||
self.sql
|
||||
.query_map(
|
||||
concat!(
|
||||
@@ -333,7 +327,7 @@ impl Context {
|
||||
" AND (c.blocked=0 OR c.blocked=?)",
|
||||
" ORDER BY m.timestamp DESC,m.id DESC;"
|
||||
),
|
||||
&[10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|
||||
paramsv![10, 9, if 0 != show_deaddrop { 2 } else { 0 }],
|
||||
|row| row.get::<_, MsgId>(0),
|
||||
|rows| {
|
||||
let mut ret = Vec::new();
|
||||
@@ -388,7 +382,7 @@ impl Context {
|
||||
self.sql
|
||||
.query_map(
|
||||
query,
|
||||
params![chat_id, &strLikeInText, &strLikeBeg],
|
||||
paramsv![chat_id, strLikeInText, strLikeBeg],
|
||||
|row| row.get::<_, MsgId>("id"),
|
||||
|rows| {
|
||||
let mut ret = Vec::new();
|
||||
@@ -475,7 +469,7 @@ impl Drop for Context {
|
||||
info!(self, "disconnecting SMTP");
|
||||
self.smtp.disconnect().await;
|
||||
|
||||
self.sql.close(self);
|
||||
self.sql.close(self).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -519,79 +513,85 @@ mod tests {
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_wrong_db() {
|
||||
#[async_std::test]
|
||||
async fn test_wrong_db() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
std::fs::write(&dbfile, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile);
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOs".into(), dbfile).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_fresh_msgs() {
|
||||
let t = dummy_context();
|
||||
let fresh = t.ctx.get_fresh_msgs();
|
||||
#[async_std::test]
|
||||
async fn test_get_fresh_msgs() {
|
||||
let t = dummy_context().await;
|
||||
let fresh = t.ctx.get_fresh_msgs().await;
|
||||
assert!(fresh.is_empty())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_blobdir_exists() {
|
||||
#[async_std::test]
|
||||
async fn test_blobdir_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap();
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile)
|
||||
.await
|
||||
.unwrap();
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
assert!(blobdir.is_dir());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_wrong_blogdir() {
|
||||
#[async_std::test]
|
||||
async fn test_wrong_blogdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("db.sqlite-blobs");
|
||||
std::fs::write(&blobdir, b"123").unwrap();
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile);
|
||||
let res = Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sqlite_parent_not_exists() {
|
||||
#[async_std::test]
|
||||
async fn test_sqlite_parent_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let subdir = tmp.path().join("subdir");
|
||||
let dbfile = subdir.join("db.sqlite");
|
||||
let dbfile2 = dbfile.clone();
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile).unwrap();
|
||||
Context::new(Box::new(|_, _| ()), "FakeOS".into(), dbfile)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(subdir.is_dir());
|
||||
assert!(dbfile2.is_file());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_empty_blobdir() {
|
||||
#[async_std::test]
|
||||
async fn test_with_empty_blobdir() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = PathBuf::new();
|
||||
let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir);
|
||||
let res =
|
||||
Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_blobdir_not_exists() {
|
||||
#[async_std::test]
|
||||
async fn test_with_blobdir_not_exists() {
|
||||
let tmp = tempfile::tempdir().unwrap();
|
||||
let dbfile = tmp.path().join("db.sqlite");
|
||||
let blobdir = tmp.path().join("blobs");
|
||||
let res = Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir);
|
||||
let res =
|
||||
Context::with_blobdir(Box::new(|_, _| ()), "FakeOS".into(), dbfile, blobdir).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_crashes_on_context_deref() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn no_crashes_on_context_deref() {
|
||||
let t = dummy_context().await;
|
||||
std::mem::drop(t.ctx);
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_get_info() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
|
||||
let info = t.ctx.get_info().await;
|
||||
assert!(info.get("database_dir").is_some());
|
||||
|
||||
@@ -96,7 +96,7 @@ pub async fn dc_receive_imf(
|
||||
// https://github.com/deltachat/deltachat-core/issues/150)
|
||||
let (from_id, from_id_blocked, incoming_origin) =
|
||||
if let Some(field_from) = mime_parser.get(HeaderDef::From_) {
|
||||
from_field_to_contact_id(context, field_from)?
|
||||
from_field_to_contact_id(context, field_from).await?
|
||||
} else {
|
||||
(0, false, Origin::Unknown)
|
||||
};
|
||||
@@ -105,17 +105,20 @@ pub async fn dc_receive_imf(
|
||||
let mut to_ids = ContactIds::new();
|
||||
for header_def in &[HeaderDef::To, HeaderDef::Cc] {
|
||||
if let Some(field) = mime_parser.get(header_def.clone()) {
|
||||
to_ids.extend(&dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
&field,
|
||||
if !incoming {
|
||||
Origin::OutgoingTo
|
||||
} else if incoming_origin.is_known() {
|
||||
Origin::IncomingTo
|
||||
} else {
|
||||
Origin::IncomingUnknownTo
|
||||
},
|
||||
)?);
|
||||
to_ids.extend(
|
||||
&dc_add_or_lookup_contacts_by_address_list(
|
||||
context,
|
||||
&field,
|
||||
if !incoming {
|
||||
Origin::OutgoingTo
|
||||
} else if incoming_origin.is_known() {
|
||||
Origin::IncomingTo
|
||||
} else {
|
||||
Origin::IncomingUnknownTo
|
||||
},
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +181,8 @@ pub async fn dc_receive_imf(
|
||||
from_id,
|
||||
insert_msg_id,
|
||||
hidden,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(avatar_action) = &mime_parser.user_avatar {
|
||||
@@ -225,7 +229,7 @@ pub async fn dc_receive_imf(
|
||||
/// Converts "From" field to contact id.
|
||||
///
|
||||
/// Also returns whether it is blocked or not and its origin.
|
||||
pub fn from_field_to_contact_id(
|
||||
pub async fn from_field_to_contact_id(
|
||||
context: &Context,
|
||||
field_from: &str,
|
||||
) -> Result<(u32, bool, Origin)> {
|
||||
@@ -233,7 +237,8 @@ pub fn from_field_to_contact_id(
|
||||
context,
|
||||
&field_from,
|
||||
Origin::IncomingUnknownFrom,
|
||||
)?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
if from_ids.contains(&DC_CONTACT_ID_SELF) {
|
||||
Ok((DC_CONTACT_ID_SELF, false, Origin::OutgoingBcc))
|
||||
@@ -248,7 +253,7 @@ pub fn from_field_to_contact_id(
|
||||
|
||||
let mut from_id_blocked = false;
|
||||
let mut incoming_origin = Origin::Unknown;
|
||||
if let Ok(contact) = Contact::load_from_db(context, from_id) {
|
||||
if let Ok(contact) = Contact::load_from_db(context, from_id).await {
|
||||
from_id_blocked = contact.blocked;
|
||||
incoming_origin = contact.origin;
|
||||
}
|
||||
@@ -297,10 +302,11 @@ async fn add_parts(
|
||||
// (if the mail was moved around) and finish. (we may get a mail twice eg. if it is
|
||||
// moved between folders. make sure, this check is done eg. before securejoin-processing) */
|
||||
if let Ok((old_server_folder, old_server_uid, _)) =
|
||||
message::rfc724_mid_exists(context, &rfc724_mid)
|
||||
message::rfc724_mid_exists(context, &rfc724_mid).await
|
||||
{
|
||||
if old_server_folder != server_folder.as_ref() || old_server_uid != server_uid {
|
||||
message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid);
|
||||
message::update_server_uid(context, &rfc724_mid, server_folder.as_ref(), server_uid)
|
||||
.await;
|
||||
}
|
||||
|
||||
bail!("Message already in DB");
|
||||
@@ -308,7 +314,7 @@ async fn add_parts(
|
||||
|
||||
let mut msgrmsg = if mime_parser.has_chat_version() {
|
||||
MessengerMessage::Yes
|
||||
} else if is_reply_to_messenger_message(context, mime_parser) {
|
||||
} else if is_reply_to_messenger_message(context, mime_parser).await {
|
||||
MessengerMessage::Reply
|
||||
} else {
|
||||
MessengerMessage::No
|
||||
@@ -368,8 +374,8 @@ async fn add_parts(
|
||||
}
|
||||
Err(err) => {
|
||||
*hidden = true;
|
||||
context.bob.write().unwrap().status = 0; // secure-join failed
|
||||
context.stop_ongoing();
|
||||
context.bob.write().await.status = 0; // secure-join failed
|
||||
context.stop_ongoing().await;
|
||||
error!(context, "Error in Secure-Join message handling: {}", err);
|
||||
}
|
||||
}
|
||||
@@ -408,7 +414,7 @@ async fn add_parts(
|
||||
&& chat_id_blocked != Blocked::Not
|
||||
&& create_blocked == Blocked::Not
|
||||
{
|
||||
new_chat_id.unblock(context);
|
||||
new_chat_id.unblock(context).await;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
@@ -442,12 +448,12 @@ async fn add_parts(
|
||||
}
|
||||
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
|
||||
if Blocked::Not == create_blocked {
|
||||
chat_id.unblock(context);
|
||||
chat_id.unblock(context).await;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
} else if is_reply_to_known_message(context, mime_parser) {
|
||||
} else if is_reply_to_known_message(context, mime_parser).await {
|
||||
// we do not want any chat to be created implicitly. Because of the origin-scale-up,
|
||||
// the contact requests will pop up and this should be just fine.
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo);
|
||||
Contact::scaleup_origin_by_id(context, from_id, Origin::IncomingReplyTo).await;
|
||||
info!(
|
||||
context,
|
||||
"Message is a reply to a known message, mark sender as known.",
|
||||
@@ -496,7 +502,7 @@ async fn add_parts(
|
||||
chat_id_blocked = new_chat_id_blocked;
|
||||
// automatically unblock chat when the user sends a message
|
||||
if !chat_id.is_unset() && chat_id_blocked != Blocked::Not {
|
||||
new_chat_id.unblock(context);
|
||||
new_chat_id.unblock(context).await;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
@@ -518,7 +524,7 @@ async fn add_parts(
|
||||
&& Blocked::Not != chat_id_blocked
|
||||
&& Blocked::Not == create_blocked
|
||||
{
|
||||
chat_id.unblock(context);
|
||||
chat_id.unblock(context).await;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
@@ -538,7 +544,7 @@ async fn add_parts(
|
||||
chat_id_blocked = bl;
|
||||
|
||||
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
|
||||
chat_id.unblock(context);
|
||||
chat_id.unblock(context).await;
|
||||
chat_id_blocked = Blocked::Not;
|
||||
}
|
||||
}
|
||||
@@ -557,7 +563,8 @@ async fn add_parts(
|
||||
&mut sort_timestamp,
|
||||
sent_timestamp,
|
||||
&mut rcvd_timestamp,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
// unarchive chat
|
||||
chat_id.unarchive(context).await?;
|
||||
@@ -613,9 +620,9 @@ async fn add_parts(
|
||||
.set_int(Param::Cmd, mime_parser.is_system_message as i32);
|
||||
}
|
||||
|
||||
stmt.execute(params![
|
||||
stmt.execute(paramsv![
|
||||
rfc724_mid,
|
||||
server_folder.as_ref(),
|
||||
server_folder.as_ref().to_string(),
|
||||
server_uid as i32,
|
||||
*chat_id,
|
||||
from_id as i32,
|
||||
@@ -626,7 +633,7 @@ async fn add_parts(
|
||||
part.typ,
|
||||
state,
|
||||
msgrmsg,
|
||||
&part.msg,
|
||||
part.msg,
|
||||
// txt_raw might contain invalid utf8
|
||||
txt_raw.unwrap_or_default(),
|
||||
part.param.to_string(),
|
||||
@@ -675,7 +682,7 @@ async fn add_parts(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn save_locations(
|
||||
async fn save_locations(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
chat_id: ChatId,
|
||||
@@ -691,11 +698,14 @@ 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();
|
||||
let newest_location_id = location::save(context, chat_id, from_id, locations, true)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if 0 != newest_location_id
|
||||
&& !hidden
|
||||
&& location::set_msg_location_id(context, insert_msg_id, newest_location_id).is_ok()
|
||||
&& location::set_msg_location_id(context, insert_msg_id, newest_location_id)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
location_id_written = true;
|
||||
send_event = true;
|
||||
@@ -704,18 +714,21 @@ fn save_locations(
|
||||
|
||||
if mime_parser.location_kml.is_some() {
|
||||
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr {
|
||||
if let Ok(contact) = Contact::get_by_id(context, from_id) {
|
||||
if let Ok(contact) = Contact::get_by_id(context, from_id).await {
|
||||
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)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if newest_location_id != 0 && !hidden && !location_id_written {
|
||||
if let Err(err) = location::set_msg_location_id(
|
||||
context,
|
||||
insert_msg_id,
|
||||
newest_location_id,
|
||||
) {
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!(context, "Failed to set msg_location_id: {:?}", err);
|
||||
}
|
||||
}
|
||||
@@ -730,7 +743,7 @@ fn save_locations(
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn calc_timestamps(
|
||||
async fn calc_timestamps(
|
||||
context: &Context,
|
||||
chat_id: ChatId,
|
||||
from_id: u32,
|
||||
@@ -747,19 +760,22 @@ fn calc_timestamps(
|
||||
}
|
||||
*sort_timestamp = message_timestamp;
|
||||
if is_fresh_msg {
|
||||
let last_msg_time: Option<i64> = context.sql.query_get_value(
|
||||
context,
|
||||
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?",
|
||||
params![chat_id, from_id as i32, *sort_timestamp],
|
||||
);
|
||||
let last_msg_time: Option<i64> = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?",
|
||||
paramsv![chat_id, from_id as i32, *sort_timestamp],
|
||||
)
|
||||
.await;
|
||||
if let Some(last_msg_time) = last_msg_time {
|
||||
if last_msg_time > 0 && *sort_timestamp <= last_msg_time {
|
||||
*sort_timestamp = last_msg_time + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if *sort_timestamp >= dc_smeared_time(context) {
|
||||
*sort_timestamp = dc_create_smeared_timestamp(context);
|
||||
if *sort_timestamp >= dc_smeared_time(context).await {
|
||||
*sort_timestamp = dc_create_smeared_timestamp(context).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -792,8 +808,9 @@ async fn create_or_lookup_group(
|
||||
let mut better_msg: String = From::from("");
|
||||
|
||||
if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled {
|
||||
better_msg =
|
||||
context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32);
|
||||
better_msg = context
|
||||
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32)
|
||||
.await;
|
||||
set_better_msg(mime_parser, &better_msg);
|
||||
}
|
||||
|
||||
@@ -823,6 +840,7 @@ async fn create_or_lookup_group(
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
info!(context, "could not create adhoc-group: {:?}", err);
|
||||
err
|
||||
@@ -841,41 +859,47 @@ async fn create_or_lookup_group(
|
||||
let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap())
|
||||
.await
|
||||
== from_id as u32;
|
||||
better_msg = context.stock_system_msg(
|
||||
if left_group {
|
||||
StockMessage::MsgGroupLeft
|
||||
} else {
|
||||
StockMessage::MsgDelMember
|
||||
},
|
||||
X_MrRemoveFromGrp.as_ref().unwrap(),
|
||||
"",
|
||||
from_id as u32,
|
||||
)
|
||||
better_msg = context
|
||||
.stock_system_msg(
|
||||
if left_group {
|
||||
StockMessage::MsgGroupLeft
|
||||
} else {
|
||||
StockMessage::MsgDelMember
|
||||
},
|
||||
X_MrRemoveFromGrp.as_ref().unwrap(),
|
||||
"",
|
||||
from_id as u32,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned();
|
||||
if let Some(optional_field) = field {
|
||||
mime_parser.is_system_message = SystemMessage::MemberAddedToGroup;
|
||||
better_msg = context.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
&optional_field,
|
||||
"",
|
||||
from_id as u32,
|
||||
);
|
||||
better_msg = context
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
&optional_field,
|
||||
"",
|
||||
from_id as u32,
|
||||
)
|
||||
.await;
|
||||
X_MrAddToGrp = Some(optional_field);
|
||||
} else {
|
||||
let field = mime_parser.get(HeaderDef::ChatGroupNameChanged);
|
||||
if let Some(field) = field {
|
||||
X_MrGrpNameChanged = true;
|
||||
better_msg = context.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
field,
|
||||
if let Some(ref name) = grpname {
|
||||
name
|
||||
} else {
|
||||
""
|
||||
},
|
||||
from_id as u32,
|
||||
);
|
||||
better_msg = context
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
field,
|
||||
if let Some(ref name) = grpname {
|
||||
name
|
||||
} else {
|
||||
""
|
||||
},
|
||||
from_id as u32,
|
||||
)
|
||||
.await;
|
||||
|
||||
mime_parser.is_system_message = SystemMessage::GroupNameChanged;
|
||||
} else if let Some(value) = mime_parser.get(HeaderDef::ChatContent) {
|
||||
@@ -884,15 +908,17 @@ async fn create_or_lookup_group(
|
||||
// this is just an explicit message containing the group-avatar,
|
||||
// apart from that, the group-avatar is send along with various other messages
|
||||
mime_parser.is_system_message = SystemMessage::GroupImageChanged;
|
||||
better_msg = context.stock_system_msg(
|
||||
match avatar_action {
|
||||
AvatarAction::Delete => StockMessage::MsgGrpImgDeleted,
|
||||
AvatarAction::Change(_) => StockMessage::MsgGrpImgChanged,
|
||||
},
|
||||
"",
|
||||
"",
|
||||
from_id as u32,
|
||||
)
|
||||
better_msg = context
|
||||
.stock_system_msg(
|
||||
match avatar_action {
|
||||
AvatarAction::Delete => StockMessage::MsgGrpImgDeleted,
|
||||
AvatarAction::Change(_) => StockMessage::MsgGrpImgChanged,
|
||||
},
|
||||
"",
|
||||
"",
|
||||
from_id as u32,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -907,7 +933,7 @@ async fn create_or_lookup_group(
|
||||
if !chat_id.is_error() {
|
||||
if chat_id_verified {
|
||||
if let Err(err) =
|
||||
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
|
||||
check_verified_properties(context, mime_parser, from_id as u32, to_ids).await
|
||||
{
|
||||
warn!(context, "verification problem: {}", err);
|
||||
let s = format!("{}. See 'Info' for more details", err);
|
||||
@@ -921,7 +947,7 @@ async fn create_or_lookup_group(
|
||||
// but still show the message as part of the chat.
|
||||
// After all, the sender has a reference/in-reply-to that
|
||||
// points to this chat.
|
||||
let s = context.stock_str(StockMessage::UnknownSenderForChat);
|
||||
let s = context.stock_str(StockMessage::UnknownSenderForChat).await;
|
||||
mime_parser.repl_msg_by_error(s.to_string());
|
||||
}
|
||||
}
|
||||
@@ -948,7 +974,7 @@ async fn create_or_lookup_group(
|
||||
// group does not exist but should be created
|
||||
let create_verified = if mime_parser.get(HeaderDef::ChatVerified).is_some() {
|
||||
if let Err(err) =
|
||||
check_verified_properties(context, mime_parser, from_id as u32, to_ids)
|
||||
check_verified_properties(context, mime_parser, from_id as u32, to_ids).await
|
||||
{
|
||||
warn!(context, "verification problem: {}", err);
|
||||
let s = format!("{}. See 'Info' for more details", err);
|
||||
@@ -989,6 +1015,7 @@ async fn create_or_lookup_group(
|
||||
from_id,
|
||||
to_ids,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!(context, "failed to create ad-hoc group: {:?}", err);
|
||||
err
|
||||
@@ -1019,7 +1046,7 @@ async fn create_or_lookup_group(
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats SET name=? WHERE id=?;",
|
||||
params![grpname, chat_id],
|
||||
paramsv![grpname.to_string(), chat_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -1061,7 +1088,7 @@ async fn create_or_lookup_group(
|
||||
if !Contact::addr_equals_contact(context, &self_addr, to_id).await
|
||||
&& !chat::is_contact_in_chat(context, chat_id, to_id).await
|
||||
{
|
||||
chat::add_to_chat_contacts_table(context, chat_id, to_id);
|
||||
chat::add_to_chat_contacts_table(context, chat_id, to_id).await;
|
||||
}
|
||||
}
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
@@ -1069,7 +1096,7 @@ async fn create_or_lookup_group(
|
||||
let contact_id = Contact::lookup_id_by_addr(context, removed_addr).await;
|
||||
if contact_id != 0 {
|
||||
info!(context, "remove {:?} from chat id={}", contact_id, chat_id);
|
||||
chat::remove_from_chat_contacts_table(context, chat_id, contact_id);
|
||||
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await;
|
||||
}
|
||||
send_EVENT_CHAT_MODIFIED = true;
|
||||
}
|
||||
@@ -1091,7 +1118,7 @@ fn extract_grpid(mime_parser: &MimeMessage, headerdef: HeaderDef) -> Option<&str
|
||||
}
|
||||
|
||||
/// Handle groups for received messages, return chat_id/Blocked status on success
|
||||
fn create_or_lookup_adhoc_group(
|
||||
async fn create_or_lookup_adhoc_group(
|
||||
context: &Context,
|
||||
mime_parser: &MimeMessage,
|
||||
allow_creation: bool,
|
||||
@@ -1126,12 +1153,14 @@ fn create_or_lookup_adhoc_group(
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
}
|
||||
|
||||
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids)?;
|
||||
let chat_ids = search_chat_ids_by_contact_ids(context, &member_ids).await?;
|
||||
if !chat_ids.is_empty() {
|
||||
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
|
||||
let res = context.sql.query_row(
|
||||
format!(
|
||||
"SELECT c.id,
|
||||
let res = context
|
||||
.sql
|
||||
.query_row(
|
||||
format!(
|
||||
"SELECT c.id,
|
||||
c.blocked
|
||||
FROM chats c
|
||||
LEFT JOIN msgs m
|
||||
@@ -1140,16 +1169,17 @@ fn create_or_lookup_adhoc_group(
|
||||
ORDER BY m.timestamp DESC,
|
||||
m.id DESC
|
||||
LIMIT 1;",
|
||||
chat_ids_str
|
||||
),
|
||||
params![],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, ChatId>(0)?,
|
||||
row.get::<_, Option<Blocked>>(1)?.unwrap_or_default(),
|
||||
))
|
||||
},
|
||||
);
|
||||
chat_ids_str
|
||||
),
|
||||
paramsv![],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, ChatId>(0)?,
|
||||
row.get::<_, Option<Blocked>>(1)?.unwrap_or_default(),
|
||||
))
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Ok((id, id_blocked)) = res {
|
||||
/* success, chat found */
|
||||
@@ -1167,7 +1197,7 @@ fn create_or_lookup_adhoc_group(
|
||||
|
||||
// create a new ad-hoc group
|
||||
// - there is no need to check if this group exists; otherwise we would have caught it above
|
||||
let grpid = create_adhoc_grp_id(context, &member_ids);
|
||||
let grpid = create_adhoc_grp_id(context, &member_ids).await;
|
||||
if grpid.is_empty() {
|
||||
warn!(
|
||||
context,
|
||||
@@ -1176,9 +1206,10 @@ fn create_or_lookup_adhoc_group(
|
||||
return Ok((ChatId::new(0), Blocked::Not));
|
||||
}
|
||||
// use subject as initial chat name
|
||||
let grpname = mime_parser.get_subject().unwrap_or_else(|| {
|
||||
context.stock_string_repl_int(StockMessage::Member, member_ids.len() as i32)
|
||||
});
|
||||
let default_name = context
|
||||
.stock_string_repl_int(StockMessage::Member, member_ids.len() as i32)
|
||||
.await;
|
||||
let grpname = mime_parser.get_subject().unwrap_or_else(|| default_name);
|
||||
|
||||
// create group record
|
||||
let new_chat_id: ChatId = create_group_record(
|
||||
@@ -1187,9 +1218,10 @@ fn create_or_lookup_adhoc_group(
|
||||
grpname,
|
||||
create_blocked,
|
||||
VerifiedStatus::Unverified,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
for &member_id in &member_ids {
|
||||
chat::add_to_chat_contacts_table(context, new_chat_id, member_id);
|
||||
chat::add_to_chat_contacts_table(context, new_chat_id, member_id).await;
|
||||
}
|
||||
|
||||
context.call_cb(Event::ChatModified(new_chat_id));
|
||||
@@ -1206,7 +1238,7 @@ async fn create_group_record(
|
||||
) -> ChatId {
|
||||
if context.sql.execute(
|
||||
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);",
|
||||
params![
|
||||
paramsv![
|
||||
if VerifiedStatus::Unverified != create_verified {
|
||||
Chattype::VerifiedGroup
|
||||
} else {
|
||||
@@ -1245,7 +1277,7 @@ async fn create_group_record(
|
||||
chat_id
|
||||
}
|
||||
|
||||
fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
async fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
/* algorithm:
|
||||
- sort normalized, lowercased, e-mail addresses alphabetically
|
||||
- put all e-mail addresses into a single string, separate the address by a single comma
|
||||
@@ -1255,6 +1287,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
let member_ids_str = join(member_ids.iter().map(|x| x.to_string()), ",");
|
||||
let member_cs = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_else(|| "no-self".to_string())
|
||||
.to_lowercase();
|
||||
|
||||
@@ -1265,7 +1298,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
"SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
|
||||
member_ids_str
|
||||
),
|
||||
params![],
|
||||
paramsv![],
|
||||
|row| row.get::<_, String>(0),
|
||||
|rows| {
|
||||
let mut addrs = rows.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||
@@ -1278,6 +1311,7 @@ fn create_adhoc_grp_id(context: &Context, member_ids: &[u32]) -> String {
|
||||
Ok(acc)
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|_| member_cs);
|
||||
|
||||
hex_hash(&members)
|
||||
@@ -1289,7 +1323,7 @@ fn hex_hash(s: impl AsRef<str>) -> String {
|
||||
hex::encode(&result[..8])
|
||||
}
|
||||
|
||||
fn search_chat_ids_by_contact_ids(
|
||||
async fn search_chat_ids_by_contact_ids(
|
||||
context: &Context,
|
||||
unsorted_contact_ids: &[u32],
|
||||
) -> Result<Vec<ChatId>> {
|
||||
@@ -1318,7 +1352,7 @@ fn search_chat_ids_by_contact_ids(
|
||||
ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
|
||||
contact_ids_str
|
||||
),
|
||||
params![],
|
||||
paramsv![],
|
||||
|row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)),
|
||||
|rows| {
|
||||
let mut last_chat_id = ChatId::new(0);
|
||||
@@ -1347,20 +1381,20 @@ fn search_chat_ids_by_contact_ids(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
)?;
|
||||
).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(chat_ids)
|
||||
}
|
||||
|
||||
fn check_verified_properties(
|
||||
async fn check_verified_properties(
|
||||
context: &Context,
|
||||
mimeparser: &MimeMessage,
|
||||
from_id: u32,
|
||||
to_ids: &ContactIds,
|
||||
) -> Result<()> {
|
||||
let contact = Contact::load_from_db(context, from_id)?;
|
||||
let contact = Contact::load_from_db(context, from_id).await?;
|
||||
|
||||
ensure!(mimeparser.was_encrypted(), "This message is not encrypted.");
|
||||
|
||||
@@ -1369,10 +1403,10 @@ fn check_verified_properties(
|
||||
// this check is skipped for SELF as there is no proper SELF-peerstate
|
||||
// and results in group-splits otherwise.
|
||||
if from_id != DC_CONTACT_ID_SELF {
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr());
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, contact.get_addr()).await;
|
||||
|
||||
if peerstate.is_none()
|
||||
|| contact.is_verified_ex(context, peerstate.as_ref())
|
||||
|| contact.is_verified_ex(context, peerstate.as_ref()).await
|
||||
!= VerifiedStatus::BidirectVerified
|
||||
{
|
||||
bail!(
|
||||
@@ -1398,29 +1432,32 @@ fn check_verified_properties(
|
||||
}
|
||||
let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ",");
|
||||
|
||||
let rows = context.sql.query_map(
|
||||
format!(
|
||||
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
|
||||
let rows = context
|
||||
.sql
|
||||
.query_map(
|
||||
format!(
|
||||
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \
|
||||
LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
|
||||
to_ids_str
|
||||
),
|
||||
params![],
|
||||
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))),
|
||||
|rows| {
|
||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)?;
|
||||
to_ids_str
|
||||
),
|
||||
paramsv![],
|
||||
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))),
|
||||
|rows| {
|
||||
rows.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
for (to_addr, _is_verified) in rows.into_iter() {
|
||||
info!(
|
||||
context,
|
||||
"check_verified_properties: {:?} self={:?}",
|
||||
to_addr,
|
||||
context.is_self_addr(&to_addr)
|
||||
context.is_self_addr(&to_addr).await
|
||||
);
|
||||
let mut is_verified = _is_verified != 0;
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &to_addr);
|
||||
let peerstate = Peerstate::from_addr(context, &context.sql, &to_addr).await;
|
||||
|
||||
// mark gossiped keys (if any) as verified
|
||||
if mimeparser.gossipped_addr.contains(&to_addr) {
|
||||
@@ -1442,7 +1479,7 @@ fn check_verified_properties(
|
||||
&fp,
|
||||
PeerstateVerifiedStatus::BidirectVerified,
|
||||
);
|
||||
peerstate.save_to_db(&context.sql, false)?;
|
||||
peerstate.save_to_db(&context.sql, false).await?;
|
||||
is_verified = true;
|
||||
}
|
||||
}
|
||||
@@ -1468,18 +1505,18 @@ fn set_better_msg(mime_parser: &mut MimeMessage, better_msg: impl AsRef<str>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bool {
|
||||
async fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bool {
|
||||
/* check if the message is a reply to a known message; the replies are identified by the Message-ID from
|
||||
`In-Reply-To`/`References:` (to support non-Delta-Clients) */
|
||||
|
||||
if let Some(field) = mime_parser.get(HeaderDef::InReplyTo) {
|
||||
if is_known_rfc724_mid_in_list(context, &field) {
|
||||
if is_known_rfc724_mid_in_list(context, &field).await {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(field) = mime_parser.get(HeaderDef::References) {
|
||||
if is_known_rfc724_mid_in_list(context, &field) {
|
||||
if is_known_rfc724_mid_in_list(context, &field).await {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1487,14 +1524,14 @@ fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bo
|
||||
false
|
||||
}
|
||||
|
||||
fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
async fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
if mid_list.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Ok(ids) = mailparse::msgidparse(mid_list) {
|
||||
for id in ids.iter() {
|
||||
if is_known_rfc724_mid(context, id) {
|
||||
if is_known_rfc724_mid(context, id).await {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1504,7 +1541,7 @@ fn is_known_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Check if a message is a reply to a known message (messenger or non-messenger).
|
||||
fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
|
||||
async fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
@@ -1512,8 +1549,9 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
|
||||
LEFT JOIN chats c ON m.chat_id=c.id \
|
||||
WHERE m.rfc724_mid=? \
|
||||
AND m.chat_id>9 AND c.blocked=0;",
|
||||
params![rfc724_mid],
|
||||
paramsv![rfc724_mid],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -1522,15 +1560,15 @@ fn is_known_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
|
||||
/// - checks also if any of the referenced IDs are send by a messenger
|
||||
/// - it is okay, if the referenced messages are moved to trash here
|
||||
/// - no check for the Chat-* headers (function is only called if it is no messenger message itself)
|
||||
fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -> bool {
|
||||
async fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -> bool {
|
||||
if let Some(value) = mime_parser.get(HeaderDef::InReplyTo) {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value) {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = mime_parser.get(HeaderDef::References) {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value) {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1538,10 +1576,10 @@ fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
pub(crate) async fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -> bool {
|
||||
if let Ok(ids) = mailparse::msgidparse(mid_list) {
|
||||
for id in ids.iter() {
|
||||
if is_msgrmsg_rfc724_mid(context, id) {
|
||||
if is_msgrmsg_rfc724_mid(context, id).await {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1550,17 +1588,18 @@ pub(crate) fn is_msgrmsg_rfc724_mid_in_list(context: &Context, mid_list: &str) -
|
||||
}
|
||||
|
||||
/// Check if a message is a reply to any messenger message.
|
||||
fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
|
||||
async fn is_msgrmsg_rfc724_mid(context: &Context, rfc724_mid: &str) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;",
|
||||
params![rfc724_mid],
|
||||
paramsv![rfc724_mid],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn dc_add_or_lookup_contacts_by_address_list(
|
||||
async fn dc_add_or_lookup_contacts_by_address_list(
|
||||
context: &Context,
|
||||
addr_list_raw: &str,
|
||||
origin: Origin,
|
||||
@@ -1576,21 +1615,22 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
for addr in addrs.iter() {
|
||||
match addr {
|
||||
mailparse::MailAddr::Single(info) => {
|
||||
contact_ids.insert(add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
origin,
|
||||
)?);
|
||||
contact_ids.insert(
|
||||
add_or_lookup_contact_by_addr(context, &info.display_name, &info.addr, origin)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
mailparse::MailAddr::Group(infos) => {
|
||||
for info in &infos.addrs {
|
||||
contact_ids.insert(add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
origin,
|
||||
)?);
|
||||
contact_ids.insert(
|
||||
add_or_lookup_contact_by_addr(
|
||||
context,
|
||||
&info.display_name,
|
||||
&info.addr,
|
||||
origin,
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1600,13 +1640,13 @@ fn dc_add_or_lookup_contacts_by_address_list(
|
||||
}
|
||||
|
||||
/// Add contacts to database on receiving messages.
|
||||
fn add_or_lookup_contact_by_addr(
|
||||
async fn add_or_lookup_contact_by_addr(
|
||||
context: &Context,
|
||||
display_name: &Option<String>,
|
||||
addr: &str,
|
||||
origin: Origin,
|
||||
) -> Result<u32> {
|
||||
if context.is_self_addr(addr)? {
|
||||
if context.is_self_addr(addr).await? {
|
||||
return Ok(DC_CONTACT_ID_SELF);
|
||||
}
|
||||
let display_name_normalized = display_name
|
||||
@@ -1615,7 +1655,7 @@ fn add_or_lookup_contact_by_addr(
|
||||
.unwrap_or_default();
|
||||
|
||||
let (row_id, _modified) =
|
||||
Contact::add_or_lookup(context, display_name_normalized, addr, origin)?;
|
||||
Contact::add_or_lookup(context, display_name_normalized, addr, origin).await?;
|
||||
ensure!(row_id > 0, "could not add contact: {:?}", addr);
|
||||
|
||||
Ok(row_id)
|
||||
@@ -1650,31 +1690,35 @@ mod tests {
|
||||
assert_eq!(res, "b94d27b9934d3e08");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grpid_simple() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_grpid_simple() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"From: hello\n\
|
||||
Subject: outer-subject\n\
|
||||
In-Reply-To: <lqkjwelq123@123123>\n\
|
||||
References: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\n\
|
||||
\n\
|
||||
hello\x00";
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None);
|
||||
let grpid = Some("HcxyMARjyJy");
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_grpid_from_multiple() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_grpid_from_multiple() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"From: hello\n\
|
||||
Subject: outer-subject\n\
|
||||
In-Reply-To: <Gr.HcxyMARjyJy.9-qweqwe@asd.net>\n\
|
||||
References: <qweqweqwe>, <Gr.HcxyMARjyJy.9-uvzWPTLtV@nau.ca>\n\
|
||||
\n\
|
||||
hello\x00";
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
let grpid = Some("HcxyMARjyJy");
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid);
|
||||
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
|
||||
|
||||
@@ -99,9 +99,9 @@ const MAX_SECONDS_TO_LEND_FROM_FUTURE: i64 = 5;
|
||||
// returns the currently smeared timestamp,
|
||||
// may be used to check if call to dc_create_smeared_timestamp() is needed or not.
|
||||
// the returned timestamp MUST NOT be used to be sent out or saved in the database!
|
||||
pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
|
||||
pub(crate) async fn dc_smeared_time(context: &Context) -> i64 {
|
||||
let mut now = time();
|
||||
let ts = *context.last_smeared_timestamp.read().unwrap();
|
||||
let ts = *context.last_smeared_timestamp.read().await;
|
||||
if ts >= now {
|
||||
now = ts + 1;
|
||||
}
|
||||
@@ -110,11 +110,11 @@ pub(crate) fn dc_smeared_time(context: &Context) -> i64 {
|
||||
}
|
||||
|
||||
// returns a timestamp that is guaranteed to be unique.
|
||||
pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 {
|
||||
pub(crate) async fn dc_create_smeared_timestamp(context: &Context) -> i64 {
|
||||
let now = time();
|
||||
let mut ret = now;
|
||||
|
||||
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
|
||||
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().await;
|
||||
if ret <= *last_smeared_timestamp {
|
||||
ret = *last_smeared_timestamp + 1;
|
||||
if ret - now > MAX_SECONDS_TO_LEND_FROM_FUTURE {
|
||||
@@ -129,12 +129,12 @@ pub(crate) fn dc_create_smeared_timestamp(context: &Context) -> i64 {
|
||||
// creates `count` timestamps that are guaranteed to be unique.
|
||||
// the frist created timestamps is returned directly,
|
||||
// get the other timestamps just by adding 1..count-1
|
||||
pub(crate) fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 {
|
||||
pub(crate) async fn dc_create_smeared_timestamps(context: &Context, count: usize) -> i64 {
|
||||
let now = time();
|
||||
let count = count as i64;
|
||||
let mut start = now + min(count, MAX_SECONDS_TO_LEND_FROM_FUTURE) - count;
|
||||
|
||||
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().unwrap();
|
||||
let mut last_smeared_timestamp = context.last_smeared_timestamp.write().await;
|
||||
start = max(*last_smeared_timestamp + 1, start);
|
||||
|
||||
*last_smeared_timestamp = start + count - 1;
|
||||
@@ -711,9 +711,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_file_handling() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_file_handling() {
|
||||
let t = dummy_context().await;
|
||||
let context = &t.ctx;
|
||||
let dc_file_exist = |ctx: &Context, fname: &str| {
|
||||
ctx.get_blobdir()
|
||||
@@ -784,15 +784,15 @@ mod tests {
|
||||
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_smeared_timestamp() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_smeared_timestamp() {
|
||||
let t = dummy_context().await;
|
||||
assert_ne!(
|
||||
dc_create_smeared_timestamp(&t.ctx),
|
||||
dc_create_smeared_timestamp(&t.ctx)
|
||||
dc_create_smeared_timestamp(&t.ctx).await,
|
||||
dc_create_smeared_timestamp(&t.ctx).await
|
||||
);
|
||||
assert!(
|
||||
dc_create_smeared_timestamp(&t.ctx)
|
||||
dc_create_smeared_timestamp(&t.ctx).await
|
||||
>= SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
@@ -800,17 +800,17 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_smeared_timestamps() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_smeared_timestamps() {
|
||||
let t = dummy_context().await;
|
||||
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
|
||||
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
|
||||
let next = dc_smeared_time(&t.ctx);
|
||||
let start = dc_create_smeared_timestamps(&t.ctx, count as usize).await;
|
||||
let next = dc_smeared_time(&t.ctx).await;
|
||||
assert!((start + count - 1) < next);
|
||||
|
||||
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30;
|
||||
let start = dc_create_smeared_timestamps(&t.ctx, count as usize);
|
||||
let next = dc_smeared_time(&t.ctx);
|
||||
let start = dc_create_smeared_timestamps(&t.ctx, count as usize).await;
|
||||
let next = dc_smeared_time(&t.ctx).await;
|
||||
assert!((start + count - 1) < next);
|
||||
}
|
||||
}
|
||||
|
||||
50
src/e2ee.rs
50
src/e2ee.rs
@@ -137,7 +137,7 @@ pub async fn try_decrypt(
|
||||
let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
|
||||
|
||||
if message_time > 0 {
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, &from);
|
||||
peerstate = Peerstate::from_addr(context, &context.sql, &from).await;
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if let Some(ref header) = autocryptheader {
|
||||
@@ -167,7 +167,7 @@ pub async fn try_decrypt(
|
||||
.await
|
||||
{
|
||||
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
|
||||
peerstate = Peerstate::from_addr(&context, &context.sql, &from);
|
||||
peerstate = Peerstate::from_addr(&context, &context.sql, &from).await;
|
||||
}
|
||||
if let Some(ref peerstate) = peerstate {
|
||||
if peerstate.degrade_event.is_some() {
|
||||
@@ -207,7 +207,7 @@ async fn load_or_generate_self_public_key(
|
||||
return SignedPublicKey::try_from(key)
|
||||
.map_err(|_| Error::Message("Not a public key".into()));
|
||||
}
|
||||
let _guard = context.generating_key_mutex.lock().unwrap();
|
||||
let _guard = context.generating_key_mutex.lock().await;
|
||||
|
||||
// Check again in case the key was generated while we were waiting for the lock.
|
||||
if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await {
|
||||
@@ -377,15 +377,15 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_prexisting() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let test_addr = configure_alice_keypair(&t.ctx).await;
|
||||
assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_configured() {
|
||||
let t = dummy_context();
|
||||
assert!(ensure_secret_key_exists(&t.ctx).is_err());
|
||||
#[async_std::test]
|
||||
async fn test_not_configured() {
|
||||
let t = dummy_context().await;
|
||||
assert!(ensure_secret_key_exists(&t.ctx).await.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,40 +418,42 @@ Sent with my Delta Chat Messenger: https://delta.chat";
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_existing() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let addr = configure_alice_keypair(&t.ctx).await;
|
||||
let key = load_or_generate_self_public_key(&t.ctx, addr).await;
|
||||
assert!(key.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[async_std::test]
|
||||
#[ignore] // generating keys is expensive
|
||||
fn test_generate() {
|
||||
let t = dummy_context();
|
||||
async fn test_generate() {
|
||||
let t = dummy_context().await;
|
||||
let addr = "alice@example.org";
|
||||
let key0 = load_or_generate_self_public_key(&t.ctx, addr);
|
||||
let key0 = load_or_generate_self_public_key(&t.ctx, addr).await;
|
||||
assert!(key0.is_ok());
|
||||
let key1 = load_or_generate_self_public_key(&t.ctx, addr);
|
||||
let key1 = load_or_generate_self_public_key(&t.ctx, addr).await;
|
||||
assert!(key1.is_ok());
|
||||
assert_eq!(key0.unwrap(), key1.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[async_std::test]
|
||||
#[ignore]
|
||||
fn test_generate_concurrent() {
|
||||
async fn test_generate_concurrent() {
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
let ctx = Arc::new(t.ctx);
|
||||
let ctx0 = Arc::clone(&ctx);
|
||||
let thr0 =
|
||||
thread::spawn(move || load_or_generate_self_public_key(&ctx0, "alice@example.org"));
|
||||
let thr0 = async_std::task::spawn(async move {
|
||||
load_or_generate_self_public_key(&ctx0, "alice@example.org").await
|
||||
});
|
||||
let ctx1 = Arc::clone(&ctx);
|
||||
let thr1 =
|
||||
thread::spawn(move || load_or_generate_self_public_key(&ctx1, "alice@example.org"));
|
||||
let res0 = thr0.join().unwrap();
|
||||
let res1 = thr1.join().unwrap();
|
||||
let thr1 = async_std::task::spawn(async move {
|
||||
load_or_generate_self_public_key(&ctx1, "alice@example.org").await
|
||||
});
|
||||
|
||||
let res0 = thr0.await;
|
||||
let res1 = thr1.await;
|
||||
assert_eq!(res0.unwrap(), res1.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,9 @@ impl Imap {
|
||||
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
|
||||
let addr: &str = config.addr.as_ref();
|
||||
|
||||
if let Some(token) = dc_get_oauth2_access_token(context, addr, imap_pw, true) {
|
||||
if let Some(token) =
|
||||
dc_get_oauth2_access_token(context, addr, imap_pw, true).await
|
||||
{
|
||||
let auth = OAuth2 {
|
||||
user: imap_user.into(),
|
||||
access_token: token,
|
||||
@@ -298,11 +300,13 @@ impl Imap {
|
||||
let config = self.config.read().await;
|
||||
let imap_server: &str = config.imap_server.as_ref();
|
||||
let imap_port = config.imap_port;
|
||||
context.stock_string_repl_str2(
|
||||
StockMessage::ServerResponse,
|
||||
format!("{}:{}", imap_server, imap_port),
|
||||
err.to_string(),
|
||||
)
|
||||
context
|
||||
.stock_string_repl_str2(
|
||||
StockMessage::ServerResponse,
|
||||
format!("{}:{}", imap_server, imap_port),
|
||||
err.to_string(),
|
||||
)
|
||||
.await
|
||||
};
|
||||
// IMAP connection failures are reported to users
|
||||
emit_event!(context, Event::ErrorNetwork(message));
|
||||
@@ -319,7 +323,9 @@ impl Imap {
|
||||
}
|
||||
Err((err, _)) => {
|
||||
let imap_user = self.config.read().await.imap_user.to_owned();
|
||||
let message = context.stock_string_repl_str(StockMessage::CannotLogin, &imap_user);
|
||||
let message = context
|
||||
.stock_string_repl_str(StockMessage::CannotLogin, &imap_user)
|
||||
.await;
|
||||
|
||||
emit_event!(
|
||||
context,
|
||||
@@ -539,7 +545,8 @@ impl Imap {
|
||||
// id we do not do this here, we'll miss the first message
|
||||
// as we will get in here again and fetch from lastseenuid+1 then
|
||||
|
||||
self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0);
|
||||
self.set_config_last_seen_uid(context, &folder, new_uid_validity, 0)
|
||||
.await;
|
||||
return Ok((new_uid_validity, 0));
|
||||
}
|
||||
|
||||
@@ -577,7 +584,8 @@ impl Imap {
|
||||
}
|
||||
};
|
||||
|
||||
self.set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid);
|
||||
self.set_config_last_seen_uid(context, &folder, new_uid_validity, new_last_seen_uid)
|
||||
.await;
|
||||
info!(
|
||||
context,
|
||||
"uid/validity change: new {}/{} current {}/{}",
|
||||
@@ -651,6 +659,7 @@ impl Imap {
|
||||
);
|
||||
} else {
|
||||
let show = prefetch_should_download(context, &headers, show_emails)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!(context, "prefetch_should_download error: {}", err);
|
||||
err
|
||||
@@ -687,7 +696,8 @@ impl Imap {
|
||||
};
|
||||
|
||||
if new_last_seen_uid > last_seen_uid {
|
||||
self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid);
|
||||
self.set_config_last_seen_uid(context, &folder, uid_validity, new_last_seen_uid)
|
||||
.await;
|
||||
}
|
||||
|
||||
if read_errors > 0 {
|
||||
@@ -1322,7 +1332,7 @@ async fn precheck_imf(
|
||||
server_uid: u32,
|
||||
) -> bool {
|
||||
if let Ok((old_server_folder, old_server_uid, msg_id)) =
|
||||
message::rfc724_mid_exists(context, &rfc724_mid)
|
||||
message::rfc724_mid_exists(context, &rfc724_mid).await
|
||||
{
|
||||
if old_server_folder.is_empty() && old_server_uid == 0 {
|
||||
info!(context, "[move] detected bcc-self {}", rfc724_mid,);
|
||||
@@ -1342,7 +1352,7 @@ async fn precheck_imf(
|
||||
}
|
||||
|
||||
if old_server_folder != server_folder || old_server_uid != server_uid {
|
||||
update_server_uid(context, &rfc724_mid, server_folder, server_uid);
|
||||
update_server_uid(context, &rfc724_mid, server_folder, server_uid).await;
|
||||
}
|
||||
true
|
||||
} else {
|
||||
@@ -1367,18 +1377,18 @@ fn prefetch_get_message_id(headers: &[mailparse::MailHeader]) -> Result<String>
|
||||
}
|
||||
}
|
||||
|
||||
fn prefetch_is_reply_to_chat_message(
|
||||
async fn prefetch_is_reply_to_chat_message(
|
||||
context: &Context,
|
||||
headers: &[mailparse::MailHeader],
|
||||
headers: &[mailparse::MailHeader<'_>],
|
||||
) -> Result<bool> {
|
||||
if let Some(value) = headers.get_header_value(HeaderDef::InReplyTo)? {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value) {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(value) = headers.get_header_value(HeaderDef::References)? {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value) {
|
||||
if is_msgrmsg_rfc724_mid_in_list(context, &value).await {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
@@ -1386,13 +1396,13 @@ fn prefetch_is_reply_to_chat_message(
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn prefetch_should_download(
|
||||
async fn prefetch_should_download(
|
||||
context: &Context,
|
||||
headers: &[mailparse::MailHeader],
|
||||
headers: &[mailparse::MailHeader<'_>],
|
||||
show_emails: ShowEmails,
|
||||
) -> Result<bool> {
|
||||
let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion)?.is_some();
|
||||
let is_reply_to_chat_message = prefetch_is_reply_to_chat_message(context, &headers)?;
|
||||
let is_reply_to_chat_message = prefetch_is_reply_to_chat_message(context, &headers).await?;
|
||||
|
||||
// Autocrypt Setup Message should be shown even if it is from non-chat client.
|
||||
let is_autocrypt_setup_message = headers
|
||||
@@ -1403,7 +1413,8 @@ fn prefetch_should_download(
|
||||
.get_header_value(HeaderDef::From_)?
|
||||
.unwrap_or_default();
|
||||
|
||||
let (_contact_id, blocked_contact, origin) = from_field_to_contact_id(context, &from_field)?;
|
||||
let (_contact_id, blocked_contact, origin) =
|
||||
from_field_to_contact_id(context, &from_field).await?;
|
||||
let accepted_contact = origin.is_known();
|
||||
|
||||
let show = is_autocrypt_setup_message
|
||||
|
||||
@@ -29,15 +29,10 @@ impl Session {
|
||||
mailbox_pattern: Option<&str>,
|
||||
) -> ImapResult<impl Stream<Item = ImapResult<Name>> + '_ + Send + Unpin> {
|
||||
match self {
|
||||
Session::Secure(i) => {
|
||||
i.list(reference_name, mailbox_pattern).await
|
||||
// list.collect::<ImapResult<_>>().await?
|
||||
}
|
||||
Session::Insecure(i) => {
|
||||
Session::Secure(i) => i.list(reference_name, mailbox_pattern).await,
|
||||
Session::Insecure(_i) => {
|
||||
unimplemented!()
|
||||
// i.list(reference_name, mailbox_pattern).await
|
||||
// .collect::<ImapResult<_>>()
|
||||
// .await?
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,7 +81,7 @@ impl Session {
|
||||
{
|
||||
let res = match self {
|
||||
Session::Secure(i) => i.fetch(sequence_set, query).await?,
|
||||
Session::Insecure(i) => {
|
||||
Session::Insecure(_i) => {
|
||||
unimplemented!()
|
||||
// i.fetch(sequence_set, query).await?
|
||||
}
|
||||
@@ -105,7 +100,7 @@ impl Session {
|
||||
{
|
||||
let res = match self {
|
||||
Session::Secure(i) => i.uid_fetch(uid_set, query).await?,
|
||||
Session::Insecure(i) => {
|
||||
Session::Insecure(_i) => {
|
||||
unimplemented!()
|
||||
// i.uid_fetch(uid_set, query).await?
|
||||
}
|
||||
@@ -125,7 +120,7 @@ impl Session {
|
||||
{
|
||||
let res = match self {
|
||||
Session::Secure(i) => i.uid_store(uid_set, query).await?,
|
||||
Session::Insecure(i) => {
|
||||
Session::Insecure(_i) => {
|
||||
unimplemented!()
|
||||
// i.uid_store(uid_set, query).await?
|
||||
}
|
||||
|
||||
153
src/imex.rs
153
src/imex.rs
@@ -115,9 +115,9 @@ pub async fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result
|
||||
}
|
||||
|
||||
pub async fn initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
ensure!(context.alloc_ongoing().await, "could not allocate ongoing");
|
||||
let res = do_initiate_key_transfer(context).await;
|
||||
context.free_ongoing();
|
||||
context.free_ongoing().await;
|
||||
res
|
||||
}
|
||||
|
||||
@@ -125,10 +125,10 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
let mut msg: Message;
|
||||
let setup_code = create_setup_code(context);
|
||||
/* this may require a keypair to be created. this may take a second ... */
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
ensure!(!context.shall_stop_ongoing().await, "canceled");
|
||||
let setup_file_content = render_setup_file(context, &setup_code).await?;
|
||||
/* encrypting may also take a while ... */
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
ensure!(!context.shall_stop_ongoing().await, "canceled");
|
||||
let setup_file_blob = BlobObject::create(
|
||||
context,
|
||||
"autocrypt-setup-message.html",
|
||||
@@ -148,11 +148,11 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
|
||||
ForcePlaintext::NoAutocryptHeader as i32,
|
||||
);
|
||||
|
||||
ensure!(!context.shall_stop_ongoing(), "canceled");
|
||||
ensure!(!context.shall_stop_ongoing().await, "canceled");
|
||||
let msg_id = chat::send_msg(context, chat_id, &mut msg).await?;
|
||||
info!(context, "Wait for setup message being sent ...",);
|
||||
while !context.shall_stop_ongoing() {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
while !context.shall_stop_ongoing().await {
|
||||
async_std::task::sleep(std::time::Duration::from_secs(1)).await;
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id).await {
|
||||
if msg.is_sent() {
|
||||
info!(context, "... setup message sent.",);
|
||||
@@ -197,8 +197,8 @@ pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<St
|
||||
);
|
||||
let pgp_msg = encr.replace("-----BEGIN PGP MESSAGE-----", &replacement);
|
||||
|
||||
let msg_subj = context.stock_str(StockMessage::AcSetupMsgSubject);
|
||||
let msg_body = context.stock_str(StockMessage::AcSetupMsgBody);
|
||||
let msg_subj = context.stock_str(StockMessage::AcSetupMsgSubject).await;
|
||||
let msg_body = context.stock_str(StockMessage::AcSetupMsgBody).await;
|
||||
let msg_body_html = msg_body.replace("\r", "").replace("\n", "<br>");
|
||||
Ok(format!(
|
||||
concat!(
|
||||
@@ -368,7 +368,7 @@ pub fn normalize_setup_code(s: &str) -> String {
|
||||
}
|
||||
|
||||
pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> {
|
||||
ensure!(context.alloc_ongoing(), "could not allocate ongoing");
|
||||
ensure!(context.alloc_ongoing().await, "could not allocate ongoing");
|
||||
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
|
||||
let param = job.param.get(Param::Arg).unwrap_or_default();
|
||||
|
||||
@@ -380,7 +380,7 @@ pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> {
|
||||
if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
|
||||
// before we export anything, make sure the private key exists
|
||||
if e2ee::ensure_secret_key_exists(context).await.is_err() {
|
||||
context.free_ongoing();
|
||||
context.free_ongoing().await;
|
||||
bail!("Cannot create private key or private key not available.");
|
||||
} else {
|
||||
dc_create_folder(context, ¶m)?;
|
||||
@@ -396,7 +396,7 @@ pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> {
|
||||
bail!("unknown IMEX type");
|
||||
}
|
||||
};
|
||||
context.free_ongoing();
|
||||
context.free_ongoing().await;
|
||||
match success {
|
||||
Ok(()) => {
|
||||
info!(context, "IMEX successfully completed");
|
||||
@@ -423,7 +423,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
|
||||
!context.is_configured().await,
|
||||
"Cannot import backups to accounts in use."
|
||||
);
|
||||
context.sql.close(&context);
|
||||
context.sql.close(&context).await;
|
||||
dc_delete_file(context, context.get_dbfile());
|
||||
ensure!(
|
||||
!context.get_dbfile().exists(),
|
||||
@@ -448,7 +448,7 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
|
||||
|
||||
let total_files_cnt = context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![])
|
||||
.query_get_value::<isize>(context, "SELECT COUNT(*) FROM backup_blobs;", paramsv![])
|
||||
.await
|
||||
.unwrap_or_default() as usize;
|
||||
info!(
|
||||
@@ -456,11 +456,11 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
|
||||
"***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
|
||||
);
|
||||
|
||||
let res = context
|
||||
let files = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
|
||||
params![],
|
||||
paramsv![],
|
||||
|row| {
|
||||
let name: String = row.get(0)?;
|
||||
let blob: Vec<u8> = row.get(1)?;
|
||||
@@ -468,46 +468,45 @@ async fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) ->
|
||||
Ok((name, blob))
|
||||
},
|
||||
|files| {
|
||||
for (processed_files_cnt, file) in files.enumerate() {
|
||||
let (file_name, file_blob) = file?;
|
||||
if context.shall_stop_ongoing() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
}
|
||||
if permille > 990 {
|
||||
permille = 990
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
if file_blob.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path_filename = context.get_blobdir().join(file_name);
|
||||
dc_write_file(context, &path_filename, &file_blob)?;
|
||||
}
|
||||
Ok(true)
|
||||
files
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.await;
|
||||
.await?;
|
||||
|
||||
match res {
|
||||
Ok(all_files_extracted) => {
|
||||
if all_files_extracted {
|
||||
// only delete backup_blobs if all files were successfully extracted
|
||||
context
|
||||
.sql
|
||||
.execute("DROP TABLE backup_blobs;", params![])
|
||||
.await?;
|
||||
context.sql.execute("VACUUM;", params![]).await.ok();
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("received stop signal");
|
||||
}
|
||||
let mut all_files_extracted = true;
|
||||
for (processed_files_cnt, (file_name, file_blob)) in files.into_iter().enumerate() {
|
||||
if context.shall_stop_ongoing().await {
|
||||
all_files_extracted = false;
|
||||
break;
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
let mut permille = processed_files_cnt * 1000 / total_files_cnt;
|
||||
if permille < 10 {
|
||||
permille = 10
|
||||
}
|
||||
if permille > 990 {
|
||||
permille = 990
|
||||
}
|
||||
context.call_cb(Event::ImexProgress(permille));
|
||||
if file_blob.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let path_filename = context.get_blobdir().join(file_name);
|
||||
dc_write_file(context, &path_filename, &file_blob)?;
|
||||
}
|
||||
|
||||
if all_files_extracted {
|
||||
// only delete backup_blobs if all files were successfully extracted
|
||||
context
|
||||
.sql
|
||||
.execute("DROP TABLE backup_blobs;", paramsv![])
|
||||
.await?;
|
||||
context.sql.execute("VACUUM;", paramsv![]).await.ok();
|
||||
Ok(())
|
||||
} else {
|
||||
bail!("received stop signal");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,10 +525,10 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
|
||||
sql::housekeeping(context).await;
|
||||
|
||||
context.sql.execute("VACUUM;", params![]).await.ok();
|
||||
context.sql.execute("VACUUM;", paramsv![]).await.ok();
|
||||
|
||||
// we close the database during the copy of the dbfile
|
||||
context.sql.close(context);
|
||||
context.sql.close(context).await;
|
||||
info!(
|
||||
context,
|
||||
"Backup '{}' to '{}'.",
|
||||
@@ -537,7 +536,10 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
dest_path_filename.display(),
|
||||
);
|
||||
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
|
||||
context.sql.open(&context, &context.get_dbfile(), false);
|
||||
context
|
||||
.sql
|
||||
.open(&context, &context.get_dbfile(), false)
|
||||
.await;
|
||||
|
||||
if !copied {
|
||||
bail!(
|
||||
@@ -566,7 +568,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
dest_sql.close(context);
|
||||
dest_sql.close(context).await;
|
||||
|
||||
Ok(res?)
|
||||
}
|
||||
@@ -577,7 +579,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
if !sql.table_exists("backup_blobs").await? {
|
||||
sql.execute(
|
||||
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -589,17 +591,14 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
|
||||
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
|
||||
|
||||
sql.with_conn(|conn| {
|
||||
sql.with_conn_async(|conn| async move {
|
||||
// scan directory, pass 2: copy files
|
||||
let dir_handle = std::fs::read_dir(&dir)?;
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare_cached("INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);")?;
|
||||
|
||||
let mut processed_files_cnt = 0;
|
||||
for entry in dir_handle {
|
||||
let entry = entry?;
|
||||
if context.shall_stop_ongoing() {
|
||||
if context.shall_stop_ongoing().await {
|
||||
return Ok(());
|
||||
}
|
||||
processed_files_cnt += 1;
|
||||
@@ -618,7 +617,10 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
|
||||
continue;
|
||||
}
|
||||
// bail out if we can't insert
|
||||
stmt.execute(params![name, buf])?;
|
||||
let mut stmt = conn.prepare_cached(
|
||||
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
|
||||
)?;
|
||||
stmt.execute(paramsv![name, buf])?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -690,7 +692,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, public_key, private_key, is_default FROM keypairs;",
|
||||
params![],
|
||||
paramsv![],
|
||||
|row| {
|
||||
let id = row.get(0)?;
|
||||
let public_key_blob: Vec<u8> = row.get(1)?;
|
||||
@@ -766,7 +768,7 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_render_setup_file() {
|
||||
let t = test_context(Some(Box::new(logging_cb)));
|
||||
let t = test_context(Some(Box::new(logging_cb))).await;
|
||||
|
||||
configure_alice_keypair(&t.ctx).await;
|
||||
let msg = render_setup_file(&t.ctx, "hello").await.unwrap();
|
||||
@@ -786,9 +788,10 @@ mod tests {
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_render_setup_file_newline_replace() {
|
||||
let t = dummy_context();
|
||||
let t = dummy_context().await;
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
configure_alice_keypair(&t.ctx).await;
|
||||
let msg = render_setup_file(&t.ctx, "pw").await.unwrap();
|
||||
@@ -796,9 +799,9 @@ mod tests {
|
||||
assert!(msg.contains("<p>hello<br>there</p>"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_setup_code() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_create_setup_code() {
|
||||
let t = dummy_context().await;
|
||||
let setupcode = create_setup_code(&t.ctx);
|
||||
assert_eq!(setupcode.len(), 44);
|
||||
assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
|
||||
@@ -811,9 +814,9 @@ mod tests {
|
||||
assert_eq!(setupcode.chars().nth(39).unwrap(), '-');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_key_to_asc_file() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_export_key_to_asc_file() {
|
||||
let context = dummy_context().await;
|
||||
let key = Key::from(alice_keypair().public);
|
||||
let blobdir = "$BLOBDIR";
|
||||
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok());
|
||||
@@ -839,9 +842,9 @@ mod tests {
|
||||
const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597";
|
||||
const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt");
|
||||
|
||||
#[test]
|
||||
fn test_split_and_decrypt() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_split_and_decrypt() {
|
||||
let ctx = dummy_context().await;
|
||||
let context = &ctx.ctx;
|
||||
|
||||
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
|
||||
|
||||
224
src/job.rs
224
src/job.rs
@@ -149,7 +149,7 @@ impl Job {
|
||||
async fn delete(&self, context: &Context) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32])
|
||||
.execute("DELETE FROM jobs WHERE id=?;", paramsv![self.job_id as i32])
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
@@ -162,7 +162,7 @@ impl Job {
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
|
||||
params![
|
||||
paramsv![
|
||||
self.desired_timestamp,
|
||||
self.tries as i64,
|
||||
self.param.to_string(),
|
||||
@@ -283,7 +283,7 @@ impl Job {
|
||||
/* if there is a msg-id and it does not exist in the db, cancel sending.
|
||||
this happends if dc_delete_msgs() was called
|
||||
before the generated mime was sent out */
|
||||
if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)) {
|
||||
if 0 != self.foreign_id && !message::exists(context, MsgId::new(self.foreign_id)).await {
|
||||
return Status::Finished(Err(format_err!(
|
||||
"Not sending Message {} as it was deleted",
|
||||
self.foreign_id
|
||||
@@ -295,7 +295,7 @@ impl Job {
|
||||
async move {
|
||||
// smtp success, update db ASAP, then delete smtp file
|
||||
if 0 != foreign_id {
|
||||
set_delivered(context, MsgId::new(foreign_id));
|
||||
set_delivered(context, MsgId::new(foreign_id)).await;
|
||||
}
|
||||
// now also delete the generated file
|
||||
dc_delete_file(context, filename);
|
||||
@@ -316,7 +316,7 @@ impl Job {
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
|
||||
params![contact_id, self.job_id],
|
||||
paramsv![contact_id, self.job_id],
|
||||
|row| {
|
||||
let job_id: u32 = row.get(0)?;
|
||||
let params_str: String = row.get(1)?;
|
||||
@@ -385,8 +385,9 @@ impl Job {
|
||||
}
|
||||
|
||||
let msg = job_try!(Message::load_from_db(context, msg_id).await);
|
||||
let mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids));
|
||||
let rendered_msg = job_try!(mimefactory.render());
|
||||
let mimefactory =
|
||||
job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids).await);
|
||||
let rendered_msg = job_try!(mimefactory.render().await);
|
||||
let body = rendered_msg.message;
|
||||
|
||||
let addr = contact.get_addr();
|
||||
@@ -443,7 +444,8 @@ impl Job {
|
||||
{
|
||||
ImapActionResult::RetryLater => Status::RetryLater,
|
||||
ImapActionResult::Success => {
|
||||
message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid);
|
||||
message::update_server_uid(context, &msg.rfc724_mid, &dest_folder, dest_uid)
|
||||
.await;
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
ImapActionResult::Failed => {
|
||||
@@ -462,7 +464,7 @@ impl Job {
|
||||
let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
|
||||
|
||||
if !msg.rfc724_mid.is_empty() {
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 {
|
||||
if message::rfc724_mid_cnt(context, &msg.rfc724_mid).await > 1 {
|
||||
info!(
|
||||
context,
|
||||
"The message is deleted from the server when all parts are deleted.",
|
||||
@@ -480,7 +482,7 @@ impl Job {
|
||||
return Status::RetryNow;
|
||||
}
|
||||
}
|
||||
Message::delete_from_db(context, msg.id);
|
||||
Message::delete_from_db(context, msg.id).await;
|
||||
Status::Finished(Ok(()))
|
||||
} else {
|
||||
/* eg. device messages have no Message-ID */
|
||||
@@ -576,7 +578,7 @@ impl Job {
|
||||
pub async fn kill_action(context: &Context, action: Action) -> bool {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE action=?;", params![action])
|
||||
.execute("DELETE FROM jobs WHERE action=?;", paramsv![action])
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
@@ -590,7 +592,7 @@ pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
|
||||
"DELETE FROM jobs WHERE id IN({})",
|
||||
job_ids.iter().map(|_| "?").join(",")
|
||||
),
|
||||
job_ids,
|
||||
job_ids.iter().map(|i| i as &dyn crate::ToSql).collect(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -715,7 +717,7 @@ async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Durati
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
|
||||
params![thread],
|
||||
paramsv![thread],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
@@ -750,19 +752,19 @@ pub async fn maybe_network(context: &Context) {
|
||||
pub async fn action_exists(context: &Context, action: Action) -> bool {
|
||||
context
|
||||
.sql
|
||||
.exists("SELECT id FROM jobs WHERE action=?;", params![action])
|
||||
.exists("SELECT id FROM jobs WHERE action=?;", paramsv![action])
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
message::update_msg_state(context, msg_id, MessageState::OutDelivered);
|
||||
message::update_msg_state(context, msg_id, MessageState::OutDelivered).await;
|
||||
let chat_id: ChatId = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?",
|
||||
params![msg_id],
|
||||
paramsv![msg_id],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
@@ -772,7 +774,7 @@ async fn set_delivered(context: &Context, msg_id: MsgId) {
|
||||
// special case for DC_JOB_SEND_MSG_TO_SMTP
|
||||
pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
let mut msg = Message::load_from_db(context, msg_id).await?;
|
||||
msg.try_calc_and_set_dimensions(context).ok();
|
||||
msg.try_calc_and_set_dimensions(context).await.ok();
|
||||
|
||||
/* create message */
|
||||
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default();
|
||||
@@ -785,7 +787,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
let mimefactory = MimeFactory::from_msg(context, &msg, attach_selfavatar)?;
|
||||
let mimefactory = MimeFactory::from_msg(context, &msg, attach_selfavatar).await?;
|
||||
|
||||
let mut recipients = mimefactory.recipients();
|
||||
|
||||
@@ -808,14 +810,17 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
context,
|
||||
"message {} has no recipient, skipping smtp-send", msg_id
|
||||
);
|
||||
set_delivered(context, msg_id);
|
||||
set_delivered(context, msg_id).await;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let rendered_msg = mimefactory.render().map_err(|err| {
|
||||
message::set_msg_failed(context, msg_id, Some(err.to_string()));
|
||||
err
|
||||
})?;
|
||||
let rendered_msg = match mimefactory.render().await {
|
||||
Ok(res) => Ok(res),
|
||||
Err(err) => {
|
||||
message::set_msg_failed(context, msg_id, Some(err.to_string())).await;
|
||||
Err(err)
|
||||
}
|
||||
}?;
|
||||
|
||||
if needs_encryption && !rendered_msg.is_encrypted {
|
||||
/* unrecoverable */
|
||||
@@ -823,7 +828,8 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
context,
|
||||
msg_id,
|
||||
Some("End-to-end-encryption unavailable unexpectedly."),
|
||||
);
|
||||
)
|
||||
.await;
|
||||
bail!(
|
||||
"e2e encryption unavailable {} - {:?}",
|
||||
msg_id,
|
||||
@@ -857,7 +863,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
|
||||
|
||||
if rendered_msg.is_encrypted && !needs_encryption {
|
||||
msg.param.set_int(Param::GuaranteeE2ee, 1);
|
||||
msg.save_param_to_disk(context);
|
||||
msg.save_param_to_disk(context).await;
|
||||
}
|
||||
|
||||
add_smtp_job(
|
||||
@@ -917,77 +923,78 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
|
||||
x => x,
|
||||
};
|
||||
|
||||
// if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
// context.sentbox_thread.unsuspend(context).await;
|
||||
// context.mvbox_thread.unsuspend(context).await;
|
||||
// suspend_smtp_thread(context, false).await;
|
||||
// break;
|
||||
// }
|
||||
if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
|
||||
context.sentbox_thread.unsuspend(context).await;
|
||||
context.mvbox_thread.unsuspend(context).await;
|
||||
suspend_smtp_thread(context, false).await;
|
||||
break;
|
||||
}
|
||||
|
||||
// match try_res {
|
||||
// Status::RetryNow | Status::RetryLater => {
|
||||
// let tries = job.tries + 1;
|
||||
match try_res {
|
||||
Status::RetryNow | Status::RetryLater => {
|
||||
let tries = job.tries + 1;
|
||||
|
||||
// if tries < JOB_RETRIES {
|
||||
// info!(
|
||||
// context,
|
||||
// "{} thread increases job {} tries to {}", thread, job, tries
|
||||
// );
|
||||
// job.tries = tries;
|
||||
// let time_offset = get_backoff_time_offset(tries);
|
||||
// job.desired_timestamp = time() + time_offset;
|
||||
// job.update(context).await;
|
||||
// info!(
|
||||
// context,
|
||||
// "{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
// thread,
|
||||
// job.job_id as u32,
|
||||
// tries,
|
||||
// time_offset
|
||||
// );
|
||||
// if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
// context.smtp.state.write().await.perform_jobs_needed =
|
||||
// PerformJobsNeeded::AvoidDos;
|
||||
// }
|
||||
// } else {
|
||||
// info!(
|
||||
// context,
|
||||
// "{} thread removes job {} as it exhausted {} retries",
|
||||
// thread,
|
||||
// job,
|
||||
// JOB_RETRIES
|
||||
// );
|
||||
// if job.action == Action::SendMsgToSmtp {
|
||||
// message::set_msg_failed(
|
||||
// context,
|
||||
// MsgId::new(job.foreign_id),
|
||||
// job.pending_error.as_ref(),
|
||||
// );
|
||||
// }
|
||||
// job.delete(context).await;
|
||||
// }
|
||||
// if !probe_network {
|
||||
// continue;
|
||||
// }
|
||||
// // on dc_maybe_network() we stop trying here;
|
||||
// // these jobs are already tried once.
|
||||
// // otherwise, we just continue with the next job
|
||||
// // to give other jobs a chance being tried at least once.
|
||||
// break;
|
||||
// }
|
||||
// Status::Finished(res) => {
|
||||
// if let Err(err) = res {
|
||||
// warn!(
|
||||
// context,
|
||||
// "{} removes job {} as it failed with error {:?}", thread, job, err
|
||||
// );
|
||||
// } else {
|
||||
// info!(context, "{} removes job {} as it succeeded", thread, job);
|
||||
// }
|
||||
if tries < JOB_RETRIES {
|
||||
info!(
|
||||
context,
|
||||
"{} thread increases job {} tries to {}", thread, job, tries
|
||||
);
|
||||
job.tries = tries;
|
||||
let time_offset = get_backoff_time_offset(tries);
|
||||
job.desired_timestamp = time() + time_offset;
|
||||
job.update(context).await;
|
||||
info!(
|
||||
context,
|
||||
"{}-job #{} not succeeded on try #{}, retry in {} seconds.",
|
||||
thread,
|
||||
job.job_id as u32,
|
||||
tries,
|
||||
time_offset
|
||||
);
|
||||
if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
|
||||
context.smtp.state.write().await.perform_jobs_needed =
|
||||
PerformJobsNeeded::AvoidDos;
|
||||
}
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"{} thread removes job {} as it exhausted {} retries",
|
||||
thread,
|
||||
job,
|
||||
JOB_RETRIES
|
||||
);
|
||||
if job.action == Action::SendMsgToSmtp {
|
||||
message::set_msg_failed(
|
||||
context,
|
||||
MsgId::new(job.foreign_id),
|
||||
job.pending_error.as_ref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
job.delete(context).await;
|
||||
}
|
||||
if !probe_network {
|
||||
continue;
|
||||
}
|
||||
// on dc_maybe_network() we stop trying here;
|
||||
// these jobs are already tried once.
|
||||
// otherwise, we just continue with the next job
|
||||
// to give other jobs a chance being tried at least once.
|
||||
break;
|
||||
}
|
||||
Status::Finished(res) => {
|
||||
if let Err(err) = res {
|
||||
warn!(
|
||||
context,
|
||||
"{} removes job {} as it failed with error {:?}", thread, job, err
|
||||
);
|
||||
} else {
|
||||
info!(context, "{} removes job {} as it succeeded", thread, job);
|
||||
}
|
||||
|
||||
// job.delete(context).await;
|
||||
// }
|
||||
// }
|
||||
job.delete(context).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1024,7 +1031,7 @@ async fn perform_job_action(
|
||||
location::job_maybe_send_locations_ended(context, &mut job).await
|
||||
}
|
||||
Action::Housekeeping => {
|
||||
sql::housekeeping(context);
|
||||
sql::housekeeping(context).await;
|
||||
Status::Finished(Ok(()))
|
||||
}
|
||||
};
|
||||
@@ -1108,7 +1115,7 @@ pub async fn add(
|
||||
|
||||
context.sql.execute(
|
||||
"INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);",
|
||||
params![
|
||||
paramsv![
|
||||
timestamp,
|
||||
thread,
|
||||
action,
|
||||
@@ -1154,9 +1161,11 @@ async fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -
|
||||
FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
|
||||
};
|
||||
|
||||
let params_no_probe = params![thread as i64, time()];
|
||||
let params_probe = params![thread as i64];
|
||||
let params: &[&dyn rusqlite::ToSql] = if !probe_network {
|
||||
let thread_i = thread as i64;
|
||||
let t = time();
|
||||
let params_no_probe = paramsv![thread_i, t];
|
||||
let params_probe = paramsv![thread_i];
|
||||
let params = if !probe_network {
|
||||
params_no_probe
|
||||
} else {
|
||||
params_probe
|
||||
@@ -1201,7 +1210,7 @@ mod tests {
|
||||
|
||||
use crate::test_utils::*;
|
||||
|
||||
fn insert_job(context: &Context, foreign_id: i64) {
|
||||
async fn insert_job(context: &Context, foreign_id: i64) {
|
||||
let now = time();
|
||||
context
|
||||
.sql
|
||||
@@ -1209,7 +1218,7 @@ mod tests {
|
||||
"INSERT INTO jobs
|
||||
(added_timestamp, thread, action, foreign_id, param, desired_timestamp)
|
||||
VALUES (?, ?, ?, ?, ?, ?);",
|
||||
params![
|
||||
paramsv![
|
||||
now,
|
||||
Thread::from(Action::MoveMsg),
|
||||
Action::MoveMsg,
|
||||
@@ -1218,21 +1227,22 @@ mod tests {
|
||||
now
|
||||
],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_next_job() {
|
||||
#[async_std::test]
|
||||
async fn test_load_next_job() {
|
||||
// We want to ensure that loading jobs skips over jobs which
|
||||
// fails to load from the database instead of failing to load
|
||||
// all jobs.
|
||||
let t = dummy_context();
|
||||
insert_job(&t.ctx, -1); // This can not be loaded into Job struct.
|
||||
let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false);
|
||||
let t = dummy_context().await;
|
||||
insert_job(&t.ctx, -1).await; // This can not be loaded into Job struct.
|
||||
let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await;
|
||||
assert!(jobs.is_none());
|
||||
|
||||
insert_job(&t.ctx, 1);
|
||||
let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false);
|
||||
insert_job(&t.ctx, 1).await;
|
||||
let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await;
|
||||
assert!(jobs.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
62
src/key.rs
62
src/key.rs
@@ -207,7 +207,7 @@ impl Key {
|
||||
sql.query_get_value(
|
||||
context,
|
||||
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[addr],
|
||||
paramsv![addr],
|
||||
)
|
||||
.await
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
|
||||
@@ -221,7 +221,7 @@ impl Key {
|
||||
sql.query_get_value(
|
||||
context,
|
||||
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
|
||||
&[self_addr.as_ref()],
|
||||
paramsv![self_addr.as_ref()],
|
||||
)
|
||||
.await
|
||||
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
|
||||
@@ -368,37 +368,37 @@ pub async fn store_self_keypair(
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
|
||||
params![public_key, secret_key],
|
||||
paramsv![public_key, secret_key],
|
||||
)
|
||||
.await
|
||||
.map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?;
|
||||
if default == KeyPairUse::Default {
|
||||
context
|
||||
.sql
|
||||
.execute("UPDATE keypairs SET is_default=0;", params![])
|
||||
.execute("UPDATE keypairs SET is_default=0;", paramsv![])
|
||||
.await
|
||||
.map_err(|err| SaveKeyError::new("failed to clear default", err))?;
|
||||
}
|
||||
let is_default = match default {
|
||||
KeyPairUse::Default => true,
|
||||
KeyPairUse::ReadOnly => false,
|
||||
KeyPairUse::Default => true as i32,
|
||||
KeyPairUse::ReadOnly => false as i32,
|
||||
};
|
||||
|
||||
let addr = keypair.addr.to_string();
|
||||
let t = time();
|
||||
|
||||
let params = paramsv![addr, is_default, public_key, secret_key, t];
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created)
|
||||
VALUES (?,?,?,?,?);",
|
||||
params![
|
||||
keypair.addr.to_string(),
|
||||
is_default as i32,
|
||||
public_key,
|
||||
secret_key,
|
||||
time()
|
||||
],
|
||||
params,
|
||||
)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|err| SaveKeyError::new("failed to insert keypair", err))
|
||||
.map_err(|err| SaveKeyError::new("failed to insert keypair", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Make a fingerprint human-readable, in hex format.
|
||||
@@ -433,6 +433,7 @@ mod tests {
|
||||
use crate::test_utils::*;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use async_std::sync::Arc;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
@@ -583,22 +584,29 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
|
||||
assert_eq!(public.primary_key, KEYPAIR.public.primary_key);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_save_self_key_twice() {
|
||||
#[async_std::test]
|
||||
async fn test_save_self_key_twice() {
|
||||
// Saving the same key twice should result in only one row in
|
||||
// the keypairs table.
|
||||
let t = dummy_context();
|
||||
let nrows = || {
|
||||
t.ctx
|
||||
.sql
|
||||
.query_get_value::<_, u32>(&t.ctx, "SELECT COUNT(*) FROM keypairs;", params![])
|
||||
let t = dummy_context().await;
|
||||
let ctx = Arc::new(t.ctx);
|
||||
|
||||
let ctx1 = ctx.clone();
|
||||
let nrows = || async {
|
||||
ctx1.sql
|
||||
.query_get_value::<u32>(&ctx1, "SELECT COUNT(*) FROM keypairs;", paramsv![])
|
||||
.await
|
||||
.unwrap()
|
||||
};
|
||||
assert_eq!(nrows(), 0);
|
||||
store_self_keypair(&t.ctx, &KEYPAIR, KeyPairUse::Default).unwrap();
|
||||
assert_eq!(nrows(), 1);
|
||||
store_self_keypair(&t.ctx, &KEYPAIR, KeyPairUse::Default).unwrap();
|
||||
assert_eq!(nrows(), 1);
|
||||
assert_eq!(nrows().await, 0);
|
||||
store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(nrows().await, 1);
|
||||
store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(nrows().await, 1);
|
||||
}
|
||||
|
||||
// Convenient way to create a new key if you need one, run with
|
||||
|
||||
@@ -36,7 +36,7 @@ impl<'a> Keyring<'a> {
|
||||
sql.query_get_value(
|
||||
context,
|
||||
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
|
||||
&[self_addr.as_ref()],
|
||||
paramsv![self_addr.as_ref().to_string()],
|
||||
)
|
||||
.await
|
||||
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))
|
||||
|
||||
15
src/lib.rs
15
src/lib.rs
@@ -16,6 +16,19 @@ extern crate strum_macros;
|
||||
#[macro_use]
|
||||
extern crate debug_stub_derive;
|
||||
|
||||
pub trait ToSql: rusqlite::ToSql + Send + Sync {}
|
||||
|
||||
impl<T: rusqlite::ToSql + Send + Sync> ToSql for T {}
|
||||
|
||||
macro_rules! paramsv {
|
||||
() => {
|
||||
Vec::new()
|
||||
};
|
||||
($($param:expr),+ $(,)?) => {
|
||||
vec![$(&$param as &dyn $crate::ToSql),+]
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_use]
|
||||
pub mod log;
|
||||
#[macro_use]
|
||||
@@ -58,7 +71,7 @@ pub mod qr;
|
||||
pub mod securejoin;
|
||||
mod simplify;
|
||||
mod smtp;
|
||||
pub mod sql;
|
||||
mod sql;
|
||||
pub mod stock;
|
||||
mod token;
|
||||
#[macro_use]
|
||||
|
||||
@@ -202,7 +202,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
|
||||
SET locations_send_begin=?, \
|
||||
locations_send_until=? \
|
||||
WHERE id=?",
|
||||
params![
|
||||
paramsv![
|
||||
if 0 != seconds { now } else { 0 },
|
||||
if 0 != seconds { now + seconds } else { 0 },
|
||||
chat_id,
|
||||
@@ -213,16 +213,20 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds:
|
||||
{
|
||||
if 0 != seconds && !is_sending_locations_before {
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text =
|
||||
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0));
|
||||
msg.text = Some(
|
||||
context
|
||||
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)
|
||||
.await,
|
||||
);
|
||||
msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
|
||||
chat::send_msg(context, chat_id, &mut msg)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
} 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);
|
||||
let stock_str = context
|
||||
.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0)
|
||||
.await;
|
||||
chat::add_info_msg(context, chat_id, stock_str).await;
|
||||
}
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
if 0 != seconds {
|
||||
@@ -258,7 +262,7 @@ pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) ->
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;",
|
||||
params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
|
||||
paramsv![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
@@ -274,7 +278,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT id FROM chats WHERE locations_send_until>?;",
|
||||
params![time()],
|
||||
paramsv![time()],
|
||||
|row| row.get::<_, i32>(0),
|
||||
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
@@ -284,7 +288,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
|
||||
if let Err(err) = context.sql.execute(
|
||||
"INSERT INTO locations \
|
||||
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
|
||||
params![
|
||||
paramsv![
|
||||
latitude,
|
||||
longitude,
|
||||
accuracy,
|
||||
@@ -326,7 +330,7 @@ pub async fn get_range(
|
||||
AND (? OR l.from_id=?) \
|
||||
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
|
||||
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
|
||||
params![
|
||||
paramsv![
|
||||
if chat_id.is_unset() { 1 } else { 0 },
|
||||
chat_id,
|
||||
if contact_id == 0 { 1 } else { 0 },
|
||||
@@ -377,7 +381,7 @@ fn is_marker(txt: &str) -> bool {
|
||||
pub async fn delete_all(context: &Context) -> Result<(), Error> {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM locations;", params![])
|
||||
.execute("DELETE FROM locations;", paramsv![])
|
||||
.await?;
|
||||
context.call_cb(Event::LocationChanged(None));
|
||||
Ok(())
|
||||
@@ -393,7 +397,7 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)
|
||||
|
||||
let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until, locations_last_sent FROM chats WHERE id=?;",
|
||||
params![chat_id], |row| {
|
||||
paramsv![chat_id], |row| {
|
||||
let send_begin: i64 = row.get(0)?;
|
||||
let send_until: i64 = row.get(1)?;
|
||||
let last_sent: i64 = row.get(2)?;
|
||||
@@ -419,7 +423,7 @@ pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32)
|
||||
AND independent=0 \
|
||||
GROUP BY timestamp \
|
||||
ORDER BY timestamp;",
|
||||
params![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF],
|
||||
paramsv![DC_CONTACT_ID_SELF, locations_send_begin, locations_last_sent, DC_CONTACT_ID_SELF],
|
||||
|row| {
|
||||
let location_id: i32 = row.get(0)?;
|
||||
let latitude: f64 = row.get(1)?;
|
||||
@@ -486,7 +490,7 @@ pub async fn set_kml_sent_timestamp(
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE chats SET locations_last_sent=? WHERE id=?;",
|
||||
params![timestamp, chat_id],
|
||||
paramsv![timestamp, chat_id],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
@@ -501,7 +505,7 @@ pub async fn set_msg_location_id(
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET location_id=? WHERE id=?;",
|
||||
params![location_id, msg_id],
|
||||
paramsv![location_id, msg_id],
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -532,10 +536,10 @@ pub async fn save(
|
||||
VALUES (?,?,?,?,?,?,?);",
|
||||
)?;
|
||||
|
||||
let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?;
|
||||
let exists = stmt_test.exists(paramsv![location.timestamp, contact_id as i32])?;
|
||||
|
||||
if independent || !exists {
|
||||
stmt_insert.execute(params![
|
||||
stmt_insert.execute(paramsv![
|
||||
location.timestamp,
|
||||
contact_id as i32,
|
||||
chat_id,
|
||||
@@ -582,7 +586,7 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
||||
"SELECT id, locations_send_begin, locations_last_sent \
|
||||
FROM chats \
|
||||
WHERE locations_send_until>?;",
|
||||
params![now],
|
||||
paramsv![now],
|
||||
|row| {
|
||||
let chat_id: ChatId = row.get(0)?;
|
||||
let locations_send_begin: i64 = row.get(1)?;
|
||||
@@ -621,10 +625,10 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j
|
||||
.iter()
|
||||
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
|
||||
if !stmt_locations
|
||||
.exists(params![
|
||||
.exists(paramsv![
|
||||
DC_CONTACT_ID_SELF,
|
||||
locations_send_begin,
|
||||
locations_last_sent,
|
||||
*locations_send_begin,
|
||||
*locations_last_sent,
|
||||
])
|
||||
.unwrap_or_default()
|
||||
{
|
||||
@@ -682,7 +686,7 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?",
|
||||
params![chat_id],
|
||||
paramsv![chat_id],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
|
||||
)
|
||||
.await
|
||||
@@ -696,11 +700,13 @@ pub(crate) async fn job_maybe_send_locations_ended(
|
||||
// not streaming, device-message already sent
|
||||
job_try!(context.sql.execute(
|
||||
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
|
||||
params![chat_id],
|
||||
paramsv![chat_id],
|
||||
).await);
|
||||
|
||||
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0);
|
||||
chat::add_info_msg(context, chat_id, stock_str);
|
||||
let stock_str = context
|
||||
.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0)
|
||||
.await;
|
||||
chat::add_info_msg(context, chat_id, stock_str).await;
|
||||
context.call_cb(Event::ChatModified(chat_id));
|
||||
}
|
||||
}
|
||||
@@ -712,9 +718,9 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::dummy_context;
|
||||
|
||||
#[test]
|
||||
fn test_kml_parse() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_kml_parse() {
|
||||
let context = dummy_context().await;
|
||||
|
||||
let xml =
|
||||
b"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n<Document addr=\"user@example.org\">\n<Placemark><Timestamp><when>2019-03-06T21:09:57Z</when></Timestamp><Point><coordinates accuracy=\"32.000000\">9.423110,53.790302</coordinates></Point></Placemark>\n<PlaceMARK>\n<Timestamp><WHEN > \n\t2018-12-13T22:11:12Z\t</WHEN></Timestamp><Point><coordinates aCCuracy=\"2.500000\"> 19.423110 \t , \n 63.790302\n </coordinates></Point></PlaceMARK>\n</Document>\n</kml>";
|
||||
|
||||
349
src/message.rs
349
src/message.rs
@@ -251,7 +251,7 @@ impl Message {
|
||||
" FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
|
||||
" WHERE m.id=?;"
|
||||
),
|
||||
params![id],
|
||||
paramsv![id],
|
||||
|row| {
|
||||
let mut msg = Message::default();
|
||||
// msg.id = row.get::<_, AnyMsgId>("id")?;
|
||||
@@ -310,12 +310,12 @@ impl Message {
|
||||
if let Ok(msg) = Message::load_from_db(context, msg_id).await {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM msgs WHERE id=?;", params![msg.id])
|
||||
.execute("DELETE FROM msgs WHERE id=?;", paramsv![msg.id])
|
||||
.await
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", params![msg.id])
|
||||
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![msg.id])
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
@@ -339,7 +339,7 @@ impl Message {
|
||||
self.param.get_path(Param::File, context).unwrap_or(None)
|
||||
}
|
||||
|
||||
pub fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<(), Error> {
|
||||
pub async fn try_calc_and_set_dimensions(&mut self, context: &Context) -> Result<(), Error> {
|
||||
if chat::msgtype_has_file(self.viewtype) {
|
||||
let file_param = self.param.get_path(Param::File, context)?;
|
||||
if let Some(path_and_filename) = file_param {
|
||||
@@ -357,7 +357,7 @@ impl Message {
|
||||
}
|
||||
|
||||
if !self.id.is_unset() {
|
||||
self.save_param_to_disk(context);
|
||||
self.save_param_to_disk(context).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,12 +495,12 @@ impl Message {
|
||||
None
|
||||
};
|
||||
|
||||
ret.fill(self, chat, contact.as_ref(), context);
|
||||
ret.fill(self, chat, contact.as_ref(), context).await;
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String {
|
||||
pub async fn get_summarytext(&self, context: &Context, approx_characters: usize) -> String {
|
||||
get_summarytext_by_raw(
|
||||
self.viewtype,
|
||||
self.text.as_ref(),
|
||||
@@ -508,6 +508,7 @@ impl Message {
|
||||
approx_characters,
|
||||
context,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn has_deviating_timestamp(&self) -> bool {
|
||||
@@ -595,7 +596,7 @@ impl Message {
|
||||
self.param.set_int(Param::Duration, duration);
|
||||
}
|
||||
|
||||
pub fn latefiling_mediasize(
|
||||
pub async fn latefiling_mediasize(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
width: i32,
|
||||
@@ -609,7 +610,7 @@ impl Message {
|
||||
if duration > 0 {
|
||||
self.param.set_int(Param::Duration, duration);
|
||||
}
|
||||
self.save_param_to_disk(context);
|
||||
self.save_param_to_disk(context).await;
|
||||
}
|
||||
|
||||
pub async fn save_param_to_disk(&mut self, context: &Context) -> bool {
|
||||
@@ -617,7 +618,7 @@ impl Message {
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET param=? WHERE id=?;",
|
||||
params![self.param.to_string(), self.id],
|
||||
paramsv![self.param.to_string(), self.id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -740,7 +741,7 @@ impl MessageState {
|
||||
impl Lot {
|
||||
/* library-internal */
|
||||
/* in practice, the user additionally cuts the string himself pixel-accurate */
|
||||
pub fn fill(
|
||||
pub async fn fill(
|
||||
&mut self,
|
||||
msg: &mut Message,
|
||||
chat: &Chat,
|
||||
@@ -748,14 +749,26 @@ impl Lot {
|
||||
context: &Context,
|
||||
) {
|
||||
if msg.state == MessageState::OutDraft {
|
||||
self.text1 = Some(context.stock_str(StockMessage::Draft).to_owned().into());
|
||||
self.text1 = Some(
|
||||
context
|
||||
.stock_str(StockMessage::Draft)
|
||||
.await
|
||||
.to_owned()
|
||||
.into(),
|
||||
);
|
||||
self.text1_meaning = Meaning::Text1Draft;
|
||||
} else if msg.from_id == DC_CONTACT_ID_SELF {
|
||||
if msg.is_info() || chat.is_self_talk() {
|
||||
self.text1 = None;
|
||||
self.text1_meaning = Meaning::None;
|
||||
} else {
|
||||
self.text1 = Some(context.stock_str(StockMessage::SelfMsg).to_owned().into());
|
||||
self.text1 = Some(
|
||||
context
|
||||
.stock_str(StockMessage::SelfMsg)
|
||||
.await
|
||||
.to_owned()
|
||||
.into(),
|
||||
);
|
||||
self.text1_meaning = Meaning::Text1Self;
|
||||
}
|
||||
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
@@ -778,13 +791,16 @@ impl Lot {
|
||||
}
|
||||
}
|
||||
|
||||
self.text2 = Some(get_summarytext_by_raw(
|
||||
msg.viewtype,
|
||||
msg.text.as_ref(),
|
||||
&msg.param,
|
||||
SUMMARY_CHARACTERS,
|
||||
context,
|
||||
));
|
||||
self.text2 = Some(
|
||||
get_summarytext_by_raw(
|
||||
msg.viewtype,
|
||||
msg.text.as_ref(),
|
||||
&msg.param,
|
||||
SUMMARY_CHARACTERS,
|
||||
context,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
||||
self.timestamp = msg.get_timestamp();
|
||||
self.state = msg.state.into();
|
||||
@@ -806,7 +822,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT txt_raw FROM msgs WHERE id=?;",
|
||||
params![msg_id],
|
||||
paramsv![msg_id],
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -843,21 +859,26 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if let Ok(rows) = context.sql.query_map(
|
||||
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
|
||||
params![msg_id],
|
||||
|row| {
|
||||
let contact_id: i32 = row.get(0)?;
|
||||
let ts: i64 = row.get(1)?;
|
||||
Ok((contact_id, ts))
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
) {
|
||||
if let Ok(rows) = context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
|
||||
paramsv![msg_id],
|
||||
|row| {
|
||||
let contact_id: i32 = row.get(0)?;
|
||||
let ts: i64 = row.get(1)?;
|
||||
Ok((contact_id, ts))
|
||||
},
|
||||
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
|
||||
)
|
||||
.await
|
||||
{
|
||||
for (contact_id, ts) in rows {
|
||||
let fts = dc_timestamp_to_str(ts);
|
||||
ret += &format!("Read: {}", fts);
|
||||
|
||||
let name = Contact::load_from_db(context, contact_id as u32)
|
||||
.await
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -944,22 +965,25 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
|
||||
Some(info)
|
||||
}
|
||||
|
||||
pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
|
||||
context.sql.query_get_value(
|
||||
context,
|
||||
"SELECT mime_headers FROM msgs WHERE id=?;",
|
||||
params![msg_id],
|
||||
)
|
||||
pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
|
||||
context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT mime_headers FROM msgs WHERE id=?;",
|
||||
paramsv![msg_id],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
|
||||
for msg_id in msg_ids.iter() {
|
||||
if let Ok(msg) = Message::load_from_db(context, *msg_id).await {
|
||||
if msg.location_id > 0 {
|
||||
delete_poi_location(context, msg.location_id);
|
||||
delete_poi_location(context, msg.location_id).await;
|
||||
}
|
||||
}
|
||||
update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH));
|
||||
update_msg_chat_id(context, *msg_id, ChatId::new(DC_CHAT_ID_TRASH)).await;
|
||||
job::add(
|
||||
context,
|
||||
Action::DeleteMsgOnImap,
|
||||
@@ -985,7 +1009,7 @@ async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET chat_id=? WHERE id=?;",
|
||||
params![chat_id, msg_id],
|
||||
paramsv![chat_id, msg_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -996,7 +1020,7 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
|
||||
.sql
|
||||
.execute(
|
||||
"DELETE FROM locations WHERE independent = 1 AND id=?;",
|
||||
params![location_id as i32],
|
||||
paramsv![location_id as i32],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -1020,7 +1044,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
|
||||
let mut msgs = Vec::with_capacity(msg_ids.len());
|
||||
for id in msg_ids.iter() {
|
||||
let query_res = stmt.query_row(params![*id], |row| {
|
||||
let query_res = stmt.query_row(paramsv![*id], |row| {
|
||||
Ok((
|
||||
row.get::<_, MessageState>("state")?,
|
||||
row.get::<_, Option<Blocked>>("blocked")?
|
||||
@@ -1046,7 +1070,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
for (id, curr_state, curr_blocked) in msgs.into_iter() {
|
||||
if curr_blocked == Blocked::Not {
|
||||
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed {
|
||||
update_msg_state(context, *id, MessageState::InSeen);
|
||||
update_msg_state(context, *id, MessageState::InSeen).await;
|
||||
info!(context, "Seen message {}.", id);
|
||||
|
||||
job::add(
|
||||
@@ -1060,7 +1084,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
|
||||
send_event = true;
|
||||
}
|
||||
} else if curr_state == MessageState::InFresh {
|
||||
update_msg_state(context, *id, MessageState::InNoticed);
|
||||
update_msg_state(context, *id, MessageState::InNoticed).await;
|
||||
send_event = true;
|
||||
}
|
||||
}
|
||||
@@ -1080,29 +1104,31 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET state=? WHERE id=?;",
|
||||
params![state, msg_id],
|
||||
paramsv![state, msg_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool {
|
||||
pub async fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool {
|
||||
if msg_ids.is_empty() {
|
||||
return false;
|
||||
}
|
||||
context
|
||||
.sql
|
||||
.prepare("UPDATE msgs SET starred=? WHERE id=?;", |mut stmt, _| {
|
||||
.with_conn(|conn| {
|
||||
let mut stmt = conn.prepare("UPDATE msgs SET starred=? WHERE id=?;")?;
|
||||
for msg_id in msg_ids.iter() {
|
||||
stmt.execute(params![star as i32, *msg_id])?;
|
||||
stmt.execute(paramsv![star as i32, *msg_id])?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
/// Returns a summary test.
|
||||
pub fn get_summarytext_by_raw(
|
||||
pub async fn get_summarytext_by_raw(
|
||||
viewtype: Viewtype,
|
||||
text: Option<impl AsRef<str>>,
|
||||
param: &Params,
|
||||
@@ -1111,16 +1137,20 @@ pub fn get_summarytext_by_raw(
|
||||
) -> String {
|
||||
let mut append_text = true;
|
||||
let prefix = match viewtype {
|
||||
Viewtype::Image => context.stock_str(StockMessage::Image).into_owned(),
|
||||
Viewtype::Gif => context.stock_str(StockMessage::Gif).into_owned(),
|
||||
Viewtype::Sticker => context.stock_str(StockMessage::Sticker).into_owned(),
|
||||
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(),
|
||||
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(),
|
||||
Viewtype::Image => context.stock_str(StockMessage::Image).await.into_owned(),
|
||||
Viewtype::Gif => context.stock_str(StockMessage::Gif).await.into_owned(),
|
||||
Viewtype::Sticker => context.stock_str(StockMessage::Sticker).await.into_owned(),
|
||||
Viewtype::Video => context.stock_str(StockMessage::Video).await.into_owned(),
|
||||
Viewtype::Voice => context
|
||||
.stock_str(StockMessage::VoiceMessage)
|
||||
.await
|
||||
.into_owned(),
|
||||
Viewtype::Audio | Viewtype::File => {
|
||||
if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
append_text = false;
|
||||
context
|
||||
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||
.await
|
||||
.to_string()
|
||||
} else {
|
||||
let file_name: String = param
|
||||
@@ -1131,11 +1161,13 @@ pub fn get_summarytext_by_raw(
|
||||
.map(|fname| fname.to_string_lossy().into_owned())
|
||||
})
|
||||
.unwrap_or_else(|| String::from("ErrFileName"));
|
||||
let label = context.stock_str(if viewtype == Viewtype::Audio {
|
||||
StockMessage::Audio
|
||||
} else {
|
||||
StockMessage::File
|
||||
});
|
||||
let label = context
|
||||
.stock_str(if viewtype == Viewtype::Audio {
|
||||
StockMessage::Audio
|
||||
} else {
|
||||
StockMessage::File
|
||||
})
|
||||
.await;
|
||||
format!("{} – {}", label, file_name)
|
||||
}
|
||||
}
|
||||
@@ -1144,7 +1176,7 @@ pub fn get_summarytext_by_raw(
|
||||
"".to_string()
|
||||
} else {
|
||||
append_text = false;
|
||||
context.stock_str(StockMessage::Location).to_string()
|
||||
context.stock_str(StockMessage::Location).await.to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1176,16 +1208,19 @@ pub fn get_summarytext_by_raw(
|
||||
|
||||
// Context functions to work with messages
|
||||
|
||||
pub fn exists(context: &Context, msg_id: MsgId) -> bool {
|
||||
pub async fn exists(context: &Context, msg_id: MsgId) -> bool {
|
||||
if msg_id.is_special() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let chat_id: Option<ChatId> = context.sql.query_get_value(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?;",
|
||||
params![msg_id],
|
||||
);
|
||||
let chat_id: Option<ChatId> = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT chat_id FROM msgs WHERE id=?;",
|
||||
paramsv![msg_id],
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Some(chat_id) = chat_id {
|
||||
!chat_id.is_trash()
|
||||
@@ -1208,7 +1243,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET state=?, param=? WHERE id=?;",
|
||||
params![msg.state, msg.param.to_string(), msg_id],
|
||||
paramsv![msg.state, msg.param.to_string(), msg_id],
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
@@ -1222,7 +1257,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl
|
||||
}
|
||||
|
||||
/// returns Some if an event should be send
|
||||
pub fn mdn_from_ext(
|
||||
pub async fn mdn_from_ext(
|
||||
context: &Context,
|
||||
from_id: u32,
|
||||
rfc724_mid: &str,
|
||||
@@ -1232,27 +1267,30 @@ pub fn mdn_from_ext(
|
||||
return None;
|
||||
}
|
||||
|
||||
let res = context.sql.query_row(
|
||||
concat!(
|
||||
"SELECT",
|
||||
" m.id AS msg_id,",
|
||||
" c.id AS chat_id,",
|
||||
" c.type AS type,",
|
||||
" m.state AS state",
|
||||
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
|
||||
" WHERE rfc724_mid=? AND from_id=1",
|
||||
" ORDER BY m.id;"
|
||||
),
|
||||
params![rfc724_mid],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, MsgId>("msg_id")?,
|
||||
row.get::<_, ChatId>("chat_id")?,
|
||||
row.get::<_, Chattype>("type")?,
|
||||
row.get::<_, MessageState>("state")?,
|
||||
))
|
||||
},
|
||||
);
|
||||
let res = context
|
||||
.sql
|
||||
.query_row(
|
||||
concat!(
|
||||
"SELECT",
|
||||
" m.id AS msg_id,",
|
||||
" c.id AS chat_id,",
|
||||
" c.type AS type,",
|
||||
" m.state AS state",
|
||||
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
|
||||
" WHERE rfc724_mid=? AND from_id=1",
|
||||
" ORDER BY m.id;"
|
||||
),
|
||||
paramsv![rfc724_mid],
|
||||
|row| {
|
||||
Ok((
|
||||
row.get::<_, MsgId>("msg_id")?,
|
||||
row.get::<_, ChatId>("chat_id")?,
|
||||
row.get::<_, Chattype>("type")?,
|
||||
row.get::<_, MessageState>("state")?,
|
||||
))
|
||||
},
|
||||
)
|
||||
.await;
|
||||
if let Err(ref err) = res {
|
||||
info!(context, "Failed to select MDN {:?}", err);
|
||||
}
|
||||
@@ -1268,30 +1306,34 @@ pub fn mdn_from_ext(
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;",
|
||||
params![msg_id, from_id as i32,],
|
||||
paramsv![msg_id, from_id as i32,],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if !mdn_already_in_table {
|
||||
context.sql.execute(
|
||||
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
|
||||
params![msg_id, from_id as i32, timestamp_sent],
|
||||
).unwrap_or_default(); // TODO: better error handling
|
||||
paramsv![msg_id, from_id as i32, timestamp_sent],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default(); // TODO: better error handling
|
||||
}
|
||||
|
||||
// Normal chat? that's quite easy.
|
||||
if chat_type == Chattype::Single {
|
||||
update_msg_state(context, msg_id, MessageState::OutMdnRcvd);
|
||||
update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await;
|
||||
read_by_all = true;
|
||||
} else {
|
||||
// send event about new state
|
||||
let ist_cnt = context
|
||||
.sql
|
||||
.query_get_value::<_, isize>(
|
||||
.query_get_value::<isize>(
|
||||
context,
|
||||
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
|
||||
params![msg_id],
|
||||
paramsv![msg_id],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default() as usize;
|
||||
/*
|
||||
Groupsize: Min. MDNs
|
||||
@@ -1306,9 +1348,9 @@ pub fn mdn_from_ext(
|
||||
(S=Sender, R=Recipient)
|
||||
*/
|
||||
// for rounding, SELF is already included!
|
||||
let soll_cnt = (chat::get_chat_contact_cnt(context, chat_id) + 1) / 2;
|
||||
let soll_cnt = (chat::get_chat_contact_cnt(context, chat_id).await + 1) / 2;
|
||||
if ist_cnt >= soll_cnt {
|
||||
update_msg_state(context, msg_id, MessageState::OutMdnRcvd);
|
||||
update_msg_state(context, msg_id, MessageState::OutMdnRcvd).await;
|
||||
read_by_all = true;
|
||||
} // else wait for more receipts
|
||||
}
|
||||
@@ -1323,14 +1365,18 @@ pub fn mdn_from_ext(
|
||||
}
|
||||
|
||||
/// The number of messages assigned to real chat (!=deaddrop, !=trash)
|
||||
pub fn get_real_msg_cnt(context: &Context) -> i32 {
|
||||
match context.sql.query_row(
|
||||
"SELECT COUNT(*) \
|
||||
pub async fn get_real_msg_cnt(context: &Context) -> i32 {
|
||||
match context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT COUNT(*) \
|
||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
|
||||
rusqlite::NO_PARAMS,
|
||||
|row| row.get(0),
|
||||
) {
|
||||
paramsv![],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
error!(context, "dc_get_real_msg_cnt() failed. {}", err);
|
||||
@@ -1339,14 +1385,18 @@ pub fn get_real_msg_cnt(context: &Context) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_deaddrop_msg_cnt(context: &Context) -> usize {
|
||||
match context.sql.query_row(
|
||||
"SELECT COUNT(*) \
|
||||
pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize {
|
||||
match context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT COUNT(*) \
|
||||
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
|
||||
WHERE c.blocked=2;",
|
||||
rusqlite::NO_PARAMS,
|
||||
|row| row.get::<_, isize>(0),
|
||||
) {
|
||||
paramsv![],
|
||||
|row| row.get::<_, isize>(0),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res as usize,
|
||||
Err(err) => {
|
||||
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", err);
|
||||
@@ -1355,13 +1405,17 @@ pub fn get_deaddrop_msg_cnt(context: &Context) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
|
||||
pub async fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
|
||||
// check the number of messages with the same rfc724_mid
|
||||
match context.sql.query_row(
|
||||
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;",
|
||||
&[rfc724_mid],
|
||||
|row| row.get(0),
|
||||
) {
|
||||
match context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;",
|
||||
paramsv![rfc724_mid],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
error!(context, "dc_get_rfc724_mid_cnt() failed. {}", err);
|
||||
@@ -1370,17 +1424,17 @@ pub fn rfc724_mid_cnt(context: &Context, rfc724_mid: &str) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn rfc724_mid_exists(
|
||||
pub(crate) async fn rfc724_mid_exists(
|
||||
context: &Context,
|
||||
rfc724_mid: &str,
|
||||
) -> Result<(String, u32, MsgId), Error> {
|
||||
ensure!(!rfc724_mid.is_empty(), "empty rfc724_mid");
|
||||
|
||||
context
|
||||
let res = context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
|
||||
&[rfc724_mid],
|
||||
paramsv![rfc724_mid],
|
||||
|row| {
|
||||
let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default();
|
||||
let server_uid = row.get(1)?;
|
||||
@@ -1389,19 +1443,25 @@ pub(crate) fn rfc724_mid_exists(
|
||||
Ok((server_folder, server_uid, msg_id))
|
||||
},
|
||||
)
|
||||
.map_err(Into::into)
|
||||
.await?;
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn update_server_uid(
|
||||
pub async fn update_server_uid(
|
||||
context: &Context,
|
||||
rfc724_mid: &str,
|
||||
server_folder: impl AsRef<str>,
|
||||
server_uid: u32,
|
||||
) {
|
||||
match context.sql.execute(
|
||||
"UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;",
|
||||
params![server_folder.as_ref(), server_uid, rfc724_mid],
|
||||
) {
|
||||
match context
|
||||
.sql
|
||||
.execute(
|
||||
"UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;",
|
||||
paramsv![server_folder.as_ref().to_string(), server_uid, rfc724_mid],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
warn!(context, "msg: failed to update server_uid: {}", err);
|
||||
@@ -1432,7 +1492,7 @@ mod tests {
|
||||
async fn test_prepare_message_and_send() {
|
||||
use crate::config::Config;
|
||||
|
||||
let d = test::dummy_context();
|
||||
let d = test::dummy_context().await;
|
||||
let ctx = &d.ctx;
|
||||
|
||||
let contact = Contact::create(ctx, "", "dest@example.com")
|
||||
@@ -1454,9 +1514,9 @@ mod tests {
|
||||
assert_eq!(_msg2.get_filemime(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_get_summarytext_by_raw() {
|
||||
let d = test::dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_get_summarytext_by_raw() {
|
||||
let d = test::dummy_context().await;
|
||||
let ctx = &d.ctx;
|
||||
|
||||
let some_text = Some("bla bla".to_string());
|
||||
@@ -1467,62 +1527,69 @@ mod tests {
|
||||
some_file.set(Param::File, "foo.bar");
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Text, some_text.as_ref(), &Params::new(), 50, &ctx),
|
||||
get_summarytext_by_raw(Viewtype::Text, some_text.as_ref(), &Params::new(), 50, &ctx)
|
||||
.await,
|
||||
"bla bla" // for simple text, the type is not added to the summary
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Image, no_text.as_ref(), &some_file, 50, &ctx).await,
|
||||
"Image" // file names are not added for images
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Video, no_text.as_ref(), &some_file, 50, &ctx).await,
|
||||
"Video" // file names are not added for videos
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Gif, no_text.as_ref(), &some_file, 50, &ctx,).await,
|
||||
"GIF" // file names are not added for GIFs
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Sticker, no_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Sticker, no_text.as_ref(), &some_file, 50, &ctx,)
|
||||
.await,
|
||||
"Sticker" // file names are not added for stickers
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Voice, empty_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Voice, empty_text.as_ref(), &some_file, 50, &ctx,)
|
||||
.await,
|
||||
"Voice message" // file names are not added for voice messages, empty text is skipped
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Voice, no_text.as_ref(), &mut some_file, 50, &ctx),
|
||||
get_summarytext_by_raw(Viewtype::Voice, no_text.as_ref(), &mut some_file, 50, &ctx)
|
||||
.await,
|
||||
"Voice message" // file names are not added for voice messages
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Voice, some_text.as_ref(), &some_file, 50, &ctx),
|
||||
get_summarytext_by_raw(Viewtype::Voice, some_text.as_ref(), &some_file, 50, &ctx).await,
|
||||
"Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Audio, no_text.as_ref(), &mut some_file, 50, &ctx),
|
||||
get_summarytext_by_raw(Viewtype::Audio, no_text.as_ref(), &mut some_file, 50, &ctx)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar" // file name is added for audio
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Audio, empty_text.as_ref(), &some_file, 50, &ctx,),
|
||||
get_summarytext_by_raw(Viewtype::Audio, empty_text.as_ref(), &some_file, 50, &ctx,)
|
||||
.await,
|
||||
"Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::Audio, some_text.as_ref(), &some_file, 50, &ctx),
|
||||
get_summarytext_by_raw(Viewtype::Audio, some_text.as_ref(), &some_file, 50, &ctx).await,
|
||||
"Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::File, some_text.as_ref(), &mut some_file, 50, &ctx),
|
||||
get_summarytext_by_raw(Viewtype::File, some_text.as_ref(), &mut some_file, 50, &ctx)
|
||||
.await,
|
||||
"File \u{2013} foo.bar \u{2013} bla bla" // file name is added for files
|
||||
);
|
||||
|
||||
@@ -1530,7 +1597,7 @@ mod tests {
|
||||
asm_file.set(Param::File, "foo.bar");
|
||||
asm_file.set_cmd(SystemMessage::AutocryptSetupMessage);
|
||||
assert_eq!(
|
||||
get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), &mut asm_file, 50, &ctx),
|
||||
get_summarytext_by_raw(Viewtype::File, no_text.as_ref(), &mut asm_file, 50, &ctx).await,
|
||||
"Autocrypt Setup Message" // file name is not added for autocrypt setup messages
|
||||
);
|
||||
}
|
||||
|
||||
@@ -65,44 +65,51 @@ pub struct RenderedEmail {
|
||||
}
|
||||
|
||||
impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
pub fn from_msg(
|
||||
pub async fn from_msg(
|
||||
context: &'a Context,
|
||||
msg: &'b Message,
|
||||
attach_selfavatar: bool,
|
||||
) -> Result<MimeFactory<'a, 'b>, Error> {
|
||||
let chat = Chat::load_from_db(context, msg.chat_id)?;
|
||||
let chat = Chat::load_from_db(context, msg.chat_id).await?;
|
||||
|
||||
let from_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let from_displayname = context
|
||||
.get_config(Config::Displayname)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let from_displayname = context.get_config(Config::Displayname).unwrap_or_default();
|
||||
let mut recipients = Vec::with_capacity(5);
|
||||
let mut req_mdn = false;
|
||||
|
||||
if chat.is_self_talk() {
|
||||
recipients.push((from_displayname.to_string(), from_addr.to_string()));
|
||||
} else {
|
||||
context.sql.query_map(
|
||||
"SELECT c.authname, c.addr \
|
||||
context
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT c.authname, c.addr \
|
||||
FROM chats_contacts cc \
|
||||
LEFT JOIN contacts c ON cc.contact_id=c.id \
|
||||
WHERE cc.chat_id=? AND cc.contact_id>9;",
|
||||
params![msg.chat_id],
|
||||
|row| {
|
||||
let authname: String = row.get(0)?;
|
||||
let addr: String = row.get(1)?;
|
||||
Ok((authname, addr))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (authname, addr) = row?;
|
||||
if !recipients_contain_addr(&recipients, &addr) {
|
||||
recipients.push((authname, addr));
|
||||
paramsv![msg.chat_id],
|
||||
|row| {
|
||||
let authname: String = row.get(0)?;
|
||||
let addr: String = row.get(1)?;
|
||||
Ok((authname, addr))
|
||||
},
|
||||
|rows| {
|
||||
for row in rows {
|
||||
let (authname, addr) = row?;
|
||||
if !recipients_contain_addr(&recipients, &addr) {
|
||||
recipients.push((authname, addr));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let command = msg.param.get_cmd();
|
||||
|
||||
@@ -112,6 +119,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
|
||||
let self_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if !email_to_remove.is_empty()
|
||||
@@ -123,31 +131,39 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
if command != SystemMessage::AutocryptSetupMessage
|
||||
&& command != SystemMessage::SecurejoinMessage
|
||||
&& context.get_config_bool(Config::MdnsEnabled)
|
||||
&& context.get_config_bool(Config::MdnsEnabled).await
|
||||
{
|
||||
req_mdn = true;
|
||||
}
|
||||
}
|
||||
let (in_reply_to, references) = context.sql.query_row(
|
||||
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
|
||||
params![msg.id],
|
||||
|row| {
|
||||
let in_reply_to: String = row.get(0)?;
|
||||
let references: String = row.get(1)?;
|
||||
let (in_reply_to, references) = context
|
||||
.sql
|
||||
.query_row(
|
||||
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
|
||||
paramsv![msg.id],
|
||||
|row| {
|
||||
let in_reply_to: String = row.get(0)?;
|
||||
let references: String = row.get(1)?;
|
||||
|
||||
Ok((
|
||||
render_rfc724_mid_list(&in_reply_to),
|
||||
render_rfc724_mid_list(&references),
|
||||
))
|
||||
},
|
||||
)?;
|
||||
Ok((
|
||||
render_rfc724_mid_list(&in_reply_to),
|
||||
render_rfc724_mid_list(&references),
|
||||
))
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let default_str = context
|
||||
.stock_str(StockMessage::StatusLine)
|
||||
.await
|
||||
.to_string();
|
||||
let factory = MimeFactory {
|
||||
from_addr,
|
||||
from_displayname,
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
.await
|
||||
.unwrap_or_else(|| default_str),
|
||||
recipients,
|
||||
timestamp: msg.timestamp_sort,
|
||||
loaded: Loaded::Message { chat },
|
||||
@@ -162,29 +178,42 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
Ok(factory)
|
||||
}
|
||||
|
||||
pub fn from_mdn(
|
||||
pub async fn from_mdn(
|
||||
context: &'a Context,
|
||||
msg: &'b Message,
|
||||
additional_msg_ids: Vec<String>,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<MimeFactory<'a, 'b>, Error> {
|
||||
ensure!(!msg.chat_id.is_special(), "Invalid chat id");
|
||||
|
||||
let contact = Contact::load_from_db(context, msg.from_id)?;
|
||||
let contact = Contact::load_from_db(context, msg.from_id).await?;
|
||||
let from_addr = context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let from_displayname = context
|
||||
.get_config(Config::Displayname)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let default_str = context
|
||||
.stock_str(StockMessage::StatusLine)
|
||||
.await
|
||||
.to_string();
|
||||
let selfstatus = context
|
||||
.get_config(Config::Selfstatus)
|
||||
.await
|
||||
.unwrap_or_else(|| default_str);
|
||||
let timestamp = dc_create_smeared_timestamp(context).await;
|
||||
|
||||
Ok(MimeFactory {
|
||||
let res = MimeFactory::<'a, 'b> {
|
||||
context,
|
||||
from_addr: context
|
||||
.get_config(Config::ConfiguredAddr)
|
||||
.unwrap_or_default(),
|
||||
from_displayname: context.get_config(Config::Displayname).unwrap_or_default(),
|
||||
selfstatus: context
|
||||
.get_config(Config::Selfstatus)
|
||||
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()),
|
||||
from_addr,
|
||||
from_displayname,
|
||||
selfstatus,
|
||||
recipients: vec![(
|
||||
contact.get_authname().to_string(),
|
||||
contact.get_addr().to_string(),
|
||||
)],
|
||||
timestamp: dc_create_smeared_timestamp(context),
|
||||
timestamp,
|
||||
loaded: Loaded::MDN { additional_msg_ids },
|
||||
msg,
|
||||
in_reply_to: String::default(),
|
||||
@@ -192,7 +221,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
req_mdn: false,
|
||||
last_added_location_id: 0,
|
||||
attach_selfavatar: false,
|
||||
})
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate<'_>>, &str)>, Error> {
|
||||
@@ -202,17 +233,19 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
.await
|
||||
.ok_or_else(|| format_err!("Not configured"))?;
|
||||
|
||||
Ok(self
|
||||
let mut res = Vec::new();
|
||||
for (_, addr) in self
|
||||
.recipients
|
||||
.iter()
|
||||
.filter(|(_, addr)| addr != &self_addr)
|
||||
.map(|(_, addr)| {
|
||||
(
|
||||
Peerstate::from_addr(self.context, &self.context.sql, addr),
|
||||
addr.as_str(),
|
||||
)
|
||||
})
|
||||
.collect())
|
||||
{
|
||||
res.push((
|
||||
Peerstate::from_addr(self.context, &self.context.sql, addr).await,
|
||||
addr.as_str(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn is_e2ee_guaranteed(&self) -> bool {
|
||||
@@ -272,11 +305,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
fn should_do_gossip(&self) -> bool {
|
||||
async fn should_do_gossip(&self) -> bool {
|
||||
match &self.loaded {
|
||||
Loaded::Message { chat } => {
|
||||
// beside key- and member-changes, force re-gossip every 48 hours
|
||||
let gossiped_timestamp = chat.get_gossiped_timestamp(self.context);
|
||||
let gossiped_timestamp = chat.get_gossiped_timestamp(self.context).await;
|
||||
if time() > gossiped_timestamp + (2 * 24 * 60 * 60) {
|
||||
return true;
|
||||
}
|
||||
@@ -317,12 +350,13 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
fn subject_str(&self) -> String {
|
||||
async fn subject_str(&self) -> String {
|
||||
match self.loaded {
|
||||
Loaded::Message { ref chat } => {
|
||||
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
|
||||
self.context
|
||||
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||
.await
|
||||
.into_owned()
|
||||
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
|
||||
let re = if self.in_reply_to.is_empty() {
|
||||
@@ -338,12 +372,17 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
&self.msg.param,
|
||||
32,
|
||||
self.context,
|
||||
);
|
||||
)
|
||||
.await;
|
||||
let raw_subject = raw.lines().next().unwrap_or_default();
|
||||
format!("Chat: {}", raw_subject)
|
||||
}
|
||||
}
|
||||
Loaded::MDN { .. } => self.context.stock_str(StockMessage::ReadRcpt).into_owned(),
|
||||
Loaded::MDN { .. } => self
|
||||
.context
|
||||
.stock_str(StockMessage::ReadRcpt)
|
||||
.await
|
||||
.into_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -354,7 +393,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn render(mut self) -> Result<RenderedEmail, Error> {
|
||||
pub async fn render(mut self) -> Result<RenderedEmail, Error> {
|
||||
// Headers that are encrypted
|
||||
// - Chat-*, except Chat-Version
|
||||
// - Secure-Join*
|
||||
@@ -433,17 +472,18 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
let min_verified = self.min_verified();
|
||||
let grpimage = self.grpimage();
|
||||
let force_plaintext = self.should_force_plaintext();
|
||||
let subject_str = self.subject_str();
|
||||
let subject_str = self.subject_str().await;
|
||||
let e2ee_guaranteed = self.is_e2ee_guaranteed();
|
||||
let mut encrypt_helper = EncryptHelper::new(self.context)?;
|
||||
let mut encrypt_helper = EncryptHelper::new(self.context).await?;
|
||||
|
||||
let subject = encode_words(&subject_str);
|
||||
|
||||
let mut message = match self.loaded {
|
||||
Loaded::Message { .. } => {
|
||||
self.render_message(&mut protected_headers, &mut unprotected_headers, &grpimage)?
|
||||
self.render_message(&mut protected_headers, &mut unprotected_headers, &grpimage)
|
||||
.await?
|
||||
}
|
||||
Loaded::MDN { .. } => self.render_mdn()?,
|
||||
Loaded::MDN { .. } => self.render_mdn().await?,
|
||||
};
|
||||
|
||||
if force_plaintext != ForcePlaintext::NoAutocryptHeader as i32 {
|
||||
@@ -454,7 +494,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
|
||||
protected_headers.push(Header::new("Subject".into(), subject));
|
||||
|
||||
let peerstates = self.peerstates_for_recipients()?;
|
||||
let peerstates = self.peerstates_for_recipients().await?;
|
||||
let should_encrypt =
|
||||
encrypt_helper.should_encrypt(self.context, e2ee_guaranteed, &peerstates)?;
|
||||
let is_encrypted = should_encrypt && force_plaintext == 0;
|
||||
@@ -482,7 +522,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
|
||||
let outer_message = if is_encrypted {
|
||||
// Add gossip headers in chats with multiple recipients
|
||||
if peerstates.len() > 1 && self.should_do_gossip() {
|
||||
if peerstates.len() > 1 && self.should_do_gossip().await {
|
||||
for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
|
||||
if peerstate.peek_key(min_verified).is_some() {
|
||||
if let Some(header) = peerstate.render_gossip_header(min_verified) {
|
||||
@@ -530,8 +570,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
println!("{}", raw_message);
|
||||
}
|
||||
|
||||
let encrypted =
|
||||
encrypt_helper.encrypt(self.context, min_verified, message, &peerstates)?;
|
||||
let encrypted = encrypt_helper
|
||||
.encrypt(self.context, min_verified, message, &peerstates)
|
||||
.await?;
|
||||
|
||||
outer_message = outer_message
|
||||
.child(
|
||||
@@ -603,9 +644,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
Some(part)
|
||||
}
|
||||
|
||||
fn get_location_kml_part(&mut self) -> Result<PartBuilder, Error> {
|
||||
async fn get_location_kml_part(&mut self) -> Result<PartBuilder, Error> {
|
||||
let (kml_content, last_added_location_id) =
|
||||
location::get_kml(self.context, self.msg.chat_id)?;
|
||||
location::get_kml(self.context, self.msg.chat_id).await?;
|
||||
let part = PartBuilder::new()
|
||||
.content_type(
|
||||
&"application/vnd.google-earth.kml+xml"
|
||||
@@ -625,7 +666,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
#[allow(clippy::cognitive_complexity)]
|
||||
fn render_message(
|
||||
async fn render_message(
|
||||
&mut self,
|
||||
protected_headers: &mut Vec<Header>,
|
||||
unprotected_headers: &mut Vec<Header>,
|
||||
@@ -720,6 +761,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
placeholdertext = Some(
|
||||
self.context
|
||||
.stock_str(StockMessage::AcSetupMsgBody)
|
||||
.await
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
@@ -856,8 +898,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
parts.push(msg_kml_part);
|
||||
}
|
||||
|
||||
if location::is_sending_locations_to_chat(context, self.msg.chat_id) {
|
||||
match self.get_location_kml_part() {
|
||||
if location::is_sending_locations_to_chat(context, self.msg.chat_id).await {
|
||||
match self.get_location_kml_part().await {
|
||||
Ok(part) => parts.push(part),
|
||||
Err(err) => {
|
||||
warn!(context, "mimefactory: could not send location: {}", err);
|
||||
@@ -866,7 +908,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
if self.attach_selfavatar {
|
||||
match context.get_config(Config::Selfavatar) {
|
||||
match context.get_config(Config::Selfavatar).await {
|
||||
Some(path) => match build_selfavatar_file(context, &path) {
|
||||
Ok((part, filename)) => {
|
||||
parts.push(part);
|
||||
@@ -893,7 +935,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
}
|
||||
|
||||
/// Render an MDN
|
||||
fn render_mdn(&mut self) -> Result<PartBuilder, Error> {
|
||||
async fn render_mdn(&mut self) -> Result<PartBuilder, Error> {
|
||||
// RFC 6522, this also requires the `report-type` parameter which is equal
|
||||
// to the MIME subtype of the second body part of the multipart/report
|
||||
//
|
||||
@@ -928,13 +970,15 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
||||
{
|
||||
self.context
|
||||
.stock_str(StockMessage::EncryptedMsg)
|
||||
.await
|
||||
.into_owned()
|
||||
} else {
|
||||
self.msg.get_summarytext(self.context, 32)
|
||||
self.msg.get_summarytext(self.context, 32).await
|
||||
};
|
||||
let p2 = self
|
||||
.context
|
||||
.stock_string_repl_str(StockMessage::ReadRcptMailBody, p1);
|
||||
.stock_string_repl_str(StockMessage::ReadRcptMailBody, p1)
|
||||
.await;
|
||||
let message_text = format!("{}\r\n", p2);
|
||||
message = message.child(
|
||||
PartBuilder::new()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
||||
use deltachat_derive::{FromSql, ToSql};
|
||||
use lettre_email::mime::{self, Mime};
|
||||
@@ -156,7 +158,7 @@ impl MimeMessage {
|
||||
user_avatar: None,
|
||||
group_avatar: None,
|
||||
};
|
||||
parser.parse_mime_recursive(context, &mail)?;
|
||||
parser.parse_mime_recursive(context, &mail).await?;
|
||||
parser.parse_headers(context)?;
|
||||
|
||||
Ok(parser)
|
||||
@@ -405,63 +407,69 @@ impl MimeMessage {
|
||||
None
|
||||
}
|
||||
|
||||
fn parse_mime_recursive(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
mail: &mailparse::ParsedMail<'_>,
|
||||
) -> Result<bool> {
|
||||
if mail.ctype.params.get("protected-headers").is_some() {
|
||||
if mail.ctype.mimetype == "text/rfc822-headers" {
|
||||
warn!(
|
||||
fn parse_mime_recursive<'a>(
|
||||
&'a mut self,
|
||||
context: &'a Context,
|
||||
mail: &'a mailparse::ParsedMail<'a>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<bool>> + 'a + Send>> {
|
||||
use futures::future::FutureExt;
|
||||
|
||||
// Boxed future to deal with recursion
|
||||
async move {
|
||||
if mail.ctype.params.get("protected-headers").is_some() {
|
||||
if mail.ctype.mimetype == "text/rfc822-headers" {
|
||||
warn!(
|
||||
context,
|
||||
"Protected headers found in text/rfc822-headers attachment: Will be ignored.",
|
||||
);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
warn!(context, "Ignoring nested protected headers");
|
||||
}
|
||||
|
||||
enum MimeS {
|
||||
Multiple,
|
||||
Single,
|
||||
Message,
|
||||
}
|
||||
|
||||
let mimetype = mail.ctype.mimetype.to_lowercase();
|
||||
|
||||
let m = if mimetype.starts_with("multipart") {
|
||||
if mail.ctype.params.get("boundary").is_some() {
|
||||
MimeS::Multiple
|
||||
} else {
|
||||
MimeS::Single
|
||||
}
|
||||
} else if mimetype.starts_with("message") {
|
||||
if mimetype == "message/rfc822" {
|
||||
MimeS::Message
|
||||
} else {
|
||||
MimeS::Single
|
||||
}
|
||||
} else {
|
||||
MimeS::Single
|
||||
};
|
||||
|
||||
match m {
|
||||
MimeS::Multiple => self.handle_multiple(context, mail),
|
||||
MimeS::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mail = mailparse::parse_mail(&raw).unwrap();
|
||||
|
||||
self.parse_mime_recursive(context, &mail)
|
||||
warn!(context, "Ignoring nested protected headers");
|
||||
}
|
||||
|
||||
enum MimeS {
|
||||
Multiple,
|
||||
Single,
|
||||
Message,
|
||||
}
|
||||
|
||||
let mimetype = mail.ctype.mimetype.to_lowercase();
|
||||
|
||||
let m = if mimetype.starts_with("multipart") {
|
||||
if mail.ctype.params.get("boundary").is_some() {
|
||||
MimeS::Multiple
|
||||
} else {
|
||||
MimeS::Single
|
||||
}
|
||||
} else if mimetype.starts_with("message") {
|
||||
if mimetype == "message/rfc822" {
|
||||
MimeS::Message
|
||||
} else {
|
||||
MimeS::Single
|
||||
}
|
||||
} else {
|
||||
MimeS::Single
|
||||
};
|
||||
|
||||
match m {
|
||||
MimeS::Multiple => self.handle_multiple(context, mail).await,
|
||||
MimeS::Message => {
|
||||
let raw = mail.get_body_raw()?;
|
||||
if raw.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
let mail = mailparse::parse_mail(&raw).unwrap();
|
||||
|
||||
self.parse_mime_recursive(context, &mail).await
|
||||
}
|
||||
MimeS::Single => self.add_single_part_if_known(context, mail),
|
||||
}
|
||||
MimeS::Single => self.add_single_part_if_known(context, mail),
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
|
||||
fn handle_multiple(
|
||||
async fn handle_multiple(
|
||||
&mut self,
|
||||
context: &Context,
|
||||
mail: &mailparse::ParsedMail<'_>,
|
||||
@@ -476,7 +484,7 @@ impl MimeMessage {
|
||||
(mime::MULTIPART, "alternative") => {
|
||||
for cur_data in &mail.subparts {
|
||||
if get_mime_type(cur_data)?.0 == "multipart/mixed" {
|
||||
any_part_added = self.parse_mime_recursive(context, cur_data)?;
|
||||
any_part_added = self.parse_mime_recursive(context, cur_data).await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -484,7 +492,7 @@ impl MimeMessage {
|
||||
/* search for text/plain and add this */
|
||||
for cur_data in &mail.subparts {
|
||||
if get_mime_type(cur_data)?.0.type_() == mime::TEXT {
|
||||
any_part_added = self.parse_mime_recursive(context, cur_data)?;
|
||||
any_part_added = self.parse_mime_recursive(context, cur_data).await?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -492,7 +500,7 @@ impl MimeMessage {
|
||||
if !any_part_added {
|
||||
/* `text/plain` not found - use the first part */
|
||||
for cur_part in &mail.subparts {
|
||||
if self.parse_mime_recursive(context, cur_part)? {
|
||||
if self.parse_mime_recursive(context, cur_part).await? {
|
||||
any_part_added = true;
|
||||
break;
|
||||
}
|
||||
@@ -505,14 +513,14 @@ impl MimeMessage {
|
||||
being the first one, which may not be always true ...
|
||||
however, most times it seems okay. */
|
||||
if let Some(first) = mail.subparts.iter().next() {
|
||||
any_part_added = self.parse_mime_recursive(context, first)?;
|
||||
any_part_added = self.parse_mime_recursive(context, first).await?;
|
||||
}
|
||||
}
|
||||
(mime::MULTIPART, "encrypted") => {
|
||||
// we currently do not try to decrypt non-autocrypt messages
|
||||
// at all. If we see an encrypted part, we set
|
||||
// decrypting_failed.
|
||||
let msg_body = context.stock_str(StockMessage::CantDecryptMsgBody);
|
||||
let msg_body = context.stock_str(StockMessage::CantDecryptMsgBody).await;
|
||||
let txt = format!("[{}]", msg_body);
|
||||
|
||||
let mut part = Part::default();
|
||||
@@ -535,7 +543,7 @@ impl MimeMessage {
|
||||
https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html
|
||||
for background information why we use encrypted+signed) */
|
||||
if let Some(first) = mail.subparts.iter().next() {
|
||||
any_part_added = self.parse_mime_recursive(context, first)?;
|
||||
any_part_added = self.parse_mime_recursive(context, first).await?;
|
||||
}
|
||||
}
|
||||
(mime::MULTIPART, "report") => {
|
||||
@@ -550,7 +558,7 @@ impl MimeMessage {
|
||||
/* eg. `report-type=delivery-status`;
|
||||
maybe we should show them as a little error icon */
|
||||
if let Some(first) = mail.subparts.iter().next() {
|
||||
any_part_added = self.parse_mime_recursive(context, first)?;
|
||||
any_part_added = self.parse_mime_recursive(context, first).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -560,7 +568,7 @@ impl MimeMessage {
|
||||
// Add all parts (in fact, AddSinglePartIfKnown() later check if
|
||||
// the parts are really supported)
|
||||
for cur_data in mail.subparts.iter() {
|
||||
if self.parse_mime_recursive(context, cur_data)? {
|
||||
if self.parse_mime_recursive(context, cur_data).await? {
|
||||
any_part_added = true;
|
||||
}
|
||||
}
|
||||
@@ -827,6 +835,7 @@ impl MimeMessage {
|
||||
{
|
||||
if let Some((chat_id, msg_id)) =
|
||||
message::mdn_from_ext(context, from_id, original_message_id, sent_timestamp)
|
||||
.await
|
||||
{
|
||||
context.call_cb(Event::MsgRead { chat_id, msg_id });
|
||||
mdn_recognized = true;
|
||||
@@ -876,7 +885,7 @@ async fn update_gossip_peerstates(
|
||||
.unwrap()
|
||||
.contains(&header.addr.to_lowercase())
|
||||
{
|
||||
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr);
|
||||
let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr).await;
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
peerstate.apply_gossip(header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false).await?;
|
||||
@@ -1103,21 +1112,25 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_mimeparser_crash() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_dc_mimeparser_crash() {
|
||||
let context = dummy_context().await;
|
||||
let raw = include_bytes!("../test-data/message/issue_523.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(mimeparser.get_subject(), None);
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_rfc724_mid_exists() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_get_rfc724_mid_exists() {
|
||||
let context = dummy_context().await;
|
||||
let raw = include_bytes!("../test-data/message/mail_with_message_id.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
mimeparser.get_rfc724_mid(),
|
||||
@@ -1125,19 +1138,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_rfc724_mid_not_exists() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_get_rfc724_mid_not_exists() {
|
||||
let context = dummy_context().await;
|
||||
let raw = include_bytes!("../test-data/message/issue_523.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(mimeparser.get_rfc724_mid(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_recipients() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_get_recipients() {
|
||||
let context = dummy_context().await;
|
||||
let raw = include_bytes!("../test-data/message/mail_with_cc.txt");
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
let recipients = get_recipients(mimeparser.header.iter());
|
||||
assert!(recipients.contains("abc@bcd.com"));
|
||||
assert!(recipients.contains("def@def.de"));
|
||||
@@ -1182,9 +1199,9 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_first_addr() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_parse_first_addr() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"From: hello@one.org, world@two.org\n\
|
||||
Chat-Disposition-Notification-To: wrong\n\
|
||||
Content-Type: text/plain\n\
|
||||
@@ -1193,7 +1210,9 @@ mod tests {
|
||||
test1\n\
|
||||
";
|
||||
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let of = mimeparser
|
||||
.parse_first_addr(&context.ctx, HeaderDef::From_)
|
||||
@@ -1205,9 +1224,9 @@ mod tests {
|
||||
assert!(of.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mimeparser_with_context() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_mimeparser_with_context() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"From: hello\n\
|
||||
Content-Type: multipart/mixed; boundary=\"==break==\";\n\
|
||||
Subject: outer-subject\n\
|
||||
@@ -1226,7 +1245,9 @@ mod tests {
|
||||
--==break==--\n\
|
||||
\n";
|
||||
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// non-overwritten headers do not bubble up
|
||||
let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap();
|
||||
@@ -1246,31 +1267,31 @@ mod tests {
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mimeparser_with_avatars() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_mimeparser_with_avatars() {
|
||||
let t = dummy_context().await;
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_attach_txt.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
|
||||
assert_eq!(mimeparser.user_avatar, None);
|
||||
assert_eq!(mimeparser.group_avatar, None);
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_avatar.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
|
||||
assert!(mimeparser.user_avatar.unwrap().is_change());
|
||||
assert_eq!(mimeparser.group_avatar, None);
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_avatar_deleted.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
|
||||
assert_eq!(mimeparser.user_avatar, Some(AvatarAction::Delete));
|
||||
assert_eq!(mimeparser.group_avatar, None);
|
||||
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, &raw[..]).await.unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
|
||||
assert!(mimeparser.user_avatar.unwrap().is_change());
|
||||
@@ -1280,16 +1301,18 @@ mod tests {
|
||||
let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml");
|
||||
let raw = String::from_utf8_lossy(raw).to_string();
|
||||
let raw = raw.replace("Chat-User-Avatar:", "Xhat-Xser-Xvatar:");
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, raw.as_bytes()).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&t.ctx, raw.as_bytes())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
assert_eq!(mimeparser.parts[0].typ, Viewtype::Image);
|
||||
assert_eq!(mimeparser.user_avatar, None);
|
||||
assert!(mimeparser.group_avatar.unwrap().is_change());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mimeparser_message_kml() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_mimeparser_message_kml() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"Chat-Version: 1.0\n\
|
||||
From: foo <foo@example.org>\n\
|
||||
To: bar <bar@example.org>\n\
|
||||
@@ -1317,7 +1340,9 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\
|
||||
--==break==--\n\
|
||||
;";
|
||||
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
mimeparser.get_subject(),
|
||||
Some("Location streaming".to_string())
|
||||
@@ -1330,9 +1355,9 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\
|
||||
assert_eq!(mimeparser.parts.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mdn() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_parse_mdn() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
|
||||
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
|
||||
Chat-Version: 1.0\n\
|
||||
@@ -1364,7 +1389,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
|
||||
";
|
||||
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
message.get_subject(),
|
||||
Some("Chat: Message opened".to_string())
|
||||
@@ -1378,9 +1405,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||
///
|
||||
/// RFC 6522 specifically allows MDNs to be nested inside
|
||||
/// multipart MIME messages.
|
||||
#[test]
|
||||
fn test_parse_multiple_mdns() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_parse_multiple_mdns() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
|
||||
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
|
||||
Chat-Version: 1.0\n\
|
||||
@@ -1442,7 +1469,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||
--outer--\n\
|
||||
";
|
||||
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
message.get_subject(),
|
||||
Some("Chat: Message opened".to_string())
|
||||
@@ -1452,9 +1481,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
|
||||
assert_eq!(message.reports.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_mdn_with_additional_message_ids() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_parse_mdn_with_additional_message_ids() {
|
||||
let context = dummy_context().await;
|
||||
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
|
||||
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
|
||||
Chat-Version: 1.0\n\
|
||||
@@ -1487,7 +1516,9 @@ Additional-Message-IDs: <foo@example.com> <foo@example.net>\n\
|
||||
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
|
||||
";
|
||||
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
message.get_subject(),
|
||||
Some("Chat: Message opened".to_string())
|
||||
@@ -1502,9 +1533,9 @@ Additional-Message-IDs: <foo@example.com> <foo@example.net>\n\
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_inline_attachment() {
|
||||
let context = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_parse_inline_attachment() {
|
||||
let context = dummy_context().await;
|
||||
let raw = br#"Date: Thu, 13 Feb 2020 22:41:20 +0000 (UTC)
|
||||
From: sender@example.com
|
||||
To: receiver@example.com
|
||||
@@ -1529,7 +1560,9 @@ MDYyMDYxNTE1RTlDOEE4Cj4+CnN0YXJ0eHJlZgo4Mjc4CiUlRU9GCg==
|
||||
------=_Part_25_46172632.1581201680436--
|
||||
"#;
|
||||
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap();
|
||||
let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
message.get_subject(),
|
||||
Some("Mail with inline attachment".to_string())
|
||||
|
||||
@@ -48,7 +48,7 @@ struct Response {
|
||||
scope: Option<String>,
|
||||
}
|
||||
|
||||
pub fn dc_get_oauth2_url(
|
||||
pub async fn dc_get_oauth2_url(
|
||||
context: &Context,
|
||||
addr: impl AsRef<str>,
|
||||
redirect_uri: impl AsRef<str>,
|
||||
@@ -61,6 +61,7 @@ pub fn dc_get_oauth2_url(
|
||||
"oauth2_pending_redirect_uri",
|
||||
Some(redirect_uri.as_ref()),
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
return None;
|
||||
@@ -76,7 +77,7 @@ pub fn dc_get_oauth2_url(
|
||||
|
||||
// The following function may block due http-requests;
|
||||
// must not be called from the main thread or by the ui!
|
||||
pub fn dc_get_oauth2_access_token(
|
||||
pub async fn dc_get_oauth2_access_token(
|
||||
context: &Context,
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
@@ -84,11 +85,14 @@ pub fn dc_get_oauth2_access_token(
|
||||
) -> Option<String> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
let lock = context.oauth2_critical.clone();
|
||||
let _l = lock.lock().unwrap();
|
||||
let _l = lock.lock().await;
|
||||
|
||||
// read generated token
|
||||
if !regenerate && !is_expired(context) {
|
||||
let access_token = context.sql.get_raw_config(context, "oauth2_access_token");
|
||||
if !regenerate && !is_expired(context).await {
|
||||
let access_token = context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_access_token")
|
||||
.await;
|
||||
if access_token.is_some() {
|
||||
// success
|
||||
return access_token;
|
||||
@@ -96,10 +100,14 @@ pub fn dc_get_oauth2_access_token(
|
||||
}
|
||||
|
||||
// generate new token: build & call auth url
|
||||
let refresh_token = context.sql.get_raw_config(context, "oauth2_refresh_token");
|
||||
let refresh_token = context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_refresh_token")
|
||||
.await;
|
||||
let refresh_token_for = context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_refresh_token_for")
|
||||
.await
|
||||
.unwrap_or_else(|| "unset".into());
|
||||
|
||||
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||
@@ -109,6 +117,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_pending_redirect_uri")
|
||||
.await
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.init_token,
|
||||
true,
|
||||
@@ -122,6 +131,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
context
|
||||
.sql
|
||||
.get_raw_config(context, "oauth2_redirect_uri")
|
||||
.await
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.refresh_token,
|
||||
false,
|
||||
@@ -193,10 +203,12 @@ pub fn dc_get_oauth2_access_token(
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_refresh_token", Some(token))
|
||||
.await
|
||||
.ok();
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -206,6 +218,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_access_token", Some(token))
|
||||
.await
|
||||
.ok();
|
||||
let expires_in = response
|
||||
.expires_in
|
||||
@@ -215,12 +228,14 @@ pub fn dc_get_oauth2_access_token(
|
||||
context
|
||||
.sql
|
||||
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
|
||||
.await
|
||||
.ok();
|
||||
|
||||
if update_redirect_uri_on_success {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
} else {
|
||||
@@ -235,7 +250,7 @@ pub fn dc_get_oauth2_access_token(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_get_oauth2_addr(
|
||||
pub async fn dc_get_oauth2_addr(
|
||||
context: &Context,
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
@@ -244,12 +259,13 @@ pub fn dc_get_oauth2_addr(
|
||||
oauth2.get_userinfo?;
|
||||
|
||||
if let Some(access_token) =
|
||||
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false)
|
||||
dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), false).await
|
||||
{
|
||||
let addr_out = oauth2.get_addr(context, access_token);
|
||||
if addr_out.is_none() {
|
||||
// regenerate
|
||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true) {
|
||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, true).await
|
||||
{
|
||||
oauth2.get_addr(context, access_token)
|
||||
} else {
|
||||
None
|
||||
@@ -331,10 +347,11 @@ impl Oauth2 {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_expired(context: &Context) -> bool {
|
||||
async fn is_expired(context: &Context) -> bool {
|
||||
let expire_timestamp = context
|
||||
.sql
|
||||
.get_raw_config_int64(context, "oauth2_timestamp_expires")
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if expire_timestamp <= 0 {
|
||||
@@ -393,32 +410,32 @@ mod tests {
|
||||
assert_eq!(Oauth2::from_address("hello@web.de"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_addr() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_dc_get_oauth2_addr() {
|
||||
let ctx = dummy_context().await;
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
|
||||
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code).await;
|
||||
// this should fail as it is an invalid password
|
||||
assert_eq!(res, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_url() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_dc_get_oauth2_url() {
|
||||
let ctx = dummy_context().await;
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let redirect_uri = "chat.delta:/com.b44t.messenger";
|
||||
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
|
||||
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri).await;
|
||||
|
||||
assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878%2D4mvtgf6feshskf7695nfln6002mom908%2Eapps%2Egoogleusercontent%2Ecom&redirect_uri=chat%2Edelta%3A%2Fcom%2Eb44t%2Emessenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_token() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_dc_get_oauth2_token() {
|
||||
let ctx = dummy_context().await;
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, false);
|
||||
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, false).await;
|
||||
// this should fail as it is an invalid password
|
||||
assert_eq!(res, None);
|
||||
}
|
||||
|
||||
18
src/param.rs
18
src/param.rs
@@ -417,9 +417,9 @@ mod tests {
|
||||
assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_params_file_fs_path() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_params_file_fs_path() {
|
||||
let t = dummy_context().await;
|
||||
if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() {
|
||||
assert_eq!(p, Path::new("/foo/bar/baz"));
|
||||
} else {
|
||||
@@ -427,9 +427,9 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_params_file_blob() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_params_file_blob() {
|
||||
let t = dummy_context().await;
|
||||
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() {
|
||||
assert_eq!(b.as_name(), "$BLOBDIR/foo");
|
||||
} else {
|
||||
@@ -438,9 +438,9 @@ mod tests {
|
||||
}
|
||||
|
||||
// Tests for Params::get_file(), Params::get_path() and Params::get_blob().
|
||||
#[test]
|
||||
fn test_params_get_fileparam() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_params_get_fileparam() {
|
||||
let t = dummy_context().await;
|
||||
let fname = t.dir.path().join("foo");
|
||||
let mut p = Params::new();
|
||||
p.set(Param::File, fname.to_str().unwrap());
|
||||
|
||||
@@ -144,12 +144,16 @@ impl<'a> Peerstate<'a> {
|
||||
res
|
||||
}
|
||||
|
||||
pub fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option<Self> {
|
||||
pub async fn from_addr(context: &'a Context, _sql: &Sql, addr: &str) -> Option<Peerstate<'a>> {
|
||||
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, verified_key, verified_key_fingerprint FROM acpeerstates WHERE addr=? COLLATE NOCASE;";
|
||||
Self::from_stmt(context, query, &[addr])
|
||||
Self::from_stmt(context, query, paramsv![addr]).await
|
||||
}
|
||||
|
||||
pub fn from_fingerprint(context: &'a Context, _sql: &Sql, fingerprint: &str) -> Option<Self> {
|
||||
pub async fn from_fingerprint(
|
||||
context: &'a Context,
|
||||
_sql: &Sql,
|
||||
fingerprint: &str,
|
||||
) -> Option<Peerstate<'a>> {
|
||||
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
|
||||
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
|
||||
verified_key, verified_key_fingerprint \
|
||||
@@ -161,15 +165,16 @@ impl<'a> Peerstate<'a> {
|
||||
Self::from_stmt(
|
||||
context,
|
||||
query,
|
||||
params![fingerprint, fingerprint, fingerprint],
|
||||
paramsv![fingerprint, fingerprint, fingerprint],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self>
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: rusqlite::ToSql,
|
||||
{
|
||||
async fn from_stmt(
|
||||
context: &'a Context,
|
||||
query: &str,
|
||||
params: Vec<&dyn crate::ToSql>,
|
||||
) -> Option<Peerstate<'a>> {
|
||||
context
|
||||
.sql
|
||||
.query_row(query, params, |row| {
|
||||
@@ -227,6 +232,7 @@ impl<'a> Peerstate<'a> {
|
||||
|
||||
Ok(res)
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
@@ -413,7 +419,7 @@ impl<'a> Peerstate<'a> {
|
||||
if create {
|
||||
sql.execute(
|
||||
"INSERT INTO acpeerstates (addr) VALUES(?);",
|
||||
params![self.addr],
|
||||
paramsv![self.addr],
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -425,29 +431,29 @@ impl<'a> Peerstate<'a> {
|
||||
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
|
||||
verified_key=?, verified_key_fingerprint=? \
|
||||
WHERE addr=?;",
|
||||
params![
|
||||
paramsv![
|
||||
self.last_seen,
|
||||
self.last_seen_autocrypt,
|
||||
self.prefer_encrypt as i64,
|
||||
self.public_key.as_ref().map(|k| k.to_bytes()),
|
||||
self.gossip_timestamp,
|
||||
self.gossip_key.as_ref().map(|k| k.to_bytes()),
|
||||
&self.public_key_fingerprint,
|
||||
&self.gossip_key_fingerprint,
|
||||
self.public_key_fingerprint,
|
||||
self.gossip_key_fingerprint,
|
||||
self.verified_key.as_ref().map(|k| k.to_bytes()),
|
||||
&self.verified_key_fingerprint,
|
||||
&self.addr,
|
||||
self.verified_key_fingerprint,
|
||||
self.addr,
|
||||
],
|
||||
).await?;
|
||||
} else if self.to_save == Some(ToSave::Timestamps) {
|
||||
sql.execute(
|
||||
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
|
||||
WHERE addr=?;",
|
||||
params![
|
||||
paramsv![
|
||||
self.last_seen,
|
||||
self.last_seen_autocrypt,
|
||||
self.gossip_timestamp,
|
||||
&self.addr
|
||||
self.addr
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
@@ -475,9 +481,9 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_save_to_db() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_peerstate_save_to_db() {
|
||||
let ctx = crate::test_utils::dummy_context().await;
|
||||
let addr = "hello@mail.com";
|
||||
|
||||
let pub_key = crate::key::Key::from(alice_keypair().public);
|
||||
@@ -500,11 +506,12 @@ mod tests {
|
||||
};
|
||||
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(),
|
||||
"failed to save to db"
|
||||
);
|
||||
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||
.await
|
||||
.expect("failed to load peerstate from db");
|
||||
|
||||
// clear to_save, as that is not persissted
|
||||
@@ -512,13 +519,14 @@ mod tests {
|
||||
assert_eq!(peerstate, peerstate_new);
|
||||
let peerstate_new2 =
|
||||
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
|
||||
.await
|
||||
.expect("failed to load peerstate from db");
|
||||
assert_eq!(peerstate, peerstate_new2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_double_create() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_peerstate_double_create() {
|
||||
let ctx = crate::test_utils::dummy_context().await;
|
||||
let addr = "hello@mail.com";
|
||||
let pub_key = crate::key::Key::from(alice_keypair().public);
|
||||
|
||||
@@ -540,18 +548,18 @@ mod tests {
|
||||
};
|
||||
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(),
|
||||
"failed to save"
|
||||
);
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(),
|
||||
"double-call with create failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_peerstate_with_empty_gossip_key_save_to_db() {
|
||||
let ctx = crate::test_utils::dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_peerstate_with_empty_gossip_key_save_to_db() {
|
||||
let ctx = crate::test_utils::dummy_context().await;
|
||||
let addr = "hello@mail.com";
|
||||
|
||||
let pub_key = crate::key::Key::from(alice_keypair().public);
|
||||
@@ -574,11 +582,12 @@ mod tests {
|
||||
};
|
||||
|
||||
assert!(
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).is_ok(),
|
||||
peerstate.save_to_db(&ctx.ctx.sql, true).await.is_ok(),
|
||||
"failed to save"
|
||||
);
|
||||
|
||||
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
|
||||
.await
|
||||
.expect("failed to load peerstate from db");
|
||||
|
||||
// clear to_save, as that is not persissted
|
||||
|
||||
154
src/qr.rs
154
src/qr.rs
@@ -50,13 +50,13 @@ pub async fn check_qr(context: &Context, qr: impl AsRef<str>) -> Lot {
|
||||
} else if qr.starts_with(DCACCOUNT_SCHEME) {
|
||||
decode_account(context, qr)
|
||||
} else if qr.starts_with(MAILTO_SCHEME) {
|
||||
decode_mailto(context, qr)
|
||||
decode_mailto(context, qr).await
|
||||
} else if qr.starts_with(SMTP_SCHEME) {
|
||||
decode_smtp(context, qr)
|
||||
decode_smtp(context, qr).await
|
||||
} else if qr.starts_with(MATMSG_SCHEME) {
|
||||
decode_matmsg(context, qr)
|
||||
decode_matmsg(context, qr).await
|
||||
} else if qr.starts_with(VCARD_SCHEME) {
|
||||
decode_vcard(context, qr)
|
||||
decode_vcard(context, qr).await
|
||||
} else if qr.starts_with(HTTP_SCHEME) || qr.starts_with(HTTPS_SCHEME) {
|
||||
Lot::from_url(qr)
|
||||
} else {
|
||||
@@ -140,7 +140,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
let mut lot = Lot::new();
|
||||
|
||||
// retrieve known state for this fingerprint
|
||||
let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint);
|
||||
let peerstate = Peerstate::from_fingerprint(context, &context.sql, &fingerprint).await;
|
||||
|
||||
if invitenumber.is_none() || auth.is_none() {
|
||||
if let Some(peerstate) = peerstate {
|
||||
@@ -160,7 +160,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr));
|
||||
chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)).await;
|
||||
} else {
|
||||
lot.state = LotState::QrFprWithoutAddr;
|
||||
lot.text1 = Some(dc_format_fingerprint(&fingerprint));
|
||||
@@ -262,7 +262,7 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<(), Error
|
||||
/// Extract address for the mailto scheme.
|
||||
///
|
||||
/// Scheme: `mailto:addr...?subject=...&body=..`
|
||||
fn decode_mailto(context: &Context, qr: &str) -> Lot {
|
||||
async fn decode_mailto(context: &Context, qr: &str) -> Lot {
|
||||
let payload = &qr[MAILTO_SCHEME.len()..];
|
||||
|
||||
let addr = if let Some(query_index) = payload.find('?') {
|
||||
@@ -277,13 +277,13 @@ fn decode_mailto(context: &Context, qr: &str) -> Lot {
|
||||
};
|
||||
|
||||
let name = "".to_string();
|
||||
Lot::from_address(context, name, addr)
|
||||
Lot::from_address(context, name, addr).await
|
||||
}
|
||||
|
||||
/// Extract address for the smtp scheme.
|
||||
///
|
||||
/// Scheme: `SMTP:addr...:subject...:body...`
|
||||
fn decode_smtp(context: &Context, qr: &str) -> Lot {
|
||||
async fn decode_smtp(context: &Context, qr: &str) -> Lot {
|
||||
let payload = &qr[SMTP_SCHEME.len()..];
|
||||
|
||||
let addr = if let Some(query_index) = payload.find(':') {
|
||||
@@ -298,7 +298,7 @@ fn decode_smtp(context: &Context, qr: &str) -> Lot {
|
||||
};
|
||||
|
||||
let name = "".to_string();
|
||||
Lot::from_address(context, name, addr)
|
||||
Lot::from_address(context, name, addr).await
|
||||
}
|
||||
|
||||
/// Extract address for the matmsg scheme.
|
||||
@@ -306,7 +306,7 @@ fn decode_smtp(context: &Context, qr: &str) -> Lot {
|
||||
/// Scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;`
|
||||
///
|
||||
/// There may or may not be linebreaks after the fields.
|
||||
fn decode_matmsg(context: &Context, qr: &str) -> Lot {
|
||||
async fn decode_matmsg(context: &Context, qr: &str) -> Lot {
|
||||
// Does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field.
|
||||
// we ignore this case.
|
||||
let addr = if let Some(to_index) = qr.find("TO:") {
|
||||
@@ -326,7 +326,7 @@ fn decode_matmsg(context: &Context, qr: &str) -> Lot {
|
||||
};
|
||||
|
||||
let name = "".to_string();
|
||||
Lot::from_address(context, name, addr)
|
||||
Lot::from_address(context, name, addr).await
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
@@ -339,7 +339,7 @@ lazy_static! {
|
||||
/// Extract address for the matmsg scheme.
|
||||
///
|
||||
/// Scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL;<type>:addr...;
|
||||
fn decode_vcard(context: &Context, qr: &str) -> Lot {
|
||||
async fn decode_vcard(context: &Context, qr: &str) -> Lot {
|
||||
let name = VCARD_NAME_RE
|
||||
.captures(qr)
|
||||
.map(|caps| {
|
||||
@@ -359,7 +359,7 @@ fn decode_vcard(context: &Context, qr: &str) -> Lot {
|
||||
return format_err!("Bad e-mail address").into();
|
||||
};
|
||||
|
||||
Lot::from_address(context, name, addr)
|
||||
Lot::from_address(context, name, addr).await
|
||||
}
|
||||
|
||||
impl Lot {
|
||||
@@ -379,10 +379,10 @@ impl Lot {
|
||||
l
|
||||
}
|
||||
|
||||
pub fn from_address(context: &Context, name: String, addr: String) -> Self {
|
||||
pub async fn from_address(context: &Context, name: String, addr: String) -> Self {
|
||||
let mut l = Lot::new();
|
||||
l.state = LotState::QrAddr;
|
||||
l.id = match Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan) {
|
||||
l.id = match Contact::add_or_lookup(context, name, addr, Origin::UnhandledQrScan).await {
|
||||
Ok((id, _)) => id,
|
||||
Err(err) => return err.into(),
|
||||
};
|
||||
@@ -408,11 +408,11 @@ mod tests {
|
||||
|
||||
use crate::test_utils::dummy_context;
|
||||
|
||||
#[test]
|
||||
fn test_decode_http() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_http() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(&ctx.ctx, "http://www.hello.com");
|
||||
let res = check_qr(&ctx.ctx, "http://www.hello.com").await;
|
||||
|
||||
assert_eq!(res.get_state(), LotState::QrUrl);
|
||||
assert_eq!(res.get_id(), 0);
|
||||
@@ -420,11 +420,11 @@ mod tests {
|
||||
assert!(res.get_text2().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_https() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_https() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(&ctx.ctx, "https://www.hello.com");
|
||||
let res = check_qr(&ctx.ctx, "https://www.hello.com").await;
|
||||
|
||||
assert_eq!(res.get_state(), LotState::QrUrl);
|
||||
assert_eq!(res.get_id(), 0);
|
||||
@@ -432,11 +432,11 @@ mod tests {
|
||||
assert!(res.get_text2().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_text() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_text() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(&ctx.ctx, "I am so cool");
|
||||
let res = check_qr(&ctx.ctx, "I am so cool").await;
|
||||
|
||||
assert_eq!(res.get_state(), LotState::QrText);
|
||||
assert_eq!(res.get_id(), 0);
|
||||
@@ -444,124 +444,127 @@ mod tests {
|
||||
assert!(res.get_text2().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_vcard() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_vcard() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD"
|
||||
);
|
||||
).await;
|
||||
|
||||
println!("{:?}", res);
|
||||
assert_eq!(res.get_state(), LotState::QrAddr);
|
||||
assert_ne!(res.get_id(), 0);
|
||||
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap();
|
||||
assert_eq!(contact.get_addr(), "stress@test.local");
|
||||
assert_eq!(contact.get_name(), "First Last");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_matmsg() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_matmsg() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"MATMSG:TO:\n\nstress@test.local ; \n\nSUB:\n\nSubject here\n\nBODY:\n\nhelloworld\n;;",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
|
||||
println!("{:?}", res);
|
||||
assert_eq!(res.get_state(), LotState::QrAddr);
|
||||
assert_ne!(res.get_id(), 0);
|
||||
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap();
|
||||
assert_eq!(contact.get_addr(), "stress@test.local");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_mailto() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_mailto() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"mailto:stress@test.local?subject=hello&body=world",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
println!("{:?}", res);
|
||||
assert_eq!(res.get_state(), LotState::QrAddr);
|
||||
assert_ne!(res.get_id(), 0);
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap();
|
||||
assert_eq!(contact.get_addr(), "stress@test.local");
|
||||
|
||||
let res = check_qr(&ctx.ctx, "mailto:no-questionmark@example.org");
|
||||
let res = check_qr(&ctx.ctx, "mailto:no-questionmark@example.org").await;
|
||||
assert_eq!(res.get_state(), LotState::QrAddr);
|
||||
assert_ne!(res.get_id(), 0);
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap();
|
||||
assert_eq!(contact.get_addr(), "no-questionmark@example.org");
|
||||
|
||||
let res = check_qr(&ctx.ctx, "mailto:no-addr");
|
||||
let res = check_qr(&ctx.ctx, "mailto:no-addr").await;
|
||||
assert_eq!(res.get_state(), LotState::QrError);
|
||||
assert!(res.get_text1().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_smtp() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_smtp() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld");
|
||||
let res = check_qr(&ctx.ctx, "SMTP:stress@test.local:subjecthello:bodyworld").await;
|
||||
|
||||
println!("{:?}", res);
|
||||
assert_eq!(res.get_state(), LotState::QrAddr);
|
||||
assert_ne!(res.get_id(), 0);
|
||||
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap();
|
||||
assert_eq!(contact.get_addr(), "stress@test.local");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_openpgp_group() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_openpgp_group() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=test%20%3F+test%20%21&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL"
|
||||
);
|
||||
).await;
|
||||
|
||||
println!("{:?}", res);
|
||||
assert_eq!(res.get_state(), LotState::QrAskVerifyGroup);
|
||||
assert_ne!(res.get_id(), 0);
|
||||
assert_eq!(res.get_text1().unwrap(), "test ? test !");
|
||||
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap();
|
||||
assert_eq!(contact.get_addr(), "cli@deltachat.de");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_openpgp_secure_join() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_openpgp_secure_join() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=J%C3%B6rn%20P.+P.&i=TbnwJ6lSvD5&s=0ejvbdFSQxB"
|
||||
);
|
||||
).await;
|
||||
|
||||
println!("{:?}", res);
|
||||
assert_eq!(res.get_state(), LotState::QrAskVerifyContact);
|
||||
assert_ne!(res.get_id(), 0);
|
||||
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).unwrap();
|
||||
let contact = Contact::get_by_id(&ctx.ctx, res.get_id()).await.unwrap();
|
||||
assert_eq!(contact.get_addr(), "cli@deltachat.de");
|
||||
assert_eq!(contact.get_name(), "Jörn P. P.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_openpgp_without_addr() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_openpgp_without_addr() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"OPENPGP4FPR:1234567890123456789012345678901234567890",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
assert_eq!(res.get_state(), LotState::QrFprWithoutAddr);
|
||||
assert_eq!(
|
||||
res.get_text1().unwrap(),
|
||||
@@ -569,31 +572,32 @@ mod tests {
|
||||
);
|
||||
assert_eq!(res.get_id(), 0);
|
||||
|
||||
let res = check_qr(&ctx.ctx, "OPENPGP4FPR:12345678901234567890");
|
||||
let res = check_qr(&ctx.ctx, "OPENPGP4FPR:12345678901234567890").await;
|
||||
assert_eq!(res.get_state(), LotState::QrError);
|
||||
assert_eq!(res.get_id(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_account() {
|
||||
let ctx = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_decode_account() {
|
||||
let ctx = dummy_context().await;
|
||||
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
assert_eq!(res.get_state(), LotState::QrAccount);
|
||||
assert_eq!(res.get_text1().unwrap(), "example.org");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_account_bad_scheme() {
|
||||
let ctx = dummy_context();
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_decode_account_bad_scheme() {
|
||||
let ctx = dummy_context().await;
|
||||
let res = check_qr(
|
||||
&ctx.ctx,
|
||||
"DCACCOUNT:http://example.org/new_email?t=1w_7wDjgjelxeX884x96v3",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
assert_eq!(res.get_state(), LotState::QrError);
|
||||
assert!(res.get_text1().is_some());
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ macro_rules! get_qr_attr {
|
||||
$context
|
||||
.bob
|
||||
.read()
|
||||
.unwrap()
|
||||
.await
|
||||
.qr_scan
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
@@ -65,7 +65,7 @@ macro_rules! get_qr_attr {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option<String> {
|
||||
pub async fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option<String> {
|
||||
/*=======================================================
|
||||
==== Alice - the inviter side ====
|
||||
==== Step 1 in "Setup verified contact" protocol ====
|
||||
@@ -73,13 +73,14 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option<
|
||||
|
||||
let fingerprint: String;
|
||||
|
||||
ensure_secret_key_exists(context).ok();
|
||||
ensure_secret_key_exists(context).await.ok();
|
||||
|
||||
// invitenumber will be used to allow starting the handshake,
|
||||
// auth will be used to verify the fingerprint
|
||||
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id);
|
||||
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id);
|
||||
let self_addr = match context.get_config(Config::ConfiguredAddr) {
|
||||
let invitenumber =
|
||||
token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id).await;
|
||||
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id).await;
|
||||
let self_addr = match context.get_config(Config::ConfiguredAddr).await {
|
||||
Some(addr) => addr,
|
||||
None => {
|
||||
error!(context, "Not configured, cannot generate QR code.",);
|
||||
@@ -87,9 +88,12 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option<
|
||||
}
|
||||
};
|
||||
|
||||
let self_name = context.get_config(Config::Displayname).unwrap_or_default();
|
||||
let self_name = context
|
||||
.get_config(Config::Displayname)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
fingerprint = match get_self_fingerprint(context) {
|
||||
fingerprint = match get_self_fingerprint(context).await {
|
||||
Some(fp) => fp,
|
||||
None => {
|
||||
return None;
|
||||
@@ -103,7 +107,7 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option<
|
||||
|
||||
let qr = if !group_chat_id.is_unset() {
|
||||
// parameters used: a=g=x=i=s=
|
||||
if let Ok(chat) = Chat::load_from_db(context, group_chat_id) {
|
||||
if let Ok(chat) = Chat::load_from_db(context, group_chat_id).await {
|
||||
let group_name = chat.get_name();
|
||||
let group_name_urlencoded =
|
||||
utf8_percent_encode(&group_name, NON_ALPHANUMERIC).to_string();
|
||||
@@ -134,9 +138,9 @@ pub fn dc_get_securejoin_qr(context: &Context, group_chat_id: ChatId) -> Option<
|
||||
qr
|
||||
}
|
||||
|
||||
fn get_self_fingerprint(context: &Context) -> Option<String> {
|
||||
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) {
|
||||
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) {
|
||||
async fn get_self_fingerprint(context: &Context) -> Option<String> {
|
||||
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await {
|
||||
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql).await {
|
||||
return Some(key.fingerprint());
|
||||
}
|
||||
}
|
||||
@@ -149,7 +153,7 @@ async fn cleanup(
|
||||
ongoing_allocated: bool,
|
||||
join_vg: bool,
|
||||
) -> ChatId {
|
||||
let mut bob = context.bob.write().unwrap();
|
||||
let mut bob = context.bob.write().await;
|
||||
bob.expects = 0;
|
||||
let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS {
|
||||
if join_vg {
|
||||
@@ -169,7 +173,7 @@ async fn cleanup(
|
||||
bob.qr_scan = None;
|
||||
|
||||
if ongoing_allocated {
|
||||
context.free_ongoing();
|
||||
context.free_ongoing().await;
|
||||
}
|
||||
ret_chat_id
|
||||
}
|
||||
@@ -187,7 +191,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
|
||||
info!(context, "Requesting secure-join ...",);
|
||||
ensure_secret_key_exists(context).await.ok();
|
||||
if !context.alloc_ongoing() {
|
||||
if !context.alloc_ongoing().await {
|
||||
return cleanup(&context, contact_chat_id, false, join_vg).await;
|
||||
}
|
||||
let qr_scan = check_qr(context, &qr).await;
|
||||
@@ -203,12 +207,12 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
return cleanup(&context, contact_chat_id, true, join_vg).await;
|
||||
}
|
||||
};
|
||||
if context.shall_stop_ongoing() {
|
||||
if context.shall_stop_ongoing().await {
|
||||
return cleanup(&context, contact_chat_id, true, join_vg).await;
|
||||
}
|
||||
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup;
|
||||
{
|
||||
let mut bob = context.bob.write().unwrap();
|
||||
let mut bob = context.bob.write().await;
|
||||
bob.status = 0;
|
||||
bob.qr_scan = Some(qr_scan);
|
||||
}
|
||||
@@ -217,7 +221,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
context
|
||||
.bob
|
||||
.read()
|
||||
.unwrap()
|
||||
.await
|
||||
.qr_scan
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
@@ -225,13 +229,19 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
.as_ref()
|
||||
.unwrap(),
|
||||
contact_chat_id,
|
||||
) {
|
||||
)
|
||||
.await
|
||||
{
|
||||
// the scanned fingerprint matches Alice's key,
|
||||
// we can proceed to step 4b) directly and save two mails
|
||||
info!(context, "Taking protocol shortcut.");
|
||||
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
|
||||
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400);
|
||||
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default();
|
||||
context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM;
|
||||
joiner_progress!(
|
||||
context,
|
||||
chat_id_2_contact_id(context, contact_chat_id).await,
|
||||
400
|
||||
);
|
||||
let own_fingerprint = get_self_fingerprint(context).await.unwrap_or_default();
|
||||
|
||||
// Bob -> Alice
|
||||
send_handshake_msg(
|
||||
@@ -252,7 +262,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
context.bob.write().unwrap().expects = DC_VC_AUTH_REQUIRED;
|
||||
context.bob.write().await.expects = DC_VC_AUTH_REQUIRED;
|
||||
|
||||
// Bob -> Alice
|
||||
send_handshake_msg(
|
||||
@@ -268,14 +278,14 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
|
||||
|
||||
if join_vg {
|
||||
// for a group-join, wait until the secure-join is done and the group is created
|
||||
while !context.shall_stop_ongoing() {
|
||||
while !context.shall_stop_ongoing().await {
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
}
|
||||
cleanup(&context, contact_chat_id, true, join_vg).await
|
||||
} else {
|
||||
// for a one-to-one-chat, the chat is already known, return the chat-id,
|
||||
// the verification runs in background
|
||||
context.free_ongoing();
|
||||
context.free_ongoing().await;
|
||||
contact_chat_id
|
||||
}
|
||||
}
|
||||
@@ -321,8 +331,8 @@ async fn send_handshake_msg(
|
||||
.unwrap_or_default();
|
||||
}
|
||||
|
||||
fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 {
|
||||
let contacts = chat::get_chat_contacts(context, contact_chat_id);
|
||||
async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 {
|
||||
let contacts = chat::get_chat_contacts(context, contact_chat_id).await;
|
||||
if contacts.len() == 1 {
|
||||
contacts[0]
|
||||
} else {
|
||||
@@ -330,16 +340,17 @@ fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 {
|
||||
}
|
||||
}
|
||||
|
||||
fn fingerprint_equals_sender(
|
||||
async fn fingerprint_equals_sender(
|
||||
context: &Context,
|
||||
fingerprint: impl AsRef<str>,
|
||||
contact_chat_id: ChatId,
|
||||
) -> bool {
|
||||
let contacts = chat::get_chat_contacts(context, contact_chat_id);
|
||||
let contacts = chat::get_chat_contacts(context, contact_chat_id).await;
|
||||
|
||||
if contacts.len() == 1 {
|
||||
if let Ok(contact) = Contact::load_from_db(context, contacts[0]) {
|
||||
if let Some(peerstate) = Peerstate::from_addr(context, &context.sql, contact.get_addr())
|
||||
if let Ok(contact) = Contact::load_from_db(context, contacts[0]).await {
|
||||
if let Some(peerstate) =
|
||||
Peerstate::from_addr(context, &context.sql, contact.get_addr()).await
|
||||
{
|
||||
let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref());
|
||||
if peerstate.public_key_fingerprint.is_some()
|
||||
@@ -420,7 +431,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).await {
|
||||
Ok((chat_id, blocked)) => {
|
||||
if blocked != Blocked::Not {
|
||||
chat_id.unblock(context);
|
||||
chat_id.unblock(context).await;
|
||||
}
|
||||
chat_id
|
||||
}
|
||||
@@ -480,7 +491,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
|
||||
// verify that Alice's Autocrypt key and fingerprint matches the QR-code
|
||||
let cond = {
|
||||
let bob = context.bob.read().unwrap();
|
||||
let bob = context.bob.read().await;
|
||||
let scan = bob.qr_scan.as_ref();
|
||||
scan.is_none()
|
||||
|| bob.expects != DC_VC_AUTH_REQUIRED
|
||||
@@ -504,25 +515,29 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
} else {
|
||||
"Not encrypted."
|
||||
},
|
||||
);
|
||||
context.bob.write().unwrap().status = 0; // secure-join failed
|
||||
context.stop_ongoing();
|
||||
)
|
||||
.await;
|
||||
context.bob.write().await.status = 0; // secure-join failed
|
||||
context.stop_ongoing().await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) {
|
||||
if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id)
|
||||
.await
|
||||
{
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on joiner-side.",
|
||||
);
|
||||
context.bob.write().unwrap().status = 0; // secure-join failed
|
||||
context.stop_ongoing();
|
||||
)
|
||||
.await;
|
||||
context.bob.write().await.status = 0; // secure-join failed
|
||||
context.stop_ongoing().await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
info!(context, "Fingerprint verified.",);
|
||||
own_fingerprint = get_self_fingerprint(context).unwrap();
|
||||
own_fingerprint = get_self_fingerprint(context).await.unwrap();
|
||||
joiner_progress!(context, contact_id, 400);
|
||||
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM;
|
||||
context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM;
|
||||
|
||||
// Bob -> Alice
|
||||
send_handshake_msg(
|
||||
@@ -555,7 +570,8 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint not provided.",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
@@ -564,15 +580,17 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Auth not encrypted.",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) {
|
||||
if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id).await {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on inviter-side.",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
info!(context, "Fingerprint verified.",);
|
||||
@@ -584,25 +602,28 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Auth not provided.",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
};
|
||||
if !token::exists(context, token::Namespace::Auth, &auth_0).await {
|
||||
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.");
|
||||
could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid.")
|
||||
.await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
if mark_peer_as_verified(context, fingerprint).is_err() {
|
||||
if mark_peer_as_verified(context, fingerprint).await.is_err() {
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on inviter-side.",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited);
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited).await;
|
||||
info!(context, "Auth verified.",);
|
||||
secure_connection_established(context, contact_chat_id);
|
||||
secure_connection_established(context, contact_chat_id).await;
|
||||
emit_event!(context, Event::ContactsChanged(Some(contact_id)));
|
||||
inviter_progress!(context, contact_id, 600);
|
||||
if join_vg {
|
||||
@@ -651,12 +672,12 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
HandshakeMessage::Ignore
|
||||
};
|
||||
|
||||
if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM {
|
||||
if context.bob.read().await.expects != DC_VC_CONTACT_CONFIRM {
|
||||
info!(context, "Message belongs to a different handshake.",);
|
||||
return Ok(abort_retval);
|
||||
}
|
||||
let cond = {
|
||||
let bob = context.bob.read().unwrap();
|
||||
let bob = context.bob.read().await;
|
||||
let scan = bob.qr_scan.as_ref();
|
||||
scan.is_none() || (join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup)
|
||||
};
|
||||
@@ -695,20 +716,25 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Contact confirm message not encrypted.",
|
||||
);
|
||||
context.bob.write().unwrap().status = 0;
|
||||
)
|
||||
.await;
|
||||
context.bob.write().await.status = 0;
|
||||
return Ok(abort_retval);
|
||||
}
|
||||
|
||||
if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() {
|
||||
if mark_peer_as_verified(context, &scanned_fingerprint_of_alice)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
could_not_establish_secure_connection(
|
||||
context,
|
||||
contact_chat_id,
|
||||
"Fingerprint mismatch on joiner-side.",
|
||||
);
|
||||
)
|
||||
.await;
|
||||
return Ok(abort_retval);
|
||||
}
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined);
|
||||
Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined).await;
|
||||
emit_event!(context, Event::ContactsChanged(None));
|
||||
let cg_member_added = mime_message
|
||||
.get(HeaderDef::ChatGroupMemberAdded)
|
||||
@@ -723,8 +749,8 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
|
||||
return Ok(abort_retval);
|
||||
}
|
||||
secure_connection_established(context, contact_chat_id);
|
||||
context.bob.write().unwrap().expects = 0;
|
||||
secure_connection_established(context, contact_chat_id).await;
|
||||
context.bob.write().await.expects = 0;
|
||||
if join_vg {
|
||||
// Bob -> Alice
|
||||
send_handshake_msg(
|
||||
@@ -737,8 +763,8 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
)
|
||||
.await;
|
||||
}
|
||||
context.bob.write().unwrap().status = 1;
|
||||
context.stop_ongoing();
|
||||
context.bob.write().await.status = 1;
|
||||
context.stop_ongoing().await;
|
||||
Ok(if join_vg {
|
||||
HandshakeMessage::Propagate
|
||||
} else {
|
||||
@@ -752,7 +778,7 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
==========================================================*/
|
||||
|
||||
if let Ok(contact) = Contact::get_by_id(context, contact_id).await {
|
||||
if contact.is_verified(context) == VerifiedStatus::Unverified {
|
||||
if contact.is_verified(context).await == VerifiedStatus::Unverified {
|
||||
warn!(context, "vg-member-added-received invalid.",);
|
||||
return Ok(HandshakeMessage::Ignore);
|
||||
}
|
||||
@@ -787,42 +813,49 @@ pub(crate) async fn handle_securejoin_handshake(
|
||||
}
|
||||
}
|
||||
|
||||
fn secure_connection_established(context: &Context, contact_chat_id: ChatId) {
|
||||
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
async fn secure_connection_established(context: &Context, contact_chat_id: ChatId) {
|
||||
let contact_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await;
|
||||
let contact = Contact::get_by_id(context, contact_id).await;
|
||||
let addr = if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
} else {
|
||||
"?"
|
||||
};
|
||||
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr);
|
||||
chat::add_info_msg(context, contact_chat_id, msg);
|
||||
let msg = context
|
||||
.stock_string_repl_str(StockMessage::ContactVerified, addr)
|
||||
.await;
|
||||
chat::add_info_msg(context, contact_chat_id, msg).await;
|
||||
emit_event!(context, Event::ChatModified(contact_chat_id));
|
||||
}
|
||||
|
||||
fn could_not_establish_secure_connection(
|
||||
async fn could_not_establish_secure_connection(
|
||||
context: &Context,
|
||||
contact_chat_id: ChatId,
|
||||
details: &str,
|
||||
) {
|
||||
let contact_id = chat_id_2_contact_id(context, contact_chat_id);
|
||||
let contact = Contact::get_by_id(context, contact_id);
|
||||
let msg = context.stock_string_repl_str(
|
||||
StockMessage::ContactNotVerified,
|
||||
if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
} else {
|
||||
"?"
|
||||
},
|
||||
);
|
||||
let contact_id = chat_id_2_contact_id(context, contact_chat_id).await;
|
||||
let contact = Contact::get_by_id(context, contact_id).await;
|
||||
let msg = context
|
||||
.stock_string_repl_str(
|
||||
StockMessage::ContactNotVerified,
|
||||
if let Ok(ref contact) = contact {
|
||||
contact.get_addr()
|
||||
} else {
|
||||
"?"
|
||||
},
|
||||
)
|
||||
.await;
|
||||
|
||||
chat::add_info_msg(context, contact_chat_id, &msg);
|
||||
chat::add_info_msg(context, contact_chat_id, &msg).await;
|
||||
error!(context, "{} ({})", &msg, details);
|
||||
}
|
||||
|
||||
fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Result<(), Error> {
|
||||
async fn mark_peer_as_verified(
|
||||
context: &Context,
|
||||
fingerprint: impl AsRef<str>,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(ref mut peerstate) =
|
||||
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref())
|
||||
Peerstate::from_fingerprint(context, &context.sql, fingerprint.as_ref()).await
|
||||
{
|
||||
if peerstate.set_verified(
|
||||
PeerstateKeyType::PublicKey,
|
||||
@@ -833,6 +866,7 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
|
||||
peerstate.to_save = Some(ToSave::All);
|
||||
peerstate
|
||||
.save_to_db(&context.sql, false)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
return Ok(());
|
||||
}
|
||||
@@ -891,7 +925,7 @@ pub async fn handle_degrade_event(
|
||||
.query_get_value(
|
||||
context,
|
||||
"SELECT id FROM contacts WHERE addr=?;",
|
||||
params![&peerstate.addr],
|
||||
paramsv![peerstate.addr],
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -908,9 +942,10 @@ pub async fn handle_degrade_event(
|
||||
.unwrap_or_default();
|
||||
|
||||
let msg = context
|
||||
.stock_string_repl_str(StockMessage::ContactSetupChanged, peerstate.addr.clone());
|
||||
.stock_string_repl_str(StockMessage::ContactSetupChanged, peerstate.addr.clone())
|
||||
.await;
|
||||
|
||||
chat::add_info_msg(context, contact_chat_id, msg);
|
||||
chat::add_info_msg(context, contact_chat_id, msg).await;
|
||||
emit_event!(context, Event::ChatModified(contact_chat_id));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ impl Smtp {
|
||||
// oauth2
|
||||
let addr = &lp.addr;
|
||||
let send_pw = &lp.send_pw;
|
||||
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false);
|
||||
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false).await;
|
||||
if access_token.is_none() {
|
||||
return Err(Error::Oauth2Error {
|
||||
address: addr.to_string(),
|
||||
|
||||
329
src/sql.rs
329
src/sql.rs
@@ -7,7 +7,7 @@ use std::collections::HashSet;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
use rusqlite::{Connection, Error as SqlError, OpenFlags, NO_PARAMS};
|
||||
use rusqlite::{Connection, Error as SqlError, OpenFlags};
|
||||
use thread_local_object::ThreadLocal;
|
||||
|
||||
use crate::chat::{update_device_icon, update_saved_messages_icon};
|
||||
@@ -107,34 +107,34 @@ impl Sql {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute<P, S: AsRef<str>>(&self, sql: S, params: P) -> Result<usize>
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: rusqlite::ToSql,
|
||||
{
|
||||
pub async fn execute<S: AsRef<str>>(
|
||||
&self,
|
||||
sql: S,
|
||||
params: Vec<&dyn crate::ToSql>,
|
||||
) -> Result<usize> {
|
||||
self.start_stmt(sql.as_ref());
|
||||
|
||||
let res = {
|
||||
let conn = self.get_conn().await?;
|
||||
conn.execute(sql.as_ref(), params)?
|
||||
let res = conn.execute(sql.as_ref(), params);
|
||||
self.in_use.remove();
|
||||
res
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
res.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Prepares and executes the statement and maps a function over the resulting rows.
|
||||
/// Then executes the second function over the returned iterator and returns the
|
||||
/// result of that function.
|
||||
pub async fn query_map<T, P, F, G, H>(
|
||||
pub async fn query_map<T, F, G, H>(
|
||||
&self,
|
||||
sql: impl AsRef<str>,
|
||||
params: P,
|
||||
params: Vec<&dyn crate::ToSql>,
|
||||
f: F,
|
||||
mut g: G,
|
||||
) -> Result<H>
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: rusqlite::ToSql,
|
||||
F: FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
|
||||
G: FnMut(rusqlite::MappedRows<F>) -> Result<H>,
|
||||
{
|
||||
@@ -144,41 +144,12 @@ impl Sql {
|
||||
let res = {
|
||||
let conn = self.get_conn().await?;
|
||||
let mut stmt = conn.prepare(sql)?;
|
||||
let res = stmt.query_map(params, f)?;
|
||||
g(res)?
|
||||
let res = stmt.query_map(¶ms, f)?;
|
||||
self.in_use.remove();
|
||||
g(res)
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Prepares and executes the statement and maps a function over the resulting rows.
|
||||
/// Then executes the second function over the returned iterator and returns the
|
||||
/// result of that function.
|
||||
pub async fn query_map_async<'a, T, P, F, G, H, Fut>(
|
||||
&self,
|
||||
sql: impl AsRef<str>,
|
||||
params: P,
|
||||
f: F,
|
||||
g: G,
|
||||
) -> Result<H>
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: rusqlite::ToSql,
|
||||
F: FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
|
||||
G: FnMut(rusqlite::MappedRows<'a, F>) -> Fut,
|
||||
Fut: Future<Output = Result<H>> + 'a,
|
||||
{
|
||||
self.start_stmt(sql.as_ref().to_string());
|
||||
unimplemented!()
|
||||
// let sql = sql.as_ref();
|
||||
// self.with_conn_async(|conn| async move {
|
||||
// let mut stmt = conn.prepare(sql)?;
|
||||
// {
|
||||
// let res = stmt.query_map(params, f)?;
|
||||
// g(res).await
|
||||
// }
|
||||
// })
|
||||
// .await
|
||||
res
|
||||
}
|
||||
|
||||
pub async fn get_conn(
|
||||
@@ -226,36 +197,39 @@ impl Sql {
|
||||
|
||||
/// Return `true` if a query in the SQL statement it executes returns one or more
|
||||
/// rows and false if the SQL returns an empty set.
|
||||
pub async fn exists<P>(&self, sql: &str, params: P) -> Result<bool>
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: rusqlite::ToSql,
|
||||
{
|
||||
pub async fn exists(&self, sql: &str, params: Vec<&dyn crate::ToSql>) -> Result<bool> {
|
||||
self.start_stmt(sql.to_string());
|
||||
let res = {
|
||||
let conn = self.get_conn().await?;
|
||||
let mut stmt = conn.prepare(sql)?;
|
||||
stmt.exists(params)?
|
||||
let res = stmt.exists(¶ms);
|
||||
self.in_use.remove();
|
||||
res
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
res.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Execute a query which is expected to return one row.
|
||||
pub async fn query_row<T, P, F>(&self, sql: impl AsRef<str>, params: P, f: F) -> Result<T>
|
||||
pub async fn query_row<T, F>(
|
||||
&self,
|
||||
sql: impl AsRef<str>,
|
||||
params: Vec<&dyn crate::ToSql>,
|
||||
f: F,
|
||||
) -> Result<T>
|
||||
where
|
||||
P: IntoIterator + Copy,
|
||||
P::Item: rusqlite::ToSql,
|
||||
F: FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
|
||||
{
|
||||
self.start_stmt(sql.as_ref().to_string());
|
||||
let sql = sql.as_ref();
|
||||
let res = {
|
||||
let conn = self.get_conn().await?;
|
||||
conn.query_row(sql, params, f)?
|
||||
let res = conn.query_row(sql, params, f);
|
||||
self.in_use.remove();
|
||||
res
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
res.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn table_exists(&self, name: impl AsRef<str>) -> Result<bool> {
|
||||
@@ -276,10 +250,12 @@ impl Sql {
|
||||
/// Executes a query which is expected to return one row and one
|
||||
/// column. If the query does not return a value or returns SQL
|
||||
/// `NULL`, returns `Ok(None)`.
|
||||
pub async fn query_get_value_result<P, T>(&self, query: &str, params: P) -> Result<Option<T>>
|
||||
pub async fn query_get_value_result<T>(
|
||||
&self,
|
||||
query: &str,
|
||||
params: Vec<&dyn crate::ToSql>,
|
||||
) -> Result<Option<T>>
|
||||
where
|
||||
P: IntoIterator + Copy,
|
||||
P::Item: rusqlite::ToSql,
|
||||
T: rusqlite::types::FromSql,
|
||||
{
|
||||
match self
|
||||
@@ -299,15 +275,13 @@ impl Sql {
|
||||
|
||||
/// Not resultified version of `query_get_value_result`. Returns
|
||||
/// `None` on error.
|
||||
pub async fn query_get_value<P, T>(
|
||||
pub async fn query_get_value<T>(
|
||||
&self,
|
||||
context: &Context,
|
||||
query: &str,
|
||||
params: P,
|
||||
params: Vec<&dyn crate::ToSql>,
|
||||
) -> Option<T>
|
||||
where
|
||||
P: IntoIterator + Copy,
|
||||
P::Item: rusqlite::ToSql,
|
||||
T: rusqlite::types::FromSql,
|
||||
{
|
||||
match self.query_get_value_result(query, params).await {
|
||||
@@ -337,23 +311,23 @@ impl Sql {
|
||||
let key = key.as_ref();
|
||||
let res = if let Some(ref value) = value {
|
||||
let exists = self
|
||||
.exists("SELECT value FROM config WHERE keyname=?;", params![key])
|
||||
.exists("SELECT value FROM config WHERE keyname=?;", paramsv![key])
|
||||
.await?;
|
||||
if exists {
|
||||
self.execute(
|
||||
"UPDATE config SET value=? WHERE keyname=?;",
|
||||
params![value, key],
|
||||
paramsv![value.to_string(), key.to_string()],
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
self.execute(
|
||||
"INSERT INTO config (keyname, value) VALUES (?, ?);",
|
||||
params![key, value],
|
||||
paramsv![key.to_string(), value.to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
} else {
|
||||
self.execute("DELETE FROM config WHERE keyname=?;", params![key])
|
||||
self.execute("DELETE FROM config WHERE keyname=?;", paramsv![key])
|
||||
.await
|
||||
};
|
||||
|
||||
@@ -374,7 +348,7 @@ impl Sql {
|
||||
self.query_get_value(
|
||||
context,
|
||||
"SELECT value FROM config WHERE keyname=?;",
|
||||
params![key.as_ref()],
|
||||
paramsv![key.as_ref().to_string()],
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -455,18 +429,14 @@ impl Sql {
|
||||
) -> Result<u32> {
|
||||
self.start_stmt("get rowid".to_string());
|
||||
|
||||
let query = format!(
|
||||
"SELECT id FROM {} WHERE {}=? ORDER BY id DESC",
|
||||
table.as_ref(),
|
||||
field.as_ref(),
|
||||
);
|
||||
|
||||
let res = {
|
||||
let mut conn = self.get_conn().await?;
|
||||
get_rowid(context, &mut conn, table, field, value)?
|
||||
let res = get_rowid(context, &mut conn, table, field, value);
|
||||
self.in_use.remove();
|
||||
res
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
res.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub async fn get_rowid2(
|
||||
@@ -482,15 +452,17 @@ impl Sql {
|
||||
|
||||
let res = {
|
||||
let mut conn = self.get_conn().await?;
|
||||
get_rowid2(context, &mut conn, table, field, value, field2, value2)?
|
||||
let res = get_rowid2(context, &mut conn, table, field, value, field2, value2);
|
||||
self.in_use.remove();
|
||||
res
|
||||
};
|
||||
|
||||
Ok(res)
|
||||
res.map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rowid(
|
||||
context: &Context,
|
||||
_context: &Context,
|
||||
conn: &mut Connection,
|
||||
table: impl AsRef<str>,
|
||||
field: impl AsRef<str>,
|
||||
@@ -509,7 +481,7 @@ pub fn get_rowid(
|
||||
}
|
||||
|
||||
pub fn get_rowid2(
|
||||
context: &Context,
|
||||
_context: &Context,
|
||||
conn: &mut Connection,
|
||||
table: impl AsRef<str>,
|
||||
field: impl AsRef<str>,
|
||||
@@ -526,7 +498,7 @@ pub fn get_rowid2(
|
||||
field2.as_ref(),
|
||||
value2,
|
||||
),
|
||||
NO_PARAMS,
|
||||
params![],
|
||||
|row| row.get::<_, u32>(0),
|
||||
)
|
||||
}
|
||||
@@ -569,7 +541,7 @@ pub async fn housekeeping(context: &Context) {
|
||||
.sql
|
||||
.query_map(
|
||||
"SELECT value FROM config;",
|
||||
params![],
|
||||
paramsv![],
|
||||
|row| row.get::<_, String>(0),
|
||||
|rows| {
|
||||
for row in rows {
|
||||
@@ -683,7 +655,7 @@ async fn maybe_add_from_param(
|
||||
.sql
|
||||
.query_map(
|
||||
query,
|
||||
NO_PARAMS,
|
||||
paramsv![],
|
||||
|row| row.get::<_, String>(0),
|
||||
|rows| {
|
||||
for row in rows {
|
||||
@@ -762,11 +734,14 @@ async fn open(
|
||||
);
|
||||
sql.execute(
|
||||
"CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);",
|
||||
NO_PARAMS,
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX config_index1 ON config (keyname);",
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX config_index1 ON config (keyname);", NO_PARAMS)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE TABLE contacts (\
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, \
|
||||
@@ -776,17 +751,17 @@ async fn open(
|
||||
blocked INTEGER DEFAULT 0, \
|
||||
last_seen INTEGER DEFAULT 0, \
|
||||
param TEXT DEFAULT '');",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
@@ -794,7 +769,7 @@ async fn open(
|
||||
(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![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
@@ -807,19 +782,19 @@ async fn open(
|
||||
blocked INTEGER DEFAULT 0, \
|
||||
grpid TEXT DEFAULT '', \
|
||||
param TEXT DEFAULT '');",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", params![])
|
||||
sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", paramsv![])
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
@@ -827,7 +802,7 @@ async fn open(
|
||||
(1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \
|
||||
(4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \
|
||||
(7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
@@ -847,23 +822,23 @@ async fn open(
|
||||
txt TEXT DEFAULT '', \
|
||||
txt_raw TEXT DEFAULT '', \
|
||||
param TEXT DEFAULT '');",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", params![])
|
||||
sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", paramsv![])
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", params![])
|
||||
sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", paramsv![])
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", params![])
|
||||
sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", paramsv![])
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", params![])
|
||||
sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", paramsv![])
|
||||
.await?;
|
||||
sql.execute(
|
||||
"INSERT INTO msgs (id,msgrmsg,txt) VALUES \
|
||||
(1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \
|
||||
(4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \
|
||||
(8,0,'rsvd'), (9,0,'daymarker');",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
@@ -874,12 +849,12 @@ async fn open(
|
||||
action INTEGER, \
|
||||
foreign_id INTEGER, \
|
||||
param TEXT DEFAULT '');",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX jobs_index1 ON jobs (desired_timestamp);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
if !sql.table_exists("config").await?
|
||||
@@ -920,12 +895,12 @@ async fn open(
|
||||
info!(context, "[migration] v1");
|
||||
sql.execute(
|
||||
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX leftgrps_index1 ON leftgrps (grpid);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 1;
|
||||
@@ -935,7 +910,7 @@ async fn open(
|
||||
info!(context, "[migration] v2");
|
||||
sql.execute(
|
||||
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 2;
|
||||
@@ -951,7 +926,7 @@ async fn open(
|
||||
private_key, \
|
||||
public_key, \
|
||||
created INTEGER DEFAULT 0);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 7;
|
||||
@@ -967,12 +942,12 @@ async fn open(
|
||||
last_seen_autocrypt INTEGER DEFAULT 0, \
|
||||
public_key, \
|
||||
prefer_encrypted INTEGER DEFAULT 0);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 10;
|
||||
@@ -982,12 +957,12 @@ async fn open(
|
||||
info!(context, "[migration] v12");
|
||||
sql.execute(
|
||||
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 12;
|
||||
@@ -997,17 +972,17 @@ async fn open(
|
||||
info!(context, "[migration] v17");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX chats_index2 ON chats (archived);", params![])
|
||||
sql.execute("CREATE INDEX chats_index2 ON chats (archived);", paramsv![])
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])
|
||||
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", paramsv![])
|
||||
.await?;
|
||||
dbversion = 17;
|
||||
sql.set_raw_config_int(context, "dbversion", 17).await?;
|
||||
@@ -1016,11 +991,14 @@ async fn open(
|
||||
info!(context, "[migration] v18");
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN gossip_key;",
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])
|
||||
.await?;
|
||||
dbversion = 18;
|
||||
sql.set_raw_config_int(context, "dbversion", 18).await?;
|
||||
}
|
||||
@@ -1028,21 +1006,21 @@ async fn open(
|
||||
info!(context, "[migration] v27");
|
||||
// chat.id=1 and chat.id=2 are the old deaddrops,
|
||||
// the current ones are defined by chats.blocked=2
|
||||
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])
|
||||
sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", paramsv![])
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 27;
|
||||
@@ -1052,32 +1030,32 @@ async fn open(
|
||||
info!(context, "[migration] v34");
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
recalc_fingerprints = true;
|
||||
@@ -1088,21 +1066,21 @@ async fn open(
|
||||
info!(context, "[migration] v39");
|
||||
sql.execute(
|
||||
"CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);",
|
||||
params![]
|
||||
paramsv![]
|
||||
).await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN verified_key;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 39;
|
||||
@@ -1112,7 +1090,7 @@ async fn open(
|
||||
info!(context, "[migration] v40");
|
||||
sql.execute(
|
||||
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 40;
|
||||
@@ -1120,7 +1098,7 @@ async fn open(
|
||||
}
|
||||
if dbversion < 44 {
|
||||
info!(context, "[migration] v44");
|
||||
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])
|
||||
sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", paramsv![])
|
||||
.await?;
|
||||
dbversion = 44;
|
||||
sql.set_raw_config_int(context, "dbversion", 44).await?;
|
||||
@@ -1129,12 +1107,12 @@ async fn open(
|
||||
info!(context, "[migration] v46");
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN mime_references TEXT;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 46;
|
||||
@@ -1144,7 +1122,7 @@ async fn open(
|
||||
info!(context, "[migration] v47");
|
||||
sql.execute(
|
||||
"ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 47;
|
||||
@@ -1155,7 +1133,7 @@ async fn open(
|
||||
// NOTE: move_state is not used anymore
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1166,7 +1144,7 @@ async fn open(
|
||||
info!(context, "[migration] v49");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 49;
|
||||
@@ -1190,36 +1168,36 @@ async fn open(
|
||||
// are also added to the database as _hidden_.
|
||||
sql.execute(
|
||||
"CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);",
|
||||
params![]
|
||||
paramsv![]
|
||||
).await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX locations_index1 ON locations (from_id);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX locations_index2 ON locations (timestamp);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX chats_index3 ON chats (locations_send_until);",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
dbversion = 53;
|
||||
@@ -1229,11 +1207,14 @@ async fn open(
|
||||
info!(context, "[migration] v54");
|
||||
sql.execute(
|
||||
"ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX msgs_index6 ON msgs (location_id);",
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])
|
||||
.await?;
|
||||
dbversion = 54;
|
||||
sql.set_raw_config_int(context, "dbversion", 54).await?;
|
||||
}
|
||||
@@ -1241,7 +1222,7 @@ async fn open(
|
||||
info!(context, "[migration] v55");
|
||||
sql.execute(
|
||||
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
|
||||
params![],
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.set_raw_config_int(context, "dbversion", 55).await?;
|
||||
@@ -1252,11 +1233,11 @@ async fn open(
|
||||
// so, msg_id may or may not exist.
|
||||
sql.execute(
|
||||
"CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);",
|
||||
NO_PARAMS,
|
||||
paramsv![],
|
||||
).await?;
|
||||
sql.execute(
|
||||
"CREATE INDEX devmsglabels_index1 ON devmsglabels (label);",
|
||||
NO_PARAMS,
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
if exists_before_update && sql.get_raw_config_int(context, "bcc_self").await.is_none() {
|
||||
@@ -1268,7 +1249,7 @@ async fn open(
|
||||
info!(context, "[migration] v60");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.set_raw_config_int(context, "dbversion", 60).await?;
|
||||
@@ -1277,7 +1258,7 @@ async fn open(
|
||||
info!(context, "[migration] v61");
|
||||
sql.execute(
|
||||
"ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
update_icons = true;
|
||||
@@ -1287,14 +1268,14 @@ async fn open(
|
||||
info!(context, "[migration] v62");
|
||||
sql.execute(
|
||||
"ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;",
|
||||
NO_PARAMS,
|
||||
paramsv![],
|
||||
)
|
||||
.await?;
|
||||
sql.set_raw_config_int(context, "dbversion", 62).await?;
|
||||
}
|
||||
if dbversion < 63 {
|
||||
info!(context, "[migration] v63");
|
||||
sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS)
|
||||
sql.execute("UPDATE chats SET grpid='' WHERE type=100", paramsv![])
|
||||
.await?;
|
||||
sql.set_raw_config_int(context, "dbversion", 63).await?;
|
||||
}
|
||||
@@ -1305,22 +1286,24 @@ async fn open(
|
||||
|
||||
if recalc_fingerprints {
|
||||
info!(context, "[migration] recalc fingerprints");
|
||||
sql.query_map_async(
|
||||
"SELECT addr FROM acpeerstates;",
|
||||
params![],
|
||||
|row| row.get::<_, String>(0),
|
||||
|addrs| async move {
|
||||
for addr in addrs {
|
||||
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?)
|
||||
{
|
||||
peerstate.recalc_fingerprint();
|
||||
peerstate.save_to_db(sql, false).await?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
let addrs = sql
|
||||
.query_map(
|
||||
"SELECT addr FROM acpeerstates;",
|
||||
paramsv![],
|
||||
|row| row.get::<_, String>(0),
|
||||
|addrs| {
|
||||
addrs
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(Into::into)
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
for addr in &addrs {
|
||||
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, addr).await {
|
||||
peerstate.recalc_fingerprint();
|
||||
peerstate.save_to_db(sql, false).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
if update_icons {
|
||||
update_saved_messages_icon(context).await?;
|
||||
|
||||
223
src/stock.rs
223
src/stock.rs
@@ -206,7 +206,7 @@ impl StockMessage {
|
||||
impl Context {
|
||||
/// Set the stock string for the [StockMessage].
|
||||
///
|
||||
pub fn set_stock_translation(
|
||||
pub async fn set_stock_translation(
|
||||
&self,
|
||||
id: StockMessage,
|
||||
stockstring: String,
|
||||
@@ -227,7 +227,7 @@ impl Context {
|
||||
}
|
||||
self.translated_stockstrings
|
||||
.write()
|
||||
.unwrap()
|
||||
.await
|
||||
.insert(id as usize, stockstring);
|
||||
Ok(())
|
||||
}
|
||||
@@ -236,11 +236,11 @@ impl Context {
|
||||
///
|
||||
/// Return a translation (if it was set with set_stock_translation before)
|
||||
/// or a default (English) string.
|
||||
pub fn stock_str(&self, id: StockMessage) -> Cow<str> {
|
||||
pub async fn stock_str(&self, id: StockMessage) -> Cow<'_, str> {
|
||||
match self
|
||||
.translated_stockstrings
|
||||
.read()
|
||||
.unwrap()
|
||||
.await
|
||||
.get(&(id as usize))
|
||||
{
|
||||
Some(ref x) => Cow::Owned((*x).to_string()),
|
||||
@@ -253,8 +253,9 @@ impl Context {
|
||||
/// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
|
||||
/// placeholders with the provided string.
|
||||
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
|
||||
pub fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef<str>) -> String {
|
||||
pub async fn stock_string_repl_str(&self, id: StockMessage, insert: impl AsRef<str>) -> String {
|
||||
self.stock_str(id)
|
||||
.await
|
||||
.replacen("%1$s", insert.as_ref(), 1)
|
||||
.replacen("%1$d", insert.as_ref(), 1)
|
||||
.replacen("%1$@", insert.as_ref(), 1)
|
||||
@@ -264,8 +265,9 @@ impl Context {
|
||||
///
|
||||
/// Like [Context::stock_string_repl_str] but substitute the placeholders
|
||||
/// with an integer.
|
||||
pub fn stock_string_repl_int(&self, id: StockMessage, insert: i32) -> String {
|
||||
pub async fn stock_string_repl_int(&self, id: StockMessage, insert: i32) -> String {
|
||||
self.stock_string_repl_str(id, format!("{}", insert).as_str())
|
||||
.await
|
||||
}
|
||||
|
||||
/// Return stock string, replacing 2 placeholders with provided string.
|
||||
@@ -274,13 +276,14 @@ impl Context {
|
||||
/// placeholders with the string in `insert` and does the same for
|
||||
/// `%2$s`, `%2$d` and `%2$@` for `insert2`.
|
||||
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop)
|
||||
pub fn stock_string_repl_str2(
|
||||
pub async fn stock_string_repl_str2(
|
||||
&self,
|
||||
id: StockMessage,
|
||||
insert: impl AsRef<str>,
|
||||
insert2: impl AsRef<str>,
|
||||
) -> String {
|
||||
self.stock_str(id)
|
||||
.await
|
||||
.replacen("%1$s", insert.as_ref(), 1)
|
||||
.replacen("%1$d", insert.as_ref(), 1)
|
||||
.replacen("%1$@", insert.as_ref(), 1)
|
||||
@@ -306,7 +309,7 @@ impl Context {
|
||||
/// used as the second parameter to [StockMessage::MsgActionByUser] with
|
||||
/// again the original stock string being used as the first parameter,
|
||||
/// resulting in a string like "Member Alice added by Bob.".
|
||||
pub fn stock_system_msg(
|
||||
pub async fn stock_system_msg(
|
||||
&self,
|
||||
id: StockMessage,
|
||||
param1: impl AsRef<str>,
|
||||
@@ -314,9 +317,10 @@ impl Context {
|
||||
from_id: u32,
|
||||
) -> String {
|
||||
let insert1 = if id == StockMessage::MsgAddMember || id == StockMessage::MsgDelMember {
|
||||
let contact_id = Contact::lookup_id_by_addr(self, param1.as_ref());
|
||||
let contact_id = Contact::lookup_id_by_addr(self, param1.as_ref()).await;
|
||||
if contact_id != 0 {
|
||||
Contact::get_by_id(self, contact_id)
|
||||
.await
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
@@ -326,52 +330,60 @@ impl Context {
|
||||
param1.as_ref().to_string()
|
||||
};
|
||||
|
||||
let action = self.stock_string_repl_str2(id, insert1, param2.as_ref().to_string());
|
||||
let action = self
|
||||
.stock_string_repl_str2(id, insert1, param2.as_ref().to_string())
|
||||
.await;
|
||||
let action1 = action.trim_end_matches('.');
|
||||
match from_id {
|
||||
0 => action,
|
||||
1 => self.stock_string_repl_str(StockMessage::MsgActionByMe, action1), // DC_CONTACT_ID_SELF
|
||||
1 => {
|
||||
self.stock_string_repl_str(StockMessage::MsgActionByMe, action1)
|
||||
.await
|
||||
} // DC_CONTACT_ID_SELF
|
||||
_ => {
|
||||
let displayname = Contact::get_by_id(self, from_id)
|
||||
.await
|
||||
.map(|contact| contact.get_name_n_addr())
|
||||
.unwrap_or_default();
|
||||
|
||||
self.stock_string_repl_str2(StockMessage::MsgActionByUser, action1, &displayname)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_device_chats(&self) -> Result<(), Error> {
|
||||
pub async fn update_device_chats(&self) -> Result<(), Error> {
|
||||
// check for the LAST added device message - if it is present, we can skip message creation.
|
||||
// this is worthwhile as this function is typically called
|
||||
// by the ui on every probram start or even on every opening of the chatlist.
|
||||
if chat::was_device_msg_ever_added(&self, "core-welcome")? {
|
||||
if chat::was_device_msg_ever_added(&self, "core-welcome").await? {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// create saved-messages chat;
|
||||
// we do this only once, if the user has deleted the chat, he can recreate it manually.
|
||||
if !self.sql.get_raw_config_bool(&self, "self-chat-added") {
|
||||
if !self.sql.get_raw_config_bool(&self, "self-chat-added").await {
|
||||
self.sql
|
||||
.set_raw_config_bool(&self, "self-chat-added", true)?;
|
||||
chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF)?;
|
||||
.set_raw_config_bool(&self, "self-chat-added", true)
|
||||
.await?;
|
||||
chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF).await?;
|
||||
}
|
||||
|
||||
// add welcome-messages. by the label, this is done only once,
|
||||
// if the user has deleted the message or the chat, it is not added again.
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(self.stock_str(DeviceMessagesHint).to_string());
|
||||
chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg))?;
|
||||
msg.text = Some(self.stock_str(DeviceMessagesHint).await.to_string());
|
||||
chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg)).await?;
|
||||
|
||||
let image = include_bytes!("../assets/welcome-image.jpg");
|
||||
let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?;
|
||||
let mut msg = Message::new(Viewtype::Image);
|
||||
msg.param.set(Param::File, blob.as_name());
|
||||
chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg))?;
|
||||
chat::add_device_msg(&self, Some("core-welcome-image"), Some(&mut msg)).await?;
|
||||
|
||||
let mut msg = Message::new(Viewtype::Text);
|
||||
msg.text = Some(self.stock_str(WelcomeMessage).to_string());
|
||||
chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg))?;
|
||||
msg.text = Some(self.stock_str(WelcomeMessage).await.to_string());
|
||||
chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg)).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -397,153 +409,178 @@ mod tests {
|
||||
assert_eq!(StockMessage::NoMessages.fallback(), "No messages.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_stock_translation() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_set_stock_translation() {
|
||||
let t = dummy_context().await;
|
||||
t.ctx
|
||||
.set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "xyz")
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages).await, "xyz")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_stock_translation_wrong_replacements() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_set_stock_translation_wrong_replacements() {
|
||||
let t = dummy_context().await;
|
||||
assert!(t
|
||||
.ctx
|
||||
.set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())
|
||||
.await
|
||||
.is_err());
|
||||
assert!(t
|
||||
.ctx
|
||||
.set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string())
|
||||
.await
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_str() {
|
||||
let t = dummy_context();
|
||||
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages.");
|
||||
#[async_std::test]
|
||||
async fn test_stock_str() {
|
||||
let t = dummy_context().await;
|
||||
assert_eq!(
|
||||
t.ctx.stock_str(StockMessage::NoMessages).await,
|
||||
"No messages."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_string_repl_str() {
|
||||
let t = dummy_context().await;
|
||||
// uses %1$s substitution
|
||||
assert_eq!(
|
||||
t.ctx.stock_string_repl_str(StockMessage::Member, "42"),
|
||||
t.ctx
|
||||
.stock_string_repl_str(StockMessage::Member, "42")
|
||||
.await,
|
||||
"42 member(s)"
|
||||
);
|
||||
// We have no string using %1$d to test...
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_int() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_string_repl_int() {
|
||||
let t = dummy_context().await;
|
||||
assert_eq!(
|
||||
t.ctx.stock_string_repl_int(StockMessage::Member, 42),
|
||||
t.ctx.stock_string_repl_int(StockMessage::Member, 42).await,
|
||||
"42 member(s)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_string_repl_str2() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_string_repl_str2() {
|
||||
let t = dummy_context().await;
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar"),
|
||||
.stock_string_repl_str2(StockMessage::ServerResponse, "foo", "bar")
|
||||
.await,
|
||||
"Could not connect to foo: bar"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_simple() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_system_msg_simple() {
|
||||
let t = dummy_context().await;
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0),
|
||||
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)
|
||||
.await,
|
||||
"Location streaming enabled."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_me() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_system_msg_add_member_by_me() {
|
||||
let t = dummy_context().await;
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
t.ctx
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF
|
||||
)
|
||||
.await,
|
||||
"Member alice@example.com added by me."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_me_with_displayname() {
|
||||
let t = dummy_context();
|
||||
Contact::create(&t.ctx, "Alice", "alice@example.com").expect("failed to create contact");
|
||||
#[async_std::test]
|
||||
async fn test_stock_system_msg_add_member_by_me_with_displayname() {
|
||||
let t = dummy_context().await;
|
||||
Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||
.await
|
||||
.expect("failed to create contact");
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
t.ctx
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
DC_CONTACT_ID_SELF
|
||||
)
|
||||
.await,
|
||||
"Member Alice (alice@example.com) added by me."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_add_member_by_other_with_displayname() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_system_msg_add_member_by_other_with_displayname() {
|
||||
let t = dummy_context().await;
|
||||
let contact_id = {
|
||||
Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||
.await
|
||||
.expect("Failed to create contact Alice");
|
||||
Contact::create(&t.ctx, "Bob", "bob@example.com").expect("failed to create bob")
|
||||
Contact::create(&t.ctx, "Bob", "bob@example.com")
|
||||
.await
|
||||
.expect("failed to create bob")
|
||||
};
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
contact_id,
|
||||
),
|
||||
t.ctx
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgAddMember,
|
||||
"alice@example.com",
|
||||
"",
|
||||
contact_id,
|
||||
)
|
||||
.await,
|
||||
"Member Alice (alice@example.com) added by Bob (bob@example.com)."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_grp_name() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_system_msg_grp_name() {
|
||||
let t = dummy_context().await;
|
||||
assert_eq!(
|
||||
t.ctx.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
"Some chat",
|
||||
"Other chat",
|
||||
DC_CONTACT_ID_SELF
|
||||
),
|
||||
t.ctx
|
||||
.stock_system_msg(
|
||||
StockMessage::MsgGrpName,
|
||||
"Some chat",
|
||||
"Other chat",
|
||||
DC_CONTACT_ID_SELF
|
||||
)
|
||||
.await,
|
||||
"Group name changed from \"Some chat\" to \"Other chat\" by me."
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stock_system_msg_grp_name_other() {
|
||||
let t = dummy_context();
|
||||
#[async_std::test]
|
||||
async fn test_stock_system_msg_grp_name_other() {
|
||||
let t = dummy_context().await;
|
||||
let id = Contact::create(&t.ctx, "Alice", "alice@example.com")
|
||||
.await
|
||||
.expect("failed to create contact");
|
||||
|
||||
assert_eq!(
|
||||
t.ctx
|
||||
.stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id,),
|
||||
.stock_system_msg(StockMessage::MsgGrpName, "Some chat", "Other chat", id)
|
||||
.await,
|
||||
"Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)."
|
||||
)
|
||||
}
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_update_device_chats() {
|
||||
let t = dummy_context();
|
||||
t.ctx.update_device_chats().ok();
|
||||
let t = dummy_context().await;
|
||||
t.ctx.update_device_chats().await.ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 2);
|
||||
|
||||
@@ -553,7 +590,7 @@ mod tests {
|
||||
assert_eq!(chats.len(), 0);
|
||||
|
||||
// a subsequent call to update_device_chats() must not re-add manally deleted messages or chats
|
||||
t.ctx.update_device_chats().ok();
|
||||
t.ctx.update_device_chats().await.ok();
|
||||
let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
|
||||
assert_eq!(chats.len(), 0);
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ pub(crate) struct TestContext {
|
||||
/// "db.sqlite" in the [TestContext.dir] directory.
|
||||
///
|
||||
/// [Context]: crate::context::Context
|
||||
pub(crate) fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
pub(crate) async fn test_context(callback: Option<Box<ContextCallback>>) -> TestContext {
|
||||
let dir = tempdir().unwrap();
|
||||
let dbfile = dir.path().join("db.sqlite");
|
||||
let cb: Box<ContextCallback> = match callback {
|
||||
Some(cb) => cb,
|
||||
None => Box::new(|_, _| ()),
|
||||
};
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).unwrap();
|
||||
let ctx = Context::new(cb, "FakeOs".into(), dbfile).await.unwrap();
|
||||
TestContext { ctx, dir }
|
||||
}
|
||||
|
||||
@@ -41,8 +41,8 @@ pub(crate) fn test_context(callback: Option<Box<ContextCallback>>) -> TestContex
|
||||
/// The context will be opened and use the SQLite database as
|
||||
/// specified in [test_context] but there is no callback hooked up,
|
||||
/// i.e. [Context::call_cb] will always return `0`.
|
||||
pub(crate) fn dummy_context() -> TestContext {
|
||||
test_context(None)
|
||||
pub(crate) async fn dummy_context() -> TestContext {
|
||||
test_context(None).await
|
||||
}
|
||||
|
||||
pub(crate) fn logging_cb(_ctx: &Context, evt: Event) {
|
||||
|
||||
@@ -34,7 +34,7 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
|
||||
params![namespace, foreign_id, &token, time()],
|
||||
paramsv![namespace, foreign_id, token, time()],
|
||||
)
|
||||
.await
|
||||
.ok();
|
||||
@@ -44,10 +44,10 @@ pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -
|
||||
pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
|
||||
context
|
||||
.sql
|
||||
.query_get_value::<_, String>(
|
||||
.query_get_value::<String>(
|
||||
context,
|
||||
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
|
||||
params![namespace, foreign_id],
|
||||
paramsv![namespace, foreign_id],
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -65,7 +65,7 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> boo
|
||||
.sql
|
||||
.exists(
|
||||
"SELECT id FROM tokens WHERE namespc=? AND token=?;",
|
||||
params![namespace, token],
|
||||
paramsv![namespace, token],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
|
||||
Reference in New Issue
Block a user