it compiles

This commit is contained in:
dignifiedquire
2020-03-08 17:42:31 +01:00
parent 6ea1d665bb
commit 818e921192
33 changed files with 2103 additions and 1699 deletions

View File

@@ -36,10 +36,11 @@ async fn main() {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
println!("creating database {:?}", dbfile);
let ctx =
Context::new(Box::new(cb), "FakeOs".into(), dbfile).expect("Failed to create context");
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile)
.await
.expect("Failed to create context");
let running = Arc::new(RwLock::new(true));
let info = ctx.get_info();
let info = ctx.get_info().await;
let duration = time::Duration::from_millis(4000);
println!("info: {:#?}", info);
@@ -54,18 +55,19 @@ async fn main() {
perform_inbox_fetch(&ctx1).await;
if *r1.read().await {
// perform_inbox_idle(&ctx1).await;
perform_inbox_idle(&ctx1).await;
}
}
}
});
let r1 = running.clone();
let ctx1 = ctx.clone();
let t2 = async_std::task::spawn(async move {
while *r1.read().await {
// perform_smtp_jobs(&ctx1).await;
perform_smtp_jobs(&ctx1).await;
if *r1.read().await {
// perform_smtp_idle(&ctx1).await;
perform_smtp_idle(&ctx1).await;
}
}
});
@@ -85,17 +87,19 @@ async fn main() {
async_std::task::sleep(duration).await;
println!("sending a message");
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com").unwrap();
let chat_id = chat::create_by_contact_id(&ctx, contact_id).unwrap();
let contact_id = Contact::create(&ctx, "dignifiedquire", "dignifiedquire@gmail.com")
.await
.unwrap();
let chat_id = chat::create_by_contact_id(&ctx, contact_id).await.unwrap();
chat::send_text_msg(&ctx, chat_id, "Hi, here is my first message!".into())
.await
.unwrap();
println!("fetching chats..");
let chats = Chatlist::try_load(&ctx, 0, None, None).unwrap();
let chats = Chatlist::try_load(&ctx, 0, None, None).await.unwrap();
for i in 0..chats.len() {
let summary = chats.get_summary(&ctx, 0, None);
let summary = chats.get_summary(&ctx, 0, None).await;
let text1 = summary.get_text1();
let text2 = summary.get_text2();
println!("chat: {} - {:?} - {:?}", i, text1, text2,);

View File

@@ -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();

File diff suppressed because it is too large Load Diff

View File

@@ -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;

View File

@@ -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)

View File

@@ -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, &param.addr, &param.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();

View File

@@ -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");

View File

@@ -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());

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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?
}

View File

@@ -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, &param)?;
@@ -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();

View File

@@ -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());
}
}

View File

@@ -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

View File

@@ -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))

View File

@@ -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]

View File

@@ -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>";

View File

@@ -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
);
}

View File

@@ -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()

View File

@@ -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())

View File

@@ -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);
}

View File

@@ -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());

View File

@@ -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
View File

@@ -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());
}

View File

@@ -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));
}
}

View File

@@ -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(),

View File

@@ -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(&params, 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(&params);
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?;

View File

@@ -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);
}

View File

@@ -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) {

View File

@@ -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()

View File

@@ -8,8 +8,11 @@ use tempfile::{tempdir, TempDir};
/* some data used for testing
******************************************************************************/
fn stress_functions(context: &Context) {
let res = context.get_config(config::Config::SysConfigKeys).unwrap();
async fn stress_functions(context: &Context) {
let res = context
.get_config(config::Config::SysConfigKeys)
.await
.unwrap();
assert!(!res.contains(" probably_never_a_key "));
assert!(res.contains(" addr "));
@@ -98,15 +101,17 @@ struct TestContext {
dir: TempDir,
}
fn create_test_context() -> TestContext {
async fn create_test_context() -> TestContext {
let dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite");
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile).unwrap();
let ctx = Context::new(Box::new(cb), "FakeOs".into(), dbfile)
.await
.unwrap();
TestContext { ctx, dir }
}
#[test]
fn test_stress_tests() {
let context = create_test_context();
stress_functions(&context.ctx);
#[async_std::test]
async fn test_stress_tests() {
let context = create_test_context().await;
stress_functions(&context.ctx).await;
}