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

View File

@@ -486,9 +486,9 @@ mod tests {
use crate::test_utils::*; use crate::test_utils::*;
#[test] #[async_std::test]
fn test_create() { async fn test_create() {
let t = dummy_context(); let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo", b"hello").unwrap(); let blob = BlobObject::create(&t.ctx, "foo", b"hello").unwrap();
let fname = t.ctx.get_blobdir().join("foo"); let fname = t.ctx.get_blobdir().join("foo");
let data = fs::read(fname).unwrap(); let data = fs::read(fname).unwrap();
@@ -497,39 +497,39 @@ mod tests {
assert_eq!(blob.to_abs_path(), t.ctx.get_blobdir().join("foo")); assert_eq!(blob.to_abs_path(), t.ctx.get_blobdir().join("foo"));
} }
#[test] #[async_std::test]
fn test_lowercase_ext() { async fn test_lowercase_ext() {
let t = dummy_context(); let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello").unwrap(); let blob = BlobObject::create(&t.ctx, "foo.TXT", b"hello").unwrap();
assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt"); assert_eq!(blob.as_name(), "$BLOBDIR/foo.txt");
} }
#[test] #[async_std::test]
fn test_as_file_name() { async fn test_as_file_name() {
let t = dummy_context(); let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(blob.as_file_name(), "foo.txt"); assert_eq!(blob.as_file_name(), "foo.txt");
} }
#[test] #[async_std::test]
fn test_as_rel_path() { async fn test_as_rel_path() {
let t = dummy_context(); let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(blob.as_rel_path(), Path::new("foo.txt")); assert_eq!(blob.as_rel_path(), Path::new("foo.txt"));
} }
#[test] #[async_std::test]
fn test_suffix() { async fn test_suffix() {
let t = dummy_context(); let t = dummy_context().await;
let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); let blob = BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
assert_eq!(blob.suffix(), Some("txt")); assert_eq!(blob.suffix(), Some("txt"));
let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap(); let blob = BlobObject::create(&t.ctx, "bar", b"world").unwrap();
assert_eq!(blob.suffix(), None); assert_eq!(blob.suffix(), None);
} }
#[test] #[async_std::test]
fn test_create_dup() { async fn test_create_dup() {
let t = dummy_context(); let t = dummy_context().await;
BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap(); BlobObject::create(&t.ctx, "foo.txt", b"hello").unwrap();
let foo_path = t.ctx.get_blobdir().join("foo.txt"); let foo_path = t.ctx.get_blobdir().join("foo.txt");
assert!(foo_path.exists()); assert!(foo_path.exists());
@@ -546,9 +546,9 @@ mod tests {
} }
} }
#[test] #[async_std::test]
fn test_double_ext_preserved() { async fn test_double_ext_preserved() {
let t = dummy_context(); let t = dummy_context().await;
BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap(); BlobObject::create(&t.ctx, "foo.tar.gz", b"hello").unwrap();
let foo_path = t.ctx.get_blobdir().join("foo.tar.gz"); let foo_path = t.ctx.get_blobdir().join("foo.tar.gz");
assert!(foo_path.exists()); assert!(foo_path.exists());
@@ -566,18 +566,18 @@ mod tests {
} }
} }
#[test] #[async_std::test]
fn test_create_long_names() { async fn test_create_long_names() {
let t = dummy_context(); let t = dummy_context().await;
let s = "1".repeat(150); let s = "1".repeat(150);
let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap(); let blob = BlobObject::create(&t.ctx, &s, b"data").unwrap();
let blobname = blob.as_name().split('/').last().unwrap(); let blobname = blob.as_name().split('/').last().unwrap();
assert!(blobname.len() < 128); assert!(blobname.len() < 128);
} }
#[test] #[async_std::test]
fn test_create_and_copy() { async fn test_create_and_copy() {
let t = dummy_context(); let t = dummy_context().await;
let src = t.dir.path().join("src"); let src = t.dir.path().join("src");
fs::write(&src, b"boo").unwrap(); fs::write(&src, b"boo").unwrap();
let blob = BlobObject::create_and_copy(&t.ctx, &src).unwrap(); let blob = BlobObject::create_and_copy(&t.ctx, &src).unwrap();
@@ -591,9 +591,9 @@ mod tests {
assert!(!whoops.exists()); assert!(!whoops.exists());
} }
#[test] #[async_std::test]
fn test_create_from_path() { async fn test_create_from_path() {
let t = dummy_context(); let t = dummy_context().await;
let src_ext = t.dir.path().join("external"); let src_ext = t.dir.path().join("external");
fs::write(&src_ext, b"boo").unwrap(); fs::write(&src_ext, b"boo").unwrap();
@@ -609,9 +609,9 @@ mod tests {
let data = fs::read(blob.to_abs_path()).unwrap(); let data = fs::read(blob.to_abs_path()).unwrap();
assert_eq!(data, b"boo"); assert_eq!(data, b"boo");
} }
#[test] #[async_std::test]
fn test_create_from_name_long() { async fn test_create_from_name_long() {
let t = dummy_context(); let t = dummy_context().await;
let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html"); let src_ext = t.dir.path().join("autocrypt-setup-message-4137848473.html");
fs::write(&src_ext, b"boo").unwrap(); fs::write(&src_ext, b"boo").unwrap();
let blob = BlobObject::new_from_path(&t.ctx, &src_ext).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) AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?2)
GROUP BY c.id GROUP BY c.id
ORDER BY c.archived=?3 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", 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_row,
process_rows, process_rows,
).await? ).await?
@@ -159,7 +159,7 @@ impl Chatlist {
AND c.archived=1 AND c.archived=1
GROUP BY c.id GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;",
params![MessageState::OutDraft], paramsv![MessageState::OutDraft],
process_row, process_row,
process_rows, process_rows,
) )
@@ -192,7 +192,7 @@ impl Chatlist {
AND c.name LIKE ? AND c.name LIKE ?
GROUP BY c.id GROUP BY c.id
ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", 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_row,
process_rows, process_rows,
) )
@@ -221,7 +221,7 @@ impl Chatlist {
AND NOT c.archived=?2 AND NOT c.archived=?2
GROUP BY c.id GROUP BY c.id
ORDER BY c.id=?3 DESC, c.archived=?4 DESC, IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", 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_row,
process_rows, process_rows,
).await?; ).await?;
@@ -333,9 +333,15 @@ impl Chatlist {
ret.text2 = None; ret.text2 = None;
} else if lastmsg.is_none() || lastmsg.as_ref().unwrap().from_id == DC_CONTACT_ID_UNDEFINED } 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 { } else {
ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context); ret.fill(&mut lastmsg.unwrap(), chat, lastcontact.as_ref(), context)
.await;
} }
ret ret
@@ -353,7 +359,7 @@ pub async fn dc_get_archived_cnt(context: &Context) -> u32 {
.query_get_value( .query_get_value(
context, context,
"SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;", "SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;",
params![], paramsv![],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -376,7 +382,7 @@ async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option<MsgId> {
" AND c.blocked=2", " AND c.blocked=2",
" ORDER BY m.timestamp DESC, m.id DESC;" " ORDER BY m.timestamp DESC, m.id DESC;"
), ),
params![], paramsv![],
) )
.await .await
} }
@@ -389,7 +395,7 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_try_load() { 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") let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
.await .await
.unwrap(); .unwrap();
@@ -410,7 +416,7 @@ mod tests {
// drafts are sorted to the top // drafts are sorted to the top
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some("hello".to_string())); 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(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.get_chat_id(0), chat_id2); assert_eq!(chats.get_chat_id(0), chat_id2);
@@ -453,8 +459,8 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_search_special_chat_names() { async fn test_search_special_chat_names() {
let t = dummy_context(); let t = dummy_context().await;
t.ctx.update_device_chats().unwrap(); t.ctx.update_device_chats().await.unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None)
.await .await
@@ -467,6 +473,7 @@ mod tests {
t.ctx t.ctx
.set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string()) .set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string())
.await
.unwrap(); .unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None)
.await .await
@@ -475,6 +482,7 @@ mod tests {
t.ctx t.ctx
.set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string()) .set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string())
.await
.unwrap(); .unwrap();
let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None) let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None)
.await .await
@@ -484,14 +492,14 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_get_summary_unwrap() { 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") let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat")
.await .await
.unwrap(); .unwrap();
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
msg.set_text(Some("foo:\nbar \r\n test".to_string())); 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 chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
let summary = chats.get_summary(&t.ctx, 0, None).await; 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::job::*;
use crate::mimefactory::RECOMMENDED_FILE_SIZE; use crate::mimefactory::RECOMMENDED_FILE_SIZE;
use crate::stock::StockMessage; use crate::stock::StockMessage;
use rusqlite::NO_PARAMS;
/// The available configuration keys. /// The available configuration keys.
#[derive( #[derive(
@@ -113,7 +112,7 @@ impl Context {
// Default values // Default values
match key { 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()), _ => key.get_str("default").map(|s| s.to_string()),
} }
} }
@@ -135,7 +134,7 @@ impl Context {
match key { match key {
Config::Selfavatar => { Config::Selfavatar => {
self.sql self.sql
.execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS) .execute("UPDATE contacts SET selfavatar_sent=0;", paramsv![])
.await?; .await?;
self.sql self.sql
.set_raw_config_bool(self, "attach_selfavatar", true) .set_raw_config_bool(self, "attach_selfavatar", true)
@@ -167,7 +166,7 @@ impl Context {
ret ret
} }
Config::Selfstatus => { 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 { let val = if value.is_none() || value.unwrap() == def {
None None
} else { } else {
@@ -224,7 +223,7 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_selfavatar_outside_blobdir() { 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_src = t.dir.path().join("avatar.jpg");
let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg"); let avatar_bytes = include_bytes!("../test-data/image/avatar1000x1000.jpg");
File::create(&avatar_src) File::create(&avatar_src)
@@ -253,7 +252,7 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_selfavatar_in_blobdir() { 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_src = t.ctx.get_blobdir().join("avatar.png");
let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png"); let avatar_bytes = include_bytes!("../test-data/image/avatar900x900.png");
File::create(&avatar_src) File::create(&avatar_src)

View File

@@ -33,7 +33,7 @@ macro_rules! progress {
impl Context { impl Context {
/// Starts a configuration job. /// Starts a configuration job.
pub async fn configure(&self) { pub async fn configure(&self) {
if self.has_ongoing() { if self.has_ongoing().await {
warn!(self, "There is already another ongoing process running.",); warn!(self, "There is already another ongoing process running.",);
return; return;
} }
@@ -57,7 +57,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
progress!(context, 0); progress!(context, 0);
return job::Status::Finished(Err(format_err!("Database not opened"))); return job::Status::Finished(Err(format_err!("Database not opened")));
} }
if !context.alloc_ongoing() { if !context.alloc_ongoing().await {
progress!(context, 0); progress!(context, 0);
return job::Status::Finished(Err(format_err!("Cannot allocated ongoing process"))); 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; const STEP_13_AFTER_AUTOCONFIG: u8 = 13;
let mut step_counter: u8 = 0; let mut step_counter: u8 = 0;
while !context.shall_stop_ongoing() { while !context.shall_stop_ongoing().await {
step_counter += 1; step_counter += 1;
let success = match step_counter { let success = match step_counter {
@@ -108,6 +108,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status {
progress!(context, 10); progress!(context, 10);
if let Some(oauth2_addr) = if let Some(oauth2_addr) =
dc_get_oauth2_addr(context, &param.addr, &param.mail_pw) dc_get_oauth2_addr(context, &param.addr, &param.mail_pw)
.await
.and_then(|e| e.parse().ok()) .and_then(|e| e.parse().ok())
{ {
info!(context, "Authorized address is {}", oauth2_addr); 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 }); progress!(context, if success { 1000 } else { 0 });
job::Status::Finished(Ok(())) job::Status::Finished(Ok(()))
} }
@@ -574,7 +575,7 @@ async fn try_imap_one_param(context: &Context, param: &LoginParam) -> Option<boo
info!(context, "success: {}", inf); info!(context, "success: {}", inf);
return Some(true); return Some(true);
} }
if context.shall_stop_ongoing() { if context.shall_stop_ongoing().await {
return Some(false); return Some(false);
} }
info!(context, "Could not connect: {}", inf); info!(context, "Could not connect: {}", inf);
@@ -623,7 +624,7 @@ async fn try_smtp_one_param(context: &Context, param: &LoginParam) -> Option<boo
Some(true) Some(true)
} }
Err(err) => { Err(err) => {
if context.shall_stop_ongoing() { if context.shall_stop_ongoing().await {
Some(false) Some(false)
} else { } else {
warn!(context, "could not connect: {}", err); warn!(context, "could not connect: {}", err);
@@ -642,7 +643,7 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_no_panic_on_bad_credentials() { async fn test_no_panic_on_bad_credentials() {
let t = dummy_context(); let t = dummy_context().await;
t.ctx t.ctx
.set_config(Config::Addr, Some("probably@unexistant.addr")) .set_config(Config::Addr, Some("probably@unexistant.addr"))
.await .await
@@ -654,9 +655,9 @@ mod tests {
job_configure_imap(&t.ctx).await; job_configure_imap(&t.ctx).await;
} }
#[test] #[async_std::test]
fn test_get_offline_autoconfig() { async fn test_get_offline_autoconfig() {
let context = dummy_context().ctx; let context = dummy_context().await.ctx;
let mut params = LoginParam::new(); let mut params = LoginParam::new();
params.addr = "someone123@example.org".to_string(); 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 "SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param
FROM contacts c FROM contacts c
WHERE c.id=?;", WHERE c.id=?;",
params![contact_id as i32], paramsv![contact_id as i32],
|row| { |row| {
let contact = Self { let contact = Self {
id: contact_id, id: contact_id,
@@ -186,13 +186,16 @@ impl Contact {
) )
.await?; .await?;
if contact_id == DC_CONTACT_ID_SELF { 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 res.addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await .await
.unwrap_or_default(); .unwrap_or_default();
} else if contact_id == DC_CONTACT_ID_DEVICE { } 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(); res.addr = DC_CONTACT_ID_DEVICE_ADDR.to_string();
} }
Ok(res) Ok(res)
@@ -251,7 +254,7 @@ impl Contact {
}, },
)); ));
if blocked { if blocked {
Contact::unblock(context, contact_id); Contact::unblock(context, contact_id).await;
} }
Ok(contact_id) Ok(contact_id)
@@ -266,7 +269,7 @@ impl Contact {
.sql .sql
.execute( .execute(
"UPDATE msgs SET state=? WHERE from_id=? AND state=?;", "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 .await
.is_ok() .is_ok()
@@ -301,7 +304,7 @@ impl Contact {
context.sql.query_get_value( context.sql.query_get_value(
context, context,
"SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;", "SELECT id FROM contacts WHERE addr=?1 COLLATE NOCASE AND id>?2 AND origin>=?3 AND blocked=0;",
params![ paramsv![
addr_normalized, addr_normalized,
DC_CONTACT_ID_LAST_SPECIAL as i32, DC_CONTACT_ID_LAST_SPECIAL as i32,
DC_ORIGIN_MIN_CONTACT_LIST, DC_ORIGIN_MIN_CONTACT_LIST,
@@ -348,13 +351,13 @@ impl Contact {
); );
ensure!(origin != Origin::Unknown, "Missing valid origin"); 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 let addr_self = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await .await
.unwrap_or_default(); .unwrap_or_default();
if addr_cmp(addr, addr_self) { if addr_cmp(&addr, addr_self) {
return Ok((DC_CONTACT_ID_SELF, sth_modified)); 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( 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;", "SELECT id, name, addr, origin, authname FROM contacts WHERE addr=? COLLATE NOCASE;",
params![addr], paramsv![addr.to_string()],
|row| { |row| {
let row_id = row.get(0)?; let row_id = row.get(0)?;
let row_name: String = row.get(1)?; let row_name: String = row.get(1)?;
@@ -416,30 +419,30 @@ impl Contact {
if update_name || update_authname || update_addr || origin > row_origin { if update_name || update_authname || update_addr || origin > row_origin {
let new_name = if update_name { let new_name = if update_name {
if !name.as_ref().is_empty() { if !name.as_ref().is_empty() {
name.as_ref() name.as_ref().to_string()
} else { } else {
&row_authname row_authname.clone()
} }
} else { } else {
&row_name row_name
}; };
context context
.sql .sql
.execute( .execute(
"UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;",
params![ paramsv![
new_name, new_name,
if update_addr { addr } else { &row_addr }, if update_addr { addr.to_string() } else { row_addr },
if origin > row_origin { if origin > row_origin {
origin origin
} else { } else {
row_origin row_origin
}, },
if update_authname { if update_authname {
name.as_ref() name.as_ref().to_string()
} else { } else {
&row_authname row_authname
}, },
row_id 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. // This is one of the few duplicated data, however, getting the chat list is easier this way.
context.sql.execute( context.sql.execute(
"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", "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(); ).await.ok();
} }
sth_modified = Modifier::Modified; sth_modified = Modifier::Modified;
@@ -466,11 +469,11 @@ impl Contact {
.sql .sql
.execute( .execute(
"INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);",
params![ paramsv![
name.as_ref(), name.as_ref().to_string(),
addr, addr,
origin, origin,
if update_authname { name.as_ref() } else { "" } if update_authname { name.as_ref().to_string() } else { "".to_string() }
], ],
) )
.await .await
@@ -478,10 +481,10 @@ impl Contact {
{ {
row_id = context row_id = context
.sql .sql
.get_rowid(context, "contacts", "addr", addr) .get_rowid(context, "contacts", "addr", &addr)
.await?; .await?;
sth_modified = Modifier::Created; sth_modified = Modifier::Created;
info!(context, "added contact id={} addr={}", row_id, addr); info!(context, "added contact id={} addr={}", row_id, &addr);
} else { } else {
error!(context, "Cannot add contact."); error!(context, "Cannot add contact.");
} }
@@ -577,13 +580,13 @@ impl Contact {
AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ AND (c.name LIKE ?4 OR c.addr LIKE ?5) \
AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \
ORDER BY LOWER(c.name||c.addr),c.id;", ORDER BY LOWER(c.name||c.addr),c.id;",
params![ paramsv![
self_addr, self_addr,
DC_CONTACT_ID_LAST_SPECIAL as i32, DC_CONTACT_ID_LAST_SPECIAL as i32,
Origin::IncomingReplyTo, Origin::IncomingReplyTo,
&s3str_like_cmd, s3str_like_cmd,
&s3str_like_cmd, s3str_like_cmd,
if flag_verified_only { 0 } else { 1 }, if flag_verified_only { 0i32 } else { 1i32 },
], ],
|row| row.get::<_, i32>(0), |row| row.get::<_, i32>(0),
|ids| { |ids| {
@@ -604,7 +607,7 @@ impl Contact {
if let Some(query) = query { if let Some(query) = query {
if self_addr.contains(query.as_ref()) if self_addr.contains(query.as_ref())
|| self_name.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; add_self = true;
} }
@@ -616,7 +619,7 @@ impl Contact {
context.sql.query_map( 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;", "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), |row| row.get::<_, i32>(0),
|ids| { |ids| {
for id in ids { for id in ids {
@@ -637,10 +640,10 @@ impl Contact {
pub async fn get_blocked_cnt(context: &Context) -> usize { pub async fn get_blocked_cnt(context: &Context) -> usize {
context context
.sql .sql
.query_get_value::<_, isize>( .query_get_value::<isize>(
context, context,
"SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", "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 .await
.unwrap_or_default() as usize .unwrap_or_default() as usize
@@ -652,7 +655,7 @@ impl Contact {
.sql .sql
.query_map( .query_map(
"SELECT id FROM contacts WHERE id>? AND blocked!=0 ORDER BY LOWER(name||addr),id;", "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), |row| row.get::<_, u32>(0),
|ids| { |ids| {
ids.collect::<std::result::Result<Vec<_>, _>>() ids.collect::<std::result::Result<Vec<_>, _>>()
@@ -672,7 +675,7 @@ impl Contact {
let mut ret = String::new(); let mut ret = String::new();
if let Ok(contact) = Contact::load_from_db(context, contact_id).await { 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 loginparam = LoginParam::from_database(context, "configured_").await;
let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await;
@@ -684,18 +687,19 @@ impl Contact {
.is_some() .is_some()
{ {
let peerstate = peerstate.as_ref().unwrap(); let peerstate = peerstate.as_ref().unwrap();
let p = let p = context
context.stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual { .stock_str(if peerstate.prefer_encrypt == EncryptPreference::Mutual {
StockMessage::E2ePreferred StockMessage::E2ePreferred
} else { } else {
StockMessage::E2eAvailable StockMessage::E2eAvailable
}); })
.await;
ret += &p; ret += &p;
if self_key.is_none() { if self_key.is_none() {
e2ee::ensure_secret_key_exists(context).await?; e2ee::ensure_secret_key_exists(context).await?;
self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).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); ret += &format!(" {}:", p);
let fingerprint_self = self_key let fingerprint_self = self_key
@@ -729,9 +733,9 @@ impl Contact {
} else if 0 == loginparam.server_flags & DC_LP_IMAP_SOCKET_PLAIN as i32 } else if 0 == loginparam.server_flags & DC_LP_IMAP_SOCKET_PLAIN as i32
&& 0 == loginparam.server_flags & DC_LP_SMTP_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 { } else {
ret += &context.stock_str(StockMessage::EncrNone); ret += &context.stock_str(StockMessage::EncrNone).await;
} }
} }
@@ -753,7 +757,7 @@ impl Contact {
.query_get_value( .query_get_value(
context, context,
"SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;",
params![contact_id as i32], paramsv![contact_id as i32],
) )
.await .await
.unwrap_or_default(); .unwrap_or_default();
@@ -764,7 +768,7 @@ impl Contact {
.query_get_value( .query_get_value(
context, context,
"SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", "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 .await
.unwrap_or_default() .unwrap_or_default()
@@ -777,7 +781,7 @@ impl Contact {
.sql .sql
.execute( .execute(
"DELETE FROM contacts WHERE id=?;", "DELETE FROM contacts WHERE id=?;",
params![contact_id as i32], paramsv![contact_id as i32],
) )
.await .await
{ {
@@ -815,7 +819,7 @@ impl Contact {
.sql .sql
.execute( .execute(
"UPDATE contacts SET param=? WHERE id=?", "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?; .await?;
Ok(()) Ok(())
@@ -913,17 +917,17 @@ impl Contact {
/// ///
/// The UI may draw a checkbox or something like that beside verified contacts. /// The UI may draw a checkbox or something like that beside verified contacts.
/// ///
pub fn is_verified(&self, context: &Context) -> VerifiedStatus { pub async fn is_verified(&self, context: &Context) -> VerifiedStatus {
self.is_verified_ex(context, None) self.is_verified_ex(context, None).await
} }
/// Same as `Contact::is_verified` but allows speeding up things /// Same as `Contact::is_verified` but allows speeding up things
/// by adding the peerstate belonging to the contact. /// by adding the peerstate belonging to the contact.
/// If you do not have the peerstate available, it is loaded automatically. /// If you do not have the peerstate available, it is loaded automatically.
pub fn is_verified_ex( pub async fn is_verified_ex(
&self, &self,
context: &Context, context: &Context,
peerstate: Option<&Peerstate>, peerstate: Option<&Peerstate<'_>>,
) -> VerifiedStatus { ) -> VerifiedStatus {
// We're always sort of secured-verified as we could verify the key on this device any time with the key // We're always sort of secured-verified as we could verify the key on this device any time with the key
// on this device // 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 let Some(ps) = peerstate {
if ps.verified_key.is_some() { if ps.verified_key.is_some() {
return VerifiedStatus::BidirectVerified; return VerifiedStatus::BidirectVerified;
@@ -975,10 +979,10 @@ impl Contact {
context context
.sql .sql
.query_get_value::<_, isize>( .query_get_value::<isize>(
context, context,
"SELECT COUNT(*) FROM contacts WHERE id>?;", "SELECT COUNT(*) FROM contacts WHERE id>?;",
params![DC_CONTACT_ID_LAST_SPECIAL as i32], paramsv![DC_CONTACT_ID_LAST_SPECIAL as i32],
) )
.await .await
.unwrap_or_default() as usize .unwrap_or_default() as usize
@@ -993,7 +997,7 @@ impl Contact {
.sql .sql
.exists( .exists(
"SELECT id FROM contacts WHERE id=?;", "SELECT id FROM contacts WHERE id=?;",
params![contact_id as i32], paramsv![contact_id as i32],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()
@@ -1004,7 +1008,7 @@ impl Contact {
.sql .sql
.execute( .execute(
"UPDATE contacts SET origin=? WHERE id=? AND origin<?;", "UPDATE contacts SET origin=? WHERE id=? AND origin<?;",
params![origin, contact_id as i32, origin], paramsv![origin, contact_id as i32, origin],
) )
.await .await
.is_ok() .is_ok()
@@ -1044,7 +1048,7 @@ async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: boo
.sql .sql
.execute( .execute(
"UPDATE contacts SET blocked=? WHERE id=?;", "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 .await
.is_ok() .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...) // this would result in recreating the same group...)
if context.sql.execute( if context.sql.execute(
"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);", "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() { ).await.is_ok() {
Contact::mark_noticed(context, contact_id); Contact::mark_noticed(context, contact_id).await;
context.call_cb(Event::ContactsChanged(None)); context.call_cb(Event::ContactsChanged(None));
} }
} }
@@ -1240,25 +1244,33 @@ mod tests {
) )
} }
#[test] #[async_std::test]
fn test_get_contacts() { async fn test_get_contacts() {
let context = dummy_context(); let context = dummy_context().await;
let contacts = Contact::get_all(&context.ctx, 0, Some("some2")).unwrap(); let contacts = Contact::get_all(&context.ctx, 0, Some("some2"))
.await
.unwrap();
assert_eq!(contacts.len(), 0); 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); 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); 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); assert_eq!(contacts.len(), 0);
} }
#[async_std::test] #[async_std::test]
async fn test_is_self_addr() -> Result<()> { 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()); assert!(t.ctx.is_self_addr("me@me.org").await.is_err());
let addr = configure_alice_keypair(&t.ctx).await; let addr = configure_alice_keypair(&t.ctx).await;
@@ -1268,10 +1280,10 @@ mod tests {
Ok(()) Ok(())
} }
#[test] #[async_std::test]
fn test_add_or_lookup() { async fn test_add_or_lookup() {
// add some contacts, this also tests add_address_book() // add some contacts, this also tests add_address_book()
let t = dummy_context(); let t = dummy_context().await;
let book = concat!( let book = concat!(
" Name one \n one@eins.org \n", " Name one \n one@eins.org \n",
"Name two\ntwo@deux.net\n", "Name two\ntwo@deux.net\n",
@@ -1279,15 +1291,16 @@ mod tests {
"\nthree@drei.sam\n", "\nthree@drei.sam\n",
"Name two\ntwo@deux.net\n" // should not be added again "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 // check first added contact, this does not modify because of lower origin
let (contact_id, sth_modified) = let (contact_id, sth_modified) =
Contact::add_or_lookup(&t.ctx, "bla foo", "one@eins.org", Origin::IncomingUnknownTo) Contact::add_or_lookup(&t.ctx, "bla foo", "one@eins.org", Origin::IncomingUnknownTo)
.await
.unwrap(); .unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None); 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_id(), contact_id);
assert_eq!(contact.get_name(), "Name one"); assert_eq!(contact.get_name(), "Name one");
assert_eq!(contact.get_display_name(), "Name one"); assert_eq!(contact.get_display_name(), "Name one");
@@ -1301,10 +1314,11 @@ mod tests {
" one@eins.org ", " one@eins.org ",
Origin::ManuallyCreated, Origin::ManuallyCreated,
) )
.await
.unwrap(); .unwrap();
assert_eq!(contact_id, contact_id_test); assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified); 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_name(), "Real one");
assert_eq!(contact.get_addr(), "one@eins.org"); assert_eq!(contact.get_addr(), "one@eins.org");
assert!(!contact.is_blocked()); assert!(!contact.is_blocked());
@@ -1312,10 +1326,11 @@ mod tests {
// check third added contact (contact without name) // check third added contact (contact without name)
let (contact_id, sth_modified) = let (contact_id, sth_modified) =
Contact::add_or_lookup(&t.ctx, "", "three@drei.sam", Origin::IncomingUnknownTo) Contact::add_or_lookup(&t.ctx, "", "three@drei.sam", Origin::IncomingUnknownTo)
.await
.unwrap(); .unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::None); 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_name(), "");
assert_eq!(contact.get_display_name(), "three@drei.sam"); assert_eq!(contact.get_display_name(), "three@drei.sam");
assert_eq!(contact.get_addr(), "three@drei.sam"); assert_eq!(contact.get_addr(), "three@drei.sam");
@@ -1328,10 +1343,11 @@ mod tests {
"three@drei.sam", "three@drei.sam",
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
) )
.await
.unwrap(); .unwrap();
assert_eq!(contact_id, contact_id_test); assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified); 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_eq!(contact.get_name_n_addr(), "m. serious (three@drei.sam)");
assert!(!contact.is_blocked()); assert!(!contact.is_blocked());
@@ -1342,25 +1358,31 @@ mod tests {
"three@drei.sam", "three@drei.sam",
Origin::ManuallyCreated, Origin::ManuallyCreated,
) )
.await
.unwrap(); .unwrap();
assert_eq!(contact_id, contact_id_test); assert_eq!(contact_id, contact_id_test);
assert_eq!(sth_modified, Modifier::Modified); 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_authname(), "m. serious");
assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)"); assert_eq!(contact.get_name_n_addr(), "schnucki (three@drei.sam)");
assert!(!contact.is_blocked()); assert!(!contact.is_blocked());
// check SELF // 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!(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_eq!(contact.get_addr(), ""); // we're not configured
assert!(!contact.is_blocked()); assert!(!contact.is_blocked());
} }
#[test] #[async_std::test]
fn test_remote_authnames() { async fn test_remote_authnames() {
let t = dummy_context(); let t = dummy_context().await;
// incoming mail `From: bob1 <bob@example.org>` - this should init authname and name // incoming mail `From: bob1 <bob@example.org>` - this should init authname and name
let (contact_id, sth_modified) = Contact::add_or_lookup( let (contact_id, sth_modified) = Contact::add_or_lookup(
@@ -1369,10 +1391,11 @@ mod tests {
"bob@example.org", "bob@example.org",
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
) )
.await
.unwrap(); .unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::Created); 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_authname(), "bob1");
assert_eq!(contact.get_name(), "bob1"); assert_eq!(contact.get_name(), "bob1");
assert_eq!(contact.get_display_name(), "bob1"); assert_eq!(contact.get_display_name(), "bob1");
@@ -1384,18 +1407,21 @@ mod tests {
"bob@example.org", "bob@example.org",
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
) )
.await
.unwrap(); .unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::Modified); 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_authname(), "bob2");
assert_eq!(contact.get_name(), "bob2"); assert_eq!(contact.get_name(), "bob2");
assert_eq!(contact.get_display_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 // 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); 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_authname(), "bob2");
assert_eq!(contact.get_name(), "bob3"); assert_eq!(contact.get_name(), "bob3");
assert_eq!(contact.get_display_name(), "bob3"); assert_eq!(contact.get_display_name(), "bob3");
@@ -1407,23 +1433,26 @@ mod tests {
"bob@example.org", "bob@example.org",
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
) )
.await
.unwrap(); .unwrap();
assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL); assert!(contact_id > DC_CONTACT_ID_LAST_SPECIAL);
assert_eq!(sth_modified, Modifier::Modified); 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_authname(), "bob4");
assert_eq!(contact.get_name(), "bob3"); assert_eq!(contact.get_name(), "bob3");
assert_eq!(contact.get_display_name(), "bob3"); assert_eq!(contact.get_display_name(), "bob3");
} }
#[test] #[async_std::test]
fn test_remote_authnames_create_empty() { async fn test_remote_authnames_create_empty() {
let t = dummy_context(); let t = dummy_context().await;
// manually create "claire@example.org" without a given name // 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); 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_authname(), "");
assert_eq!(contact.get_name(), ""); assert_eq!(contact.get_name(), "");
assert_eq!(contact.get_display_name(), "claire@example.org"); assert_eq!(contact.get_display_name(), "claire@example.org");
@@ -1435,10 +1464,11 @@ mod tests {
"claire@example.org", "claire@example.org",
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
) )
.await
.unwrap(); .unwrap();
assert_eq!(contact_id, contact_id_same); assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified); 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_authname(), "claire1");
assert_eq!(contact.get_name(), "claire1"); assert_eq!(contact.get_name(), "claire1");
assert_eq!(contact.get_display_name(), "claire1"); assert_eq!(contact.get_display_name(), "claire1");
@@ -1450,22 +1480,25 @@ mod tests {
"claire@example.org", "claire@example.org",
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
) )
.await
.unwrap(); .unwrap();
assert_eq!(contact_id, contact_id_same); assert_eq!(contact_id, contact_id_same);
assert_eq!(sth_modified, Modifier::Modified); 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_authname(), "claire2");
assert_eq!(contact.get_name(), "claire2"); assert_eq!(contact.get_name(), "claire2");
assert_eq!(contact.get_display_name(), "claire2"); assert_eq!(contact.get_display_name(), "claire2");
} }
#[test] #[async_std::test]
fn test_remote_authnames_edit_empty() { async fn test_remote_authnames_edit_empty() {
let t = dummy_context(); let t = dummy_context().await;
// manually create "dave@example.org" // manually create "dave@example.org"
let contact_id = Contact::create(&t.ctx, "dave1", "dave@example.org").unwrap(); let contact_id = Contact::create(&t.ctx, "dave1", "dave@example.org")
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); .await
.unwrap();
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), ""); assert_eq!(contact.get_authname(), "");
assert_eq!(contact.get_name(), "dave1"); assert_eq!(contact.get_name(), "dave1");
assert_eq!(contact.get_display_name(), "dave1"); assert_eq!(contact.get_display_name(), "dave1");
@@ -1477,15 +1510,18 @@ mod tests {
"dave@example.org", "dave@example.org",
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
) )
.await
.unwrap(); .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_authname(), "dave2");
assert_eq!(contact.get_name(), "dave1"); assert_eq!(contact.get_name(), "dave1");
assert_eq!(contact.get_display_name(), "dave1"); assert_eq!(contact.get_display_name(), "dave1");
// manually clear the name // manually clear the name
Contact::create(&t.ctx, "", "dave@example.org").unwrap(); Contact::create(&t.ctx, "", "dave@example.org")
let contact = Contact::load_from_db(&t.ctx, contact_id).unwrap(); .await
.unwrap();
let contact = Contact::load_from_db(&t.ctx, contact_id).await.unwrap();
assert_eq!(contact.get_authname(), "dave2"); assert_eq!(contact.get_authname(), "dave2");
assert_eq!(contact.get_name(), "dave2"); assert_eq!(contact.get_name(), "dave2");
assert_eq!(contact.get_display_name(), "dave2"); assert_eq!(contact.get_display_name(), "dave2");

View File

@@ -3,7 +3,9 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::ffi::OsString; use std::ffi::OsString;
use std::path::{Path, PathBuf}; 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::chat::*;
use crate::config::Config; use crate::config::Config;
@@ -156,14 +158,14 @@ impl Context {
* Ongoing process allocation/free/check * Ongoing process allocation/free/check
******************************************************************************/ ******************************************************************************/
pub fn alloc_ongoing(&self) -> bool { pub async fn alloc_ongoing(&self) -> bool {
if self.has_ongoing() { if self.has_ongoing().await {
warn!(self, "There is already another ongoing process running.",); warn!(self, "There is already another ongoing process running.",);
false false
} else { } else {
let s_a = self.running_state.clone(); 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.ongoing_running = true;
s.shall_stop_ongoing = false; 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 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.ongoing_running = false;
s.shall_stop_ongoing = true; 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_a = self.running_state.clone();
let s = s_a.read().unwrap(); let s = s_a.read().await;
s.ongoing_running || !s.shall_stop_ongoing s.ongoing_running || !s.shall_stop_ongoing
} }
/// Signal an ongoing process to stop. /// 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 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 { if s.ongoing_running && !s.shall_stop_ongoing {
info!(self, "Signaling the ongoing process to stop ASAP.",); info!(self, "Signaling the ongoing process to stop ASAP.",);
@@ -200,12 +202,8 @@ impl Context {
}; };
} }
pub fn shall_stop_ongoing(&self) -> bool { pub async fn shall_stop_ongoing(&self) -> bool {
self.running_state self.running_state.clone().read().await.shall_stop_ongoing
.clone()
.read()
.unwrap()
.shall_stop_ongoing
} }
/******************************************************************************* /*******************************************************************************
@@ -218,8 +216,8 @@ impl Context {
let l2 = LoginParam::from_database(self, "configured_").await; let l2 = LoginParam::from_database(self, "configured_").await;
let displayname = self.get_config(Config::Displayname).await; let displayname = self.get_config(Config::Displayname).await;
let chats = get_chat_cnt(self).await as usize; let chats = get_chat_cnt(self).await as usize;
let real_msgs = message::get_real_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) 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 contacts = Contact::get_real_cnt(self).await as usize;
let is_configured = self.get_config_int(Config::Configured).await; let is_configured = self.get_config_int(Config::Configured).await;
let dbversion = self let dbversion = self
@@ -234,16 +232,12 @@ impl Context {
let prv_key_cnt: Option<isize> = self let prv_key_cnt: Option<isize> = self
.sql .sql
.query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS) .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", paramsv![])
.await; .await;
let pub_key_cnt: Option<isize> = self let pub_key_cnt: Option<isize> = self
.sql .sql
.query_get_value( .query_get_value(self, "SELECT COUNT(*) FROM acpeerstates;", paramsv![])
self,
"SELECT COUNT(*) FROM acpeerstates;",
rusqlite::NO_PARAMS,
)
.await; .await;
let fingerprint_str = let fingerprint_str =
@@ -316,7 +310,7 @@ impl Context {
} }
pub async fn get_fresh_msgs(&self) -> Vec<MsgId> { pub async fn get_fresh_msgs(&self) -> Vec<MsgId> {
let show_deaddrop = 0; let show_deaddrop: i32 = 0;
self.sql self.sql
.query_map( .query_map(
concat!( concat!(
@@ -333,7 +327,7 @@ impl Context {
" AND (c.blocked=0 OR c.blocked=?)", " AND (c.blocked=0 OR c.blocked=?)",
" ORDER BY m.timestamp DESC,m.id DESC;" " 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), |row| row.get::<_, MsgId>(0),
|rows| { |rows| {
let mut ret = Vec::new(); let mut ret = Vec::new();
@@ -388,7 +382,7 @@ impl Context {
self.sql self.sql
.query_map( .query_map(
query, query,
params![chat_id, &strLikeInText, &strLikeBeg], paramsv![chat_id, strLikeInText, strLikeBeg],
|row| row.get::<_, MsgId>("id"), |row| row.get::<_, MsgId>("id"),
|rows| { |rows| {
let mut ret = Vec::new(); let mut ret = Vec::new();
@@ -475,7 +469,7 @@ impl Drop for Context {
info!(self, "disconnecting SMTP"); info!(self, "disconnecting SMTP");
self.smtp.disconnect().await; self.smtp.disconnect().await;
self.sql.close(self); self.sql.close(self).await;
}); });
} }
} }
@@ -519,79 +513,85 @@ mod tests {
use crate::test_utils::*; use crate::test_utils::*;
#[test] #[async_std::test]
fn test_wrong_db() { async fn test_wrong_db() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite"); let dbfile = tmp.path().join("db.sqlite");
std::fs::write(&dbfile, b"123").unwrap(); 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()); assert!(res.is_err());
} }
#[test] #[async_std::test]
fn test_get_fresh_msgs() { async fn test_get_fresh_msgs() {
let t = dummy_context(); let t = dummy_context().await;
let fresh = t.ctx.get_fresh_msgs(); let fresh = t.ctx.get_fresh_msgs().await;
assert!(fresh.is_empty()) assert!(fresh.is_empty())
} }
#[test] #[async_std::test]
fn test_blobdir_exists() { async fn test_blobdir_exists() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite"); 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"); let blobdir = tmp.path().join("db.sqlite-blobs");
assert!(blobdir.is_dir()); assert!(blobdir.is_dir());
} }
#[test] #[async_std::test]
fn test_wrong_blogdir() { async fn test_wrong_blogdir() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite"); let dbfile = tmp.path().join("db.sqlite");
let blobdir = tmp.path().join("db.sqlite-blobs"); let blobdir = tmp.path().join("db.sqlite-blobs");
std::fs::write(&blobdir, b"123").unwrap(); 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()); assert!(res.is_err());
} }
#[test] #[async_std::test]
fn test_sqlite_parent_not_exists() { async fn test_sqlite_parent_not_exists() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let subdir = tmp.path().join("subdir"); let subdir = tmp.path().join("subdir");
let dbfile = subdir.join("db.sqlite"); let dbfile = subdir.join("db.sqlite");
let dbfile2 = dbfile.clone(); 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!(subdir.is_dir());
assert!(dbfile2.is_file()); assert!(dbfile2.is_file());
} }
#[test] #[async_std::test]
fn test_with_empty_blobdir() { async fn test_with_empty_blobdir() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite"); let dbfile = tmp.path().join("db.sqlite");
let blobdir = PathBuf::new(); 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()); assert!(res.is_err());
} }
#[test] #[async_std::test]
fn test_with_blobdir_not_exists() { async fn test_with_blobdir_not_exists() {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite"); let dbfile = tmp.path().join("db.sqlite");
let blobdir = tmp.path().join("blobs"); 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()); assert!(res.is_err());
} }
#[test] #[async_std::test]
fn no_crashes_on_context_deref() { async fn no_crashes_on_context_deref() {
let t = dummy_context(); let t = dummy_context().await;
std::mem::drop(t.ctx); std::mem::drop(t.ctx);
} }
#[async_std::test] #[async_std::test]
async fn test_get_info() { async fn test_get_info() {
let t = dummy_context(); let t = dummy_context().await;
let info = t.ctx.get_info().await; let info = t.ctx.get_info().await;
assert!(info.get("database_dir").is_some()); 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) // https://github.com/deltachat/deltachat-core/issues/150)
let (from_id, from_id_blocked, incoming_origin) = let (from_id, from_id_blocked, incoming_origin) =
if let Some(field_from) = mime_parser.get(HeaderDef::From_) { 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 { } else {
(0, false, Origin::Unknown) (0, false, Origin::Unknown)
}; };
@@ -105,17 +105,20 @@ pub async fn dc_receive_imf(
let mut to_ids = ContactIds::new(); let mut to_ids = ContactIds::new();
for header_def in &[HeaderDef::To, HeaderDef::Cc] { for header_def in &[HeaderDef::To, HeaderDef::Cc] {
if let Some(field) = mime_parser.get(header_def.clone()) { if let Some(field) = mime_parser.get(header_def.clone()) {
to_ids.extend(&dc_add_or_lookup_contacts_by_address_list( to_ids.extend(
context, &dc_add_or_lookup_contacts_by_address_list(
&field, context,
if !incoming { &field,
Origin::OutgoingTo if !incoming {
} else if incoming_origin.is_known() { Origin::OutgoingTo
Origin::IncomingTo } else if incoming_origin.is_known() {
} else { Origin::IncomingTo
Origin::IncomingUnknownTo } else {
}, Origin::IncomingUnknownTo
)?); },
)
.await?,
);
} }
} }
@@ -178,7 +181,8 @@ pub async fn dc_receive_imf(
from_id, from_id,
insert_msg_id, insert_msg_id,
hidden, hidden,
); )
.await;
} }
if let Some(avatar_action) = &mime_parser.user_avatar { 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. /// Converts "From" field to contact id.
/// ///
/// Also returns whether it is blocked or not and its origin. /// 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, context: &Context,
field_from: &str, field_from: &str,
) -> Result<(u32, bool, Origin)> { ) -> Result<(u32, bool, Origin)> {
@@ -233,7 +237,8 @@ pub fn from_field_to_contact_id(
context, context,
&field_from, &field_from,
Origin::IncomingUnknownFrom, Origin::IncomingUnknownFrom,
)?; )
.await?;
if from_ids.contains(&DC_CONTACT_ID_SELF) { if from_ids.contains(&DC_CONTACT_ID_SELF) {
Ok((DC_CONTACT_ID_SELF, false, Origin::OutgoingBcc)) 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 from_id_blocked = false;
let mut incoming_origin = Origin::Unknown; 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; from_id_blocked = contact.blocked;
incoming_origin = contact.origin; 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 // (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) */ // moved between folders. make sure, this check is done eg. before securejoin-processing) */
if let Ok((old_server_folder, old_server_uid, _)) = 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 { 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"); bail!("Message already in DB");
@@ -308,7 +314,7 @@ async fn add_parts(
let mut msgrmsg = if mime_parser.has_chat_version() { let mut msgrmsg = if mime_parser.has_chat_version() {
MessengerMessage::Yes 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 MessengerMessage::Reply
} else { } else {
MessengerMessage::No MessengerMessage::No
@@ -368,8 +374,8 @@ async fn add_parts(
} }
Err(err) => { Err(err) => {
*hidden = true; *hidden = true;
context.bob.write().unwrap().status = 0; // secure-join failed context.bob.write().await.status = 0; // secure-join failed
context.stop_ongoing(); context.stop_ongoing().await;
error!(context, "Error in Secure-Join message handling: {}", err); error!(context, "Error in Secure-Join message handling: {}", err);
} }
} }
@@ -408,7 +414,7 @@ async fn add_parts(
&& chat_id_blocked != Blocked::Not && chat_id_blocked != Blocked::Not
&& create_blocked == Blocked::Not && create_blocked == Blocked::Not
{ {
new_chat_id.unblock(context); new_chat_id.unblock(context).await;
chat_id_blocked = Blocked::Not; chat_id_blocked = Blocked::Not;
} }
} }
@@ -442,12 +448,12 @@ async fn add_parts(
} }
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
if Blocked::Not == create_blocked { if Blocked::Not == create_blocked {
chat_id.unblock(context); chat_id.unblock(context).await;
chat_id_blocked = Blocked::Not; 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, // 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. // 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!( info!(
context, context,
"Message is a reply to a known message, mark sender as known.", "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; chat_id_blocked = new_chat_id_blocked;
// automatically unblock chat when the user sends a message // automatically unblock chat when the user sends a message
if !chat_id.is_unset() && chat_id_blocked != Blocked::Not { 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; chat_id_blocked = Blocked::Not;
} }
} }
@@ -518,7 +524,7 @@ async fn add_parts(
&& Blocked::Not != chat_id_blocked && Blocked::Not != chat_id_blocked
&& Blocked::Not == create_blocked && Blocked::Not == create_blocked
{ {
chat_id.unblock(context); chat_id.unblock(context).await;
chat_id_blocked = Blocked::Not; chat_id_blocked = Blocked::Not;
} }
} }
@@ -538,7 +544,7 @@ async fn add_parts(
chat_id_blocked = bl; chat_id_blocked = bl;
if !chat_id.is_unset() && Blocked::Not != chat_id_blocked { if !chat_id.is_unset() && Blocked::Not != chat_id_blocked {
chat_id.unblock(context); chat_id.unblock(context).await;
chat_id_blocked = Blocked::Not; chat_id_blocked = Blocked::Not;
} }
} }
@@ -557,7 +563,8 @@ async fn add_parts(
&mut sort_timestamp, &mut sort_timestamp,
sent_timestamp, sent_timestamp,
&mut rcvd_timestamp, &mut rcvd_timestamp,
); )
.await;
// unarchive chat // unarchive chat
chat_id.unarchive(context).await?; chat_id.unarchive(context).await?;
@@ -613,9 +620,9 @@ async fn add_parts(
.set_int(Param::Cmd, mime_parser.is_system_message as i32); .set_int(Param::Cmd, mime_parser.is_system_message as i32);
} }
stmt.execute(params![ stmt.execute(paramsv![
rfc724_mid, rfc724_mid,
server_folder.as_ref(), server_folder.as_ref().to_string(),
server_uid as i32, server_uid as i32,
*chat_id, *chat_id,
from_id as i32, from_id as i32,
@@ -626,7 +633,7 @@ async fn add_parts(
part.typ, part.typ,
state, state,
msgrmsg, msgrmsg,
&part.msg, part.msg,
// txt_raw might contain invalid utf8 // txt_raw might contain invalid utf8
txt_raw.unwrap_or_default(), txt_raw.unwrap_or_default(),
part.param.to_string(), part.param.to_string(),
@@ -675,7 +682,7 @@ async fn add_parts(
Ok(()) Ok(())
} }
fn save_locations( async fn save_locations(
context: &Context, context: &Context,
mime_parser: &MimeMessage, mime_parser: &MimeMessage,
chat_id: ChatId, chat_id: ChatId,
@@ -691,11 +698,14 @@ fn save_locations(
if mime_parser.message_kml.is_some() { if mime_parser.message_kml.is_some() {
let locations = &mime_parser.message_kml.as_ref().unwrap().locations; let locations = &mime_parser.message_kml.as_ref().unwrap().locations;
let newest_location_id = let newest_location_id = location::save(context, chat_id, from_id, locations, true)
location::save(context, chat_id, from_id, locations, true).unwrap_or_default(); .await
.unwrap_or_default();
if 0 != newest_location_id if 0 != newest_location_id
&& !hidden && !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; location_id_written = true;
send_event = true; send_event = true;
@@ -704,18 +714,21 @@ fn save_locations(
if mime_parser.location_kml.is_some() { if mime_parser.location_kml.is_some() {
if let Some(ref addr) = mime_parser.location_kml.as_ref().unwrap().addr { 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() { if contact.get_addr().to_lowercase() == addr.to_lowercase() {
let locations = &mime_parser.location_kml.as_ref().unwrap().locations; let locations = &mime_parser.location_kml.as_ref().unwrap().locations;
let newest_location_id = let newest_location_id =
location::save(context, chat_id, from_id, locations, false) location::save(context, chat_id, from_id, locations, false)
.await
.unwrap_or_default(); .unwrap_or_default();
if newest_location_id != 0 && !hidden && !location_id_written { if newest_location_id != 0 && !hidden && !location_id_written {
if let Err(err) = location::set_msg_location_id( if let Err(err) = location::set_msg_location_id(
context, context,
insert_msg_id, insert_msg_id,
newest_location_id, newest_location_id,
) { )
.await
{
error!(context, "Failed to set msg_location_id: {:?}", err); error!(context, "Failed to set msg_location_id: {:?}", err);
} }
} }
@@ -730,7 +743,7 @@ fn save_locations(
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn calc_timestamps( async fn calc_timestamps(
context: &Context, context: &Context,
chat_id: ChatId, chat_id: ChatId,
from_id: u32, from_id: u32,
@@ -747,19 +760,22 @@ fn calc_timestamps(
} }
*sort_timestamp = message_timestamp; *sort_timestamp = message_timestamp;
if is_fresh_msg { if is_fresh_msg {
let last_msg_time: Option<i64> = context.sql.query_get_value( let last_msg_time: Option<i64> = context
context, .sql
"SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?", .query_get_value(
params![chat_id, from_id as i32, *sort_timestamp], 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 let Some(last_msg_time) = last_msg_time {
if last_msg_time > 0 && *sort_timestamp <= last_msg_time { if last_msg_time > 0 && *sort_timestamp <= last_msg_time {
*sort_timestamp = last_msg_time + 1; *sort_timestamp = last_msg_time + 1;
} }
} }
} }
if *sort_timestamp >= dc_smeared_time(context) { if *sort_timestamp >= dc_smeared_time(context).await {
*sort_timestamp = dc_create_smeared_timestamp(context); *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(""); let mut better_msg: String = From::from("");
if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled { if mime_parser.is_system_message == SystemMessage::LocationStreamingEnabled {
better_msg = better_msg = context
context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32); .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", from_id as u32)
.await;
set_better_msg(mime_parser, &better_msg); set_better_msg(mime_parser, &better_msg);
} }
@@ -823,6 +840,7 @@ async fn create_or_lookup_group(
from_id, from_id,
to_ids, to_ids,
) )
.await
.map_err(|err| { .map_err(|err| {
info!(context, "could not create adhoc-group: {:?}", err); info!(context, "could not create adhoc-group: {:?}", err);
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()) let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap())
.await .await
== from_id as u32; == from_id as u32;
better_msg = context.stock_system_msg( better_msg = context
if left_group { .stock_system_msg(
StockMessage::MsgGroupLeft if left_group {
} else { StockMessage::MsgGroupLeft
StockMessage::MsgDelMember } else {
}, StockMessage::MsgDelMember
X_MrRemoveFromGrp.as_ref().unwrap(), },
"", X_MrRemoveFromGrp.as_ref().unwrap(),
from_id as u32, "",
) from_id as u32,
)
.await
} else { } else {
let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned(); let field = mime_parser.get(HeaderDef::ChatGroupMemberAdded).cloned();
if let Some(optional_field) = field { if let Some(optional_field) = field {
mime_parser.is_system_message = SystemMessage::MemberAddedToGroup; mime_parser.is_system_message = SystemMessage::MemberAddedToGroup;
better_msg = context.stock_system_msg( better_msg = context
StockMessage::MsgAddMember, .stock_system_msg(
&optional_field, StockMessage::MsgAddMember,
"", &optional_field,
from_id as u32, "",
); from_id as u32,
)
.await;
X_MrAddToGrp = Some(optional_field); X_MrAddToGrp = Some(optional_field);
} else { } else {
let field = mime_parser.get(HeaderDef::ChatGroupNameChanged); let field = mime_parser.get(HeaderDef::ChatGroupNameChanged);
if let Some(field) = field { if let Some(field) = field {
X_MrGrpNameChanged = true; X_MrGrpNameChanged = true;
better_msg = context.stock_system_msg( better_msg = context
StockMessage::MsgGrpName, .stock_system_msg(
field, StockMessage::MsgGrpName,
if let Some(ref name) = grpname { field,
name if let Some(ref name) = grpname {
} else { name
"" } else {
}, ""
from_id as u32, },
); from_id as u32,
)
.await;
mime_parser.is_system_message = SystemMessage::GroupNameChanged; mime_parser.is_system_message = SystemMessage::GroupNameChanged;
} else if let Some(value) = mime_parser.get(HeaderDef::ChatContent) { } 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, // this is just an explicit message containing the group-avatar,
// apart from that, the group-avatar is send along with various other messages // apart from that, the group-avatar is send along with various other messages
mime_parser.is_system_message = SystemMessage::GroupImageChanged; mime_parser.is_system_message = SystemMessage::GroupImageChanged;
better_msg = context.stock_system_msg( better_msg = context
match avatar_action { .stock_system_msg(
AvatarAction::Delete => StockMessage::MsgGrpImgDeleted, match avatar_action {
AvatarAction::Change(_) => StockMessage::MsgGrpImgChanged, AvatarAction::Delete => StockMessage::MsgGrpImgDeleted,
}, AvatarAction::Change(_) => StockMessage::MsgGrpImgChanged,
"", },
"", "",
from_id as u32, "",
) from_id as u32,
)
.await
} }
} }
} }
@@ -907,7 +933,7 @@ async fn create_or_lookup_group(
if !chat_id.is_error() { if !chat_id.is_error() {
if chat_id_verified { if chat_id_verified {
if let Err(err) = 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); warn!(context, "verification problem: {}", err);
let s = format!("{}. See 'Info' for more details", 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. // but still show the message as part of the chat.
// After all, the sender has a reference/in-reply-to that // After all, the sender has a reference/in-reply-to that
// points to this chat. // 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()); 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 // group does not exist but should be created
let create_verified = if mime_parser.get(HeaderDef::ChatVerified).is_some() { let create_verified = if mime_parser.get(HeaderDef::ChatVerified).is_some() {
if let Err(err) = 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); warn!(context, "verification problem: {}", err);
let s = format!("{}. See 'Info' for more details", err); let s = format!("{}. See 'Info' for more details", err);
@@ -989,6 +1015,7 @@ async fn create_or_lookup_group(
from_id, from_id,
to_ids, to_ids,
) )
.await
.map_err(|err| { .map_err(|err| {
warn!(context, "failed to create ad-hoc group: {:?}", err); warn!(context, "failed to create ad-hoc group: {:?}", err);
err err
@@ -1019,7 +1046,7 @@ async fn create_or_lookup_group(
.sql .sql
.execute( .execute(
"UPDATE chats SET name=? WHERE id=?;", "UPDATE chats SET name=? WHERE id=?;",
params![grpname, chat_id], paramsv![grpname.to_string(), chat_id],
) )
.await .await
.is_ok() .is_ok()
@@ -1061,7 +1088,7 @@ async fn create_or_lookup_group(
if !Contact::addr_equals_contact(context, &self_addr, to_id).await if !Contact::addr_equals_contact(context, &self_addr, to_id).await
&& !chat::is_contact_in_chat(context, chat_id, 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; 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; let contact_id = Contact::lookup_id_by_addr(context, removed_addr).await;
if contact_id != 0 { if contact_id != 0 {
info!(context, "remove {:?} from chat id={}", contact_id, chat_id); 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; 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 /// 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, context: &Context,
mime_parser: &MimeMessage, mime_parser: &MimeMessage,
allow_creation: bool, allow_creation: bool,
@@ -1126,12 +1153,14 @@ fn create_or_lookup_adhoc_group(
return Ok((ChatId::new(0), Blocked::Not)); 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() { if !chat_ids.is_empty() {
let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ","); let chat_ids_str = join(chat_ids.iter().map(|x| x.to_string()), ",");
let res = context.sql.query_row( let res = context
format!( .sql
"SELECT c.id, .query_row(
format!(
"SELECT c.id,
c.blocked c.blocked
FROM chats c FROM chats c
LEFT JOIN msgs m LEFT JOIN msgs m
@@ -1140,16 +1169,17 @@ fn create_or_lookup_adhoc_group(
ORDER BY m.timestamp DESC, ORDER BY m.timestamp DESC,
m.id DESC m.id DESC
LIMIT 1;", LIMIT 1;",
chat_ids_str chat_ids_str
), ),
params![], paramsv![],
|row| { |row| {
Ok(( Ok((
row.get::<_, ChatId>(0)?, row.get::<_, ChatId>(0)?,
row.get::<_, Option<Blocked>>(1)?.unwrap_or_default(), row.get::<_, Option<Blocked>>(1)?.unwrap_or_default(),
)) ))
}, },
); )
.await;
if let Ok((id, id_blocked)) = res { if let Ok((id, id_blocked)) = res {
/* success, chat found */ /* success, chat found */
@@ -1167,7 +1197,7 @@ fn create_or_lookup_adhoc_group(
// create a new ad-hoc group // create a new ad-hoc group
// - there is no need to check if this group exists; otherwise we would have caught it above // - 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() { if grpid.is_empty() {
warn!( warn!(
context, context,
@@ -1176,9 +1206,10 @@ fn create_or_lookup_adhoc_group(
return Ok((ChatId::new(0), Blocked::Not)); return Ok((ChatId::new(0), Blocked::Not));
} }
// use subject as initial chat name // use subject as initial chat name
let grpname = mime_parser.get_subject().unwrap_or_else(|| { let default_name = context
context.stock_string_repl_int(StockMessage::Member, member_ids.len() as i32) .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 // create group record
let new_chat_id: ChatId = create_group_record( let new_chat_id: ChatId = create_group_record(
@@ -1187,9 +1218,10 @@ fn create_or_lookup_adhoc_group(
grpname, grpname,
create_blocked, create_blocked,
VerifiedStatus::Unverified, VerifiedStatus::Unverified,
); )
.await;
for &member_id in &member_ids { 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)); context.call_cb(Event::ChatModified(new_chat_id));
@@ -1206,7 +1238,7 @@ async fn create_group_record(
) -> ChatId { ) -> ChatId {
if context.sql.execute( if context.sql.execute(
"INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);",
params![ paramsv![
if VerifiedStatus::Unverified != create_verified { if VerifiedStatus::Unverified != create_verified {
Chattype::VerifiedGroup Chattype::VerifiedGroup
} else { } else {
@@ -1245,7 +1277,7 @@ async fn create_group_record(
chat_id 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: /* algorithm:
- sort normalized, lowercased, e-mail addresses alphabetically - sort normalized, lowercased, e-mail addresses alphabetically
- put all e-mail addresses into a single string, separate the address by a single comma - 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_ids_str = join(member_ids.iter().map(|x| x.to_string()), ",");
let member_cs = context let member_cs = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_else(|| "no-self".to_string()) .unwrap_or_else(|| "no-self".to_string())
.to_lowercase(); .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 "SELECT addr FROM contacts WHERE id IN({}) AND id!=1", // 1=DC_CONTACT_ID_SELF
member_ids_str member_ids_str
), ),
params![], paramsv![],
|row| row.get::<_, String>(0), |row| row.get::<_, String>(0),
|rows| { |rows| {
let mut addrs = rows.collect::<std::result::Result<Vec<_>, _>>()?; 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) Ok(acc)
}, },
) )
.await
.unwrap_or_else(|_| member_cs); .unwrap_or_else(|_| member_cs);
hex_hash(&members) hex_hash(&members)
@@ -1289,7 +1323,7 @@ fn hex_hash(s: impl AsRef<str>) -> String {
hex::encode(&result[..8]) hex::encode(&result[..8])
} }
fn search_chat_ids_by_contact_ids( async fn search_chat_ids_by_contact_ids(
context: &Context, context: &Context,
unsorted_contact_ids: &[u32], unsorted_contact_ids: &[u32],
) -> Result<Vec<ChatId>> { ) -> 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 ORDER BY cc.chat_id, cc.contact_id;", // 1=DC_CONTACT_ID_SELF
contact_ids_str contact_ids_str
), ),
params![], paramsv![],
|row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)), |row| Ok((row.get::<_, ChatId>(0)?, row.get::<_, u32>(1)?)),
|rows| { |rows| {
let mut last_chat_id = ChatId::new(0); let mut last_chat_id = ChatId::new(0);
@@ -1347,20 +1381,20 @@ fn search_chat_ids_by_contact_ids(
} }
Ok(()) Ok(())
} }
)?; ).await?;
} }
} }
Ok(chat_ids) Ok(chat_ids)
} }
fn check_verified_properties( async fn check_verified_properties(
context: &Context, context: &Context,
mimeparser: &MimeMessage, mimeparser: &MimeMessage,
from_id: u32, from_id: u32,
to_ids: &ContactIds, to_ids: &ContactIds,
) -> Result<()> { ) -> 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."); 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 // this check is skipped for SELF as there is no proper SELF-peerstate
// and results in group-splits otherwise. // and results in group-splits otherwise.
if from_id != DC_CONTACT_ID_SELF { 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() if peerstate.is_none()
|| contact.is_verified_ex(context, peerstate.as_ref()) || contact.is_verified_ex(context, peerstate.as_ref()).await
!= VerifiedStatus::BidirectVerified != VerifiedStatus::BidirectVerified
{ {
bail!( bail!(
@@ -1398,29 +1432,32 @@ fn check_verified_properties(
} }
let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ","); let to_ids_str = join(to_ids.iter().map(|x| x.to_string()), ",");
let rows = context.sql.query_map( let rows = context
format!( .sql
"SELECT c.addr, LENGTH(ps.verified_key_fingerprint) FROM contacts c \ .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({}) ", LEFT JOIN acpeerstates ps ON c.addr=ps.addr WHERE c.id IN({}) ",
to_ids_str to_ids_str
), ),
params![], paramsv![],
|row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))), |row| Ok((row.get::<_, String>(0)?, row.get::<_, i32>(1).unwrap_or(0))),
|rows| { |rows| {
rows.collect::<std::result::Result<Vec<_>, _>>() rows.collect::<std::result::Result<Vec<_>, _>>()
.map_err(Into::into) .map_err(Into::into)
}, },
)?; )
.await?;
for (to_addr, _is_verified) in rows.into_iter() { for (to_addr, _is_verified) in rows.into_iter() {
info!( info!(
context, context,
"check_verified_properties: {:?} self={:?}", "check_verified_properties: {:?} self={:?}",
to_addr, to_addr,
context.is_self_addr(&to_addr) context.is_self_addr(&to_addr).await
); );
let mut is_verified = _is_verified != 0; 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 // mark gossiped keys (if any) as verified
if mimeparser.gossipped_addr.contains(&to_addr) { if mimeparser.gossipped_addr.contains(&to_addr) {
@@ -1442,7 +1479,7 @@ fn check_verified_properties(
&fp, &fp,
PeerstateVerifiedStatus::BidirectVerified, PeerstateVerifiedStatus::BidirectVerified,
); );
peerstate.save_to_db(&context.sql, false)?; peerstate.save_to_db(&context.sql, false).await?;
is_verified = true; 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 /* 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) */ `In-Reply-To`/`References:` (to support non-Delta-Clients) */
if let Some(field) = mime_parser.get(HeaderDef::InReplyTo) { 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; return true;
} }
} }
if let Some(field) = mime_parser.get(HeaderDef::References) { 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; return true;
} }
} }
@@ -1487,14 +1524,14 @@ fn is_reply_to_known_message(context: &Context, mime_parser: &MimeMessage) -> bo
false 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() { if mid_list.is_empty() {
return false; return false;
} }
if let Ok(ids) = mailparse::msgidparse(mid_list) { if let Ok(ids) = mailparse::msgidparse(mid_list) {
for id in ids.iter() { for id in ids.iter() {
if is_known_rfc724_mid(context, id) { if is_known_rfc724_mid(context, id).await {
return true; 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). /// 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 context
.sql .sql
.exists( .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 \ LEFT JOIN chats c ON m.chat_id=c.id \
WHERE m.rfc724_mid=? \ WHERE m.rfc724_mid=? \
AND m.chat_id>9 AND c.blocked=0;", AND m.chat_id>9 AND c.blocked=0;",
params![rfc724_mid], paramsv![rfc724_mid],
) )
.await
.unwrap_or_default() .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 /// - 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 /// - 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) /// - 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 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; return true;
} }
} }
if let Some(value) = mime_parser.get(HeaderDef::References) { 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; return true;
} }
} }
@@ -1538,10 +1576,10 @@ fn is_reply_to_messenger_message(context: &Context, mime_parser: &MimeMessage) -
false 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) { if let Ok(ids) = mailparse::msgidparse(mid_list) {
for id in ids.iter() { for id in ids.iter() {
if is_msgrmsg_rfc724_mid(context, id) { if is_msgrmsg_rfc724_mid(context, id).await {
return true; 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. /// 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 context
.sql .sql
.exists( .exists(
"SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;", "SELECT id FROM msgs WHERE rfc724_mid=? AND msgrmsg!=0 AND chat_id>9;",
params![rfc724_mid], paramsv![rfc724_mid],
) )
.await
.unwrap_or_default() .unwrap_or_default()
} }
fn dc_add_or_lookup_contacts_by_address_list( async fn dc_add_or_lookup_contacts_by_address_list(
context: &Context, context: &Context,
addr_list_raw: &str, addr_list_raw: &str,
origin: Origin, origin: Origin,
@@ -1576,21 +1615,22 @@ fn dc_add_or_lookup_contacts_by_address_list(
for addr in addrs.iter() { for addr in addrs.iter() {
match addr { match addr {
mailparse::MailAddr::Single(info) => { mailparse::MailAddr::Single(info) => {
contact_ids.insert(add_or_lookup_contact_by_addr( contact_ids.insert(
context, add_or_lookup_contact_by_addr(context, &info.display_name, &info.addr, origin)
&info.display_name, .await?,
&info.addr, );
origin,
)?);
} }
mailparse::MailAddr::Group(infos) => { mailparse::MailAddr::Group(infos) => {
for info in &infos.addrs { for info in &infos.addrs {
contact_ids.insert(add_or_lookup_contact_by_addr( contact_ids.insert(
context, add_or_lookup_contact_by_addr(
&info.display_name, context,
&info.addr, &info.display_name,
origin, &info.addr,
)?); origin,
)
.await?,
);
} }
} }
} }
@@ -1600,13 +1640,13 @@ fn dc_add_or_lookup_contacts_by_address_list(
} }
/// Add contacts to database on receiving messages. /// Add contacts to database on receiving messages.
fn add_or_lookup_contact_by_addr( async fn add_or_lookup_contact_by_addr(
context: &Context, context: &Context,
display_name: &Option<String>, display_name: &Option<String>,
addr: &str, addr: &str,
origin: Origin, origin: Origin,
) -> Result<u32> { ) -> Result<u32> {
if context.is_self_addr(addr)? { if context.is_self_addr(addr).await? {
return Ok(DC_CONTACT_ID_SELF); return Ok(DC_CONTACT_ID_SELF);
} }
let display_name_normalized = display_name let display_name_normalized = display_name
@@ -1615,7 +1655,7 @@ fn add_or_lookup_contact_by_addr(
.unwrap_or_default(); .unwrap_or_default();
let (row_id, _modified) = 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); ensure!(row_id > 0, "could not add contact: {:?}", addr);
Ok(row_id) Ok(row_id)
@@ -1650,31 +1690,35 @@ mod tests {
assert_eq!(res, "b94d27b9934d3e08"); assert_eq!(res, "b94d27b9934d3e08");
} }
#[test] #[async_std::test]
fn test_grpid_simple() { async fn test_grpid_simple() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"From: hello\n\ let raw = b"From: hello\n\
Subject: outer-subject\n\ Subject: outer-subject\n\
In-Reply-To: <lqkjwelq123@123123>\n\ In-Reply-To: <lqkjwelq123@123123>\n\
References: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\n\ References: <Gr.HcxyMARjyJy.9-uvzWPTLtV@nauta.cu>\n\
\n\ \n\
hello\x00"; 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); assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), None);
let grpid = Some("HcxyMARjyJy"); let grpid = Some("HcxyMARjyJy");
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid); assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), grpid);
} }
#[test] #[async_std::test]
fn test_grpid_from_multiple() { async fn test_grpid_from_multiple() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"From: hello\n\ let raw = b"From: hello\n\
Subject: outer-subject\n\ Subject: outer-subject\n\
In-Reply-To: <Gr.HcxyMARjyJy.9-qweqwe@asd.net>\n\ In-Reply-To: <Gr.HcxyMARjyJy.9-qweqwe@asd.net>\n\
References: <qweqweqwe>, <Gr.HcxyMARjyJy.9-uvzWPTLtV@nau.ca>\n\ References: <qweqweqwe>, <Gr.HcxyMARjyJy.9-uvzWPTLtV@nau.ca>\n\
\n\ \n\
hello\x00"; 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"); let grpid = Some("HcxyMARjyJy");
assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid); assert_eq!(extract_grpid(&mimeparser, HeaderDef::InReplyTo), grpid);
assert_eq!(extract_grpid(&mimeparser, HeaderDef::References), 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, // returns the currently smeared timestamp,
// may be used to check if call to dc_create_smeared_timestamp() is needed or not. // 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! // 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 mut now = time();
let ts = *context.last_smeared_timestamp.read().unwrap(); let ts = *context.last_smeared_timestamp.read().await;
if ts >= now { if ts >= now {
now = ts + 1; 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. // 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 now = time();
let mut ret = now; 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 { if ret <= *last_smeared_timestamp {
ret = *last_smeared_timestamp + 1; ret = *last_smeared_timestamp + 1;
if ret - now > MAX_SECONDS_TO_LEND_FROM_FUTURE { 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. // creates `count` timestamps that are guaranteed to be unique.
// the frist created timestamps is returned directly, // the frist created timestamps is returned directly,
// get the other timestamps just by adding 1..count-1 // 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 now = time();
let count = count as i64; let count = count as i64;
let mut start = now + min(count, MAX_SECONDS_TO_LEND_FROM_FUTURE) - count; 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); start = max(*last_smeared_timestamp + 1, start);
*last_smeared_timestamp = start + count - 1; *last_smeared_timestamp = start + count - 1;
@@ -711,9 +711,9 @@ mod tests {
} }
} }
#[test] #[async_std::test]
fn test_file_handling() { async fn test_file_handling() {
let t = dummy_context(); let t = dummy_context().await;
let context = &t.ctx; let context = &t.ctx;
let dc_file_exist = |ctx: &Context, fname: &str| { let dc_file_exist = |ctx: &Context, fname: &str| {
ctx.get_blobdir() ctx.get_blobdir()
@@ -784,15 +784,15 @@ mod tests {
assert!(!listflags_has(listflags, DC_GCL_ADD_SELF)); assert!(!listflags_has(listflags, DC_GCL_ADD_SELF));
} }
#[test] #[async_std::test]
fn test_create_smeared_timestamp() { async fn test_create_smeared_timestamp() {
let t = dummy_context(); let t = dummy_context().await;
assert_ne!( assert_ne!(
dc_create_smeared_timestamp(&t.ctx), dc_create_smeared_timestamp(&t.ctx).await,
dc_create_smeared_timestamp(&t.ctx) dc_create_smeared_timestamp(&t.ctx).await
); );
assert!( assert!(
dc_create_smeared_timestamp(&t.ctx) dc_create_smeared_timestamp(&t.ctx).await
>= SystemTime::now() >= SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
.unwrap() .unwrap()
@@ -800,17 +800,17 @@ mod tests {
); );
} }
#[test] #[async_std::test]
fn test_create_smeared_timestamps() { async fn test_create_smeared_timestamps() {
let t = dummy_context(); let t = dummy_context().await;
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1; let count = MAX_SECONDS_TO_LEND_FROM_FUTURE - 1;
let start = dc_create_smeared_timestamps(&t.ctx, count as usize); let start = dc_create_smeared_timestamps(&t.ctx, count as usize).await;
let next = dc_smeared_time(&t.ctx); let next = dc_smeared_time(&t.ctx).await;
assert!((start + count - 1) < next); assert!((start + count - 1) < next);
let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30; let count = MAX_SECONDS_TO_LEND_FROM_FUTURE + 30;
let start = dc_create_smeared_timestamps(&t.ctx, count as usize); let start = dc_create_smeared_timestamps(&t.ctx, count as usize).await;
let next = dc_smeared_time(&t.ctx); let next = dc_smeared_time(&t.ctx).await;
assert!((start + count - 1) < next); 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); let autocryptheader = Aheader::from_headers(context, &from, &mail.headers);
if message_time > 0 { 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 mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader { if let Some(ref header) = autocryptheader {
@@ -167,7 +167,7 @@ pub async fn try_decrypt(
.await .await
{ {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { 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 let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() { if peerstate.degrade_event.is_some() {
@@ -207,7 +207,7 @@ async fn load_or_generate_self_public_key(
return SignedPublicKey::try_from(key) return SignedPublicKey::try_from(key)
.map_err(|_| Error::Message("Not a public key".into())); .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. // 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 { if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await {
@@ -377,15 +377,15 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_prexisting() { async fn test_prexisting() {
let t = dummy_context(); let t = dummy_context().await;
let test_addr = configure_alice_keypair(&t.ctx).await; let test_addr = configure_alice_keypair(&t.ctx).await;
assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr); assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr);
} }
#[test] #[async_std::test]
fn test_not_configured() { async fn test_not_configured() {
let t = dummy_context(); let t = dummy_context().await;
assert!(ensure_secret_key_exists(&t.ctx).is_err()); 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_std::test]
async fn test_existing() { async fn test_existing() {
let t = dummy_context(); let t = dummy_context().await;
let addr = configure_alice_keypair(&t.ctx).await; let addr = configure_alice_keypair(&t.ctx).await;
let key = load_or_generate_self_public_key(&t.ctx, addr).await; let key = load_or_generate_self_public_key(&t.ctx, addr).await;
assert!(key.is_ok()); assert!(key.is_ok());
} }
#[test] #[async_std::test]
#[ignore] // generating keys is expensive #[ignore] // generating keys is expensive
fn test_generate() { async fn test_generate() {
let t = dummy_context(); let t = dummy_context().await;
let addr = "alice@example.org"; 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()); 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!(key1.is_ok());
assert_eq!(key0.unwrap(), key1.unwrap()); assert_eq!(key0.unwrap(), key1.unwrap());
} }
#[test] #[async_std::test]
#[ignore] #[ignore]
fn test_generate_concurrent() { async fn test_generate_concurrent() {
use std::sync::Arc; use std::sync::Arc;
use std::thread;
let t = dummy_context(); let t = dummy_context().await;
let ctx = Arc::new(t.ctx); let ctx = Arc::new(t.ctx);
let ctx0 = Arc::clone(&ctx); let ctx0 = Arc::clone(&ctx);
let thr0 = let thr0 = async_std::task::spawn(async move {
thread::spawn(move || load_or_generate_self_public_key(&ctx0, "alice@example.org")); load_or_generate_self_public_key(&ctx0, "alice@example.org").await
});
let ctx1 = Arc::clone(&ctx); let ctx1 = Arc::clone(&ctx);
let thr1 = let thr1 = async_std::task::spawn(async move {
thread::spawn(move || load_or_generate_self_public_key(&ctx1, "alice@example.org")); load_or_generate_self_public_key(&ctx1, "alice@example.org").await
let res0 = thr0.join().unwrap(); });
let res1 = thr1.join().unwrap();
let res0 = thr0.await;
let res1 = thr1.await;
assert_eq!(res0.unwrap(), res1.unwrap()); assert_eq!(res0.unwrap(), res1.unwrap());
} }
} }

View File

@@ -280,7 +280,9 @@ impl Imap {
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 { if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
let addr: &str = config.addr.as_ref(); 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 { let auth = OAuth2 {
user: imap_user.into(), user: imap_user.into(),
access_token: token, access_token: token,
@@ -298,11 +300,13 @@ impl Imap {
let config = self.config.read().await; let config = self.config.read().await;
let imap_server: &str = config.imap_server.as_ref(); let imap_server: &str = config.imap_server.as_ref();
let imap_port = config.imap_port; let imap_port = config.imap_port;
context.stock_string_repl_str2( context
StockMessage::ServerResponse, .stock_string_repl_str2(
format!("{}:{}", imap_server, imap_port), StockMessage::ServerResponse,
err.to_string(), format!("{}:{}", imap_server, imap_port),
) err.to_string(),
)
.await
}; };
// IMAP connection failures are reported to users // IMAP connection failures are reported to users
emit_event!(context, Event::ErrorNetwork(message)); emit_event!(context, Event::ErrorNetwork(message));
@@ -319,7 +323,9 @@ impl Imap {
} }
Err((err, _)) => { Err((err, _)) => {
let imap_user = self.config.read().await.imap_user.to_owned(); 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!( emit_event!(
context, context,
@@ -539,7 +545,8 @@ impl Imap {
// id we do not do this here, we'll miss the first message // 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 // 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)); 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!( info!(
context, context,
"uid/validity change: new {}/{} current {}/{}", "uid/validity change: new {}/{} current {}/{}",
@@ -651,6 +659,7 @@ impl Imap {
); );
} else { } else {
let show = prefetch_should_download(context, &headers, show_emails) let show = prefetch_should_download(context, &headers, show_emails)
.await
.map_err(|err| { .map_err(|err| {
warn!(context, "prefetch_should_download error: {}", err); warn!(context, "prefetch_should_download error: {}", err);
err err
@@ -687,7 +696,8 @@ impl Imap {
}; };
if new_last_seen_uid > last_seen_uid { 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 { if read_errors > 0 {
@@ -1322,7 +1332,7 @@ async fn precheck_imf(
server_uid: u32, server_uid: u32,
) -> bool { ) -> bool {
if let Ok((old_server_folder, old_server_uid, msg_id)) = 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 { if old_server_folder.is_empty() && old_server_uid == 0 {
info!(context, "[move] detected bcc-self {}", rfc724_mid,); 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 { 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 true
} else { } 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, context: &Context,
headers: &[mailparse::MailHeader], headers: &[mailparse::MailHeader<'_>],
) -> Result<bool> { ) -> Result<bool> {
if let Some(value) = headers.get_header_value(HeaderDef::InReplyTo)? { 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); return Ok(true);
} }
} }
if let Some(value) = headers.get_header_value(HeaderDef::References)? { 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); return Ok(true);
} }
} }
@@ -1386,13 +1396,13 @@ fn prefetch_is_reply_to_chat_message(
Ok(false) Ok(false)
} }
fn prefetch_should_download( async fn prefetch_should_download(
context: &Context, context: &Context,
headers: &[mailparse::MailHeader], headers: &[mailparse::MailHeader<'_>],
show_emails: ShowEmails, show_emails: ShowEmails,
) -> Result<bool> { ) -> Result<bool> {
let is_chat_message = headers.get_header_value(HeaderDef::ChatVersion)?.is_some(); 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. // Autocrypt Setup Message should be shown even if it is from non-chat client.
let is_autocrypt_setup_message = headers let is_autocrypt_setup_message = headers
@@ -1403,7 +1413,8 @@ fn prefetch_should_download(
.get_header_value(HeaderDef::From_)? .get_header_value(HeaderDef::From_)?
.unwrap_or_default(); .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 accepted_contact = origin.is_known();
let show = is_autocrypt_setup_message let show = is_autocrypt_setup_message

View File

@@ -29,15 +29,10 @@ impl Session {
mailbox_pattern: Option<&str>, mailbox_pattern: Option<&str>,
) -> ImapResult<impl Stream<Item = ImapResult<Name>> + '_ + Send + Unpin> { ) -> ImapResult<impl Stream<Item = ImapResult<Name>> + '_ + Send + Unpin> {
match self { match self {
Session::Secure(i) => { Session::Secure(i) => i.list(reference_name, mailbox_pattern).await,
i.list(reference_name, mailbox_pattern).await Session::Insecure(_i) => {
// list.collect::<ImapResult<_>>().await?
}
Session::Insecure(i) => {
unimplemented!() unimplemented!()
// i.list(reference_name, mailbox_pattern).await // i.list(reference_name, mailbox_pattern).await
// .collect::<ImapResult<_>>()
// .await?
} }
} }
} }
@@ -86,7 +81,7 @@ impl Session {
{ {
let res = match self { let res = match self {
Session::Secure(i) => i.fetch(sequence_set, query).await?, Session::Secure(i) => i.fetch(sequence_set, query).await?,
Session::Insecure(i) => { Session::Insecure(_i) => {
unimplemented!() unimplemented!()
// i.fetch(sequence_set, query).await? // i.fetch(sequence_set, query).await?
} }
@@ -105,7 +100,7 @@ impl Session {
{ {
let res = match self { let res = match self {
Session::Secure(i) => i.uid_fetch(uid_set, query).await?, Session::Secure(i) => i.uid_fetch(uid_set, query).await?,
Session::Insecure(i) => { Session::Insecure(_i) => {
unimplemented!() unimplemented!()
// i.uid_fetch(uid_set, query).await? // i.uid_fetch(uid_set, query).await?
} }
@@ -125,7 +120,7 @@ impl Session {
{ {
let res = match self { let res = match self {
Session::Secure(i) => i.uid_store(uid_set, query).await?, Session::Secure(i) => i.uid_store(uid_set, query).await?,
Session::Insecure(i) => { Session::Insecure(_i) => {
unimplemented!() unimplemented!()
// i.uid_store(uid_set, query).await? // 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> { 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; let res = do_initiate_key_transfer(context).await;
context.free_ongoing(); context.free_ongoing().await;
res res
} }
@@ -125,10 +125,10 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
let mut msg: Message; let mut msg: Message;
let setup_code = create_setup_code(context); let setup_code = create_setup_code(context);
/* this may require a keypair to be created. this may take a second ... */ /* 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?; let setup_file_content = render_setup_file(context, &setup_code).await?;
/* encrypting may also take a while ... */ /* 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( let setup_file_blob = BlobObject::create(
context, context,
"autocrypt-setup-message.html", "autocrypt-setup-message.html",
@@ -148,11 +148,11 @@ async fn do_initiate_key_transfer(context: &Context) -> Result<String> {
ForcePlaintext::NoAutocryptHeader as i32, 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?; let msg_id = chat::send_msg(context, chat_id, &mut msg).await?;
info!(context, "Wait for setup message being sent ...",); info!(context, "Wait for setup message being sent ...",);
while !context.shall_stop_ongoing() { while !context.shall_stop_ongoing().await {
std::thread::sleep(std::time::Duration::from_secs(1)); async_std::task::sleep(std::time::Duration::from_secs(1)).await;
if let Ok(msg) = Message::load_from_db(context, msg_id).await { if let Ok(msg) = Message::load_from_db(context, msg_id).await {
if msg.is_sent() { if msg.is_sent() {
info!(context, "... setup message 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 pgp_msg = encr.replace("-----BEGIN PGP MESSAGE-----", &replacement);
let msg_subj = context.stock_str(StockMessage::AcSetupMsgSubject); let msg_subj = context.stock_str(StockMessage::AcSetupMsgSubject).await;
let msg_body = context.stock_str(StockMessage::AcSetupMsgBody); let msg_body = context.stock_str(StockMessage::AcSetupMsgBody).await;
let msg_body_html = msg_body.replace("\r", "").replace("\n", "<br>"); let msg_body_html = msg_body.replace("\r", "").replace("\n", "<br>");
Ok(format!( Ok(format!(
concat!( concat!(
@@ -368,7 +368,7 @@ pub fn normalize_setup_code(s: &str) -> String {
} }
pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { 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 what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
let param = job.param.get(Param::Arg).unwrap_or_default(); 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) { if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
// before we export anything, make sure the private key exists // before we export anything, make sure the private key exists
if e2ee::ensure_secret_key_exists(context).await.is_err() { 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."); bail!("Cannot create private key or private key not available.");
} else { } else {
dc_create_folder(context, &param)?; dc_create_folder(context, &param)?;
@@ -396,7 +396,7 @@ pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> {
bail!("unknown IMEX type"); bail!("unknown IMEX type");
} }
}; };
context.free_ongoing(); context.free_ongoing().await;
match success { match success {
Ok(()) => { Ok(()) => {
info!(context, "IMEX successfully completed"); 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, !context.is_configured().await,
"Cannot import backups to accounts in use." "Cannot import backups to accounts in use."
); );
context.sql.close(&context); context.sql.close(&context).await;
dc_delete_file(context, context.get_dbfile()); dc_delete_file(context, context.get_dbfile());
ensure!( ensure!(
!context.get_dbfile().exists(), !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 let total_files_cnt = context
.sql .sql
.query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![]) .query_get_value::<isize>(context, "SELECT COUNT(*) FROM backup_blobs;", paramsv![])
.await .await
.unwrap_or_default() as usize; .unwrap_or_default() as usize;
info!( 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, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt,
); );
let res = context let files = context
.sql .sql
.query_map( .query_map(
"SELECT file_name, file_content FROM backup_blobs ORDER BY id;", "SELECT file_name, file_content FROM backup_blobs ORDER BY id;",
params![], paramsv![],
|row| { |row| {
let name: String = row.get(0)?; let name: String = row.get(0)?;
let blob: Vec<u8> = row.get(1)?; 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)) Ok((name, blob))
}, },
|files| { |files| {
for (processed_files_cnt, file) in files.enumerate() { files
let (file_name, file_blob) = file?; .collect::<std::result::Result<Vec<_>, _>>()
if context.shall_stop_ongoing() { .map_err(Into::into)
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)
}, },
) )
.await; .await?;
match res { let mut all_files_extracted = true;
Ok(all_files_extracted) => { for (processed_files_cnt, (file_name, file_blob)) in files.into_iter().enumerate() {
if all_files_extracted { if context.shall_stop_ongoing().await {
// only delete backup_blobs if all files were successfully extracted all_files_extracted = false;
context break;
.sql
.execute("DROP TABLE backup_blobs;", params![])
.await?;
context.sql.execute("VACUUM;", params![]).await.ok();
Ok(())
} else {
bail!("received stop signal");
}
} }
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; 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 // we close the database during the copy of the dbfile
context.sql.close(context); context.sql.close(context).await;
info!( info!(
context, context,
"Backup '{}' to '{}'.", "Backup '{}' to '{}'.",
@@ -537,7 +536,10 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
dest_path_filename.display(), dest_path_filename.display(),
); );
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename); 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 { if !copied {
bail!( bail!(
@@ -566,7 +568,7 @@ async fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
Ok(()) Ok(())
} }
}; };
dest_sql.close(context); dest_sql.close(context).await;
Ok(res?) Ok(res?)
} }
@@ -577,7 +579,7 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
if !sql.table_exists("backup_blobs").await? { if !sql.table_exists("backup_blobs").await? {
sql.execute( sql.execute(
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
params![], paramsv![],
) )
.await?; .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); 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 // scan directory, pass 2: copy files
let dir_handle = std::fs::read_dir(&dir)?; 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; let mut processed_files_cnt = 0;
for entry in dir_handle { for entry in dir_handle {
let entry = entry?; let entry = entry?;
if context.shall_stop_ongoing() { if context.shall_stop_ongoing().await {
return Ok(()); return Ok(());
} }
processed_files_cnt += 1; processed_files_cnt += 1;
@@ -618,7 +617,10 @@ async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> {
continue; continue;
} }
// bail out if we can't insert // 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(()) Ok(())
@@ -690,7 +692,7 @@ async fn export_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()
.sql .sql
.query_map( .query_map(
"SELECT id, public_key, private_key, is_default FROM keypairs;", "SELECT id, public_key, private_key, is_default FROM keypairs;",
params![], paramsv![],
|row| { |row| {
let id = row.get(0)?; let id = row.get(0)?;
let public_key_blob: Vec<u8> = row.get(1)?; let public_key_blob: Vec<u8> = row.get(1)?;
@@ -766,7 +768,7 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_render_setup_file() { 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; configure_alice_keypair(&t.ctx).await;
let msg = render_setup_file(&t.ctx, "hello").await.unwrap(); let msg = render_setup_file(&t.ctx, "hello").await.unwrap();
@@ -786,9 +788,10 @@ mod tests {
#[async_std::test] #[async_std::test]
async fn test_render_setup_file_newline_replace() { async fn test_render_setup_file_newline_replace() {
let t = dummy_context(); let t = dummy_context().await;
t.ctx t.ctx
.set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string()) .set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string())
.await
.unwrap(); .unwrap();
configure_alice_keypair(&t.ctx).await; configure_alice_keypair(&t.ctx).await;
let msg = render_setup_file(&t.ctx, "pw").await.unwrap(); let msg = render_setup_file(&t.ctx, "pw").await.unwrap();
@@ -796,9 +799,9 @@ mod tests {
assert!(msg.contains("<p>hello<br>there</p>")); assert!(msg.contains("<p>hello<br>there</p>"));
} }
#[test] #[async_std::test]
fn test_create_setup_code() { async fn test_create_setup_code() {
let t = dummy_context(); let t = dummy_context().await;
let setupcode = create_setup_code(&t.ctx); let setupcode = create_setup_code(&t.ctx);
assert_eq!(setupcode.len(), 44); assert_eq!(setupcode.len(), 44);
assert_eq!(setupcode.chars().nth(4).unwrap(), '-'); assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
@@ -811,9 +814,9 @@ mod tests {
assert_eq!(setupcode.chars().nth(39).unwrap(), '-'); assert_eq!(setupcode.chars().nth(39).unwrap(), '-');
} }
#[test] #[async_std::test]
fn test_export_key_to_asc_file() { async fn test_export_key_to_asc_file() {
let context = dummy_context(); let context = dummy_context().await;
let key = Key::from(alice_keypair().public); let key = Key::from(alice_keypair().public);
let blobdir = "$BLOBDIR"; let blobdir = "$BLOBDIR";
assert!(export_key_to_asc_file(&context.ctx, blobdir, None, &key).is_ok()); 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_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597";
const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt"); const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt");
#[test] #[async_std::test]
fn test_split_and_decrypt() { async fn test_split_and_decrypt() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let context = &ctx.ctx; let context = &ctx.ctx;
let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec(); 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 { async fn delete(&self, context: &Context) -> bool {
context context
.sql .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 .await
.is_ok() .is_ok()
} }
@@ -162,7 +162,7 @@ impl Job {
.sql .sql
.execute( .execute(
"UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;",
params![ paramsv![
self.desired_timestamp, self.desired_timestamp,
self.tries as i64, self.tries as i64,
self.param.to_string(), 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. /* if there is a msg-id and it does not exist in the db, cancel sending.
this happends if dc_delete_msgs() was called this happends if dc_delete_msgs() was called
before the generated mime was sent out */ 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!( return Status::Finished(Err(format_err!(
"Not sending Message {} as it was deleted", "Not sending Message {} as it was deleted",
self.foreign_id self.foreign_id
@@ -295,7 +295,7 @@ impl Job {
async move { async move {
// smtp success, update db ASAP, then delete smtp file // smtp success, update db ASAP, then delete smtp file
if 0 != foreign_id { 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 // now also delete the generated file
dc_delete_file(context, filename); dc_delete_file(context, filename);
@@ -316,7 +316,7 @@ impl Job {
.sql .sql
.query_map( .query_map(
"SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?",
params![contact_id, self.job_id], paramsv![contact_id, self.job_id],
|row| { |row| {
let job_id: u32 = row.get(0)?; let job_id: u32 = row.get(0)?;
let params_str: String = row.get(1)?; 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 msg = job_try!(Message::load_from_db(context, msg_id).await);
let mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids)); let mimefactory =
let rendered_msg = job_try!(mimefactory.render()); 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 body = rendered_msg.message;
let addr = contact.get_addr(); let addr = contact.get_addr();
@@ -443,7 +444,8 @@ impl Job {
{ {
ImapActionResult::RetryLater => Status::RetryLater, ImapActionResult::RetryLater => Status::RetryLater,
ImapActionResult::Success => { 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(())) Status::Finished(Ok(()))
} }
ImapActionResult::Failed => { ImapActionResult::Failed => {
@@ -462,7 +464,7 @@ impl Job {
let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await);
if !msg.rfc724_mid.is_empty() { 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!( info!(
context, context,
"The message is deleted from the server when all parts are deleted.", "The message is deleted from the server when all parts are deleted.",
@@ -480,7 +482,7 @@ impl Job {
return Status::RetryNow; return Status::RetryNow;
} }
} }
Message::delete_from_db(context, msg.id); Message::delete_from_db(context, msg.id).await;
Status::Finished(Ok(())) Status::Finished(Ok(()))
} else { } else {
/* eg. device messages have no Message-ID */ /* eg. device messages have no Message-ID */
@@ -576,7 +578,7 @@ impl Job {
pub async fn kill_action(context: &Context, action: Action) -> bool { pub async fn kill_action(context: &Context, action: Action) -> bool {
context context
.sql .sql
.execute("DELETE FROM jobs WHERE action=?;", params![action]) .execute("DELETE FROM jobs WHERE action=?;", paramsv![action])
.await .await
.is_ok() .is_ok()
} }
@@ -590,7 +592,7 @@ pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> {
"DELETE FROM jobs WHERE id IN({})", "DELETE FROM jobs WHERE id IN({})",
job_ids.iter().map(|_| "?").join(",") job_ids.iter().map(|_| "?").join(",")
), ),
job_ids, job_ids.iter().map(|i| i as &dyn crate::ToSql).collect(),
) )
.await?; .await?;
Ok(()) Ok(())
@@ -715,7 +717,7 @@ async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Durati
.query_get_value( .query_get_value(
context, context,
"SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;", "SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;",
params![thread], paramsv![thread],
) )
.await .await
.unwrap_or_default(); .unwrap_or_default();
@@ -750,19 +752,19 @@ pub async fn maybe_network(context: &Context) {
pub async fn action_exists(context: &Context, action: Action) -> bool { pub async fn action_exists(context: &Context, action: Action) -> bool {
context context
.sql .sql
.exists("SELECT id FROM jobs WHERE action=?;", params![action]) .exists("SELECT id FROM jobs WHERE action=?;", paramsv![action])
.await .await
.unwrap_or_default() .unwrap_or_default()
} }
async fn set_delivered(context: &Context, msg_id: MsgId) { 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 let chat_id: ChatId = context
.sql .sql
.query_get_value( .query_get_value(
context, context,
"SELECT chat_id FROM msgs WHERE id=?", "SELECT chat_id FROM msgs WHERE id=?",
params![msg_id], paramsv![msg_id],
) )
.await .await
.unwrap_or_default(); .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 // special case for DC_JOB_SEND_MSG_TO_SMTP
pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
let mut msg = Message::load_from_db(context, msg_id).await?; 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 */ /* create message */
let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default(); 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(); let mut recipients = mimefactory.recipients();
@@ -808,14 +810,17 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
context, context,
"message {} has no recipient, skipping smtp-send", msg_id "message {} has no recipient, skipping smtp-send", msg_id
); );
set_delivered(context, msg_id); set_delivered(context, msg_id).await;
return Ok(()); return Ok(());
} }
let rendered_msg = mimefactory.render().map_err(|err| { let rendered_msg = match mimefactory.render().await {
message::set_msg_failed(context, msg_id, Some(err.to_string())); Ok(res) => Ok(res),
err Err(err) => {
})?; message::set_msg_failed(context, msg_id, Some(err.to_string())).await;
Err(err)
}
}?;
if needs_encryption && !rendered_msg.is_encrypted { if needs_encryption && !rendered_msg.is_encrypted {
/* unrecoverable */ /* unrecoverable */
@@ -823,7 +828,8 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
context, context,
msg_id, msg_id,
Some("End-to-end-encryption unavailable unexpectedly."), Some("End-to-end-encryption unavailable unexpectedly."),
); )
.await;
bail!( bail!(
"e2e encryption unavailable {} - {:?}", "e2e encryption unavailable {} - {:?}",
msg_id, msg_id,
@@ -857,7 +863,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> {
if rendered_msg.is_encrypted && !needs_encryption { if rendered_msg.is_encrypted && !needs_encryption {
msg.param.set_int(Param::GuaranteeE2ee, 1); msg.param.set_int(Param::GuaranteeE2ee, 1);
msg.save_param_to_disk(context); msg.save_param_to_disk(context).await;
} }
add_smtp_job( add_smtp_job(
@@ -917,77 +923,78 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
x => x, x => x,
}; };
// if Action::ConfigureImap == job.action || Action::ImexImap == job.action { if Action::ConfigureImap == job.action || Action::ImexImap == job.action {
// context.sentbox_thread.unsuspend(context).await; context.sentbox_thread.unsuspend(context).await;
// context.mvbox_thread.unsuspend(context).await; context.mvbox_thread.unsuspend(context).await;
// suspend_smtp_thread(context, false).await; suspend_smtp_thread(context, false).await;
// break; break;
// } }
// match try_res { match try_res {
// Status::RetryNow | Status::RetryLater => { Status::RetryNow | Status::RetryLater => {
// let tries = job.tries + 1; let tries = job.tries + 1;
// if tries < JOB_RETRIES { if tries < JOB_RETRIES {
// info!( info!(
// context, context,
// "{} thread increases job {} tries to {}", thread, job, tries "{} thread increases job {} tries to {}", thread, job, tries
// ); );
// job.tries = tries; job.tries = tries;
// let time_offset = get_backoff_time_offset(tries); let time_offset = get_backoff_time_offset(tries);
// job.desired_timestamp = time() + time_offset; job.desired_timestamp = time() + time_offset;
// job.update(context).await; job.update(context).await;
// info!( info!(
// context, context,
// "{}-job #{} not succeeded on try #{}, retry in {} seconds.", "{}-job #{} not succeeded on try #{}, retry in {} seconds.",
// thread, thread,
// job.job_id as u32, job.job_id as u32,
// tries, tries,
// time_offset time_offset
// ); );
// if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { if thread == Thread::Smtp && tries < JOB_RETRIES - 1 {
// context.smtp.state.write().await.perform_jobs_needed = context.smtp.state.write().await.perform_jobs_needed =
// PerformJobsNeeded::AvoidDos; PerformJobsNeeded::AvoidDos;
// } }
// } else { } else {
// info!( info!(
// context, context,
// "{} thread removes job {} as it exhausted {} retries", "{} thread removes job {} as it exhausted {} retries",
// thread, thread,
// job, job,
// JOB_RETRIES JOB_RETRIES
// ); );
// if job.action == Action::SendMsgToSmtp { if job.action == Action::SendMsgToSmtp {
// message::set_msg_failed( message::set_msg_failed(
// context, context,
// MsgId::new(job.foreign_id), MsgId::new(job.foreign_id),
// job.pending_error.as_ref(), job.pending_error.as_ref(),
// ); )
// } .await;
// job.delete(context).await; }
// } job.delete(context).await;
// if !probe_network { }
// continue; if !probe_network {
// } continue;
// // on dc_maybe_network() we stop trying here; }
// // these jobs are already tried once. // on dc_maybe_network() we stop trying here;
// // otherwise, we just continue with the next job // these jobs are already tried once.
// // to give other jobs a chance being tried at least once. // otherwise, we just continue with the next job
// break; // to give other jobs a chance being tried at least once.
// } break;
// Status::Finished(res) => { }
// if let Err(err) = res { Status::Finished(res) => {
// warn!( if let Err(err) = res {
// context, warn!(
// "{} removes job {} as it failed with error {:?}", thread, job, err context,
// ); "{} removes job {} as it failed with error {:?}", thread, job, err
// } else { );
// info!(context, "{} removes job {} as it succeeded", thread, job); } 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 location::job_maybe_send_locations_ended(context, &mut job).await
} }
Action::Housekeeping => { Action::Housekeeping => {
sql::housekeeping(context); sql::housekeeping(context).await;
Status::Finished(Ok(())) Status::Finished(Ok(()))
} }
}; };
@@ -1108,7 +1115,7 @@ pub async fn add(
context.sql.execute( context.sql.execute(
"INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);",
params![ paramsv![
timestamp, timestamp,
thread, thread,
action, 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;" FROM jobs WHERE thread=? AND tries>0 ORDER BY desired_timestamp, action DESC;"
}; };
let params_no_probe = params![thread as i64, time()]; let thread_i = thread as i64;
let params_probe = params![thread as i64]; let t = time();
let params: &[&dyn rusqlite::ToSql] = if !probe_network { let params_no_probe = paramsv![thread_i, t];
let params_probe = paramsv![thread_i];
let params = if !probe_network {
params_no_probe params_no_probe
} else { } else {
params_probe params_probe
@@ -1201,7 +1210,7 @@ mod tests {
use crate::test_utils::*; use crate::test_utils::*;
fn insert_job(context: &Context, foreign_id: i64) { async fn insert_job(context: &Context, foreign_id: i64) {
let now = time(); let now = time();
context context
.sql .sql
@@ -1209,7 +1218,7 @@ mod tests {
"INSERT INTO jobs "INSERT INTO jobs
(added_timestamp, thread, action, foreign_id, param, desired_timestamp) (added_timestamp, thread, action, foreign_id, param, desired_timestamp)
VALUES (?, ?, ?, ?, ?, ?);", VALUES (?, ?, ?, ?, ?, ?);",
params![ paramsv![
now, now,
Thread::from(Action::MoveMsg), Thread::from(Action::MoveMsg),
Action::MoveMsg, Action::MoveMsg,
@@ -1218,21 +1227,22 @@ mod tests {
now now
], ],
) )
.await
.unwrap(); .unwrap();
} }
#[test] #[async_std::test]
fn test_load_next_job() { async fn test_load_next_job() {
// We want to ensure that loading jobs skips over jobs which // We want to ensure that loading jobs skips over jobs which
// fails to load from the database instead of failing to load // fails to load from the database instead of failing to load
// all jobs. // all jobs.
let t = dummy_context(); let t = dummy_context().await;
insert_job(&t.ctx, -1); // This can not be loaded into Job struct. 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); let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await;
assert!(jobs.is_none()); assert!(jobs.is_none());
insert_job(&t.ctx, 1); insert_job(&t.ctx, 1).await;
let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false); let jobs = load_next_job(&t.ctx, Thread::from(Action::MoveMsg), false).await;
assert!(jobs.is_some()); assert!(jobs.is_some());
} }
} }

View File

@@ -207,7 +207,7 @@ impl Key {
sql.query_get_value( sql.query_get_value(
context, context,
"SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;", "SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;",
&[addr], paramsv![addr],
) )
.await .await
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public)) .and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Public))
@@ -221,7 +221,7 @@ impl Key {
sql.query_get_value( sql.query_get_value(
context, context,
"SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;", "SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;",
&[self_addr.as_ref()], paramsv![self_addr.as_ref()],
) )
.await .await
.and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private)) .and_then(|blob: Vec<u8>| Self::from_slice(&blob, KeyType::Private))
@@ -368,37 +368,37 @@ pub async fn store_self_keypair(
.sql .sql
.execute( .execute(
"DELETE FROM keypairs WHERE public_key=? OR private_key=?;", "DELETE FROM keypairs WHERE public_key=? OR private_key=?;",
params![public_key, secret_key], paramsv![public_key, secret_key],
) )
.await .await
.map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?; .map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?;
if default == KeyPairUse::Default { if default == KeyPairUse::Default {
context context
.sql .sql
.execute("UPDATE keypairs SET is_default=0;", params![]) .execute("UPDATE keypairs SET is_default=0;", paramsv![])
.await .await
.map_err(|err| SaveKeyError::new("failed to clear default", err))?; .map_err(|err| SaveKeyError::new("failed to clear default", err))?;
} }
let is_default = match default { let is_default = match default {
KeyPairUse::Default => true, KeyPairUse::Default => true as i32,
KeyPairUse::ReadOnly => false, 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 context
.sql .sql
.execute( .execute(
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) "INSERT INTO keypairs (addr, is_default, public_key, private_key, created)
VALUES (?,?,?,?,?);", VALUES (?,?,?,?,?);",
params![ params,
keypair.addr.to_string(),
is_default as i32,
public_key,
secret_key,
time()
],
) )
.await .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. /// Make a fingerprint human-readable, in hex format.
@@ -433,6 +433,7 @@ mod tests {
use crate::test_utils::*; use crate::test_utils::*;
use std::convert::TryFrom; use std::convert::TryFrom;
use async_std::sync::Arc;
use lazy_static::lazy_static; use lazy_static::lazy_static;
lazy_static! { lazy_static! {
@@ -583,22 +584,29 @@ i8pcjGO+IZffvyZJVRWfVooBJmWWbPB1pueo3tx8w3+fcuzpxz+RLFKaPyqXO+dD
assert_eq!(public.primary_key, KEYPAIR.public.primary_key); assert_eq!(public.primary_key, KEYPAIR.public.primary_key);
} }
#[test] #[async_std::test]
fn test_save_self_key_twice() { async fn test_save_self_key_twice() {
// Saving the same key twice should result in only one row in // Saving the same key twice should result in only one row in
// the keypairs table. // the keypairs table.
let t = dummy_context(); let t = dummy_context().await;
let nrows = || { let ctx = Arc::new(t.ctx);
t.ctx
.sql let ctx1 = ctx.clone();
.query_get_value::<_, u32>(&t.ctx, "SELECT COUNT(*) FROM keypairs;", params![]) let nrows = || async {
ctx1.sql
.query_get_value::<u32>(&ctx1, "SELECT COUNT(*) FROM keypairs;", paramsv![])
.await
.unwrap() .unwrap()
}; };
assert_eq!(nrows(), 0); assert_eq!(nrows().await, 0);
store_self_keypair(&t.ctx, &KEYPAIR, KeyPairUse::Default).unwrap(); store_self_keypair(&ctx, &KEYPAIR, KeyPairUse::Default)
assert_eq!(nrows(), 1); .await
store_self_keypair(&t.ctx, &KEYPAIR, KeyPairUse::Default).unwrap(); .unwrap();
assert_eq!(nrows(), 1); 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 // 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( sql.query_get_value(
context, context,
"SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;", "SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;",
&[self_addr.as_ref()], paramsv![self_addr.as_ref().to_string()],
) )
.await .await
.and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private)) .and_then(|blob: Vec<u8>| Key::from_slice(&blob, KeyType::Private))

View File

@@ -16,6 +16,19 @@ extern crate strum_macros;
#[macro_use] #[macro_use]
extern crate debug_stub_derive; 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] #[macro_use]
pub mod log; pub mod log;
#[macro_use] #[macro_use]
@@ -58,7 +71,7 @@ pub mod qr;
pub mod securejoin; pub mod securejoin;
mod simplify; mod simplify;
mod smtp; mod smtp;
pub mod sql; mod sql;
pub mod stock; pub mod stock;
mod token; mod token;
#[macro_use] #[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=?, \ SET locations_send_begin=?, \
locations_send_until=? \ locations_send_until=? \
WHERE id=?", WHERE id=?",
params![ paramsv![
if 0 != seconds { now } else { 0 }, if 0 != seconds { now } else { 0 },
if 0 != seconds { now + seconds } else { 0 }, if 0 != seconds { now + seconds } else { 0 },
chat_id, 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 { if 0 != seconds && !is_sending_locations_before {
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
msg.text = msg.text = Some(
Some(context.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)); context
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)
.await,
);
msg.param.set_cmd(SystemMessage::LocationStreamingEnabled); msg.param.set_cmd(SystemMessage::LocationStreamingEnabled);
chat::send_msg(context, chat_id, &mut msg) chat::send_msg(context, chat_id, &mut msg)
.await .await
.unwrap_or_default(); .unwrap_or_default();
} else if 0 == seconds && is_sending_locations_before { } else if 0 == seconds && is_sending_locations_before {
let stock_str = let stock_str = context
context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0)
chat::add_info_msg(context, chat_id, stock_str); .await;
chat::add_info_msg(context, chat_id, stock_str).await;
} }
context.call_cb(Event::ChatModified(chat_id)); context.call_cb(Event::ChatModified(chat_id));
if 0 != seconds { if 0 != seconds {
@@ -258,7 +262,7 @@ pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) ->
.sql .sql
.exists( .exists(
"SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", "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 .await
.unwrap_or_default() .unwrap_or_default()
@@ -274,7 +278,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64
.sql .sql
.query_map( .query_map(
"SELECT id FROM chats WHERE locations_send_until>?;", "SELECT id FROM chats WHERE locations_send_until>?;",
params![time()], paramsv![time()],
|row| row.get::<_, i32>(0), |row| row.get::<_, i32>(0),
|chats| chats.collect::<Result<Vec<_>, _>>().map_err(Into::into), |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( if let Err(err) = context.sql.execute(
"INSERT INTO locations \ "INSERT INTO locations \
(latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);", (latitude, longitude, accuracy, timestamp, chat_id, from_id) VALUES (?,?,?,?,?,?);",
params![ paramsv![
latitude, latitude,
longitude, longitude,
accuracy, accuracy,
@@ -326,7 +330,7 @@ pub async fn get_range(
AND (? OR l.from_id=?) \ AND (? OR l.from_id=?) \
AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \ AND (l.independent=1 OR (l.timestamp>=? AND l.timestamp<=?)) \
ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;", ORDER BY l.timestamp DESC, l.id DESC, msg_id DESC;",
params![ paramsv![
if chat_id.is_unset() { 1 } else { 0 }, if chat_id.is_unset() { 1 } else { 0 },
chat_id, chat_id,
if contact_id == 0 { 1 } else { 0 }, 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> { pub async fn delete_all(context: &Context) -> Result<(), Error> {
context context
.sql .sql
.execute("DELETE FROM locations;", params![]) .execute("DELETE FROM locations;", paramsv![])
.await?; .await?;
context.call_cb(Event::LocationChanged(None)); context.call_cb(Event::LocationChanged(None));
Ok(()) 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( 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=?;", "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_begin: i64 = row.get(0)?;
let send_until: i64 = row.get(1)?; let send_until: i64 = row.get(1)?;
let last_sent: i64 = row.get(2)?; 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 \ AND independent=0 \
GROUP BY timestamp \ GROUP BY timestamp \
ORDER 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| { |row| {
let location_id: i32 = row.get(0)?; let location_id: i32 = row.get(0)?;
let latitude: f64 = row.get(1)?; let latitude: f64 = row.get(1)?;
@@ -486,7 +490,7 @@ pub async fn set_kml_sent_timestamp(
.sql .sql
.execute( .execute(
"UPDATE chats SET locations_last_sent=? WHERE id=?;", "UPDATE chats SET locations_last_sent=? WHERE id=?;",
params![timestamp, chat_id], paramsv![timestamp, chat_id],
) )
.await?; .await?;
Ok(()) Ok(())
@@ -501,7 +505,7 @@ pub async fn set_msg_location_id(
.sql .sql
.execute( .execute(
"UPDATE msgs SET location_id=? WHERE id=?;", "UPDATE msgs SET location_id=? WHERE id=?;",
params![location_id, msg_id], paramsv![location_id, msg_id],
) )
.await?; .await?;
@@ -532,10 +536,10 @@ pub async fn save(
VALUES (?,?,?,?,?,?,?);", 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 { if independent || !exists {
stmt_insert.execute(params![ stmt_insert.execute(paramsv![
location.timestamp, location.timestamp,
contact_id as i32, contact_id as i32,
chat_id, 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 \ "SELECT id, locations_send_begin, locations_last_sent \
FROM chats \ FROM chats \
WHERE locations_send_until>?;", WHERE locations_send_until>?;",
params![now], paramsv![now],
|row| { |row| {
let chat_id: ChatId = row.get(0)?; let chat_id: ChatId = row.get(0)?;
let locations_send_begin: i64 = row.get(1)?; 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() .iter()
.filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| {
if !stmt_locations if !stmt_locations
.exists(params![ .exists(paramsv![
DC_CONTACT_ID_SELF, DC_CONTACT_ID_SELF,
locations_send_begin, *locations_send_begin,
locations_last_sent, *locations_last_sent,
]) ])
.unwrap_or_default() .unwrap_or_default()
{ {
@@ -682,7 +686,7 @@ pub(crate) async fn job_maybe_send_locations_ended(
.sql .sql
.query_row( .query_row(
"SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", "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)?)), |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)),
) )
.await .await
@@ -696,11 +700,13 @@ pub(crate) async fn job_maybe_send_locations_ended(
// not streaming, device-message already sent // not streaming, device-message already sent
job_try!(context.sql.execute( job_try!(context.sql.execute(
"UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?",
params![chat_id], paramsv![chat_id],
).await); ).await);
let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); let stock_str = context
chat::add_info_msg(context, chat_id, stock_str); .stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0)
.await;
chat::add_info_msg(context, chat_id, stock_str).await;
context.call_cb(Event::ChatModified(chat_id)); context.call_cb(Event::ChatModified(chat_id));
} }
} }
@@ -712,9 +718,9 @@ mod tests {
use super::*; use super::*;
use crate::test_utils::dummy_context; use crate::test_utils::dummy_context;
#[test] #[async_std::test]
fn test_kml_parse() { async fn test_kml_parse() {
let context = dummy_context(); let context = dummy_context().await;
let xml = 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>"; 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", " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id",
" WHERE m.id=?;" " WHERE m.id=?;"
), ),
params![id], paramsv![id],
|row| { |row| {
let mut msg = Message::default(); let mut msg = Message::default();
// msg.id = row.get::<_, AnyMsgId>("id")?; // msg.id = row.get::<_, AnyMsgId>("id")?;
@@ -310,12 +310,12 @@ impl Message {
if let Ok(msg) = Message::load_from_db(context, msg_id).await { if let Ok(msg) = Message::load_from_db(context, msg_id).await {
context context
.sql .sql
.execute("DELETE FROM msgs WHERE id=?;", params![msg.id]) .execute("DELETE FROM msgs WHERE id=?;", paramsv![msg.id])
.await .await
.ok(); .ok();
context context
.sql .sql
.execute("DELETE FROM msgs_mdns WHERE msg_id=?;", params![msg.id]) .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", paramsv![msg.id])
.await .await
.ok(); .ok();
} }
@@ -339,7 +339,7 @@ impl Message {
self.param.get_path(Param::File, context).unwrap_or(None) 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) { if chat::msgtype_has_file(self.viewtype) {
let file_param = self.param.get_path(Param::File, context)?; let file_param = self.param.get_path(Param::File, context)?;
if let Some(path_and_filename) = file_param { if let Some(path_and_filename) = file_param {
@@ -357,7 +357,7 @@ impl Message {
} }
if !self.id.is_unset() { 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 None
}; };
ret.fill(self, chat, contact.as_ref(), context); ret.fill(self, chat, contact.as_ref(), context).await;
ret 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( get_summarytext_by_raw(
self.viewtype, self.viewtype,
self.text.as_ref(), self.text.as_ref(),
@@ -508,6 +508,7 @@ impl Message {
approx_characters, approx_characters,
context, context,
) )
.await
} }
pub fn has_deviating_timestamp(&self) -> bool { pub fn has_deviating_timestamp(&self) -> bool {
@@ -595,7 +596,7 @@ impl Message {
self.param.set_int(Param::Duration, duration); self.param.set_int(Param::Duration, duration);
} }
pub fn latefiling_mediasize( pub async fn latefiling_mediasize(
&mut self, &mut self,
context: &Context, context: &Context,
width: i32, width: i32,
@@ -609,7 +610,7 @@ impl Message {
if duration > 0 { if duration > 0 {
self.param.set_int(Param::Duration, duration); 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 { pub async fn save_param_to_disk(&mut self, context: &Context) -> bool {
@@ -617,7 +618,7 @@ impl Message {
.sql .sql
.execute( .execute(
"UPDATE msgs SET param=? WHERE id=?;", "UPDATE msgs SET param=? WHERE id=?;",
params![self.param.to_string(), self.id], paramsv![self.param.to_string(), self.id],
) )
.await .await
.is_ok() .is_ok()
@@ -740,7 +741,7 @@ impl MessageState {
impl Lot { impl Lot {
/* library-internal */ /* library-internal */
/* in practice, the user additionally cuts the string himself pixel-accurate */ /* in practice, the user additionally cuts the string himself pixel-accurate */
pub fn fill( pub async fn fill(
&mut self, &mut self,
msg: &mut Message, msg: &mut Message,
chat: &Chat, chat: &Chat,
@@ -748,14 +749,26 @@ impl Lot {
context: &Context, context: &Context,
) { ) {
if msg.state == MessageState::OutDraft { 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; self.text1_meaning = Meaning::Text1Draft;
} else if msg.from_id == DC_CONTACT_ID_SELF { } else if msg.from_id == DC_CONTACT_ID_SELF {
if msg.is_info() || chat.is_self_talk() { if msg.is_info() || chat.is_self_talk() {
self.text1 = None; self.text1 = None;
self.text1_meaning = Meaning::None; self.text1_meaning = Meaning::None;
} else { } 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; self.text1_meaning = Meaning::Text1Self;
} }
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
@@ -778,13 +791,16 @@ impl Lot {
} }
} }
self.text2 = Some(get_summarytext_by_raw( self.text2 = Some(
msg.viewtype, get_summarytext_by_raw(
msg.text.as_ref(), msg.viewtype,
&msg.param, msg.text.as_ref(),
SUMMARY_CHARACTERS, &msg.param,
context, SUMMARY_CHARACTERS,
)); context,
)
.await,
);
self.timestamp = msg.get_timestamp(); self.timestamp = msg.get_timestamp();
self.state = msg.state.into(); self.state = msg.state.into();
@@ -806,7 +822,7 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
.query_get_value( .query_get_value(
context, context,
"SELECT txt_raw FROM msgs WHERE id=?;", "SELECT txt_raw FROM msgs WHERE id=?;",
params![msg_id], paramsv![msg_id],
) )
.await; .await;
@@ -843,21 +859,26 @@ pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String {
return ret; return ret;
} }
if let Ok(rows) = context.sql.query_map( if let Ok(rows) = context
"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;", .sql
params![msg_id], .query_map(
|row| { "SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;",
let contact_id: i32 = row.get(0)?; paramsv![msg_id],
let ts: i64 = row.get(1)?; |row| {
Ok((contact_id, ts)) let contact_id: i32 = row.get(0)?;
}, let ts: i64 = row.get(1)?;
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into), Ok((contact_id, ts))
) { },
|rows| rows.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await
{
for (contact_id, ts) in rows { for (contact_id, ts) in rows {
let fts = dc_timestamp_to_str(ts); let fts = dc_timestamp_to_str(ts);
ret += &format!("Read: {}", fts); ret += &format!("Read: {}", fts);
let name = Contact::load_from_db(context, contact_id as u32) let name = Contact::load_from_db(context, contact_id as u32)
.await
.map(|contact| contact.get_name_n_addr()) .map(|contact| contact.get_name_n_addr())
.unwrap_or_default(); .unwrap_or_default();
@@ -944,22 +965,25 @@ pub fn guess_msgtype_from_suffix(path: &Path) -> Option<(Viewtype, &str)> {
Some(info) Some(info)
} }
pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> { pub async fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option<String> {
context.sql.query_get_value( context
context, .sql
"SELECT mime_headers FROM msgs WHERE id=?;", .query_get_value(
params![msg_id], context,
) "SELECT mime_headers FROM msgs WHERE id=?;",
paramsv![msg_id],
)
.await
} }
pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) {
for msg_id in msg_ids.iter() { for msg_id in msg_ids.iter() {
if let Ok(msg) = Message::load_from_db(context, *msg_id).await { if let Ok(msg) = Message::load_from_db(context, *msg_id).await {
if msg.location_id > 0 { 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( job::add(
context, context,
Action::DeleteMsgOnImap, Action::DeleteMsgOnImap,
@@ -985,7 +1009,7 @@ async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -
.sql .sql
.execute( .execute(
"UPDATE msgs SET chat_id=? WHERE id=?;", "UPDATE msgs SET chat_id=? WHERE id=?;",
params![chat_id, msg_id], paramsv![chat_id, msg_id],
) )
.await .await
.is_ok() .is_ok()
@@ -996,7 +1020,7 @@ async fn delete_poi_location(context: &Context, location_id: u32) -> bool {
.sql .sql
.execute( .execute(
"DELETE FROM locations WHERE independent = 1 AND id=?;", "DELETE FROM locations WHERE independent = 1 AND id=?;",
params![location_id as i32], paramsv![location_id as i32],
) )
.await .await
.is_ok() .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()); let mut msgs = Vec::with_capacity(msg_ids.len());
for id in msg_ids.iter() { 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(( Ok((
row.get::<_, MessageState>("state")?, row.get::<_, MessageState>("state")?,
row.get::<_, Option<Blocked>>("blocked")? 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() { for (id, curr_state, curr_blocked) in msgs.into_iter() {
if curr_blocked == Blocked::Not { if curr_blocked == Blocked::Not {
if curr_state == MessageState::InFresh || curr_state == MessageState::InNoticed { 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); info!(context, "Seen message {}.", id);
job::add( job::add(
@@ -1060,7 +1084,7 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool {
send_event = true; send_event = true;
} }
} else if curr_state == MessageState::InFresh { } else if curr_state == MessageState::InFresh {
update_msg_state(context, *id, MessageState::InNoticed); update_msg_state(context, *id, MessageState::InNoticed).await;
send_event = true; send_event = true;
} }
} }
@@ -1080,29 +1104,31 @@ pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageSt
.sql .sql
.execute( .execute(
"UPDATE msgs SET state=? WHERE id=?;", "UPDATE msgs SET state=? WHERE id=?;",
params![state, msg_id], paramsv![state, msg_id],
) )
.await .await
.is_ok() .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() { if msg_ids.is_empty() {
return false; return false;
} }
context context
.sql .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() { for msg_id in msg_ids.iter() {
stmt.execute(params![star as i32, *msg_id])?; stmt.execute(paramsv![star as i32, *msg_id])?;
} }
Ok(()) Ok(())
}) })
.await
.is_ok() .is_ok()
} }
/// Returns a summary test. /// Returns a summary test.
pub fn get_summarytext_by_raw( pub async fn get_summarytext_by_raw(
viewtype: Viewtype, viewtype: Viewtype,
text: Option<impl AsRef<str>>, text: Option<impl AsRef<str>>,
param: &Params, param: &Params,
@@ -1111,16 +1137,20 @@ pub fn get_summarytext_by_raw(
) -> String { ) -> String {
let mut append_text = true; let mut append_text = true;
let prefix = match viewtype { let prefix = match viewtype {
Viewtype::Image => context.stock_str(StockMessage::Image).into_owned(), Viewtype::Image => context.stock_str(StockMessage::Image).await.into_owned(),
Viewtype::Gif => context.stock_str(StockMessage::Gif).into_owned(), Viewtype::Gif => context.stock_str(StockMessage::Gif).await.into_owned(),
Viewtype::Sticker => context.stock_str(StockMessage::Sticker).into_owned(), Viewtype::Sticker => context.stock_str(StockMessage::Sticker).await.into_owned(),
Viewtype::Video => context.stock_str(StockMessage::Video).into_owned(), Viewtype::Video => context.stock_str(StockMessage::Video).await.into_owned(),
Viewtype::Voice => context.stock_str(StockMessage::VoiceMessage).into_owned(), Viewtype::Voice => context
.stock_str(StockMessage::VoiceMessage)
.await
.into_owned(),
Viewtype::Audio | Viewtype::File => { Viewtype::Audio | Viewtype::File => {
if param.get_cmd() == SystemMessage::AutocryptSetupMessage { if param.get_cmd() == SystemMessage::AutocryptSetupMessage {
append_text = false; append_text = false;
context context
.stock_str(StockMessage::AcSetupMsgSubject) .stock_str(StockMessage::AcSetupMsgSubject)
.await
.to_string() .to_string()
} else { } else {
let file_name: String = param let file_name: String = param
@@ -1131,11 +1161,13 @@ pub fn get_summarytext_by_raw(
.map(|fname| fname.to_string_lossy().into_owned()) .map(|fname| fname.to_string_lossy().into_owned())
}) })
.unwrap_or_else(|| String::from("ErrFileName")); .unwrap_or_else(|| String::from("ErrFileName"));
let label = context.stock_str(if viewtype == Viewtype::Audio { let label = context
StockMessage::Audio .stock_str(if viewtype == Viewtype::Audio {
} else { StockMessage::Audio
StockMessage::File } else {
}); StockMessage::File
})
.await;
format!("{} {}", label, file_name) format!("{} {}", label, file_name)
} }
} }
@@ -1144,7 +1176,7 @@ pub fn get_summarytext_by_raw(
"".to_string() "".to_string()
} else { } else {
append_text = false; 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 // 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() { if msg_id.is_special() {
return false; return false;
} }
let chat_id: Option<ChatId> = context.sql.query_get_value( let chat_id: Option<ChatId> = context
context, .sql
"SELECT chat_id FROM msgs WHERE id=?;", .query_get_value(
params![msg_id], context,
); "SELECT chat_id FROM msgs WHERE id=?;",
paramsv![msg_id],
)
.await;
if let Some(chat_id) = chat_id { if let Some(chat_id) = chat_id {
!chat_id.is_trash() !chat_id.is_trash()
@@ -1208,7 +1243,7 @@ pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option<impl
.sql .sql
.execute( .execute(
"UPDATE msgs SET state=?, param=? WHERE id=?;", "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 .await
.is_ok() .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 /// returns Some if an event should be send
pub fn mdn_from_ext( pub async fn mdn_from_ext(
context: &Context, context: &Context,
from_id: u32, from_id: u32,
rfc724_mid: &str, rfc724_mid: &str,
@@ -1232,27 +1267,30 @@ pub fn mdn_from_ext(
return None; return None;
} }
let res = context.sql.query_row( let res = context
concat!( .sql
"SELECT", .query_row(
" m.id AS msg_id,", concat!(
" c.id AS chat_id,", "SELECT",
" c.type AS type,", " m.id AS msg_id,",
" m.state AS state", " c.id AS chat_id,",
" FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id", " c.type AS type,",
" WHERE rfc724_mid=? AND from_id=1", " m.state AS state",
" ORDER BY m.id;" " FROM msgs m LEFT JOIN chats c ON m.chat_id=c.id",
), " WHERE rfc724_mid=? AND from_id=1",
params![rfc724_mid], " ORDER BY m.id;"
|row| { ),
Ok(( paramsv![rfc724_mid],
row.get::<_, MsgId>("msg_id")?, |row| {
row.get::<_, ChatId>("chat_id")?, Ok((
row.get::<_, Chattype>("type")?, row.get::<_, MsgId>("msg_id")?,
row.get::<_, MessageState>("state")?, row.get::<_, ChatId>("chat_id")?,
)) row.get::<_, Chattype>("type")?,
}, row.get::<_, MessageState>("state")?,
); ))
},
)
.await;
if let Err(ref err) = res { if let Err(ref err) = res {
info!(context, "Failed to select MDN {:?}", err); info!(context, "Failed to select MDN {:?}", err);
} }
@@ -1268,30 +1306,34 @@ pub fn mdn_from_ext(
.sql .sql
.exists( .exists(
"SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;", "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(); .unwrap_or_default();
if !mdn_already_in_table { if !mdn_already_in_table {
context.sql.execute( context.sql.execute(
"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);", "INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);",
params![msg_id, from_id as i32, timestamp_sent], paramsv![msg_id, from_id as i32, timestamp_sent],
).unwrap_or_default(); // TODO: better error handling )
.await
.unwrap_or_default(); // TODO: better error handling
} }
// Normal chat? that's quite easy. // Normal chat? that's quite easy.
if chat_type == Chattype::Single { 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; read_by_all = true;
} else { } else {
// send event about new state // send event about new state
let ist_cnt = context let ist_cnt = context
.sql .sql
.query_get_value::<_, isize>( .query_get_value::<isize>(
context, context,
"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;", "SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;",
params![msg_id], paramsv![msg_id],
) )
.await
.unwrap_or_default() as usize; .unwrap_or_default() as usize;
/* /*
Groupsize: Min. MDNs Groupsize: Min. MDNs
@@ -1306,9 +1348,9 @@ pub fn mdn_from_ext(
(S=Sender, R=Recipient) (S=Sender, R=Recipient)
*/ */
// for rounding, SELF is already included! // 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 { 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; read_by_all = true;
} // else wait for more receipts } // else wait for more receipts
} }
@@ -1323,14 +1365,18 @@ pub fn mdn_from_ext(
} }
/// The number of messages assigned to real chat (!=deaddrop, !=trash) /// The number of messages assigned to real chat (!=deaddrop, !=trash)
pub fn get_real_msg_cnt(context: &Context) -> i32 { pub async fn get_real_msg_cnt(context: &Context) -> i32 {
match context.sql.query_row( match context
"SELECT COUNT(*) \ .sql
.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ 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;", WHERE m.id>9 AND m.chat_id>9 AND c.blocked=0;",
rusqlite::NO_PARAMS, paramsv![],
|row| row.get(0), |row| row.get(0),
) { )
.await
{
Ok(res) => res, Ok(res) => res,
Err(err) => { Err(err) => {
error!(context, "dc_get_real_msg_cnt() failed. {}", 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 { pub async fn get_deaddrop_msg_cnt(context: &Context) -> usize {
match context.sql.query_row( match context
"SELECT COUNT(*) \ .sql
.query_row(
"SELECT COUNT(*) \
FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \ FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id \
WHERE c.blocked=2;", WHERE c.blocked=2;",
rusqlite::NO_PARAMS, paramsv![],
|row| row.get::<_, isize>(0), |row| row.get::<_, isize>(0),
) { )
.await
{
Ok(res) => res as usize, Ok(res) => res as usize,
Err(err) => { Err(err) => {
error!(context, "dc_get_deaddrop_msg_cnt() failed. {}", 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 // check the number of messages with the same rfc724_mid
match context.sql.query_row( match context
"SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;", .sql
&[rfc724_mid], .query_row(
|row| row.get(0), "SELECT COUNT(*) FROM msgs WHERE rfc724_mid=?;",
) { paramsv![rfc724_mid],
|row| row.get(0),
)
.await
{
Ok(res) => res, Ok(res) => res,
Err(err) => { Err(err) => {
error!(context, "dc_get_rfc724_mid_cnt() failed. {}", 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, context: &Context,
rfc724_mid: &str, rfc724_mid: &str,
) -> Result<(String, u32, MsgId), Error> { ) -> Result<(String, u32, MsgId), Error> {
ensure!(!rfc724_mid.is_empty(), "empty rfc724_mid"); ensure!(!rfc724_mid.is_empty(), "empty rfc724_mid");
context let res = context
.sql .sql
.query_row( .query_row(
"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?", "SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?",
&[rfc724_mid], paramsv![rfc724_mid],
|row| { |row| {
let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default(); let server_folder = row.get::<_, Option<String>>(0)?.unwrap_or_default();
let server_uid = row.get(1)?; let server_uid = row.get(1)?;
@@ -1389,19 +1443,25 @@ pub(crate) fn rfc724_mid_exists(
Ok((server_folder, server_uid, msg_id)) 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, context: &Context,
rfc724_mid: &str, rfc724_mid: &str,
server_folder: impl AsRef<str>, server_folder: impl AsRef<str>,
server_uid: u32, server_uid: u32,
) { ) {
match context.sql.execute( match context
"UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;", .sql
params![server_folder.as_ref(), server_uid, rfc724_mid], .execute(
) { "UPDATE msgs SET server_folder=?, server_uid=? WHERE rfc724_mid=?;",
paramsv![server_folder.as_ref().to_string(), server_uid, rfc724_mid],
)
.await
{
Ok(_) => {} Ok(_) => {}
Err(err) => { Err(err) => {
warn!(context, "msg: failed to update server_uid: {}", err); warn!(context, "msg: failed to update server_uid: {}", err);
@@ -1432,7 +1492,7 @@ mod tests {
async fn test_prepare_message_and_send() { async fn test_prepare_message_and_send() {
use crate::config::Config; use crate::config::Config;
let d = test::dummy_context(); let d = test::dummy_context().await;
let ctx = &d.ctx; let ctx = &d.ctx;
let contact = Contact::create(ctx, "", "dest@example.com") let contact = Contact::create(ctx, "", "dest@example.com")
@@ -1454,9 +1514,9 @@ mod tests {
assert_eq!(_msg2.get_filemime(), None); assert_eq!(_msg2.get_filemime(), None);
} }
#[test] #[async_std::test]
pub fn test_get_summarytext_by_raw() { async fn test_get_summarytext_by_raw() {
let d = test::dummy_context(); let d = test::dummy_context().await;
let ctx = &d.ctx; let ctx = &d.ctx;
let some_text = Some("bla bla".to_string()); let some_text = Some("bla bla".to_string());
@@ -1467,62 +1527,69 @@ mod tests {
some_file.set(Param::File, "foo.bar"); some_file.set(Param::File, "foo.bar");
assert_eq!( 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 "bla bla" // for simple text, the type is not added to the summary
); );
assert_eq!( 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 "Image" // file names are not added for images
); );
assert_eq!( 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 "Video" // file names are not added for videos
); );
assert_eq!( 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 "GIF" // file names are not added for GIFs
); );
assert_eq!( 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 "Sticker" // file names are not added for stickers
); );
assert_eq!( 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 "Voice message" // file names are not added for voice messages, empty text is skipped
); );
assert_eq!( 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 "Voice message" // file names are not added for voice messages
); );
assert_eq!( 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" "Voice message \u{2013} bla bla" // `\u{2013}` explicitly checks for "EN DASH"
); );
assert_eq!( 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 "Audio \u{2013} foo.bar" // file name is added for audio
); );
assert_eq!( 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 "Audio \u{2013} foo.bar" // file name is added for audio, empty text is not added
); );
assert_eq!( 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 "Audio \u{2013} foo.bar \u{2013} bla bla" // file name and text added for audio
); );
assert_eq!( 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 "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(Param::File, "foo.bar");
asm_file.set_cmd(SystemMessage::AutocryptSetupMessage); asm_file.set_cmd(SystemMessage::AutocryptSetupMessage);
assert_eq!( 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 "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> { impl<'a, 'b> MimeFactory<'a, 'b> {
pub fn from_msg( pub async fn from_msg(
context: &'a Context, context: &'a Context,
msg: &'b Message, msg: &'b Message,
attach_selfavatar: bool, attach_selfavatar: bool,
) -> Result<MimeFactory<'a, 'b>, Error> { ) -> 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 let from_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default();
let from_displayname = context
.get_config(Config::Displayname)
.await
.unwrap_or_default(); .unwrap_or_default();
let from_displayname = context.get_config(Config::Displayname).unwrap_or_default();
let mut recipients = Vec::with_capacity(5); let mut recipients = Vec::with_capacity(5);
let mut req_mdn = false; let mut req_mdn = false;
if chat.is_self_talk() { if chat.is_self_talk() {
recipients.push((from_displayname.to_string(), from_addr.to_string())); recipients.push((from_displayname.to_string(), from_addr.to_string()));
} else { } else {
context.sql.query_map( context
"SELECT c.authname, c.addr \ .sql
.query_map(
"SELECT c.authname, c.addr \
FROM chats_contacts cc \ FROM chats_contacts cc \
LEFT JOIN contacts c ON cc.contact_id=c.id \ LEFT JOIN contacts c ON cc.contact_id=c.id \
WHERE cc.chat_id=? AND cc.contact_id>9;", WHERE cc.chat_id=? AND cc.contact_id>9;",
params![msg.chat_id], paramsv![msg.chat_id],
|row| { |row| {
let authname: String = row.get(0)?; let authname: String = row.get(0)?;
let addr: String = row.get(1)?; let addr: String = row.get(1)?;
Ok((authname, addr)) Ok((authname, addr))
}, },
|rows| { |rows| {
for row in rows { for row in rows {
let (authname, addr) = row?; let (authname, addr) = row?;
if !recipients_contain_addr(&recipients, &addr) { if !recipients_contain_addr(&recipients, &addr) {
recipients.push((authname, addr)); recipients.push((authname, addr));
}
} }
} Ok(())
Ok(()) },
}, )
)?; .await?;
let command = msg.param.get_cmd(); let command = msg.param.get_cmd();
@@ -112,6 +119,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
let self_addr = context let self_addr = context
.get_config(Config::ConfiguredAddr) .get_config(Config::ConfiguredAddr)
.await
.unwrap_or_default(); .unwrap_or_default();
if !email_to_remove.is_empty() if !email_to_remove.is_empty()
@@ -123,31 +131,39 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
} }
if command != SystemMessage::AutocryptSetupMessage if command != SystemMessage::AutocryptSetupMessage
&& command != SystemMessage::SecurejoinMessage && command != SystemMessage::SecurejoinMessage
&& context.get_config_bool(Config::MdnsEnabled) && context.get_config_bool(Config::MdnsEnabled).await
{ {
req_mdn = true; req_mdn = true;
} }
} }
let (in_reply_to, references) = context.sql.query_row( let (in_reply_to, references) = context
"SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?", .sql
params![msg.id], .query_row(
|row| { "SELECT mime_in_reply_to, mime_references FROM msgs WHERE id=?",
let in_reply_to: String = row.get(0)?; paramsv![msg.id],
let references: String = row.get(1)?; |row| {
let in_reply_to: String = row.get(0)?;
let references: String = row.get(1)?;
Ok(( Ok((
render_rfc724_mid_list(&in_reply_to), render_rfc724_mid_list(&in_reply_to),
render_rfc724_mid_list(&references), render_rfc724_mid_list(&references),
)) ))
}, },
)?; )
.await?;
let default_str = context
.stock_str(StockMessage::StatusLine)
.await
.to_string();
let factory = MimeFactory { let factory = MimeFactory {
from_addr, from_addr,
from_displayname, from_displayname,
selfstatus: context selfstatus: context
.get_config(Config::Selfstatus) .get_config(Config::Selfstatus)
.unwrap_or_else(|| context.stock_str(StockMessage::StatusLine).to_string()), .await
.unwrap_or_else(|| default_str),
recipients, recipients,
timestamp: msg.timestamp_sort, timestamp: msg.timestamp_sort,
loaded: Loaded::Message { chat }, loaded: Loaded::Message { chat },
@@ -162,29 +178,42 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
Ok(factory) Ok(factory)
} }
pub fn from_mdn( pub async fn from_mdn(
context: &'a Context, context: &'a Context,
msg: &'b Message, msg: &'b Message,
additional_msg_ids: Vec<String>, additional_msg_ids: Vec<String>,
) -> Result<Self, Error> { ) -> Result<MimeFactory<'a, 'b>, Error> {
ensure!(!msg.chat_id.is_special(), "Invalid chat id"); 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, context,
from_addr: context from_addr,
.get_config(Config::ConfiguredAddr) from_displayname,
.unwrap_or_default(), selfstatus,
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()),
recipients: vec![( recipients: vec![(
contact.get_authname().to_string(), contact.get_authname().to_string(),
contact.get_addr().to_string(), contact.get_addr().to_string(),
)], )],
timestamp: dc_create_smeared_timestamp(context), timestamp,
loaded: Loaded::MDN { additional_msg_ids }, loaded: Loaded::MDN { additional_msg_ids },
msg, msg,
in_reply_to: String::default(), in_reply_to: String::default(),
@@ -192,7 +221,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
req_mdn: false, req_mdn: false,
last_added_location_id: 0, last_added_location_id: 0,
attach_selfavatar: false, attach_selfavatar: false,
}) };
Ok(res)
} }
async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate<'_>>, &str)>, Error> { async fn peerstates_for_recipients(&self) -> Result<Vec<(Option<Peerstate<'_>>, &str)>, Error> {
@@ -202,17 +233,19 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
.await .await
.ok_or_else(|| format_err!("Not configured"))?; .ok_or_else(|| format_err!("Not configured"))?;
Ok(self let mut res = Vec::new();
for (_, addr) in self
.recipients .recipients
.iter() .iter()
.filter(|(_, addr)| addr != &self_addr) .filter(|(_, addr)| addr != &self_addr)
.map(|(_, addr)| { {
( res.push((
Peerstate::from_addr(self.context, &self.context.sql, addr), Peerstate::from_addr(self.context, &self.context.sql, addr).await,
addr.as_str(), addr.as_str(),
) ));
}) }
.collect())
Ok(res)
} }
fn is_e2ee_guaranteed(&self) -> bool { 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 { match &self.loaded {
Loaded::Message { chat } => { Loaded::Message { chat } => {
// beside key- and member-changes, force re-gossip every 48 hours // 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) { if time() > gossiped_timestamp + (2 * 24 * 60 * 60) {
return true; 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 { match self.loaded {
Loaded::Message { ref chat } => { Loaded::Message { ref chat } => {
if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage { if self.msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage {
self.context self.context
.stock_str(StockMessage::AcSetupMsgSubject) .stock_str(StockMessage::AcSetupMsgSubject)
.await
.into_owned() .into_owned()
} else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup {
let re = if self.in_reply_to.is_empty() { let re = if self.in_reply_to.is_empty() {
@@ -338,12 +372,17 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
&self.msg.param, &self.msg.param,
32, 32,
self.context, self.context,
); )
.await;
let raw_subject = raw.lines().next().unwrap_or_default(); let raw_subject = raw.lines().next().unwrap_or_default();
format!("Chat: {}", raw_subject) 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() .collect()
} }
pub fn render(mut self) -> Result<RenderedEmail, Error> { pub async fn render(mut self) -> Result<RenderedEmail, Error> {
// Headers that are encrypted // Headers that are encrypted
// - Chat-*, except Chat-Version // - Chat-*, except Chat-Version
// - Secure-Join* // - Secure-Join*
@@ -433,17 +472,18 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
let min_verified = self.min_verified(); let min_verified = self.min_verified();
let grpimage = self.grpimage(); let grpimage = self.grpimage();
let force_plaintext = self.should_force_plaintext(); 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 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 subject = encode_words(&subject_str);
let mut message = match self.loaded { let mut message = match self.loaded {
Loaded::Message { .. } => { 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 { 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)); protected_headers.push(Header::new("Subject".into(), subject));
let peerstates = self.peerstates_for_recipients()?; let peerstates = self.peerstates_for_recipients().await?;
let should_encrypt = let should_encrypt =
encrypt_helper.should_encrypt(self.context, e2ee_guaranteed, &peerstates)?; encrypt_helper.should_encrypt(self.context, e2ee_guaranteed, &peerstates)?;
let is_encrypted = should_encrypt && force_plaintext == 0; let is_encrypted = should_encrypt && force_plaintext == 0;
@@ -482,7 +522,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
let outer_message = if is_encrypted { let outer_message = if is_encrypted {
// Add gossip headers in chats with multiple recipients // 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()) { for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
if peerstate.peek_key(min_verified).is_some() { if peerstate.peek_key(min_verified).is_some() {
if let Some(header) = peerstate.render_gossip_header(min_verified) { if let Some(header) = peerstate.render_gossip_header(min_verified) {
@@ -530,8 +570,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
println!("{}", raw_message); println!("{}", raw_message);
} }
let encrypted = let encrypted = encrypt_helper
encrypt_helper.encrypt(self.context, min_verified, message, &peerstates)?; .encrypt(self.context, min_verified, message, &peerstates)
.await?;
outer_message = outer_message outer_message = outer_message
.child( .child(
@@ -603,9 +644,9 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
Some(part) 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) = 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() let part = PartBuilder::new()
.content_type( .content_type(
&"application/vnd.google-earth.kml+xml" &"application/vnd.google-earth.kml+xml"
@@ -625,7 +666,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
} }
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn render_message( async fn render_message(
&mut self, &mut self,
protected_headers: &mut Vec<Header>, protected_headers: &mut Vec<Header>,
unprotected_headers: &mut Vec<Header>, unprotected_headers: &mut Vec<Header>,
@@ -720,6 +761,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
placeholdertext = Some( placeholdertext = Some(
self.context self.context
.stock_str(StockMessage::AcSetupMsgBody) .stock_str(StockMessage::AcSetupMsgBody)
.await
.to_string(), .to_string(),
); );
} }
@@ -856,8 +898,8 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
parts.push(msg_kml_part); parts.push(msg_kml_part);
} }
if location::is_sending_locations_to_chat(context, self.msg.chat_id) { if location::is_sending_locations_to_chat(context, self.msg.chat_id).await {
match self.get_location_kml_part() { match self.get_location_kml_part().await {
Ok(part) => parts.push(part), Ok(part) => parts.push(part),
Err(err) => { Err(err) => {
warn!(context, "mimefactory: could not send location: {}", err); warn!(context, "mimefactory: could not send location: {}", err);
@@ -866,7 +908,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
} }
if self.attach_selfavatar { 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) { Some(path) => match build_selfavatar_file(context, &path) {
Ok((part, filename)) => { Ok((part, filename)) => {
parts.push(part); parts.push(part);
@@ -893,7 +935,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
} }
/// Render an MDN /// 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 // 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 // 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 self.context
.stock_str(StockMessage::EncryptedMsg) .stock_str(StockMessage::EncryptedMsg)
.await
.into_owned() .into_owned()
} else { } else {
self.msg.get_summarytext(self.context, 32) self.msg.get_summarytext(self.context, 32).await
}; };
let p2 = self let p2 = self
.context .context
.stock_string_repl_str(StockMessage::ReadRcptMailBody, p1); .stock_string_repl_str(StockMessage::ReadRcptMailBody, p1)
.await;
let message_text = format!("{}\r\n", p2); let message_text = format!("{}\r\n", p2);
message = message.child( message = message.child(
PartBuilder::new() PartBuilder::new()

View File

@@ -1,4 +1,6 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::future::Future;
use std::pin::Pin;
use deltachat_derive::{FromSql, ToSql}; use deltachat_derive::{FromSql, ToSql};
use lettre_email::mime::{self, Mime}; use lettre_email::mime::{self, Mime};
@@ -156,7 +158,7 @@ impl MimeMessage {
user_avatar: None, user_avatar: None,
group_avatar: None, group_avatar: None,
}; };
parser.parse_mime_recursive(context, &mail)?; parser.parse_mime_recursive(context, &mail).await?;
parser.parse_headers(context)?; parser.parse_headers(context)?;
Ok(parser) Ok(parser)
@@ -405,63 +407,69 @@ impl MimeMessage {
None None
} }
fn parse_mime_recursive( fn parse_mime_recursive<'a>(
&mut self, &'a mut self,
context: &Context, context: &'a Context,
mail: &mailparse::ParsedMail<'_>, mail: &'a mailparse::ParsedMail<'a>,
) -> Result<bool> { ) -> Pin<Box<dyn Future<Output = Result<bool>> + 'a + Send>> {
if mail.ctype.params.get("protected-headers").is_some() { use futures::future::FutureExt;
if mail.ctype.mimetype == "text/rfc822-headers" {
warn!( // 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, context,
"Protected headers found in text/rfc822-headers attachment: Will be ignored.", "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); 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, &mut self,
context: &Context, context: &Context,
mail: &mailparse::ParsedMail<'_>, mail: &mailparse::ParsedMail<'_>,
@@ -476,7 +484,7 @@ impl MimeMessage {
(mime::MULTIPART, "alternative") => { (mime::MULTIPART, "alternative") => {
for cur_data in &mail.subparts { for cur_data in &mail.subparts {
if get_mime_type(cur_data)?.0 == "multipart/mixed" { 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; break;
} }
} }
@@ -484,7 +492,7 @@ impl MimeMessage {
/* search for text/plain and add this */ /* search for text/plain and add this */
for cur_data in &mail.subparts { for cur_data in &mail.subparts {
if get_mime_type(cur_data)?.0.type_() == mime::TEXT { 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; break;
} }
} }
@@ -492,7 +500,7 @@ impl MimeMessage {
if !any_part_added { if !any_part_added {
/* `text/plain` not found - use the first part */ /* `text/plain` not found - use the first part */
for cur_part in &mail.subparts { 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; any_part_added = true;
break; break;
} }
@@ -505,14 +513,14 @@ impl MimeMessage {
being the first one, which may not be always true ... being the first one, which may not be always true ...
however, most times it seems okay. */ however, most times it seems okay. */
if let Some(first) = mail.subparts.iter().next() { 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") => { (mime::MULTIPART, "encrypted") => {
// we currently do not try to decrypt non-autocrypt messages // we currently do not try to decrypt non-autocrypt messages
// at all. If we see an encrypted part, we set // at all. If we see an encrypted part, we set
// decrypting_failed. // 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 txt = format!("[{}]", msg_body);
let mut part = Part::default(); let mut part = Part::default();
@@ -535,7 +543,7 @@ impl MimeMessage {
https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html
for background information why we use encrypted+signed) */ for background information why we use encrypted+signed) */
if let Some(first) = mail.subparts.iter().next() { 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") => { (mime::MULTIPART, "report") => {
@@ -550,7 +558,7 @@ impl MimeMessage {
/* eg. `report-type=delivery-status`; /* eg. `report-type=delivery-status`;
maybe we should show them as a little error icon */ maybe we should show them as a little error icon */
if let Some(first) = mail.subparts.iter().next() { 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 // Add all parts (in fact, AddSinglePartIfKnown() later check if
// the parts are really supported) // the parts are really supported)
for cur_data in mail.subparts.iter() { 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; any_part_added = true;
} }
} }
@@ -827,6 +835,7 @@ impl MimeMessage {
{ {
if let Some((chat_id, msg_id)) = if let Some((chat_id, msg_id)) =
message::mdn_from_ext(context, from_id, original_message_id, sent_timestamp) message::mdn_from_ext(context, from_id, original_message_id, sent_timestamp)
.await
{ {
context.call_cb(Event::MsgRead { chat_id, msg_id }); context.call_cb(Event::MsgRead { chat_id, msg_id });
mdn_recognized = true; mdn_recognized = true;
@@ -876,7 +885,7 @@ async fn update_gossip_peerstates(
.unwrap() .unwrap()
.contains(&header.addr.to_lowercase()) .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 { if let Some(ref mut peerstate) = peerstate {
peerstate.apply_gossip(header, message_time); peerstate.apply_gossip(header, message_time);
peerstate.save_to_db(&context.sql, false).await?; peerstate.save_to_db(&context.sql, false).await?;
@@ -1103,21 +1112,25 @@ mod tests {
} }
} }
#[test] #[async_std::test]
fn test_dc_mimeparser_crash() { async fn test_dc_mimeparser_crash() {
let context = dummy_context(); let context = dummy_context().await;
let raw = include_bytes!("../test-data/message/issue_523.txt"); 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.get_subject(), None);
assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts.len(), 1);
} }
#[test] #[async_std::test]
fn test_get_rfc724_mid_exists() { async fn test_get_rfc724_mid_exists() {
let context = dummy_context(); let context = dummy_context().await;
let raw = include_bytes!("../test-data/message/mail_with_message_id.txt"); 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!( assert_eq!(
mimeparser.get_rfc724_mid(), mimeparser.get_rfc724_mid(),
@@ -1125,19 +1138,23 @@ mod tests {
); );
} }
#[test] #[async_std::test]
fn test_get_rfc724_mid_not_exists() { async fn test_get_rfc724_mid_not_exists() {
let context = dummy_context(); let context = dummy_context().await;
let raw = include_bytes!("../test-data/message/issue_523.txt"); 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); assert_eq!(mimeparser.get_rfc724_mid(), None);
} }
#[test] #[async_std::test]
fn test_get_recipients() { async fn test_get_recipients() {
let context = dummy_context(); let context = dummy_context().await;
let raw = include_bytes!("../test-data/message/mail_with_cc.txt"); 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()); let recipients = get_recipients(mimeparser.header.iter());
assert!(recipients.contains("abc@bcd.com")); assert!(recipients.contains("abc@bcd.com"));
assert!(recipients.contains("def@def.de")); assert!(recipients.contains("def@def.de"));
@@ -1182,9 +1199,9 @@ mod tests {
); );
} }
#[test] #[async_std::test]
fn test_parse_first_addr() { async fn test_parse_first_addr() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"From: hello@one.org, world@two.org\n\ let raw = b"From: hello@one.org, world@two.org\n\
Chat-Disposition-Notification-To: wrong\n\ Chat-Disposition-Notification-To: wrong\n\
Content-Type: text/plain\n\ Content-Type: text/plain\n\
@@ -1193,7 +1210,9 @@ mod tests {
test1\n\ test1\n\
"; ";
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
.unwrap();
let of = mimeparser let of = mimeparser
.parse_first_addr(&context.ctx, HeaderDef::From_) .parse_first_addr(&context.ctx, HeaderDef::From_)
@@ -1205,9 +1224,9 @@ mod tests {
assert!(of.is_none()); assert!(of.is_none());
} }
#[test] #[async_std::test]
fn test_mimeparser_with_context() { async fn test_mimeparser_with_context() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"From: hello\n\ let raw = b"From: hello\n\
Content-Type: multipart/mixed; boundary=\"==break==\";\n\ Content-Type: multipart/mixed; boundary=\"==break==\";\n\
Subject: outer-subject\n\ Subject: outer-subject\n\
@@ -1226,7 +1245,9 @@ mod tests {
--==break==--\n\ --==break==--\n\
\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 // non-overwritten headers do not bubble up
let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap(); let of = mimeparser.get(HeaderDef::SecureJoinGroup).unwrap();
@@ -1246,31 +1267,31 @@ mod tests {
assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts.len(), 1);
} }
#[test] #[async_std::test]
fn test_mimeparser_with_avatars() { async fn test_mimeparser_with_avatars() {
let t = dummy_context(); let t = dummy_context().await;
let raw = include_bytes!("../test-data/message/mail_attach_txt.eml"); 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.user_avatar, None);
assert_eq!(mimeparser.group_avatar, None); assert_eq!(mimeparser.group_avatar, None);
let raw = include_bytes!("../test-data/message/mail_with_user_avatar.eml"); 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.len(), 1);
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
assert!(mimeparser.user_avatar.unwrap().is_change()); assert!(mimeparser.user_avatar.unwrap().is_change());
assert_eq!(mimeparser.group_avatar, None); assert_eq!(mimeparser.group_avatar, None);
let raw = include_bytes!("../test-data/message/mail_with_user_avatar_deleted.eml"); 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.len(), 1);
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
assert_eq!(mimeparser.user_avatar, Some(AvatarAction::Delete)); assert_eq!(mimeparser.user_avatar, Some(AvatarAction::Delete));
assert_eq!(mimeparser.group_avatar, None); assert_eq!(mimeparser.group_avatar, None);
let raw = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml"); 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.len(), 1);
assert_eq!(mimeparser.parts[0].typ, Viewtype::Text); assert_eq!(mimeparser.parts[0].typ, Viewtype::Text);
assert!(mimeparser.user_avatar.unwrap().is_change()); 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 = include_bytes!("../test-data/message/mail_with_user_and_group_avatars.eml");
let raw = String::from_utf8_lossy(raw).to_string(); let raw = String::from_utf8_lossy(raw).to_string();
let raw = raw.replace("Chat-User-Avatar:", "Xhat-Xser-Xvatar:"); 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.len(), 1);
assert_eq!(mimeparser.parts[0].typ, Viewtype::Image); assert_eq!(mimeparser.parts[0].typ, Viewtype::Image);
assert_eq!(mimeparser.user_avatar, None); assert_eq!(mimeparser.user_avatar, None);
assert!(mimeparser.group_avatar.unwrap().is_change()); assert!(mimeparser.group_avatar.unwrap().is_change());
} }
#[test] #[async_std::test]
fn test_mimeparser_message_kml() { async fn test_mimeparser_message_kml() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"Chat-Version: 1.0\n\ let raw = b"Chat-Version: 1.0\n\
From: foo <foo@example.org>\n\ From: foo <foo@example.org>\n\
To: bar <bar@example.org>\n\ To: bar <bar@example.org>\n\
@@ -1317,7 +1340,9 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\
--==break==--\n\ --==break==--\n\
;"; ;";
let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); let mimeparser = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
.unwrap();
assert_eq!( assert_eq!(
mimeparser.get_subject(), mimeparser.get_subject(),
Some("Location streaming".to_string()) Some("Location streaming".to_string())
@@ -1330,9 +1355,9 @@ Content-Disposition: attachment; filename=\"message.kml\"\n\
assert_eq!(mimeparser.parts.len(), 1); assert_eq!(mimeparser.parts.len(), 1);
} }
#[test] #[async_std::test]
fn test_parse_mdn() { async fn test_parse_mdn() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\ let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\ Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\ Chat-Version: 1.0\n\
@@ -1364,7 +1389,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
"; ";
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
.unwrap();
assert_eq!( assert_eq!(
message.get_subject(), message.get_subject(),
Some("Chat: Message opened".to_string()) 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 /// RFC 6522 specifically allows MDNs to be nested inside
/// multipart MIME messages. /// multipart MIME messages.
#[test] #[async_std::test]
fn test_parse_multiple_mdns() { async fn test_parse_multiple_mdns() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\ let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\ Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\ Chat-Version: 1.0\n\
@@ -1442,7 +1469,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
--outer--\n\ --outer--\n\
"; ";
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
.unwrap();
assert_eq!( assert_eq!(
message.get_subject(), message.get_subject(),
Some("Chat: Message opened".to_string()) Some("Chat: Message opened".to_string())
@@ -1452,9 +1481,9 @@ Disposition: manual-action/MDN-sent-automatically; displayed\n\
assert_eq!(message.reports.len(), 2); assert_eq!(message.reports.len(), 2);
} }
#[test] #[async_std::test]
fn test_parse_mdn_with_additional_message_ids() { async fn test_parse_mdn_with_additional_message_ids() {
let context = dummy_context(); let context = dummy_context().await;
let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\ let raw = b"Subject: =?utf-8?q?Chat=3A_Message_opened?=\n\
Date: Mon, 10 Jan 2020 00:00:00 +0000\n\ Date: Mon, 10 Jan 2020 00:00:00 +0000\n\
Chat-Version: 1.0\n\ Chat-Version: 1.0\n\
@@ -1487,7 +1516,9 @@ Additional-Message-IDs: <foo@example.com> <foo@example.net>\n\
--kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\ --kJBbU58X1xeWNHgBtTbMk80M5qnV4N--\n\
"; ";
let message = MimeMessage::from_bytes(&context.ctx, &raw[..]).unwrap(); let message = MimeMessage::from_bytes(&context.ctx, &raw[..])
.await
.unwrap();
assert_eq!( assert_eq!(
message.get_subject(), message.get_subject(),
Some("Chat: Message opened".to_string()) Some("Chat: Message opened".to_string())
@@ -1502,9 +1533,9 @@ Additional-Message-IDs: <foo@example.com> <foo@example.net>\n\
); );
} }
#[test] #[async_std::test]
fn test_parse_inline_attachment() { async fn test_parse_inline_attachment() {
let context = dummy_context(); let context = dummy_context().await;
let raw = br#"Date: Thu, 13 Feb 2020 22:41:20 +0000 (UTC) let raw = br#"Date: Thu, 13 Feb 2020 22:41:20 +0000 (UTC)
From: sender@example.com From: sender@example.com
To: receiver@example.com To: receiver@example.com
@@ -1529,7 +1560,9 @@ MDYyMDYxNTE1RTlDOEE4Cj4+CnN0YXJ0eHJlZgo4Mjc4CiUlRU9GCg==
------=_Part_25_46172632.1581201680436-- ------=_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!( assert_eq!(
message.get_subject(), message.get_subject(),
Some("Mail with inline attachment".to_string()) Some("Mail with inline attachment".to_string())

View File

@@ -48,7 +48,7 @@ struct Response {
scope: Option<String>, scope: Option<String>,
} }
pub fn dc_get_oauth2_url( pub async fn dc_get_oauth2_url(
context: &Context, context: &Context,
addr: impl AsRef<str>, addr: impl AsRef<str>,
redirect_uri: impl AsRef<str>, redirect_uri: impl AsRef<str>,
@@ -61,6 +61,7 @@ pub fn dc_get_oauth2_url(
"oauth2_pending_redirect_uri", "oauth2_pending_redirect_uri",
Some(redirect_uri.as_ref()), Some(redirect_uri.as_ref()),
) )
.await
.is_err() .is_err()
{ {
return None; return None;
@@ -76,7 +77,7 @@ pub fn dc_get_oauth2_url(
// The following function may block due http-requests; // The following function may block due http-requests;
// must not be called from the main thread or by the ui! // 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, context: &Context,
addr: impl AsRef<str>, addr: impl AsRef<str>,
code: impl AsRef<str>, code: impl AsRef<str>,
@@ -84,11 +85,14 @@ pub fn dc_get_oauth2_access_token(
) -> Option<String> { ) -> Option<String> {
if let Some(oauth2) = Oauth2::from_address(addr) { if let Some(oauth2) = Oauth2::from_address(addr) {
let lock = context.oauth2_critical.clone(); let lock = context.oauth2_critical.clone();
let _l = lock.lock().unwrap(); let _l = lock.lock().await;
// read generated token // read generated token
if !regenerate && !is_expired(context) { if !regenerate && !is_expired(context).await {
let access_token = context.sql.get_raw_config(context, "oauth2_access_token"); let access_token = context
.sql
.get_raw_config(context, "oauth2_access_token")
.await;
if access_token.is_some() { if access_token.is_some() {
// success // success
return access_token; return access_token;
@@ -96,10 +100,14 @@ pub fn dc_get_oauth2_access_token(
} }
// generate new token: build & call auth url // 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 let refresh_token_for = context
.sql .sql
.get_raw_config(context, "oauth2_refresh_token_for") .get_raw_config(context, "oauth2_refresh_token_for")
.await
.unwrap_or_else(|| "unset".into()); .unwrap_or_else(|| "unset".into());
let (redirect_uri, token_url, update_redirect_uri_on_success) = let (redirect_uri, token_url, update_redirect_uri_on_success) =
@@ -109,6 +117,7 @@ pub fn dc_get_oauth2_access_token(
context context
.sql .sql
.get_raw_config(context, "oauth2_pending_redirect_uri") .get_raw_config(context, "oauth2_pending_redirect_uri")
.await
.unwrap_or_else(|| "unset".into()), .unwrap_or_else(|| "unset".into()),
oauth2.init_token, oauth2.init_token,
true, true,
@@ -122,6 +131,7 @@ pub fn dc_get_oauth2_access_token(
context context
.sql .sql
.get_raw_config(context, "oauth2_redirect_uri") .get_raw_config(context, "oauth2_redirect_uri")
.await
.unwrap_or_else(|| "unset".into()), .unwrap_or_else(|| "unset".into()),
oauth2.refresh_token, oauth2.refresh_token,
false, false,
@@ -193,10 +203,12 @@ pub fn dc_get_oauth2_access_token(
context context
.sql .sql
.set_raw_config(context, "oauth2_refresh_token", Some(token)) .set_raw_config(context, "oauth2_refresh_token", Some(token))
.await
.ok(); .ok();
context context
.sql .sql
.set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref())) .set_raw_config(context, "oauth2_refresh_token_for", Some(code.as_ref()))
.await
.ok(); .ok();
} }
@@ -206,6 +218,7 @@ pub fn dc_get_oauth2_access_token(
context context
.sql .sql
.set_raw_config(context, "oauth2_access_token", Some(token)) .set_raw_config(context, "oauth2_access_token", Some(token))
.await
.ok(); .ok();
let expires_in = response let expires_in = response
.expires_in .expires_in
@@ -215,12 +228,14 @@ pub fn dc_get_oauth2_access_token(
context context
.sql .sql
.set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in) .set_raw_config_int64(context, "oauth2_timestamp_expires", expires_in)
.await
.ok(); .ok();
if update_redirect_uri_on_success { if update_redirect_uri_on_success {
context context
.sql .sql
.set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref())) .set_raw_config(context, "oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.await
.ok(); .ok();
} }
} else { } 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, context: &Context,
addr: impl AsRef<str>, addr: impl AsRef<str>,
code: impl AsRef<str>, code: impl AsRef<str>,
@@ -244,12 +259,13 @@ pub fn dc_get_oauth2_addr(
oauth2.get_userinfo?; oauth2.get_userinfo?;
if let Some(access_token) = 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); let addr_out = oauth2.get_addr(context, access_token);
if addr_out.is_none() { if addr_out.is_none() {
// regenerate // 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) oauth2.get_addr(context, access_token)
} else { } else {
None None
@@ -331,10 +347,11 @@ impl Oauth2 {
} }
} }
fn is_expired(context: &Context) -> bool { async fn is_expired(context: &Context) -> bool {
let expire_timestamp = context let expire_timestamp = context
.sql .sql
.get_raw_config_int64(context, "oauth2_timestamp_expires") .get_raw_config_int64(context, "oauth2_timestamp_expires")
.await
.unwrap_or_default(); .unwrap_or_default();
if expire_timestamp <= 0 { if expire_timestamp <= 0 {
@@ -393,32 +410,32 @@ mod tests {
assert_eq!(Oauth2::from_address("hello@web.de"), None); assert_eq!(Oauth2::from_address("hello@web.de"), None);
} }
#[test] #[async_std::test]
fn test_dc_get_oauth2_addr() { async fn test_dc_get_oauth2_addr() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let addr = "dignifiedquire@gmail.com"; let addr = "dignifiedquire@gmail.com";
let code = "fail"; 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 // this should fail as it is an invalid password
assert_eq!(res, None); assert_eq!(res, None);
} }
#[test] #[async_std::test]
fn test_dc_get_oauth2_url() { async fn test_dc_get_oauth2_url() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let addr = "dignifiedquire@gmail.com"; let addr = "dignifiedquire@gmail.com";
let redirect_uri = "chat.delta:/com.b44t.messenger"; 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())); 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] #[async_std::test]
fn test_dc_get_oauth2_token() { async fn test_dc_get_oauth2_token() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let addr = "dignifiedquire@gmail.com"; let addr = "dignifiedquire@gmail.com";
let code = "fail"; 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 // this should fail as it is an invalid password
assert_eq!(res, None); assert_eq!(res, None);
} }

View File

@@ -417,9 +417,9 @@ mod tests {
assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de"); assert_eq!(p1.get(Param::Forwarded).unwrap(), "cli%40deltachat.de");
} }
#[test] #[async_std::test]
fn test_params_file_fs_path() { async fn test_params_file_fs_path() {
let t = dummy_context(); let t = dummy_context().await;
if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() { if let ParamsFile::FsPath(p) = ParamsFile::from_param(&t.ctx, "/foo/bar/baz").unwrap() {
assert_eq!(p, Path::new("/foo/bar/baz")); assert_eq!(p, Path::new("/foo/bar/baz"));
} else { } else {
@@ -427,9 +427,9 @@ mod tests {
} }
} }
#[test] #[async_std::test]
fn test_params_file_blob() { async fn test_params_file_blob() {
let t = dummy_context(); let t = dummy_context().await;
if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() { if let ParamsFile::Blob(b) = ParamsFile::from_param(&t.ctx, "$BLOBDIR/foo").unwrap() {
assert_eq!(b.as_name(), "$BLOBDIR/foo"); assert_eq!(b.as_name(), "$BLOBDIR/foo");
} else { } else {
@@ -438,9 +438,9 @@ mod tests {
} }
// Tests for Params::get_file(), Params::get_path() and Params::get_blob(). // Tests for Params::get_file(), Params::get_path() and Params::get_blob().
#[test] #[async_std::test]
fn test_params_get_fileparam() { async fn test_params_get_fileparam() {
let t = dummy_context(); let t = dummy_context().await;
let fname = t.dir.path().join("foo"); let fname = t.dir.path().join("foo");
let mut p = Params::new(); let mut p = Params::new();
p.set(Param::File, fname.to_str().unwrap()); p.set(Param::File, fname.to_str().unwrap());

View File

@@ -144,12 +144,16 @@ impl<'a> Peerstate<'a> {
res 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;"; 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, \ let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \ gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint \ verified_key, verified_key_fingerprint \
@@ -161,15 +165,16 @@ impl<'a> Peerstate<'a> {
Self::from_stmt( Self::from_stmt(
context, context,
query, query,
params![fingerprint, fingerprint, fingerprint], paramsv![fingerprint, fingerprint, fingerprint],
) )
.await
} }
fn from_stmt<P>(context: &'a Context, query: &str, params: P) -> Option<Self> async fn from_stmt(
where context: &'a Context,
P: IntoIterator, query: &str,
P::Item: rusqlite::ToSql, params: Vec<&dyn crate::ToSql>,
{ ) -> Option<Peerstate<'a>> {
context context
.sql .sql
.query_row(query, params, |row| { .query_row(query, params, |row| {
@@ -227,6 +232,7 @@ impl<'a> Peerstate<'a> {
Ok(res) Ok(res)
}) })
.await
.ok() .ok()
} }
@@ -413,7 +419,7 @@ impl<'a> Peerstate<'a> {
if create { if create {
sql.execute( sql.execute(
"INSERT INTO acpeerstates (addr) VALUES(?);", "INSERT INTO acpeerstates (addr) VALUES(?);",
params![self.addr], paramsv![self.addr],
) )
.await?; .await?;
} }
@@ -425,29 +431,29 @@ impl<'a> Peerstate<'a> {
public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \ public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \
verified_key=?, verified_key_fingerprint=? \ verified_key=?, verified_key_fingerprint=? \
WHERE addr=?;", WHERE addr=?;",
params![ paramsv![
self.last_seen, self.last_seen,
self.last_seen_autocrypt, self.last_seen_autocrypt,
self.prefer_encrypt as i64, self.prefer_encrypt as i64,
self.public_key.as_ref().map(|k| k.to_bytes()), self.public_key.as_ref().map(|k| k.to_bytes()),
self.gossip_timestamp, self.gossip_timestamp,
self.gossip_key.as_ref().map(|k| k.to_bytes()), self.gossip_key.as_ref().map(|k| k.to_bytes()),
&self.public_key_fingerprint, self.public_key_fingerprint,
&self.gossip_key_fingerprint, self.gossip_key_fingerprint,
self.verified_key.as_ref().map(|k| k.to_bytes()), self.verified_key.as_ref().map(|k| k.to_bytes()),
&self.verified_key_fingerprint, self.verified_key_fingerprint,
&self.addr, self.addr,
], ],
).await?; ).await?;
} else if self.to_save == Some(ToSave::Timestamps) { } else if self.to_save == Some(ToSave::Timestamps) {
sql.execute( sql.execute(
"UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \ "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \
WHERE addr=?;", WHERE addr=?;",
params![ paramsv![
self.last_seen, self.last_seen,
self.last_seen_autocrypt, self.last_seen_autocrypt,
self.gossip_timestamp, self.gossip_timestamp,
&self.addr self.addr
], ],
) )
.await?; .await?;
@@ -475,9 +481,9 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tempfile::TempDir; use tempfile::TempDir;
#[test] #[async_std::test]
fn test_peerstate_save_to_db() { async fn test_peerstate_save_to_db() {
let ctx = crate::test_utils::dummy_context(); let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com"; let addr = "hello@mail.com";
let pub_key = crate::key::Key::from(alice_keypair().public); let pub_key = crate::key::Key::from(alice_keypair().public);
@@ -500,11 +506,12 @@ mod tests {
}; };
assert!( 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" "failed to save to db"
); );
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
.await
.expect("failed to load peerstate from db"); .expect("failed to load peerstate from db");
// clear to_save, as that is not persissted // clear to_save, as that is not persissted
@@ -512,13 +519,14 @@ mod tests {
assert_eq!(peerstate, peerstate_new); assert_eq!(peerstate, peerstate_new);
let peerstate_new2 = let peerstate_new2 =
Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint()) Peerstate::from_fingerprint(&ctx.ctx, &ctx.ctx.sql, &pub_key.fingerprint())
.await
.expect("failed to load peerstate from db"); .expect("failed to load peerstate from db");
assert_eq!(peerstate, peerstate_new2); assert_eq!(peerstate, peerstate_new2);
} }
#[test] #[async_std::test]
fn test_peerstate_double_create() { async fn test_peerstate_double_create() {
let ctx = crate::test_utils::dummy_context(); let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com"; let addr = "hello@mail.com";
let pub_key = crate::key::Key::from(alice_keypair().public); let pub_key = crate::key::Key::from(alice_keypair().public);
@@ -540,18 +548,18 @@ mod tests {
}; };
assert!( 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" "failed to save"
); );
assert!( 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" "double-call with create failed"
); );
} }
#[test] #[async_std::test]
fn test_peerstate_with_empty_gossip_key_save_to_db() { async fn test_peerstate_with_empty_gossip_key_save_to_db() {
let ctx = crate::test_utils::dummy_context(); let ctx = crate::test_utils::dummy_context().await;
let addr = "hello@mail.com"; let addr = "hello@mail.com";
let pub_key = crate::key::Key::from(alice_keypair().public); let pub_key = crate::key::Key::from(alice_keypair().public);
@@ -574,11 +582,12 @@ mod tests {
}; };
assert!( 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" "failed to save"
); );
let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr) let peerstate_new = Peerstate::from_addr(&ctx.ctx, &ctx.ctx.sql, addr)
.await
.expect("failed to load peerstate from db"); .expect("failed to load peerstate from db");
// clear to_save, as that is not persissted // 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) { } else if qr.starts_with(DCACCOUNT_SCHEME) {
decode_account(context, qr) decode_account(context, qr)
} else if qr.starts_with(MAILTO_SCHEME) { } else if qr.starts_with(MAILTO_SCHEME) {
decode_mailto(context, qr) decode_mailto(context, qr).await
} else if qr.starts_with(SMTP_SCHEME) { } else if qr.starts_with(SMTP_SCHEME) {
decode_smtp(context, qr) decode_smtp(context, qr).await
} else if qr.starts_with(MATMSG_SCHEME) { } else if qr.starts_with(MATMSG_SCHEME) {
decode_matmsg(context, qr) decode_matmsg(context, qr).await
} else if qr.starts_with(VCARD_SCHEME) { } 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) { } else if qr.starts_with(HTTP_SCHEME) || qr.starts_with(HTTPS_SCHEME) {
Lot::from_url(qr) Lot::from_url(qr)
} else { } else {
@@ -140,7 +140,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
let mut lot = Lot::new(); let mut lot = Lot::new();
// retrieve known state for this fingerprint // 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 invitenumber.is_none() || auth.is_none() {
if let Some(peerstate) = peerstate { if let Some(peerstate) = peerstate {
@@ -160,7 +160,7 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Lot {
.await .await
.unwrap_or_default(); .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 { } else {
lot.state = LotState::QrFprWithoutAddr; lot.state = LotState::QrFprWithoutAddr;
lot.text1 = Some(dc_format_fingerprint(&fingerprint)); 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. /// Extract address for the mailto scheme.
/// ///
/// Scheme: `mailto:addr...?subject=...&body=..` /// 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 payload = &qr[MAILTO_SCHEME.len()..];
let addr = if let Some(query_index) = payload.find('?') { 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(); let name = "".to_string();
Lot::from_address(context, name, addr) Lot::from_address(context, name, addr).await
} }
/// Extract address for the smtp scheme. /// Extract address for the smtp scheme.
/// ///
/// Scheme: `SMTP:addr...:subject...:body...` /// 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 payload = &qr[SMTP_SCHEME.len()..];
let addr = if let Some(query_index) = payload.find(':') { 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(); let name = "".to_string();
Lot::from_address(context, name, addr) Lot::from_address(context, name, addr).await
} }
/// Extract address for the matmsg scheme. /// 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...;` /// Scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;`
/// ///
/// There may or may not be linebreaks after the fields. /// 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. // Does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field.
// we ignore this case. // we ignore this case.
let addr = if let Some(to_index) = qr.find("TO:") { 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(); let name = "".to_string();
Lot::from_address(context, name, addr) Lot::from_address(context, name, addr).await
} }
lazy_static! { lazy_static! {
@@ -339,7 +339,7 @@ lazy_static! {
/// Extract address for the matmsg scheme. /// Extract address for the matmsg scheme.
/// ///
/// Scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL;<type>:addr...; /// 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 let name = VCARD_NAME_RE
.captures(qr) .captures(qr)
.map(|caps| { .map(|caps| {
@@ -359,7 +359,7 @@ fn decode_vcard(context: &Context, qr: &str) -> Lot {
return format_err!("Bad e-mail address").into(); return format_err!("Bad e-mail address").into();
}; };
Lot::from_address(context, name, addr) Lot::from_address(context, name, addr).await
} }
impl Lot { impl Lot {
@@ -379,10 +379,10 @@ impl Lot {
l 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(); let mut l = Lot::new();
l.state = LotState::QrAddr; 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, Ok((id, _)) => id,
Err(err) => return err.into(), Err(err) => return err.into(),
}; };
@@ -408,11 +408,11 @@ mod tests {
use crate::test_utils::dummy_context; use crate::test_utils::dummy_context;
#[test] #[async_std::test]
fn test_decode_http() { async fn test_decode_http() {
let ctx = dummy_context(); 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_state(), LotState::QrUrl);
assert_eq!(res.get_id(), 0); assert_eq!(res.get_id(), 0);
@@ -420,11 +420,11 @@ mod tests {
assert!(res.get_text2().is_none()); assert!(res.get_text2().is_none());
} }
#[test] #[async_std::test]
fn test_decode_https() { async fn test_decode_https() {
let ctx = dummy_context(); 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_state(), LotState::QrUrl);
assert_eq!(res.get_id(), 0); assert_eq!(res.get_id(), 0);
@@ -432,11 +432,11 @@ mod tests {
assert!(res.get_text2().is_none()); assert!(res.get_text2().is_none());
} }
#[test] #[async_std::test]
fn test_decode_text() { async fn test_decode_text() {
let ctx = dummy_context(); 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_state(), LotState::QrText);
assert_eq!(res.get_id(), 0); assert_eq!(res.get_id(), 0);
@@ -444,124 +444,127 @@ mod tests {
assert!(res.get_text2().is_none()); assert!(res.get_text2().is_none());
} }
#[test] #[async_std::test]
fn test_decode_vcard() { async fn test_decode_vcard() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD" "BEGIN:VCARD\nVERSION:3.0\nN:Last;First\nEMAIL;TYPE=INTERNET:stress@test.local\nEND:VCARD"
); ).await;
println!("{:?}", res); println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr); assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0); 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_addr(), "stress@test.local");
assert_eq!(contact.get_name(), "First Last"); assert_eq!(contact.get_name(), "First Last");
} }
#[test] #[async_std::test]
fn test_decode_matmsg() { async fn test_decode_matmsg() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"MATMSG:TO:\n\nstress@test.local ; \n\nSUB:\n\nSubject here\n\nBODY:\n\nhelloworld\n;;", "MATMSG:TO:\n\nstress@test.local ; \n\nSUB:\n\nSubject here\n\nBODY:\n\nhelloworld\n;;",
); )
.await;
println!("{:?}", res); println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr); assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0); 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_addr(), "stress@test.local");
} }
#[test] #[async_std::test]
fn test_decode_mailto() { async fn test_decode_mailto() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"mailto:stress@test.local?subject=hello&body=world", "mailto:stress@test.local?subject=hello&body=world",
); )
.await;
println!("{:?}", res); println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr); assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0); 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_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_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0); 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"); 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_eq!(res.get_state(), LotState::QrError);
assert!(res.get_text1().is_some()); assert!(res.get_text1().is_some());
} }
#[test] #[async_std::test]
fn test_decode_smtp() { async fn test_decode_smtp() {
let ctx = dummy_context(); 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); println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAddr); assert_eq!(res.get_state(), LotState::QrAddr);
assert_ne!(res.get_id(), 0); 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_addr(), "stress@test.local");
} }
#[test] #[async_std::test]
fn test_decode_openpgp_group() { async fn test_decode_openpgp_group() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=test%20%3F+test%20%21&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL" "OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=test%20%3F+test%20%21&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL9cxRL"
); ).await;
println!("{:?}", res); println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAskVerifyGroup); assert_eq!(res.get_state(), LotState::QrAskVerifyGroup);
assert_ne!(res.get_id(), 0); assert_ne!(res.get_id(), 0);
assert_eq!(res.get_text1().unwrap(), "test ? test !"); 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"); assert_eq!(contact.get_addr(), "cli@deltachat.de");
} }
#[test] #[async_std::test]
fn test_decode_openpgp_secure_join() { async fn test_decode_openpgp_secure_join() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=J%C3%B6rn%20P.+P.&i=TbnwJ6lSvD5&s=0ejvbdFSQxB" "OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&n=J%C3%B6rn%20P.+P.&i=TbnwJ6lSvD5&s=0ejvbdFSQxB"
); ).await;
println!("{:?}", res); println!("{:?}", res);
assert_eq!(res.get_state(), LotState::QrAskVerifyContact); assert_eq!(res.get_state(), LotState::QrAskVerifyContact);
assert_ne!(res.get_id(), 0); 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_addr(), "cli@deltachat.de");
assert_eq!(contact.get_name(), "Jörn P. P."); assert_eq!(contact.get_name(), "Jörn P. P.");
} }
#[test] #[async_std::test]
fn test_decode_openpgp_without_addr() { async fn test_decode_openpgp_without_addr() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"OPENPGP4FPR:1234567890123456789012345678901234567890", "OPENPGP4FPR:1234567890123456789012345678901234567890",
); )
.await;
assert_eq!(res.get_state(), LotState::QrFprWithoutAddr); assert_eq!(res.get_state(), LotState::QrFprWithoutAddr);
assert_eq!( assert_eq!(
res.get_text1().unwrap(), res.get_text1().unwrap(),
@@ -569,31 +572,32 @@ mod tests {
); );
assert_eq!(res.get_id(), 0); 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_state(), LotState::QrError);
assert_eq!(res.get_id(), 0); assert_eq!(res.get_id(), 0);
} }
#[test] #[async_std::test]
fn test_decode_account() { async fn test_decode_account() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3", "DCACCOUNT:https://example.org/new_email?t=1w_7wDjgjelxeX884x96v3",
); )
.await;
assert_eq!(res.get_state(), LotState::QrAccount); assert_eq!(res.get_state(), LotState::QrAccount);
assert_eq!(res.get_text1().unwrap(), "example.org"); assert_eq!(res.get_text1().unwrap(), "example.org");
} }
#[test] #[async_std::test]
fn test_decode_account_bad_scheme() { async fn test_decode_account_bad_scheme() {
let ctx = dummy_context(); let ctx = dummy_context().await;
let res = check_qr( let res = check_qr(
&ctx.ctx, &ctx.ctx,
"DCACCOUNT:http://example.org/new_email?t=1w_7wDjgjelxeX884x96v3", "DCACCOUNT:http://example.org/new_email?t=1w_7wDjgjelxeX884x96v3",
); )
.await;
assert_eq!(res.get_state(), LotState::QrError); assert_eq!(res.get_state(), LotState::QrError);
assert!(res.get_text1().is_some()); assert!(res.get_text1().is_some());
} }

View File

@@ -55,7 +55,7 @@ macro_rules! get_qr_attr {
$context $context
.bob .bob
.read() .read()
.unwrap() .await
.qr_scan .qr_scan
.as_ref() .as_ref()
.unwrap() .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 ==== ==== Alice - the inviter side ====
==== Step 1 in "Setup verified contact" protocol ==== ==== 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; 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, // invitenumber will be used to allow starting the handshake,
// auth will be used to verify the fingerprint // auth will be used to verify the fingerprint
let invitenumber = token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id); let invitenumber =
let auth = token::lookup_or_new(context, token::Namespace::Auth, group_chat_id); token::lookup_or_new(context, token::Namespace::InviteNumber, group_chat_id).await;
let self_addr = match context.get_config(Config::ConfiguredAddr) { 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, Some(addr) => addr,
None => { None => {
error!(context, "Not configured, cannot generate QR code.",); 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, Some(fp) => fp,
None => { None => {
return 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() { let qr = if !group_chat_id.is_unset() {
// parameters used: a=g=x=i=s= // 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 = chat.get_name();
let group_name_urlencoded = let group_name_urlencoded =
utf8_percent_encode(&group_name, NON_ALPHANUMERIC).to_string(); 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 qr
} }
fn get_self_fingerprint(context: &Context) -> Option<String> { async fn get_self_fingerprint(context: &Context) -> Option<String> {
if let Some(self_addr) = context.get_config(Config::ConfiguredAddr) { if let Some(self_addr) = context.get_config(Config::ConfiguredAddr).await {
if let Some(key) = Key::from_self_public(context, self_addr, &context.sql) { if let Some(key) = Key::from_self_public(context, self_addr, &context.sql).await {
return Some(key.fingerprint()); return Some(key.fingerprint());
} }
} }
@@ -149,7 +153,7 @@ async fn cleanup(
ongoing_allocated: bool, ongoing_allocated: bool,
join_vg: bool, join_vg: bool,
) -> ChatId { ) -> ChatId {
let mut bob = context.bob.write().unwrap(); let mut bob = context.bob.write().await;
bob.expects = 0; bob.expects = 0;
let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS {
if join_vg { if join_vg {
@@ -169,7 +173,7 @@ async fn cleanup(
bob.qr_scan = None; bob.qr_scan = None;
if ongoing_allocated { if ongoing_allocated {
context.free_ongoing(); context.free_ongoing().await;
} }
ret_chat_id ret_chat_id
} }
@@ -187,7 +191,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
info!(context, "Requesting secure-join ...",); info!(context, "Requesting secure-join ...",);
ensure_secret_key_exists(context).await.ok(); 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; return cleanup(&context, contact_chat_id, false, join_vg).await;
} }
let qr_scan = check_qr(context, &qr).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; 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; return cleanup(&context, contact_chat_id, true, join_vg).await;
} }
join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup; 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.status = 0;
bob.qr_scan = Some(qr_scan); bob.qr_scan = Some(qr_scan);
} }
@@ -217,7 +221,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
context context
.bob .bob
.read() .read()
.unwrap() .await
.qr_scan .qr_scan
.as_ref() .as_ref()
.unwrap() .unwrap()
@@ -225,13 +229,19 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
.as_ref() .as_ref()
.unwrap(), .unwrap(),
contact_chat_id, contact_chat_id,
) { )
.await
{
// the scanned fingerprint matches Alice's key, // the scanned fingerprint matches Alice's key,
// we can proceed to step 4b) directly and save two mails // we can proceed to step 4b) directly and save two mails
info!(context, "Taking protocol shortcut."); info!(context, "Taking protocol shortcut.");
context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM; context.bob.write().await.expects = DC_VC_CONTACT_CONFIRM;
joiner_progress!(context, chat_id_2_contact_id(context, contact_chat_id), 400); joiner_progress!(
let own_fingerprint = get_self_fingerprint(context).unwrap_or_default(); 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 // Bob -> Alice
send_handshake_msg( send_handshake_msg(
@@ -252,7 +262,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
) )
.await; .await;
} else { } else {
context.bob.write().unwrap().expects = DC_VC_AUTH_REQUIRED; context.bob.write().await.expects = DC_VC_AUTH_REQUIRED;
// Bob -> Alice // Bob -> Alice
send_handshake_msg( send_handshake_msg(
@@ -268,14 +278,14 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId {
if join_vg { if join_vg {
// for a group-join, wait until the secure-join is done and the group is created // 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)); std::thread::sleep(std::time::Duration::from_millis(200));
} }
cleanup(&context, contact_chat_id, true, join_vg).await cleanup(&context, contact_chat_id, true, join_vg).await
} else { } else {
// for a one-to-one-chat, the chat is already known, return the chat-id, // for a one-to-one-chat, the chat is already known, return the chat-id,
// the verification runs in background // the verification runs in background
context.free_ongoing(); context.free_ongoing().await;
contact_chat_id contact_chat_id
} }
} }
@@ -321,8 +331,8 @@ async fn send_handshake_msg(
.unwrap_or_default(); .unwrap_or_default();
} }
fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 { async fn chat_id_2_contact_id(context: &Context, contact_chat_id: ChatId) -> u32 {
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 contacts.len() == 1 {
contacts[0] contacts[0]
} else { } 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, context: &Context,
fingerprint: impl AsRef<str>, fingerprint: impl AsRef<str>,
contact_chat_id: ChatId, contact_chat_id: ChatId,
) -> bool { ) -> 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 contacts.len() == 1 {
if let Ok(contact) = Contact::load_from_db(context, contacts[0]) { 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()) if let Some(peerstate) =
Peerstate::from_addr(context, &context.sql, contact.get_addr()).await
{ {
let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref()); let fingerprint_normalized = dc_normalize_fingerprint(fingerprint.as_ref());
if peerstate.public_key_fingerprint.is_some() 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 { match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).await {
Ok((chat_id, blocked)) => { Ok((chat_id, blocked)) => {
if blocked != Blocked::Not { if blocked != Blocked::Not {
chat_id.unblock(context); chat_id.unblock(context).await;
} }
chat_id 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 // verify that Alice's Autocrypt key and fingerprint matches the QR-code
let cond = { let cond = {
let bob = context.bob.read().unwrap(); let bob = context.bob.read().await;
let scan = bob.qr_scan.as_ref(); let scan = bob.qr_scan.as_ref();
scan.is_none() scan.is_none()
|| bob.expects != DC_VC_AUTH_REQUIRED || bob.expects != DC_VC_AUTH_REQUIRED
@@ -504,25 +515,29 @@ pub(crate) async fn handle_securejoin_handshake(
} else { } else {
"Not encrypted." "Not encrypted."
}, },
); )
context.bob.write().unwrap().status = 0; // secure-join failed .await;
context.stop_ongoing(); context.bob.write().await.status = 0; // secure-join failed
context.stop_ongoing().await;
return Ok(HandshakeMessage::Ignore); 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( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on joiner-side.", "Fingerprint mismatch on joiner-side.",
); )
context.bob.write().unwrap().status = 0; // secure-join failed .await;
context.stop_ongoing(); context.bob.write().await.status = 0; // secure-join failed
context.stop_ongoing().await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
info!(context, "Fingerprint verified.",); 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); 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 // Bob -> Alice
send_handshake_msg( send_handshake_msg(
@@ -555,7 +570,8 @@ pub(crate) async fn handle_securejoin_handshake(
context, context,
contact_chat_id, contact_chat_id,
"Fingerprint not provided.", "Fingerprint not provided.",
); )
.await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
@@ -564,15 +580,17 @@ pub(crate) async fn handle_securejoin_handshake(
context, context,
contact_chat_id, contact_chat_id,
"Auth not encrypted.", "Auth not encrypted.",
); )
.await;
return Ok(HandshakeMessage::Ignore); 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( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on inviter-side.", "Fingerprint mismatch on inviter-side.",
); )
.await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
info!(context, "Fingerprint verified.",); info!(context, "Fingerprint verified.",);
@@ -584,25 +602,28 @@ pub(crate) async fn handle_securejoin_handshake(
context, context,
contact_chat_id, contact_chat_id,
"Auth not provided.", "Auth not provided.",
); )
.await;
return Ok(HandshakeMessage::Ignore); return Ok(HandshakeMessage::Ignore);
} }
}; };
if !token::exists(context, token::Namespace::Auth, &auth_0).await { 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); 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( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on inviter-side.", "Fingerprint mismatch on inviter-side.",
); )
.await;
return Ok(HandshakeMessage::Ignore); 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.",); 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))); emit_event!(context, Event::ContactsChanged(Some(contact_id)));
inviter_progress!(context, contact_id, 600); inviter_progress!(context, contact_id, 600);
if join_vg { if join_vg {
@@ -651,12 +672,12 @@ pub(crate) async fn handle_securejoin_handshake(
HandshakeMessage::Ignore 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.",); info!(context, "Message belongs to a different handshake.",);
return Ok(abort_retval); return Ok(abort_retval);
} }
let cond = { let cond = {
let bob = context.bob.read().unwrap(); let bob = context.bob.read().await;
let scan = bob.qr_scan.as_ref(); let scan = bob.qr_scan.as_ref();
scan.is_none() || (join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup) scan.is_none() || (join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup)
}; };
@@ -695,20 +716,25 @@ pub(crate) async fn handle_securejoin_handshake(
context, context,
contact_chat_id, contact_chat_id,
"Contact confirm message not encrypted.", "Contact confirm message not encrypted.",
); )
context.bob.write().unwrap().status = 0; .await;
context.bob.write().await.status = 0;
return Ok(abort_retval); 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( could_not_establish_secure_connection(
context, context,
contact_chat_id, contact_chat_id,
"Fingerprint mismatch on joiner-side.", "Fingerprint mismatch on joiner-side.",
); )
.await;
return Ok(abort_retval); 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)); emit_event!(context, Event::ContactsChanged(None));
let cg_member_added = mime_message let cg_member_added = mime_message
.get(HeaderDef::ChatGroupMemberAdded) .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)."); info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group).");
return Ok(abort_retval); return Ok(abort_retval);
} }
secure_connection_established(context, contact_chat_id); secure_connection_established(context, contact_chat_id).await;
context.bob.write().unwrap().expects = 0; context.bob.write().await.expects = 0;
if join_vg { if join_vg {
// Bob -> Alice // Bob -> Alice
send_handshake_msg( send_handshake_msg(
@@ -737,8 +763,8 @@ pub(crate) async fn handle_securejoin_handshake(
) )
.await; .await;
} }
context.bob.write().unwrap().status = 1; context.bob.write().await.status = 1;
context.stop_ongoing(); context.stop_ongoing().await;
Ok(if join_vg { Ok(if join_vg {
HandshakeMessage::Propagate HandshakeMessage::Propagate
} else { } 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 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.",); warn!(context, "vg-member-added-received invalid.",);
return Ok(HandshakeMessage::Ignore); 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) { async 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_id: u32 = chat_id_2_contact_id(context, contact_chat_id).await;
let contact = Contact::get_by_id(context, contact_id); let contact = Contact::get_by_id(context, contact_id).await;
let addr = if let Ok(ref contact) = contact { let addr = if let Ok(ref contact) = contact {
contact.get_addr() contact.get_addr()
} else { } else {
"?" "?"
}; };
let msg = context.stock_string_repl_str(StockMessage::ContactVerified, addr); let msg = context
chat::add_info_msg(context, contact_chat_id, msg); .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)); emit_event!(context, Event::ChatModified(contact_chat_id));
} }
fn could_not_establish_secure_connection( async fn could_not_establish_secure_connection(
context: &Context, context: &Context,
contact_chat_id: ChatId, contact_chat_id: ChatId,
details: &str, details: &str,
) { ) {
let contact_id = chat_id_2_contact_id(context, contact_chat_id); let contact_id = chat_id_2_contact_id(context, contact_chat_id).await;
let contact = Contact::get_by_id(context, contact_id); let contact = Contact::get_by_id(context, contact_id).await;
let msg = context.stock_string_repl_str( let msg = context
StockMessage::ContactNotVerified, .stock_string_repl_str(
if let Ok(ref contact) = contact { StockMessage::ContactNotVerified,
contact.get_addr() if let Ok(ref contact) = contact {
} else { 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); 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) = 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( if peerstate.set_verified(
PeerstateKeyType::PublicKey, 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.to_save = Some(ToSave::All);
peerstate peerstate
.save_to_db(&context.sql, false) .save_to_db(&context.sql, false)
.await
.unwrap_or_default(); .unwrap_or_default();
return Ok(()); return Ok(());
} }
@@ -891,7 +925,7 @@ pub async fn handle_degrade_event(
.query_get_value( .query_get_value(
context, context,
"SELECT id FROM contacts WHERE addr=?;", "SELECT id FROM contacts WHERE addr=?;",
params![&peerstate.addr], paramsv![peerstate.addr],
) )
.await .await
{ {
@@ -908,9 +942,10 @@ pub async fn handle_degrade_event(
.unwrap_or_default(); .unwrap_or_default();
let msg = context 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)); emit_event!(context, Event::ChatModified(contact_chat_id));
} }
} }

View File

@@ -159,7 +159,7 @@ impl Smtp {
// oauth2 // oauth2
let addr = &lp.addr; let addr = &lp.addr;
let send_pw = &lp.send_pw; 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() { if access_token.is_none() {
return Err(Error::Oauth2Error { return Err(Error::Oauth2Error {
address: addr.to_string(), address: addr.to_string(),

View File

@@ -7,7 +7,7 @@ use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use std::time::Duration; 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 thread_local_object::ThreadLocal;
use crate::chat::{update_device_icon, update_saved_messages_icon}; 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> pub async fn execute<S: AsRef<str>>(
where &self,
P: IntoIterator, sql: S,
P::Item: rusqlite::ToSql, params: Vec<&dyn crate::ToSql>,
{ ) -> Result<usize> {
self.start_stmt(sql.as_ref()); self.start_stmt(sql.as_ref());
let res = { let res = {
let conn = self.get_conn().await?; 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. /// 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 /// Then executes the second function over the returned iterator and returns the
/// result of that function. /// result of that function.
pub async fn query_map<T, P, F, G, H>( pub async fn query_map<T, F, G, H>(
&self, &self,
sql: impl AsRef<str>, sql: impl AsRef<str>,
params: P, params: Vec<&dyn crate::ToSql>,
f: F, f: F,
mut g: G, mut g: G,
) -> Result<H> ) -> Result<H>
where where
P: IntoIterator,
P::Item: rusqlite::ToSql,
F: FnMut(&rusqlite::Row) -> rusqlite::Result<T>, F: FnMut(&rusqlite::Row) -> rusqlite::Result<T>,
G: FnMut(rusqlite::MappedRows<F>) -> Result<H>, G: FnMut(rusqlite::MappedRows<F>) -> Result<H>,
{ {
@@ -144,41 +144,12 @@ impl Sql {
let res = { let res = {
let conn = self.get_conn().await?; let conn = self.get_conn().await?;
let mut stmt = conn.prepare(sql)?; let mut stmt = conn.prepare(sql)?;
let res = stmt.query_map(params, f)?; let res = stmt.query_map(&params, f)?;
g(res)? self.in_use.remove();
g(res)
}; };
Ok(res) 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
} }
pub async fn get_conn( 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 /// 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. /// rows and false if the SQL returns an empty set.
pub async fn exists<P>(&self, sql: &str, params: P) -> Result<bool> pub async fn exists(&self, sql: &str, params: Vec<&dyn crate::ToSql>) -> Result<bool> {
where
P: IntoIterator,
P::Item: rusqlite::ToSql,
{
self.start_stmt(sql.to_string()); self.start_stmt(sql.to_string());
let res = { let res = {
let conn = self.get_conn().await?; let conn = self.get_conn().await?;
let mut stmt = conn.prepare(sql)?; 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. /// 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 where
P: IntoIterator + Copy,
P::Item: rusqlite::ToSql,
F: FnOnce(&rusqlite::Row) -> rusqlite::Result<T>, F: FnOnce(&rusqlite::Row) -> rusqlite::Result<T>,
{ {
self.start_stmt(sql.as_ref().to_string()); self.start_stmt(sql.as_ref().to_string());
let sql = sql.as_ref(); let sql = sql.as_ref();
let res = { let res = {
let conn = self.get_conn().await?; 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> { 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 /// Executes a query which is expected to return one row and one
/// column. If the query does not return a value or returns SQL /// column. If the query does not return a value or returns SQL
/// `NULL`, returns `Ok(None)`. /// `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 where
P: IntoIterator + Copy,
P::Item: rusqlite::ToSql,
T: rusqlite::types::FromSql, T: rusqlite::types::FromSql,
{ {
match self match self
@@ -299,15 +275,13 @@ impl Sql {
/// Not resultified version of `query_get_value_result`. Returns /// Not resultified version of `query_get_value_result`. Returns
/// `None` on error. /// `None` on error.
pub async fn query_get_value<P, T>( pub async fn query_get_value<T>(
&self, &self,
context: &Context, context: &Context,
query: &str, query: &str,
params: P, params: Vec<&dyn crate::ToSql>,
) -> Option<T> ) -> Option<T>
where where
P: IntoIterator + Copy,
P::Item: rusqlite::ToSql,
T: rusqlite::types::FromSql, T: rusqlite::types::FromSql,
{ {
match self.query_get_value_result(query, params).await { match self.query_get_value_result(query, params).await {
@@ -337,23 +311,23 @@ impl Sql {
let key = key.as_ref(); let key = key.as_ref();
let res = if let Some(ref value) = value { let res = if let Some(ref value) = value {
let exists = self let exists = self
.exists("SELECT value FROM config WHERE keyname=?;", params![key]) .exists("SELECT value FROM config WHERE keyname=?;", paramsv![key])
.await?; .await?;
if exists { if exists {
self.execute( self.execute(
"UPDATE config SET value=? WHERE keyname=?;", "UPDATE config SET value=? WHERE keyname=?;",
params![value, key], paramsv![value.to_string(), key.to_string()],
) )
.await .await
} else { } else {
self.execute( self.execute(
"INSERT INTO config (keyname, value) VALUES (?, ?);", "INSERT INTO config (keyname, value) VALUES (?, ?);",
params![key, value], paramsv![key.to_string(), value.to_string()],
) )
.await .await
} }
} else { } else {
self.execute("DELETE FROM config WHERE keyname=?;", params![key]) self.execute("DELETE FROM config WHERE keyname=?;", paramsv![key])
.await .await
}; };
@@ -374,7 +348,7 @@ impl Sql {
self.query_get_value( self.query_get_value(
context, context,
"SELECT value FROM config WHERE keyname=?;", "SELECT value FROM config WHERE keyname=?;",
params![key.as_ref()], paramsv![key.as_ref().to_string()],
) )
.await .await
} }
@@ -455,18 +429,14 @@ impl Sql {
) -> Result<u32> { ) -> Result<u32> {
self.start_stmt("get rowid".to_string()); 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 res = {
let mut conn = self.get_conn().await?; 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( pub async fn get_rowid2(
@@ -482,15 +452,17 @@ impl Sql {
let res = { let res = {
let mut conn = self.get_conn().await?; 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( pub fn get_rowid(
context: &Context, _context: &Context,
conn: &mut Connection, conn: &mut Connection,
table: impl AsRef<str>, table: impl AsRef<str>,
field: impl AsRef<str>, field: impl AsRef<str>,
@@ -509,7 +481,7 @@ pub fn get_rowid(
} }
pub fn get_rowid2( pub fn get_rowid2(
context: &Context, _context: &Context,
conn: &mut Connection, conn: &mut Connection,
table: impl AsRef<str>, table: impl AsRef<str>,
field: impl AsRef<str>, field: impl AsRef<str>,
@@ -526,7 +498,7 @@ pub fn get_rowid2(
field2.as_ref(), field2.as_ref(),
value2, value2,
), ),
NO_PARAMS, params![],
|row| row.get::<_, u32>(0), |row| row.get::<_, u32>(0),
) )
} }
@@ -569,7 +541,7 @@ pub async fn housekeeping(context: &Context) {
.sql .sql
.query_map( .query_map(
"SELECT value FROM config;", "SELECT value FROM config;",
params![], paramsv![],
|row| row.get::<_, String>(0), |row| row.get::<_, String>(0),
|rows| { |rows| {
for row in rows { for row in rows {
@@ -683,7 +655,7 @@ async fn maybe_add_from_param(
.sql .sql
.query_map( .query_map(
query, query,
NO_PARAMS, paramsv![],
|row| row.get::<_, String>(0), |row| row.get::<_, String>(0),
|rows| { |rows| {
for row in rows { for row in rows {
@@ -762,11 +734,14 @@ async fn open(
); );
sql.execute( sql.execute(
"CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);", "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?; .await?;
sql.execute("CREATE INDEX config_index1 ON config (keyname);", NO_PARAMS)
.await?;
sql.execute( sql.execute(
"CREATE TABLE contacts (\ "CREATE TABLE contacts (\
id INTEGER PRIMARY KEY AUTOINCREMENT, \ id INTEGER PRIMARY KEY AUTOINCREMENT, \
@@ -776,17 +751,17 @@ async fn open(
blocked INTEGER DEFAULT 0, \ blocked INTEGER DEFAULT 0, \
last_seen INTEGER DEFAULT 0, \ last_seen INTEGER DEFAULT 0, \
param TEXT DEFAULT '');", param TEXT DEFAULT '');",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);", "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);", "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
@@ -794,7 +769,7 @@ async fn open(
(1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \ (1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \
(4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \ (4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \
(7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);", (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
@@ -807,19 +782,19 @@ async fn open(
blocked INTEGER DEFAULT 0, \ blocked INTEGER DEFAULT 0, \
grpid TEXT DEFAULT '', \ grpid TEXT DEFAULT '', \
param TEXT DEFAULT '');", param TEXT DEFAULT '');",
params![], paramsv![],
) )
.await?; .await?;
sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", params![]) sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", paramsv![])
.await?; .await?;
sql.execute( sql.execute(
"CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);", "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);", "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
@@ -827,7 +802,7 @@ async fn open(
(1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \ (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \
(4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \ (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \
(7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');", (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
@@ -847,23 +822,23 @@ async fn open(
txt TEXT DEFAULT '', \ txt TEXT DEFAULT '', \
txt_raw TEXT DEFAULT '', \ txt_raw TEXT DEFAULT '', \
param TEXT DEFAULT '');", param TEXT DEFAULT '');",
params![], paramsv![],
) )
.await?; .await?;
sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", params![]) sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", paramsv![])
.await?; .await?;
sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", params![]) sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", paramsv![])
.await?; .await?;
sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", params![]) sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", paramsv![])
.await?; .await?;
sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", params![]) sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", paramsv![])
.await?; .await?;
sql.execute( sql.execute(
"INSERT INTO msgs (id,msgrmsg,txt) VALUES \ "INSERT INTO msgs (id,msgrmsg,txt) VALUES \
(1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \ (1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \
(4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \ (4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \
(8,0,'rsvd'), (9,0,'daymarker');", (8,0,'rsvd'), (9,0,'daymarker');",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
@@ -874,12 +849,12 @@ async fn open(
action INTEGER, \ action INTEGER, \
foreign_id INTEGER, \ foreign_id INTEGER, \
param TEXT DEFAULT '');", param TEXT DEFAULT '');",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX jobs_index1 ON jobs (desired_timestamp);", "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);",
params![], paramsv![],
) )
.await?; .await?;
if !sql.table_exists("config").await? if !sql.table_exists("config").await?
@@ -920,12 +895,12 @@ async fn open(
info!(context, "[migration] v1"); info!(context, "[migration] v1");
sql.execute( sql.execute(
"CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');", "CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX leftgrps_index1 ON leftgrps (grpid);", "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 1; dbversion = 1;
@@ -935,7 +910,7 @@ async fn open(
info!(context, "[migration] v2"); info!(context, "[migration] v2");
sql.execute( sql.execute(
"ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';", "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 2; dbversion = 2;
@@ -951,7 +926,7 @@ async fn open(
private_key, \ private_key, \
public_key, \ public_key, \
created INTEGER DEFAULT 0);", created INTEGER DEFAULT 0);",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 7; dbversion = 7;
@@ -967,12 +942,12 @@ async fn open(
last_seen_autocrypt INTEGER DEFAULT 0, \ last_seen_autocrypt INTEGER DEFAULT 0, \
public_key, \ public_key, \
prefer_encrypted INTEGER DEFAULT 0);", prefer_encrypted INTEGER DEFAULT 0);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);", "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 10; dbversion = 10;
@@ -982,12 +957,12 @@ async fn open(
info!(context, "[migration] v12"); info!(context, "[migration] v12");
sql.execute( sql.execute(
"CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);", "CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);", "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 12; dbversion = 12;
@@ -997,17 +972,17 @@ async fn open(
info!(context, "[migration] v17"); info!(context, "[migration] v17");
sql.execute( sql.execute(
"ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;", "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute("CREATE INDEX chats_index2 ON chats (archived);", params![]) sql.execute("CREATE INDEX chats_index2 ON chats (archived);", paramsv![])
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;", "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![]) sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", paramsv![])
.await?; .await?;
dbversion = 17; dbversion = 17;
sql.set_raw_config_int(context, "dbversion", 17).await?; sql.set_raw_config_int(context, "dbversion", 17).await?;
@@ -1016,11 +991,14 @@ async fn open(
info!(context, "[migration] v18"); info!(context, "[migration] v18");
sql.execute( sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;", "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;",
params![], paramsv![],
)
.await?;
sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN gossip_key;",
paramsv![],
) )
.await?; .await?;
sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])
.await?;
dbversion = 18; dbversion = 18;
sql.set_raw_config_int(context, "dbversion", 18).await?; sql.set_raw_config_int(context, "dbversion", 18).await?;
} }
@@ -1028,21 +1006,21 @@ async fn open(
info!(context, "[migration] v27"); info!(context, "[migration] v27");
// chat.id=1 and chat.id=2 are the old deaddrops, // chat.id=1 and chat.id=2 are the old deaddrops,
// the current ones are defined by chats.blocked=2 // 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?; .await?;
sql.execute( sql.execute(
"CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);", "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;", "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 27; dbversion = 27;
@@ -1052,32 +1030,32 @@ async fn open(
info!(context, "[migration] v34"); info!(context, "[migration] v34");
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;", "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';", "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';", "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);", "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);", "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);",
params![], paramsv![],
) )
.await?; .await?;
recalc_fingerprints = true; recalc_fingerprints = true;
@@ -1088,21 +1066,21 @@ async fn open(
info!(context, "[migration] v39"); info!(context, "[migration] v39");
sql.execute( 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);", "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?; ).await?;
sql.execute( sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN verified_key;", "ALTER TABLE acpeerstates ADD COLUMN verified_key;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';", "ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);", "CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 39; dbversion = 39;
@@ -1112,7 +1090,7 @@ async fn open(
info!(context, "[migration] v40"); info!(context, "[migration] v40");
sql.execute( sql.execute(
"ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 40; dbversion = 40;
@@ -1120,7 +1098,7 @@ async fn open(
} }
if dbversion < 44 { if dbversion < 44 {
info!(context, "[migration] v44"); 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?; .await?;
dbversion = 44; dbversion = 44;
sql.set_raw_config_int(context, "dbversion", 44).await?; sql.set_raw_config_int(context, "dbversion", 44).await?;
@@ -1129,12 +1107,12 @@ async fn open(
info!(context, "[migration] v46"); info!(context, "[migration] v46");
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;", "ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN mime_references TEXT;", "ALTER TABLE msgs ADD COLUMN mime_references TEXT;",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 46; dbversion = 46;
@@ -1144,7 +1122,7 @@ async fn open(
info!(context, "[migration] v47"); info!(context, "[migration] v47");
sql.execute( sql.execute(
"ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", "ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 47; dbversion = 47;
@@ -1155,7 +1133,7 @@ async fn open(
// NOTE: move_state is not used anymore // NOTE: move_state is not used anymore
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;", "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;",
params![], paramsv![],
) )
.await?; .await?;
@@ -1166,7 +1144,7 @@ async fn open(
info!(context, "[migration] v49"); info!(context, "[migration] v49");
sql.execute( sql.execute(
"ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;", "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 49; dbversion = 49;
@@ -1190,36 +1168,36 @@ async fn open(
// are also added to the database as _hidden_. // are also added to the database as _hidden_.
sql.execute( 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);", "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?; ).await?;
sql.execute( sql.execute(
"CREATE INDEX locations_index1 ON locations (from_id);", "CREATE INDEX locations_index1 ON locations (from_id);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX locations_index2 ON locations (timestamp);", "CREATE INDEX locations_index2 ON locations (timestamp);",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;", "ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;", "ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;", "ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.execute( sql.execute(
"CREATE INDEX chats_index3 ON chats (locations_send_until);", "CREATE INDEX chats_index3 ON chats (locations_send_until);",
params![], paramsv![],
) )
.await?; .await?;
dbversion = 53; dbversion = 53;
@@ -1229,11 +1207,14 @@ async fn open(
info!(context, "[migration] v54"); info!(context, "[migration] v54");
sql.execute( sql.execute(
"ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;", "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?; .await?;
sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])
.await?;
dbversion = 54; dbversion = 54;
sql.set_raw_config_int(context, "dbversion", 54).await?; sql.set_raw_config_int(context, "dbversion", 54).await?;
} }
@@ -1241,7 +1222,7 @@ async fn open(
info!(context, "[migration] v55"); info!(context, "[migration] v55");
sql.execute( sql.execute(
"ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;", "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;",
params![], paramsv![],
) )
.await?; .await?;
sql.set_raw_config_int(context, "dbversion", 55).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. // so, msg_id may or may not exist.
sql.execute( sql.execute(
"CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);", "CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);",
NO_PARAMS, paramsv![],
).await?; ).await?;
sql.execute( sql.execute(
"CREATE INDEX devmsglabels_index1 ON devmsglabels (label);", "CREATE INDEX devmsglabels_index1 ON devmsglabels (label);",
NO_PARAMS, paramsv![],
) )
.await?; .await?;
if exists_before_update && sql.get_raw_config_int(context, "bcc_self").await.is_none() { 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"); info!(context, "[migration] v60");
sql.execute( sql.execute(
"ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;", "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;",
NO_PARAMS, paramsv![],
) )
.await?; .await?;
sql.set_raw_config_int(context, "dbversion", 60).await?; sql.set_raw_config_int(context, "dbversion", 60).await?;
@@ -1277,7 +1258,7 @@ async fn open(
info!(context, "[migration] v61"); info!(context, "[migration] v61");
sql.execute( sql.execute(
"ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;", "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;",
NO_PARAMS, paramsv![],
) )
.await?; .await?;
update_icons = true; update_icons = true;
@@ -1287,14 +1268,14 @@ async fn open(
info!(context, "[migration] v62"); info!(context, "[migration] v62");
sql.execute( sql.execute(
"ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;",
NO_PARAMS, paramsv![],
) )
.await?; .await?;
sql.set_raw_config_int(context, "dbversion", 62).await?; sql.set_raw_config_int(context, "dbversion", 62).await?;
} }
if dbversion < 63 { if dbversion < 63 {
info!(context, "[migration] v63"); 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?; .await?;
sql.set_raw_config_int(context, "dbversion", 63).await?; sql.set_raw_config_int(context, "dbversion", 63).await?;
} }
@@ -1305,22 +1286,24 @@ async fn open(
if recalc_fingerprints { if recalc_fingerprints {
info!(context, "[migration] recalc fingerprints"); info!(context, "[migration] recalc fingerprints");
sql.query_map_async( let addrs = sql
"SELECT addr FROM acpeerstates;", .query_map(
params![], "SELECT addr FROM acpeerstates;",
|row| row.get::<_, String>(0), paramsv![],
|addrs| async move { |row| row.get::<_, String>(0),
for addr in addrs { |addrs| {
if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?) addrs
{ .collect::<std::result::Result<Vec<_>, _>>()
peerstate.recalc_fingerprint(); .map_err(Into::into)
peerstate.save_to_db(sql, false).await?; },
} )
} .await?;
Ok(()) for addr in &addrs {
}, if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, addr).await {
) peerstate.recalc_fingerprint();
.await?; peerstate.save_to_db(sql, false).await?;
}
}
} }
if update_icons { if update_icons {
update_saved_messages_icon(context).await?; update_saved_messages_icon(context).await?;

View File

@@ -206,7 +206,7 @@ impl StockMessage {
impl Context { impl Context {
/// Set the stock string for the [StockMessage]. /// Set the stock string for the [StockMessage].
/// ///
pub fn set_stock_translation( pub async fn set_stock_translation(
&self, &self,
id: StockMessage, id: StockMessage,
stockstring: String, stockstring: String,
@@ -227,7 +227,7 @@ impl Context {
} }
self.translated_stockstrings self.translated_stockstrings
.write() .write()
.unwrap() .await
.insert(id as usize, stockstring); .insert(id as usize, stockstring);
Ok(()) Ok(())
} }
@@ -236,11 +236,11 @@ impl Context {
/// ///
/// Return a translation (if it was set with set_stock_translation before) /// Return a translation (if it was set with set_stock_translation before)
/// or a default (English) string. /// 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 match self
.translated_stockstrings .translated_stockstrings
.read() .read()
.unwrap() .await
.get(&(id as usize)) .get(&(id as usize))
{ {
Some(ref x) => Cow::Owned((*x).to_string()), 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$@` /// This replaces both the *first* `%1$s`, `%1$d` and `%1$@`
/// placeholders with the provided string. /// placeholders with the provided string.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop) /// (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) self.stock_str(id)
.await
.replacen("%1$s", insert.as_ref(), 1) .replacen("%1$s", insert.as_ref(), 1)
.replacen("%1$d", insert.as_ref(), 1) .replacen("%1$d", insert.as_ref(), 1)
.replacen("%1$@", 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 /// Like [Context::stock_string_repl_str] but substitute the placeholders
/// with an integer. /// 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()) self.stock_string_repl_str(id, format!("{}", insert).as_str())
.await
} }
/// Return stock string, replacing 2 placeholders with provided string. /// 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 /// placeholders with the string in `insert` and does the same for
/// `%2$s`, `%2$d` and `%2$@` for `insert2`. /// `%2$s`, `%2$d` and `%2$@` for `insert2`.
/// (the `%1$@` variant is used on iOS, the other are used on Android and Desktop) /// (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, &self,
id: StockMessage, id: StockMessage,
insert: impl AsRef<str>, insert: impl AsRef<str>,
insert2: impl AsRef<str>, insert2: impl AsRef<str>,
) -> String { ) -> String {
self.stock_str(id) self.stock_str(id)
.await
.replacen("%1$s", insert.as_ref(), 1) .replacen("%1$s", insert.as_ref(), 1)
.replacen("%1$d", insert.as_ref(), 1) .replacen("%1$d", insert.as_ref(), 1)
.replacen("%1$@", 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 /// used as the second parameter to [StockMessage::MsgActionByUser] with
/// again the original stock string being used as the first parameter, /// again the original stock string being used as the first parameter,
/// resulting in a string like "Member Alice added by Bob.". /// resulting in a string like "Member Alice added by Bob.".
pub fn stock_system_msg( pub async fn stock_system_msg(
&self, &self,
id: StockMessage, id: StockMessage,
param1: impl AsRef<str>, param1: impl AsRef<str>,
@@ -314,9 +317,10 @@ impl Context {
from_id: u32, from_id: u32,
) -> String { ) -> String {
let insert1 = if id == StockMessage::MsgAddMember || id == StockMessage::MsgDelMember { 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 { if contact_id != 0 {
Contact::get_by_id(self, contact_id) Contact::get_by_id(self, contact_id)
.await
.map(|contact| contact.get_name_n_addr()) .map(|contact| contact.get_name_n_addr())
.unwrap_or_default() .unwrap_or_default()
} else { } else {
@@ -326,52 +330,60 @@ impl Context {
param1.as_ref().to_string() 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('.'); let action1 = action.trim_end_matches('.');
match from_id { match from_id {
0 => action, 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) let displayname = Contact::get_by_id(self, from_id)
.await
.map(|contact| contact.get_name_n_addr()) .map(|contact| contact.get_name_n_addr())
.unwrap_or_default(); .unwrap_or_default();
self.stock_string_repl_str2(StockMessage::MsgActionByUser, action1, &displayname) 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. // 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 // this is worthwhile as this function is typically called
// by the ui on every probram start or even on every opening of the chatlist. // 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(()); return Ok(());
} }
// create saved-messages chat; // create saved-messages chat;
// we do this only once, if the user has deleted the chat, he can recreate it manually. // 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 self.sql
.set_raw_config_bool(&self, "self-chat-added", true)?; .set_raw_config_bool(&self, "self-chat-added", true)
chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF)?; .await?;
chat::create_by_contact_id(&self, DC_CONTACT_ID_SELF).await?;
} }
// add welcome-messages. by the label, this is done only once, // 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. // if the user has deleted the message or the chat, it is not added again.
let mut msg = Message::new(Viewtype::Text); let mut msg = Message::new(Viewtype::Text);
msg.text = Some(self.stock_str(DeviceMessagesHint).to_string()); msg.text = Some(self.stock_str(DeviceMessagesHint).await.to_string());
chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg))?; chat::add_device_msg(&self, Some("core-about-device-chat"), Some(&mut msg)).await?;
let image = include_bytes!("../assets/welcome-image.jpg"); let image = include_bytes!("../assets/welcome-image.jpg");
let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?; let blob = BlobObject::create(&self, "welcome-image.jpg".to_string(), image)?;
let mut msg = Message::new(Viewtype::Image); let mut msg = Message::new(Viewtype::Image);
msg.param.set(Param::File, blob.as_name()); 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); let mut msg = Message::new(Viewtype::Text);
msg.text = Some(self.stock_str(WelcomeMessage).to_string()); msg.text = Some(self.stock_str(WelcomeMessage).await.to_string());
chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg))?; chat::add_device_msg(&self, Some("core-welcome"), Some(&mut msg)).await?;
Ok(()) Ok(())
} }
} }
@@ -397,153 +409,178 @@ mod tests {
assert_eq!(StockMessage::NoMessages.fallback(), "No messages."); assert_eq!(StockMessage::NoMessages.fallback(), "No messages.");
} }
#[test] #[async_std::test]
fn test_set_stock_translation() { async fn test_set_stock_translation() {
let t = dummy_context(); let t = dummy_context().await;
t.ctx t.ctx
.set_stock_translation(StockMessage::NoMessages, "xyz".to_string()) .set_stock_translation(StockMessage::NoMessages, "xyz".to_string())
.await
.unwrap(); .unwrap();
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "xyz") assert_eq!(t.ctx.stock_str(StockMessage::NoMessages).await, "xyz")
} }
#[test] #[async_std::test]
fn test_set_stock_translation_wrong_replacements() { async fn test_set_stock_translation_wrong_replacements() {
let t = dummy_context(); let t = dummy_context().await;
assert!(t assert!(t
.ctx .ctx
.set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string()) .set_stock_translation(StockMessage::NoMessages, "xyz %1$s ".to_string())
.await
.is_err()); .is_err());
assert!(t assert!(t
.ctx .ctx
.set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string()) .set_stock_translation(StockMessage::NoMessages, "xyz %2$s ".to_string())
.await
.is_err()); .is_err());
} }
#[test] #[async_std::test]
fn test_stock_str() { async fn test_stock_str() {
let t = dummy_context(); let t = dummy_context().await;
assert_eq!(t.ctx.stock_str(StockMessage::NoMessages), "No messages."); assert_eq!(
t.ctx.stock_str(StockMessage::NoMessages).await,
"No messages."
);
} }
#[test] #[async_std::test]
fn test_stock_string_repl_str() { async fn test_stock_string_repl_str() {
let t = dummy_context(); let t = dummy_context().await;
// uses %1$s substitution // uses %1$s substitution
assert_eq!( assert_eq!(
t.ctx.stock_string_repl_str(StockMessage::Member, "42"), t.ctx
.stock_string_repl_str(StockMessage::Member, "42")
.await,
"42 member(s)" "42 member(s)"
); );
// We have no string using %1$d to test... // We have no string using %1$d to test...
} }
#[test] #[async_std::test]
fn test_stock_string_repl_int() { async fn test_stock_string_repl_int() {
let t = dummy_context(); let t = dummy_context().await;
assert_eq!( assert_eq!(
t.ctx.stock_string_repl_int(StockMessage::Member, 42), t.ctx.stock_string_repl_int(StockMessage::Member, 42).await,
"42 member(s)" "42 member(s)"
); );
} }
#[test] #[async_std::test]
fn test_stock_string_repl_str2() { async fn test_stock_string_repl_str2() {
let t = dummy_context(); let t = dummy_context().await;
assert_eq!( assert_eq!(
t.ctx 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" "Could not connect to foo: bar"
); );
} }
#[test] #[async_std::test]
fn test_stock_system_msg_simple() { async fn test_stock_system_msg_simple() {
let t = dummy_context(); let t = dummy_context().await;
assert_eq!( assert_eq!(
t.ctx t.ctx
.stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0), .stock_system_msg(StockMessage::MsgLocationEnabled, "", "", 0)
.await,
"Location streaming enabled." "Location streaming enabled."
) )
} }
#[test] #[async_std::test]
fn test_stock_system_msg_add_member_by_me() { async fn test_stock_system_msg_add_member_by_me() {
let t = dummy_context(); let t = dummy_context().await;
assert_eq!( assert_eq!(
t.ctx.stock_system_msg( t.ctx
StockMessage::MsgAddMember, .stock_system_msg(
"alice@example.com", StockMessage::MsgAddMember,
"", "alice@example.com",
DC_CONTACT_ID_SELF "",
), DC_CONTACT_ID_SELF
)
.await,
"Member alice@example.com added by me." "Member alice@example.com added by me."
) )
} }
#[test] #[async_std::test]
fn test_stock_system_msg_add_member_by_me_with_displayname() { async fn test_stock_system_msg_add_member_by_me_with_displayname() {
let t = dummy_context(); let t = dummy_context().await;
Contact::create(&t.ctx, "Alice", "alice@example.com").expect("failed to create contact"); Contact::create(&t.ctx, "Alice", "alice@example.com")
.await
.expect("failed to create contact");
assert_eq!( assert_eq!(
t.ctx.stock_system_msg( t.ctx
StockMessage::MsgAddMember, .stock_system_msg(
"alice@example.com", StockMessage::MsgAddMember,
"", "alice@example.com",
DC_CONTACT_ID_SELF "",
), DC_CONTACT_ID_SELF
)
.await,
"Member Alice (alice@example.com) added by me." "Member Alice (alice@example.com) added by me."
); );
} }
#[test] #[async_std::test]
fn test_stock_system_msg_add_member_by_other_with_displayname() { async fn test_stock_system_msg_add_member_by_other_with_displayname() {
let t = dummy_context(); let t = dummy_context().await;
let contact_id = { let contact_id = {
Contact::create(&t.ctx, "Alice", "alice@example.com") Contact::create(&t.ctx, "Alice", "alice@example.com")
.await
.expect("Failed to create contact Alice"); .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!( assert_eq!(
t.ctx.stock_system_msg( t.ctx
StockMessage::MsgAddMember, .stock_system_msg(
"alice@example.com", StockMessage::MsgAddMember,
"", "alice@example.com",
contact_id, "",
), contact_id,
)
.await,
"Member Alice (alice@example.com) added by Bob (bob@example.com)." "Member Alice (alice@example.com) added by Bob (bob@example.com)."
); );
} }
#[test] #[async_std::test]
fn test_stock_system_msg_grp_name() { async fn test_stock_system_msg_grp_name() {
let t = dummy_context(); let t = dummy_context().await;
assert_eq!( assert_eq!(
t.ctx.stock_system_msg( t.ctx
StockMessage::MsgGrpName, .stock_system_msg(
"Some chat", StockMessage::MsgGrpName,
"Other chat", "Some chat",
DC_CONTACT_ID_SELF "Other chat",
), DC_CONTACT_ID_SELF
)
.await,
"Group name changed from \"Some chat\" to \"Other chat\" by me." "Group name changed from \"Some chat\" to \"Other chat\" by me."
) )
} }
#[test] #[async_std::test]
fn test_stock_system_msg_grp_name_other() { async fn test_stock_system_msg_grp_name_other() {
let t = dummy_context(); let t = dummy_context().await;
let id = Contact::create(&t.ctx, "Alice", "alice@example.com") let id = Contact::create(&t.ctx, "Alice", "alice@example.com")
.await
.expect("failed to create contact"); .expect("failed to create contact");
assert_eq!( assert_eq!(
t.ctx 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)." "Group name changed from \"Some chat\" to \"Other chat\" by Alice (alice@example.com)."
) )
} }
#[async_std::test] #[async_std::test]
async fn test_update_device_chats() { async fn test_update_device_chats() {
let t = dummy_context(); let t = dummy_context().await;
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(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 2); assert_eq!(chats.len(), 2);
@@ -553,7 +590,7 @@ mod tests {
assert_eq!(chats.len(), 0); assert_eq!(chats.len(), 0);
// a subsequent call to update_device_chats() must not re-add manally deleted messages or chats // 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(); let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap();
assert_eq!(chats.len(), 0); assert_eq!(chats.len(), 0);
} }

View File

@@ -25,14 +25,14 @@ pub(crate) struct TestContext {
/// "db.sqlite" in the [TestContext.dir] directory. /// "db.sqlite" in the [TestContext.dir] directory.
/// ///
/// [Context]: crate::context::Context /// [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 dir = tempdir().unwrap();
let dbfile = dir.path().join("db.sqlite"); let dbfile = dir.path().join("db.sqlite");
let cb: Box<ContextCallback> = match callback { let cb: Box<ContextCallback> = match callback {
Some(cb) => cb, Some(cb) => cb,
None => Box::new(|_, _| ()), 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 } 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 /// The context will be opened and use the SQLite database as
/// specified in [test_context] but there is no callback hooked up, /// specified in [test_context] but there is no callback hooked up,
/// i.e. [Context::call_cb] will always return `0`. /// i.e. [Context::call_cb] will always return `0`.
pub(crate) fn dummy_context() -> TestContext { pub(crate) async fn dummy_context() -> TestContext {
test_context(None) test_context(None).await
} }
pub(crate) fn logging_cb(_ctx: &Context, evt: Event) { 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 .sql
.execute( .execute(
"INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);",
params![namespace, foreign_id, &token, time()], paramsv![namespace, foreign_id, token, time()],
) )
.await .await
.ok(); .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> { pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option<String> {
context context
.sql .sql
.query_get_value::<_, String>( .query_get_value::<String>(
context, context,
"SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;",
params![namespace, foreign_id], paramsv![namespace, foreign_id],
) )
.await .await
} }
@@ -65,7 +65,7 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> boo
.sql .sql
.exists( .exists(
"SELECT id FROM tokens WHERE namespc=? AND token=?;", "SELECT id FROM tokens WHERE namespc=? AND token=?;",
params![namespace, token], paramsv![namespace, token],
) )
.await .await
.unwrap_or_default() .unwrap_or_default()

View File

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