diff --git a/examples/simple.rs b/examples/simple.rs index 4686f2114..38b143fc5 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -49,7 +49,7 @@ async fn main() { let ctx1 = ctx.clone(); let t1 = async_std::task::spawn(async move { while *r1.read().await { - // perform_inbox_jobs(&ctx1).await; + perform_inbox_jobs(&ctx1).await; if *r1.read().await { perform_inbox_fetch(&ctx1).await; diff --git a/src/chat.rs b/src/chat.rs index a111cdb0a..e81589fcc 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -108,28 +108,36 @@ impl ChatId { self.0 == DC_CHAT_ID_ALLDONE_HINT } - pub fn set_selfavatar_timestamp(self, context: &Context, timestamp: i64) -> Result<(), Error> { - context.sql.execute( - "UPDATE contacts + pub async fn set_selfavatar_timestamp( + self, + context: &Context, + timestamp: i64, + ) -> Result<(), Error> { + context + .sql + .execute( + "UPDATE contacts SET selfavatar_sent=? WHERE id IN(SELECT contact_id FROM chats_contacts WHERE chat_id=?);", - params![timestamp, self], - )?; + params![timestamp, self], + ) + .await?; Ok(()) } - pub fn set_blocked(self, context: &Context, new_blocked: Blocked) -> bool { + pub async fn set_blocked(self, context: &Context, new_blocked: Blocked) -> bool { if self.is_special() { warn!(context, "ignoring setting of Block-status for {}", self); return false; } - sql::execute( - context, - &context.sql, - "UPDATE chats SET blocked=? WHERE id=?;", - params![new_blocked, self], - ) - .is_ok() + context + .sql + .execute( + "UPDATE chats SET blocked=? WHERE id=?;", + params![new_blocked, self], + ) + .await + .is_ok() } pub fn unblock(self, context: &Context) { @@ -137,7 +145,7 @@ impl ChatId { } /// Archives or unarchives a chat. - pub fn set_visibility( + pub async fn set_visibility( self, context: &Context, visibility: ChatVisibility, @@ -149,20 +157,22 @@ impl ChatId { ); if visibility == ChatVisibility::Archived { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", - params![MessageState::InNoticed, self, MessageState::InFresh], - )?; + context + .sql + .execute( + "UPDATE msgs SET state=? WHERE chat_id=? AND state=?;", + params![MessageState::InNoticed, self, MessageState::InFresh], + ) + .await?; } - sql::execute( - context, - &context.sql, - "UPDATE chats SET archived=? WHERE id=?;", - params![visibility, self], - )?; + context + .sql + .execute( + "UPDATE chats SET archived=? WHERE id=?;", + params![visibility, self], + ) + .await?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), @@ -174,13 +184,14 @@ impl ChatId { // note that unarchive() is not the same as set_visibility(Normal) - // eg. unarchive() does not modify pinned chats and does not send events. - pub fn unarchive(self, context: &Context) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE chats SET archived=0 WHERE id=? and archived=1", - params![self], - )?; + pub async fn unarchive(self, context: &Context) -> Result<(), Error> { + context + .sql + .execute( + "UPDATE chats SET archived=0 WHERE id=? and archived=1", + params![self], + ) + .await?; Ok(()) } @@ -193,34 +204,29 @@ impl ChatId { ); /* Up to 2017-11-02 deleting a group also implied leaving it, see above why we have changed this. */ - let _chat = Chat::load_from_db(context, self)?; - sql::execute( - context, - &context.sql, - "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", - params![self], - )?; + let _chat = Chat::load_from_db(context, self).await?; + context + .sql + .execute( + "DELETE FROM msgs_mdns WHERE msg_id IN (SELECT id FROM msgs WHERE chat_id=?);", + params![self], + ) + .await?; - sql::execute( - context, - &context.sql, - "DELETE FROM msgs WHERE chat_id=?;", - params![self], - )?; + context + .sql + .execute("DELETE FROM msgs WHERE chat_id=?;", params![self]) + .await?; - sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts WHERE chat_id=?;", - params![self], - )?; + context + .sql + .execute("DELETE FROM chats_contacts WHERE chat_id=?;", params![self]) + .await?; - sql::execute( - context, - &context.sql, - "DELETE FROM chats WHERE id=?;", - params![self], - )?; + context + .sql + .execute("DELETE FROM chats WHERE id=?;", params![self]) + .await?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), @@ -236,14 +242,14 @@ impl ChatId { /// Sets draft message. /// /// Passing `None` as message just deletes the draft - pub fn set_draft(self, context: &Context, msg: Option<&mut Message>) { + pub async fn set_draft(self, context: &Context, msg: Option<&mut Message>) { if self.is_special() { return; } let changed = match msg { - None => self.maybe_delete_draft(context), - Some(msg) => self.set_draft_raw(context, msg), + None => self.maybe_delete_draft(context).await, + Some(msg) => self.set_draft_raw(context, msg).await, }; if changed { @@ -255,28 +261,34 @@ impl ChatId { } // similar to as dc_set_draft() but does not emit an event - fn set_draft_raw(self, context: &Context, msg: &mut Message) -> bool { - let deleted = self.maybe_delete_draft(context); - let set = self.do_set_draft(context, msg).is_ok(); + async fn set_draft_raw(self, context: &Context, msg: &mut Message) -> bool { + let deleted = self.maybe_delete_draft(context).await; + let set = self.do_set_draft(context, msg).await.is_ok(); // Can't inline. Both functions above must be called, no shortcut! deleted || set } - fn get_draft_msg_id(self, context: &Context) -> Option { - context.sql.query_get_value::<_, MsgId>( - context, - "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![self, MessageState::OutDraft], - ) + async fn get_draft_msg_id(self, context: &Context) -> Option { + context + .sql + .query_get_value::<_, MsgId>( + context, + "SELECT id FROM msgs WHERE chat_id=? AND state=?;", + params![self, MessageState::OutDraft], + ) + .await } - pub fn get_draft(self, context: &Context) -> Result, Error> { + pub async fn get_draft(self, context: &Context) -> Result, Error> { if self.is_special() { return Ok(None); } - match self.get_draft_msg_id(context) { - Some(draft_msg_id) => Ok(Some(Message::load_from_db(context, draft_msg_id)?)), + match self.get_draft_msg_id(context).await { + Some(draft_msg_id) => { + let msg = Message::load_from_db(context, draft_msg_id).await?; + Ok(Some(msg)) + } None => Ok(None), } } @@ -284,8 +296,8 @@ impl ChatId { /// Delete draft message in specified chat, if there is one. /// /// Returns `true`, if message was deleted, `false` otherwise. - fn maybe_delete_draft(self, context: &Context) -> bool { - match self.get_draft_msg_id(context) { + async fn maybe_delete_draft(self, context: &Context) -> bool { + match self.get_draft_msg_id(context).await { Some(msg_id) => { Message::delete_from_db(context, msg_id); true @@ -297,7 +309,7 @@ impl ChatId { /// Set provided message as draft message for specified chat. /// /// Return true on success, false on database error. - fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<(), Error> { + async fn do_set_draft(self, context: &Context, msg: &mut Message) -> Result<(), Error> { match msg.viewtype { Viewtype::Unknown => bail!("Can not set draft of unknown type."), Viewtype::Text => match msg.text.as_ref() { @@ -316,27 +328,29 @@ impl ChatId { msg.param.set(Param::File, blob.as_name()); } } - sql::execute( - context, - &context.sql, - "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) + context + .sql + .execute( + "INSERT INTO msgs (chat_id, from_id, timestamp, type, state, txt, param, hidden) VALUES (?,?,?, ?,?,?,?,?);", - params![ - self, - DC_CONTACT_ID_SELF, - time(), - msg.viewtype, - MessageState::OutDraft, - msg.text.as_ref().map(String::as_str).unwrap_or(""), - msg.param.to_string(), - 1, - ], - )?; + params![ + self, + DC_CONTACT_ID_SELF, + time(), + msg.viewtype, + MessageState::OutDraft, + msg.text.as_ref().map(String::as_str).unwrap_or(""), + msg.param.to_string(), + 1, + ], + ) + .await?; + Ok(()) } /// Returns number of messages in a chat. - pub fn get_msg_cnt(self, context: &Context) -> usize { + pub async fn get_msg_cnt(self, context: &Context) -> usize { context .sql .query_get_value::<_, i32>( @@ -344,21 +358,23 @@ impl ChatId { "SELECT COUNT(*) FROM msgs WHERE chat_id=?;", params![self], ) + .await .unwrap_or_default() as usize } - pub fn get_fresh_msg_cnt(self, context: &Context) -> usize { + pub async fn get_fresh_msg_cnt(self, context: &Context) -> usize { context .sql .query_get_value::<_, i32>( context, "SELECT COUNT(*) - FROM msgs - WHERE state=10 + FROM msgs + WHERE state=10 AND hidden=0 AND chat_id=?;", params![self], ) + .await .unwrap_or_default() as usize } @@ -435,28 +451,31 @@ pub struct Chat { impl Chat { /// Loads chat from the database by its ID. - pub fn load_from_db(context: &Context, chat_id: ChatId) -> Result { - let res = context.sql.query_row( - "SELECT c.type, c.name, c.grpid, c.param, c.archived, + pub async fn load_from_db(context: &Context, chat_id: ChatId) -> Result { + let res = context + .sql + .query_row( + "SELECT c.type, c.name, c.grpid, c.param, c.archived, c.blocked, c.locations_send_until, c.muted_until FROM chats c WHERE c.id=?;", - params![chat_id], - |row| { - let c = Chat { - id: chat_id, - typ: row.get(0)?, - name: row.get::<_, String>(1)?, - grpid: row.get::<_, String>(2)?, - param: row.get::<_, String>(3)?.parse().unwrap_or_default(), - visibility: row.get(4)?, - blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), - is_sending_locations: row.get(6)?, - mute_duration: row.get(7)?, - }; - Ok(c) - }, - ); + params![chat_id], + |row| { + let c = Chat { + id: chat_id, + typ: row.get(0)?, + name: row.get::<_, String>(1)?, + grpid: row.get::<_, String>(2)?, + param: row.get::<_, String>(3)?.parse().unwrap_or_default(), + visibility: row.get(4)?, + blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), + is_sending_locations: row.get(6)?, + mute_duration: row.get(7)?, + }; + Ok(c) + }, + ) + .await; match res { Err(err @ crate::sql::Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => { @@ -474,16 +493,16 @@ impl Chat { chat.name = context.stock_str(StockMessage::DeadDrop).into(); } else if chat.id.is_archived_link() { let tempname = context.stock_str(StockMessage::ArchivedChats); - let cnt = dc_get_archived_cnt(context); + let cnt = dc_get_archived_cnt(context).await; chat.name = format!("{} ({})", tempname, cnt); } else if chat.id.is_starred() { chat.name = context.stock_str(StockMessage::StarredMsgs).into(); } else { if chat.typ == Chattype::Single { - let contacts = get_chat_contacts(context, chat.id); + let contacts = get_chat_contacts(context, chat.id).await; let mut chat_name = "Err [Name not found]".to_owned(); if let Some(contact_id) = contacts.first() { - if let Ok(contact) = Contact::get_by_id(context, *contact_id) { + if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { chat_name = contact.get_display_name().to_owned(); } } @@ -514,13 +533,14 @@ impl Chat { !self.id.is_special() && !self.is_device_talk() } - pub fn update_param(&mut self, context: &Context) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE chats SET param=? WHERE id=?", - params![self.param.to_string(), self.id], - )?; + pub async fn update_param(&mut self, context: &Context) -> Result<(), Error> { + context + .sql + .execute( + "UPDATE chats SET param=? WHERE id=?", + params![self.param.to_string(), self.id], + ) + .await?; Ok(()) } @@ -539,7 +559,7 @@ impl Chat { &self.name } - pub fn get_subtitle(&self, context: &Context) -> String { + pub async fn get_subtitle(&self, context: &Context) -> String { // returns either the address or the number of chat members if self.typ == Chattype::Single && self.param.exists(Param::Selftalk) { @@ -557,6 +577,7 @@ impl Chat { WHERE cc.chat_id=?;", params![self.id], ) + .await .unwrap_or_else(|| "Err".into()); } @@ -564,7 +585,7 @@ impl Chat { if self.id.is_deaddrop() { return context.stock_str(StockMessage::DeadDrop).into(); } - let cnt = get_chat_contact_cnt(context, self.id); + let cnt = get_chat_contact_cnt(context, self.id).await; return context.stock_string_repl_int(StockMessage::Member, cnt as i32); } @@ -583,22 +604,22 @@ impl Chat { ) } - fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> { + async fn get_parent_mime_headers(&self, context: &Context) -> Option<(String, String, String)> { let collect = |row: &rusqlite::Row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)); let params = params![self.id]; let sql = &context.sql; let query = Self::parent_query("rfc724_mid, mime_in_reply_to, mime_references"); - sql.query_row(&query, params, collect).ok() + sql.query_row(&query, params, collect).await.ok() } - fn parent_is_encrypted(&self, context: &Context) -> Result { + async fn parent_is_encrypted(&self, context: &Context) -> Result { let sql = &context.sql; let params = params![self.id]; let query = Self::parent_query("param"); - let packed: Option = sql.query_get_value_result(&query, params)?; + let packed: Option = sql.query_get_value_result(&query, params).await?; if let Some(ref packed) = packed { let param = packed.parse::()?; @@ -609,16 +630,16 @@ impl Chat { } } - pub fn get_profile_image(&self, context: &Context) -> Option { + pub async fn get_profile_image(&self, context: &Context) -> Option { if let Some(image_rel) = self.param.get(Param::ProfileImage) { if !image_rel.is_empty() { return Some(dc_get_abs_path(context, image_rel)); } } else if self.typ == Chattype::Single { - let contacts = get_chat_contacts(context, self.id); + let contacts = get_chat_contacts(context, self.id).await; if let Some(contact_id) = contacts.first() { - if let Ok(contact) = Contact::get_by_id(context, *contact_id) { - return contact.get_profile_image(context); + if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { + return contact.get_profile_image(context).await; } } } @@ -626,17 +647,17 @@ impl Chat { None } - pub fn get_gossiped_timestamp(&self, context: &Context) -> i64 { - get_gossiped_timestamp(context, self.id) + pub async fn get_gossiped_timestamp(&self, context: &Context) -> i64 { + get_gossiped_timestamp(context, self.id).await } - pub fn get_color(&self, context: &Context) -> u32 { + pub async fn get_color(&self, context: &Context) -> u32 { let mut color = 0; if self.typ == Chattype::Single { - let contacts = get_chat_contacts(context, self.id); + let contacts = get_chat_contacts(context, self.id).await; if let Some(contact_id) = contacts.first() { - if let Ok(contact) = Contact::get_by_id(context, *contact_id) { + if let Ok(contact) = Contact::get_by_id(context, *contact_id).await { color = contact.get_color(); } } @@ -651,8 +672,8 @@ impl Chat { /// /// This is somewhat experimental, even more so than the rest of /// deltachat, and the data returned is still subject to change. - pub fn get_info(&self, context: &Context) -> Result { - let draft = match self.id.get_draft(context)? { + pub async fn get_info(&self, context: &Context) -> Result { + let draft = match self.id.get_draft(context).await? { Some(message) => message.text.unwrap_or_else(String::new), _ => String::new(), }; @@ -662,11 +683,14 @@ impl Chat { name: self.name.clone(), archived: self.visibility == ChatVisibility::Archived, param: self.param.to_string(), - gossiped_timestamp: self.get_gossiped_timestamp(context), + gossiped_timestamp: self.get_gossiped_timestamp(context).await, is_sending_locations: self.is_sending_locations, - color: self.get_color(context), - profile_image: self.get_profile_image(context).unwrap_or_else(PathBuf::new), - subtitle: self.get_subtitle(context), + color: self.get_color(context).await, + profile_image: self + .get_profile_image(context) + .await + .unwrap_or_else(PathBuf::new), + subtitle: self.get_subtitle(context).await, draft, is_muted: self.is_muted(), }) @@ -702,7 +726,7 @@ impl Chat { } } - fn prepare_msg_raw( + async fn prepare_msg_raw( &mut self, context: &Context, msg: &mut Message, @@ -723,7 +747,7 @@ impl Chat { } if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup) - && !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF) + && !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await { emit_event!( context, @@ -732,7 +756,7 @@ impl Chat { bail!("Cannot set message; self not in group."); } - if let Some(from) = context.get_config(Config::ConfiguredAddr) { + if let Some(from) = context.get_config(Config::ConfiguredAddr).await { let new_rfc724_mid = { let grpid = match self.typ { Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()), @@ -742,11 +766,15 @@ impl Chat { }; if self.typ == Chattype::Single { - if let Some(id) = context.sql.query_get_value( - context, - "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", - params![self.id], - ) { + if let Some(id) = context + .sql + .query_get_value( + context, + "SELECT contact_id FROM chats_contacts WHERE chat_id=?;", + params![self.id], + ) + .await + { to_id = id; } else { error!( @@ -760,7 +788,7 @@ impl Chat { { msg.param.set_int(Param::AttachGroupImage, 1); self.param.remove(Param::Unpromoted); - self.update_param(context)?; + self.update_param(context).await?; } /* check if we want to encrypt this message. If yes and circumstances change @@ -768,46 +796,49 @@ impl Chat { we might not send the message out at all */ if msg.param.get_int(Param::ForcePlaintext).unwrap_or_default() == 0 { let mut can_encrypt = true; - let mut all_mutual = context.get_config_bool(Config::E2eeEnabled); + let mut all_mutual = context.get_config_bool(Config::E2eeEnabled).await; // take care that this statement returns NULL rows // if there is no peerstates for a chat member! // for DC_PARAM_SELFTALK this statement does not return any row - let res = context.sql.query_map( - "SELECT ps.prefer_encrypted, c.addr \ + let res = context + .sql + .query_map( + "SELECT ps.prefer_encrypted, c.addr \ FROM chats_contacts cc \ LEFT JOIN contacts c ON cc.contact_id=c.id \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ WHERE cc.chat_id=? AND cc.contact_id>9;", - params![self.id], - |row| { - let addr: String = row.get(1)?; + params![self.id], + |row| { + let addr: String = row.get(1)?; - if let Some(prefer_encrypted) = row.get::<_, Option>(0)? { - // the peerstate exist, so we have either public_key or gossip_key - // and can encrypt potentially - if prefer_encrypted != 1 { - info!( - context, - "[autocrypt] peerstate for {} is {}", - addr, - if prefer_encrypted == 0 { - "NOPREFERENCE" - } else { - "RESET" - }, - ); + if let Some(prefer_encrypted) = row.get::<_, Option>(0)? { + // the peerstate exist, so we have either public_key or gossip_key + // and can encrypt potentially + if prefer_encrypted != 1 { + info!( + context, + "[autocrypt] peerstate for {} is {}", + addr, + if prefer_encrypted == 0 { + "NOPREFERENCE" + } else { + "RESET" + }, + ); + all_mutual = false; + } + } else { + info!(context, "[autocrypt] no peerstate for {}", addr,); + can_encrypt = false; all_mutual = false; } - } else { - info!(context, "[autocrypt] no peerstate for {}", addr,); - can_encrypt = false; - all_mutual = false; - } - Ok(()) - }, - |rows| rows.collect::, _>>().map_err(Into::into), - ); + Ok(()) + }, + |rows| rows.collect::, _>>().map_err(Into::into), + ) + .await; match res { Ok(_) => {} Err(err) => { @@ -815,7 +846,7 @@ impl Chat { } } - if can_encrypt && (all_mutual || self.parent_is_encrypted(context)?) { + if can_encrypt && (all_mutual || self.parent_is_encrypted(context).await?) { msg.param.set_int(Param::GuaranteeE2ee, 1); } } @@ -830,7 +861,7 @@ impl Chat { // we do not set In-Reply-To/References in this case. if !self.is_self_talk() { if let Some((parent_rfc724_mid, parent_in_reply_to, parent_references)) = - self.get_parent_mime_headers(context) + self.get_parent_mime_headers(context).await { if !parent_rfc724_mid.is_empty() { new_in_reply_to = parent_rfc724_mid.clone(); @@ -860,38 +891,39 @@ impl Chat { // add independent location to database if msg.param.exists(Param::SetLatitude) - && sql::execute( - context, - &context.sql, - "INSERT INTO locations \ + && context + .sql + .execute( + "INSERT INTO locations \ (timestamp,from_id,chat_id, latitude,longitude,independent)\ VALUES (?,?,?, ?,?,1);", // 1=DC_CONTACT_ID_SELF - params![ - timestamp, - DC_CONTACT_ID_SELF, - self.id, - msg.param.get_float(Param::SetLatitude).unwrap_or_default(), - msg.param.get_float(Param::SetLongitude).unwrap_or_default(), - ], - ) - .is_ok() + params![ + timestamp, + DC_CONTACT_ID_SELF, + self.id, + msg.param.get_float(Param::SetLatitude).unwrap_or_default(), + msg.param.get_float(Param::SetLongitude).unwrap_or_default(), + ], + ) + .await + .is_ok() { - location_id = sql::get_rowid2( - context, - &context.sql, - "locations", - "timestamp", - timestamp, - "from_id", - DC_CONTACT_ID_SELF as i32, - ); + location_id = context + .sql + .get_rowid2( + context, + "locations", + "timestamp", + timestamp, + "from_id", + DC_CONTACT_ID_SELF as i32, + ) + .await?; } // add message to the database - if sql::execute( - context, - &context.sql, + if context.sql.execute( "INSERT INTO msgs (rfc724_mid, chat_id, from_id, to_id, timestamp, type, state, txt, param, hidden, mime_in_reply_to, mime_references, location_id) VALUES (?,?,?,?,?, ?,?,?,?,?, ?,?,?);", params![ new_rfc724_mid, @@ -908,14 +940,13 @@ impl Chat { new_references, location_id as i32, ] - ).is_ok() { - msg_id = sql::get_rowid( + ).await.is_ok() { + msg_id = context.sql.get_rowid( context, - &context.sql, "msgs", "rfc724_mid", new_rfc724_mid, - ); + ).await?; } else { error!( context, @@ -1054,9 +1085,9 @@ pub struct ChatInfo { /// # Returns /// /// The "created" chat ID is returned. -pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result { - let msg = Message::load_from_db(context, msg_id)?; - let chat = Chat::load_from_db(context, msg.chat_id)?; +pub async fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result { + let msg = Message::load_from_db(context, msg_id).await?; + let chat = Chat::load_from_db(context, msg.chat_id).await?; ensure!( !chat.id.is_special(), "Message can not belong to a special chat" @@ -1080,8 +1111,8 @@ pub fn create_by_msg_id(context: &Context, msg_id: MsgId) -> Result Result { - let chat_id = match lookup_by_contact_id(context, contact_id) { +pub async fn create_by_contact_id(context: &Context, contact_id: u32) -> Result { + let chat_id = match lookup_by_contact_id(context, contact_id).await { Ok((chat_id, chat_blocked)) => { if chat_blocked != Blocked::Not { // unblock chat (typically move it from the deaddrop to view @@ -1090,7 +1121,8 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result { - if !Contact::real_exists_by_id(context, contact_id) && contact_id != DC_CONTACT_ID_SELF + if !Contact::real_exists_by_id(context, contact_id).await + && contact_id != DC_CONTACT_ID_SELF { warn!( context, @@ -1099,7 +1131,7 @@ pub fn create_by_contact_id(context: &Context, contact_id: u32) -> Result Result Result<(), Error> { +pub(crate) async fn update_saved_messages_icon(context: &Context) -> Result<(), Error> { // if there is no saved-messages chat, there is nothing to update. this is no error. - if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF) { + if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_SELF).await { let icon = include_bytes!("../assets/icon-saved-messages.png"); let blob = BlobObject::create(context, "icon-saved-messages.png".to_string(), icon)?; let icon = blob.as_name().to_string(); - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; chat.param.set(Param::ProfileImage, icon); - chat.update_param(context)?; + chat.update_param(context).await?; } Ok(()) } -pub(crate) fn update_device_icon(context: &Context) -> Result<(), Error> { +pub(crate) async fn update_device_icon(context: &Context) -> Result<(), Error> { // if there is no device-chat, there is nothing to update. this is no error. - if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE) { + if let Ok((chat_id, _)) = lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE).await { let icon = include_bytes!("../assets/icon-device.png"); let blob = BlobObject::create(context, "icon-device.png".to_string(), icon)?; let icon = blob.as_name().to_string(); - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; chat.param.set(Param::ProfileImage, &icon); - chat.update_param(context)?; + chat.update_param(context).await?; - let mut contact = Contact::load_from_db(context, DC_CONTACT_ID_DEVICE)?; + let mut contact = Contact::load_from_db(context, DC_CONTACT_ID_DEVICE).await?; contact.param.set(Param::ProfileImage, icon); - contact.update_param(context)?; + contact.update_param(context).await?; } Ok(()) } -fn update_special_chat_name( +async fn update_special_chat_name( context: &Context, contact_id: u32, stock_id: StockMessage, ) -> Result<(), Error> { - if let Ok((chat_id, _)) = lookup_by_contact_id(context, contact_id) { + if let Ok((chat_id, _)) = lookup_by_contact_id(context, contact_id).await { let name: String = context.stock_str(stock_id).into(); // the `!= name` condition avoids unneeded writes - context.sql.execute( - "UPDATE chats SET name=? WHERE id=? AND name!=?;", - params![name, chat_id, name], - )?; + context + .sql + .execute( + "UPDATE chats SET name=? WHERE id=? AND name!=?;", + params![name, chat_id, name], + ) + .await?; } Ok(()) } -pub(crate) fn update_special_chat_names(context: &Context) -> Result<(), Error> { - update_special_chat_name(context, DC_CONTACT_ID_DEVICE, StockMessage::DeviceMessages)?; - update_special_chat_name(context, DC_CONTACT_ID_SELF, StockMessage::SavedMessages)?; +pub(crate) async fn update_special_chat_names(context: &Context) -> Result<(), Error> { + update_special_chat_name(context, DC_CONTACT_ID_DEVICE, StockMessage::DeviceMessages).await?; + update_special_chat_name(context, DC_CONTACT_ID_SELF, StockMessage::SavedMessages).await?; Ok(()) } -pub(crate) fn create_or_lookup_by_contact_id( +pub(crate) async fn create_or_lookup_by_contact_id( context: &Context, contact_id: u32, create_blocked: Blocked, ) -> Result<(ChatId, Blocked), Error> { - ensure!(context.sql.is_open(), "Database not available"); + ensure!(context.sql.is_open().await, "Database not available"); ensure!(contact_id > 0, "Invalid contact id requested"); - if let Ok((chat_id, chat_blocked)) = lookup_by_contact_id(context, contact_id) { + if let Ok((chat_id, chat_blocked)) = lookup_by_contact_id(context, contact_id).await { // Already exists, no need to create. return Ok((chat_id, chat_blocked)); } - let contact = Contact::load_from_db(context, contact_id)?; + let contact = Contact::load_from_db(context, contact_id).await?; let chat_name = contact.get_display_name(); context .sql - .start_stmt("create_or_lookup_by_contact_id transaction"); - context.sql.with_conn(|conn| { - let tx = conn.transaction()?; - tx.execute( - "INSERT INTO chats (type, name, param, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?)", - params![ - Chattype::Single, - chat_name, - match contact_id { - DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk - DC_CONTACT_ID_DEVICE => "D=1".to_string(), // D = Param::Devicetalk - _ => "".to_string(), - }, - create_blocked as u8, - time(), - ] - )?; - tx.execute( - "INSERT INTO chats_contacts (chat_id, contact_id) VALUES((SELECT last_insert_rowid()), ?)", - params![contact_id])?; - tx.commit()?; - Ok(()) - })?; + .with_conn(|mut conn| { + let conn2 = &mut conn; + let tx = conn2.transaction()?; + tx.execute( + "INSERT INTO chats (type, name, param, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?)", + params![ + Chattype::Single, + chat_name, + match contact_id { + DC_CONTACT_ID_SELF => "K=1".to_string(), // K = Param::Selftalk + DC_CONTACT_ID_DEVICE => "D=1".to_string(), // D = Param::Devicetalk + _ => "".to_string(), + }, + create_blocked as u8, + time(), + ], + )?; + + tx.execute( + "INSERT INTO chats_contacts (chat_id, contact_id) VALUES((SELECT last_insert_rowid()), ?)", + params![contact_id], + )?; + + tx.commit()?; + Ok(()) + }) + .await?; if contact_id == DC_CONTACT_ID_SELF { - update_saved_messages_icon(context)?; + update_saved_messages_icon(context).await?; } else if contact_id == DC_CONTACT_ID_DEVICE { - update_device_icon(context)?; + update_device_icon(context).await?; } - lookup_by_contact_id(context, contact_id) + lookup_by_contact_id(context, contact_id).await } -pub(crate) fn lookup_by_contact_id( +pub(crate) async fn lookup_by_contact_id( context: &Context, contact_id: u32, ) -> Result<(ChatId, Blocked), Error> { - ensure!(context.sql.is_open(), "Database not available"); + ensure!(context.sql.is_open().await, "Database not available"); context .sql @@ -1243,17 +1282,18 @@ pub(crate) fn lookup_by_contact_id( )) }, ) + .await .map_err(Into::into) } -pub fn get_by_contact_id(context: &Context, contact_id: u32) -> Result { - let (chat_id, blocked) = lookup_by_contact_id(context, contact_id)?; +pub async fn get_by_contact_id(context: &Context, contact_id: u32) -> Result { + let (chat_id, blocked) = lookup_by_contact_id(context, contact_id).await?; ensure_eq!(blocked, Blocked::Not, "Requested contact is blocked"); Ok(chat_id) } -pub fn prepare_msg<'a>( +pub async fn prepare_msg<'a>( context: &'a Context, chat_id: ChatId, msg: &mut Message, @@ -1264,7 +1304,7 @@ pub fn prepare_msg<'a>( ); msg.state = MessageState::OutPreparing; - let msg_id = prepare_msg_common(context, chat_id, msg)?; + let msg_id = prepare_msg_common(context, chat_id, msg).await?; context.call_cb(Event::MsgsChanged { chat_id: msg.chat_id, msg_id: msg.id, @@ -1329,16 +1369,16 @@ fn prepare_msg_blob(context: &Context, msg: &mut Message) -> Result<(), Error> { Ok(()) } -fn prepare_msg_common( +async fn prepare_msg_common( context: &Context, chat_id: ChatId, msg: &mut Message, ) -> Result { msg.id = MsgId::new_unset(); prepare_msg_blob(context, msg)?; - chat_id.unarchive(context)?; + chat_id.unarchive(context).await?; - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; ensure!(chat.can_send(), "cannot send to {}", chat_id); // The OutPreparing state is set by dc_prepare_msg() before it @@ -1349,16 +1389,20 @@ fn prepare_msg_common( msg.state = MessageState::OutPending; } - msg.id = chat.prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context))?; + msg.id = chat + .prepare_msg_raw(context, msg, dc_create_smeared_timestamp(context)) + .await?; msg.chat_id = chat_id; Ok(msg.id) } /// Returns whether a contact is in a chat or not. -pub fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { - /* this function works for group and for normal chats, however, it is more useful for group chats. - DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group chat (DC_CONTACT_ID_SELF is not added to normal chats) */ +pub async fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) -> bool { + // this function works for group and for normal chats, however, it is more useful + // for group chats. + // DC_CONTACT_ID_SELF may be used to check, if the user itself is in a group + // chat (DC_CONTACT_ID_SELF is not added to normal chats) context .sql @@ -1366,6 +1410,7 @@ pub fn is_contact_in_chat(context: &Context, chat_id: ChatId, contact_id: u32) - "SELECT contact_id FROM chats_contacts WHERE chat_id=? AND contact_id=?;", params![chat_id, contact_id as i32], ) + .await .unwrap_or_default() } @@ -1392,7 +1437,7 @@ pub async fn send_msg( .map_err(|_| InvalidMsgId) .map(MsgId::new) { - if let Ok(mut msg) = Message::load_from_db(context, msg_id) { + if let Ok(mut msg) = Message::load_from_db(context, msg_id).await { send_msg_inner(context, chat_id, &mut msg).await?; }; } @@ -1417,7 +1462,7 @@ async fn send_msg_inner( // the state to OutPending. if msg.state != MessageState::OutPreparing { // automatically prepare normal messages - prepare_msg_common(context, chat_id, msg)?; + prepare_msg_common(context, chat_id, msg).await?; } else { // update message state of separately prepared messages ensure!( @@ -1456,7 +1501,7 @@ pub async fn send_text_msg( send_msg(context, chat_id, &mut msg).await } -pub fn get_chat_msgs( +pub async fn get_chat_msgs( context: &Context, chat_id: ChatId, flags: u32, @@ -1488,10 +1533,12 @@ pub fn get_chat_msgs( Ok(ret) }; let success = if chat_id.is_deaddrop() { - let show_emails = - ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); - context.sql.query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await) + .unwrap_or_default(); + context + .sql + .query_map( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m LEFT JOIN chats ON m.chat_id=chats.id @@ -1504,13 +1551,16 @@ pub fn get_chat_msgs( AND contacts.blocked=0 AND m.msgrmsg>=? ORDER BY m.timestamp,m.id;", - params![if show_emails == ShowEmails::All { 0 } else { 1 }], - process_row, - process_rows, - ) + params![if show_emails == ShowEmails::All { 0 } else { 1 }], + process_row, + process_rows, + ) + .await } else if chat_id.is_starred() { - context.sql.query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + context + .sql + .query_map( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m LEFT JOIN contacts ct ON m.from_id=ct.id @@ -1518,21 +1568,25 @@ pub fn get_chat_msgs( AND m.hidden=0 AND ct.blocked=0 ORDER BY m.timestamp,m.id;", - params![], - process_row, - process_rows, - ) + params![], + process_row, + process_rows, + ) + .await } else { - context.sql.query_map( - "SELECT m.id AS id, m.timestamp AS timestamp + context + .sql + .query_map( + "SELECT m.id AS id, m.timestamp AS timestamp FROM msgs m WHERE m.chat_id=? AND m.hidden=0 ORDER BY m.timestamp, m.id;", - params![chat_id], - process_row, - process_rows, - ) + params![chat_id], + process_row, + process_rows, + ) + .await }; match success { Ok(ret) => ret, @@ -1543,23 +1597,28 @@ pub fn get_chat_msgs( } } -pub fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> { - if !context.sql.exists( - "SELECT id FROM msgs WHERE chat_id=? AND state=?;", - params![chat_id, MessageState::InFresh], - )? { +pub async fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> { + if !context + .sql + .exists( + "SELECT id FROM msgs WHERE chat_id=? AND state=?;", + params![chat_id, MessageState::InFresh], + ) + .await? + { return Ok(()); } - sql::execute( - context, - &context.sql, - "UPDATE msgs + context + .sql + .execute( + "UPDATE msgs SET state=13 WHERE chat_id=? AND state=10;", - params![chat_id], - )?; + params![chat_id], + ) + .await?; context.call_cb(Event::MsgsChanged { chat_id: ChatId::new(0), @@ -1569,24 +1628,29 @@ pub fn marknoticed_chat(context: &Context, chat_id: ChatId) -> Result<(), Error> Ok(()) } -pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { - if !context.sql.exists( - "SELECT id +pub async fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { + if !context + .sql + .exists( + "SELECT id FROM msgs WHERE state=10;", - params![], - )? { + params![], + ) + .await? + { return Ok(()); } - sql::execute( - context, - &context.sql, - "UPDATE msgs + context + .sql + .execute( + "UPDATE msgs SET state=13 WHERE state=10;", - params![], - )?; + params![], + ) + .await?; context.call_cb(Event::MsgsChanged { msg_id: MsgId::new(0), @@ -1596,7 +1660,7 @@ pub fn marknoticed_all_chats(context: &Context) -> Result<(), Error> { Ok(()) } -pub fn get_chat_media( +pub async fn get_chat_media( context: &Context, chat_id: ChatId, msg_type: Viewtype, @@ -1637,6 +1701,7 @@ pub fn get_chat_media( Ok(ret) }, ) + .await .unwrap_or_default() } @@ -1648,7 +1713,7 @@ pub enum Direction { Backward = -1, } -pub fn get_next_media( +pub async fn get_next_media( context: &Context, curr_msg_id: MsgId, direction: Direction, @@ -1658,7 +1723,7 @@ pub fn get_next_media( ) -> Option { let mut ret: Option = None; - if let Ok(msg) = Message::load_from_db(context, curr_msg_id) { + if let Ok(msg) = Message::load_from_db(context, curr_msg_id).await { let list: Vec = get_chat_media( context, msg.chat_id, @@ -1669,7 +1734,8 @@ pub fn get_next_media( }, msg_type2, msg_type3, - ); + ) + .await; for i in 0..list.len() { if curr_msg_id == list[i] { match direction { @@ -1691,7 +1757,7 @@ pub fn get_next_media( ret } -pub fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { +pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { /* Normal chats do not include SELF. Group chats do (as it may happen that one is deleted from a groupchat but the chats stays visible, moreover, this makes displaying lists easier) */ @@ -1715,10 +1781,11 @@ pub fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { |row| row.get::<_, u32>(0), |ids| ids.collect::, _>>().map_err(Into::into), ) + .await .unwrap_or_default() } -pub fn create_group_chat( +pub async fn create_group_chat( context: &Context, verified: VerifiedStatus, chat_name: impl AsRef, @@ -1728,9 +1795,7 @@ pub fn create_group_chat( let draft_txt = context.stock_string_repl_str(StockMessage::NewGroupDraft, &chat_name); let grpid = dc_create_id(); - sql::execute( - context, - &context.sql, + context.sql.execute( "INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);", params![ if verified != VerifiedStatus::Unverified { @@ -1742,12 +1807,15 @@ pub fn create_group_chat( grpid, time(), ], - )?; + ).await?; - let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid); + let row_id = context + .sql + .get_rowid(context, "chats", "grpid", grpid) + .await?; let chat_id = ChatId::new(row_id); if !chat_id.is_error() { - if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF) { + if add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await { let mut draft_msg = Message::new(Viewtype::Text); draft_msg.set_text(Some(draft_txt)); chat_id.set_draft_raw(context, &mut draft_msg); @@ -1762,36 +1830,38 @@ pub fn create_group_chat( Ok(chat_id) } -pub(crate) fn add_to_chat_contacts_table( +pub(crate) async fn add_to_chat_contacts_table( context: &Context, chat_id: ChatId, contact_id: u32, ) -> bool { // add a contact to a chat; the function does not check the type or if any of the record exist or are already // added to the chat! - sql::execute( - context, - &context.sql, - "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", - params![chat_id, contact_id as i32], - ) - .is_ok() + context + .sql + .execute( + "INSERT INTO chats_contacts (chat_id, contact_id) VALUES(?, ?)", + params![chat_id, contact_id as i32], + ) + .await + .is_ok() } -pub(crate) fn remove_from_chat_contacts_table( +pub(crate) async fn remove_from_chat_contacts_table( context: &Context, chat_id: ChatId, contact_id: u32, ) -> bool { // remove a contact from the chats_contact table unconditionally // the function does not check the type or if the record exist - sql::execute( - context, - &context.sql, - "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?", - params![chat_id, contact_id as i32], - ) - .is_ok() + context + .sql + .execute( + "DELETE FROM chats_contacts WHERE chat_id=? AND contact_id=?", + params![chat_id, contact_id as i32], + ) + .await + .is_ok() } /// Adds a contact to the chat. @@ -1812,25 +1882,25 @@ pub(crate) async fn add_contact_to_chat_ex( from_handshake: bool, ) -> Result { ensure!(!chat_id.is_special(), "can not add member to special chats"); - let contact = Contact::get_by_id(context, contact_id)?; + let contact = Contact::get_by_id(context, contact_id).await?; let mut msg = Message::default(); - reset_gossiped_timestamp(context, chat_id)?; + reset_gossiped_timestamp(context, chat_id).await?; /*this also makes sure, not contacts are added to special or normal chats*/ - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; ensure!( - real_group_exists(context, chat_id), + real_group_exists(context, chat_id).await, "{} is not a group where one can add members", chat_id ); ensure!( - Contact::real_exists_by_id(context, contact_id) || contact_id == DC_CONTACT_ID_SELF, + Contact::real_exists_by_id(context, contact_id).await || contact_id == DC_CONTACT_ID_SELF, "invalid contact_id {} for adding to group", contact_id ); - if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF as u32) { + if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF as u32).await { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ emit_event!( context, @@ -1840,10 +1910,11 @@ pub(crate) async fn add_contact_to_chat_ex( } if from_handshake && chat.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { chat.param.remove(Param::Unpromoted); - chat.update_param(context)?; + chat.update_param(context).await?; } let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if addr_cmp(contact.get_addr(), &self_addr) { // ourself is added using DC_CONTACT_ID_SELF, do not add this address explicitly. @@ -1855,7 +1926,7 @@ pub(crate) async fn add_contact_to_chat_ex( return Ok(false); } - if is_contact_in_chat(context, chat_id, contact_id) { + if is_contact_in_chat(context, chat_id, contact_id).await { if !from_handshake { return Ok(true); } @@ -1870,7 +1941,7 @@ pub(crate) async fn add_contact_to_chat_ex( ); return Ok(false); } - if !add_to_chat_contacts_table(context, chat_id, contact_id) { + if !add_to_chat_contacts_table(context, chat_id, contact_id).await { return Ok(false); } } @@ -1899,9 +1970,9 @@ pub(crate) async fn add_contact_to_chat_ex( Ok(true) } -fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { +async fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { // check if a group or a verified group exists under the given ID - if !context.sql.is_open() || chat_id.is_special() { + if !context.sql.is_open().await || chat_id.is_special() { return false; } @@ -1911,16 +1982,20 @@ fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", params![chat_id], ) + .await .unwrap_or_default() } -pub(crate) fn reset_gossiped_timestamp(context: &Context, chat_id: ChatId) -> Result<(), Error> { - set_gossiped_timestamp(context, chat_id, 0) +pub(crate) async fn reset_gossiped_timestamp( + context: &Context, + chat_id: ChatId, +) -> Result<(), Error> { + set_gossiped_timestamp(context, chat_id, 0).await } /// Get timestamp of the last gossip sent in the chat. /// Zero return value means that gossip was never sent. -pub fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { +pub async fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { context .sql .query_get_value::<_, i64>( @@ -1928,10 +2003,11 @@ pub fn get_gossiped_timestamp(context: &Context, chat_id: ChatId) -> i64 { "SELECT gossiped_timestamp FROM chats WHERE id=?;", params![chat_id], ) + .await .unwrap_or_default() } -pub(crate) fn set_gossiped_timestamp( +pub(crate) async fn set_gossiped_timestamp( context: &Context, chat_id: ChatId, timestamp: i64, @@ -1942,47 +2018,56 @@ pub(crate) fn set_gossiped_timestamp( "set gossiped_timestamp for chat #{} to {}.", chat_id, timestamp, ); - sql::execute( - context, - &context.sql, - "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", - params![timestamp, chat_id], - )?; + context + .sql + .execute( + "UPDATE chats SET gossiped_timestamp=? WHERE id=?;", + params![timestamp, chat_id], + ) + .await?; + Ok(()) } -pub(crate) fn shall_attach_selfavatar(context: &Context, chat_id: ChatId) -> Result { +pub(crate) async fn shall_attach_selfavatar( + context: &Context, + chat_id: ChatId, +) -> Result { // versions before 12/2019 already allowed to set selfavatar, however, it was never sent to others. // to avoid sending out previously set selfavatars unexpectedly we added this additional check. // it can be removed after some time. if !context .sql .get_raw_config_bool(context, "attach_selfavatar") + .await { return Ok(false); } let timestamp_some_days_ago = time() - DC_RESEND_USER_AVATAR_DAYS * 24 * 60 * 60; - let needs_attach = context.sql.query_map( - "SELECT c.selfavatar_sent + let needs_attach = context + .sql + .query_map( + "SELECT c.selfavatar_sent FROM chats_contacts cc LEFT JOIN contacts c ON c.id=cc.contact_id WHERE cc.chat_id=? AND cc.contact_id!=?;", - params![chat_id, DC_CONTACT_ID_SELF], - |row| Ok(row.get::<_, i64>(0)), - |rows| { - let mut needs_attach = false; - for row in rows { - if let Ok(selfavatar_sent) = row { - let selfavatar_sent = selfavatar_sent?; - if selfavatar_sent < timestamp_some_days_ago { - needs_attach = true; + params![chat_id, DC_CONTACT_ID_SELF], + |row| Ok(row.get::<_, i64>(0)), + |rows| { + let mut needs_attach = false; + for row in rows { + if let Ok(selfavatar_sent) = row { + let selfavatar_sent = selfavatar_sent?; + if selfavatar_sent < timestamp_some_days_ago { + needs_attach = true; + } } } - } - Ok(needs_attach) - }, - )?; + Ok(needs_attach) + }, + ) + .await?; Ok(needs_attach) } @@ -2028,16 +2113,21 @@ impl rusqlite::types::FromSql for MuteDuration { } } -pub fn set_muted(context: &Context, chat_id: ChatId, duration: MuteDuration) -> Result<(), Error> { +pub async fn set_muted( + context: &Context, + chat_id: ChatId, + duration: MuteDuration, +) -> Result<(), Error> { ensure!(!chat_id.is_special(), "Invalid chat ID"); - if real_group_exists(context, chat_id) - && sql::execute( - context, - &context.sql, - "UPDATE chats SET muted_until=? WHERE id=?;", - params![duration, chat_id], - ) - .is_ok() + if real_group_exists(context, chat_id).await + && context + .sql + .execute( + "UPDATE chats SET muted_until=? WHERE id=?;", + params![duration, chat_id], + ) + .await + .is_ok() { context.call_cb(Event::ChatModified(chat_id)); } else { @@ -2066,9 +2156,9 @@ pub async fn remove_contact_from_chat( /* we do not check if "contact_id" exists but just delete all records with the id from chats_contacts */ /* this allows to delete pending references to deleted contacts. Of course, this should _not_ happen. */ - if let Ok(chat) = Chat::load_from_db(context, chat_id) { - if real_group_exists(context, chat_id) { - if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { + if let Ok(chat) = Chat::load_from_db(context, chat_id).await { + if real_group_exists(context, chat_id).await { + if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, Event::ErrorSelfNotInGroup( @@ -2077,11 +2167,11 @@ pub async fn remove_contact_from_chat( ); } else { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if let Ok(contact) = Contact::get_by_id(context, contact_id).await { if chat.is_promoted() { msg.viewtype = Viewtype::Text; if contact.id == DC_CONTACT_ID_SELF { - set_group_explicitly_left(context, chat.grpid)?; + set_group_explicitly_left(context, chat.grpid).await?; msg.text = Some(context.stock_system_msg( StockMessage::MsgGroupLeft, "", @@ -2105,7 +2195,7 @@ pub async fn remove_contact_from_chat( }); } } - if remove_from_chat_contacts_table(context, chat_id, contact_id) { + if remove_from_chat_contacts_table(context, chat_id, contact_id).await { context.call_cb(Event::ChatModified(chat_id)); success = true; } @@ -2120,20 +2210,21 @@ pub async fn remove_contact_from_chat( Ok(()) } -fn set_group_explicitly_left(context: &Context, grpid: impl AsRef) -> Result<(), Error> { - if !is_group_explicitly_left(context, grpid.as_ref())? { - sql::execute( - context, - &context.sql, - "INSERT INTO leftgrps (grpid) VALUES(?);", - params![grpid.as_ref()], - )?; +async fn set_group_explicitly_left(context: &Context, grpid: impl AsRef) -> Result<(), Error> { + if !is_group_explicitly_left(context, grpid.as_ref()).await? { + context + .sql + .execute( + "INSERT INTO leftgrps (grpid) VALUES(?);", + params![grpid.as_ref()], + ) + .await?; } Ok(()) } -pub(crate) fn is_group_explicitly_left( +pub(crate) async fn is_group_explicitly_left( context: &Context, grpid: impl AsRef, ) -> Result { @@ -2143,6 +2234,7 @@ pub(crate) fn is_group_explicitly_left( "SELECT id FROM leftgrps WHERE grpid=?;", params![grpid.as_ref()], ) + .await .map_err(Into::into) } @@ -2157,26 +2249,27 @@ pub async fn set_chat_name( ensure!(!new_name.as_ref().is_empty(), "Invalid name"); ensure!(!chat_id.is_special(), "Invalid chat ID"); - let chat = Chat::load_from_db(context, chat_id)?; + let chat = Chat::load_from_db(context, chat_id).await?; let mut msg = Message::default(); - if real_group_exists(context, chat_id) { + if real_group_exists(context, chat_id).await { if chat.name == new_name.as_ref() { success = true; - } else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { + } else if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, Event::ErrorSelfNotInGroup("Cannot set chat name; self not in group".into()) ); } else { /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if sql::execute( - context, - &context.sql, - "UPDATE chats SET name=? WHERE id=?;", - params![new_name.as_ref(), chat_id], - ) - .is_ok() + if context + .sql + .execute( + "UPDATE chats SET name=? WHERE id=?;", + params![new_name.as_ref(), chat_id], + ) + .await + .is_ok() { if chat.is_promoted() { msg.viewtype = Viewtype::Text; @@ -2220,13 +2313,13 @@ pub async fn set_chat_profile_image( new_image: impl AsRef, // XXX use PathBuf ) -> Result<(), Error> { ensure!(!chat_id.is_special(), "Invalid chat ID"); - let mut chat = Chat::load_from_db(context, chat_id)?; + let mut chat = Chat::load_from_db(context, chat_id).await?; ensure!( - real_group_exists(context, chat_id), + real_group_exists(context, chat_id).await, "Failed to set profile image; group does not exist" ); /* we should respect this - whatever we send to the group, it gets discarded anyway! */ - if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { + if !is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { emit_event!( context, Event::ErrorSelfNotInGroup("Cannot set chat profile image; self not in group.".into()) @@ -2264,7 +2357,7 @@ pub async fn set_chat_profile_image( DC_CONTACT_ID_SELF, )); } - chat.update_param(context)?; + chat.update_param(context).await?; if chat.is_promoted() { msg.id = send_msg(context, chat_id, &mut msg).await?; emit_event!( @@ -2291,23 +2384,26 @@ pub async fn forward_msgs( let mut created_msgs: Vec = Vec::new(); let mut curr_timestamp: i64; - chat_id.unarchive(context)?; - if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { + chat_id.unarchive(context).await?; + if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { ensure!(chat.can_send(), "cannot send to {}", chat_id); curr_timestamp = dc_create_smeared_timestamps(context, msg_ids.len()); - let ids = context.sql.query_map( - format!( - "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", - msg_ids.iter().map(|_| "?").join(",") - ), - msg_ids, - |row| row.get::<_, MsgId>(0), - |ids| ids.collect::, _>>().map_err(Into::into), - )?; + let ids = context + .sql + .query_map( + format!( + "SELECT id FROM msgs WHERE id IN({}) ORDER BY timestamp,id", + msg_ids.iter().map(|_| "?").join(",") + ), + msg_ids, + |row| row.get::<_, MsgId>(0), + |ids| ids.collect::, _>>().map_err(Into::into), + ) + .await?; for id in ids { let src_msg_id: MsgId = id; - let msg = Message::load_from_db(context, src_msg_id); + let msg = Message::load_from_db(context, src_msg_id).await; if msg.is_err() { break; } @@ -2327,7 +2423,7 @@ pub async fn forward_msgs( if msg.state == MessageState::OutPreparing { let fresh9 = curr_timestamp; curr_timestamp += 1; - new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh9)?; + new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh9).await?; let save_param = msg.param.clone(); msg.param = original_param; msg.id = src_msg_id; @@ -2346,7 +2442,7 @@ pub async fn forward_msgs( msg.state = MessageState::OutPending; let fresh10 = curr_timestamp; curr_timestamp += 1; - new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10)?; + new_msg_id = chat.prepare_msg_raw(context, &mut msg, fresh10).await?; job::send_msg(context, new_msg_id).await?; } created_chats.push(chat_id); @@ -2362,7 +2458,7 @@ pub async fn forward_msgs( Ok(()) } -pub(crate) fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize { +pub(crate) async fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize { context .sql .query_get_value::<_, isize>( @@ -2370,11 +2466,12 @@ pub(crate) fn get_chat_contact_cnt(context: &Context, chat_id: ChatId) -> usize "SELECT COUNT(*) FROM chats_contacts WHERE chat_id=?;", params![chat_id], ) + .await .unwrap_or_default() as usize } -pub(crate) fn get_chat_cnt(context: &Context) -> usize { - if context.sql.is_open() { +pub(crate) async fn get_chat_cnt(context: &Context) -> usize { + if context.sql.is_open().await { /* no database, no chats - this is no error (needed eg. for information) */ context .sql @@ -2383,33 +2480,37 @@ pub(crate) fn get_chat_cnt(context: &Context) -> usize { "SELECT COUNT(*) FROM chats WHERE id>9 AND blocked=0;", params![], ) + .await .unwrap_or_default() as usize } else { 0 } } -pub(crate) fn get_chat_id_by_grpid( +pub(crate) async fn get_chat_id_by_grpid( context: &Context, grpid: impl AsRef, ) -> Result<(ChatId, bool, Blocked), sql::Error> { - context.sql.query_row( - "SELECT id, blocked, type FROM chats WHERE grpid=?;", - params![grpid.as_ref()], - |row| { - let chat_id = row.get::<_, ChatId>(0)?; + context + .sql + .query_row( + "SELECT id, blocked, type FROM chats WHERE grpid=?;", + params![grpid.as_ref()], + |row| { + let chat_id = row.get::<_, ChatId>(0)?; - let b = row.get::<_, Option>(1)?.unwrap_or_default(); - let v = row.get::<_, Option>(2)?.unwrap_or_default(); - Ok((chat_id, v == Chattype::VerifiedGroup, b)) - }, - ) + let b = row.get::<_, Option>(1)?.unwrap_or_default(); + let v = row.get::<_, Option>(2)?.unwrap_or_default(); + Ok((chat_id, v == Chattype::VerifiedGroup, b)) + }, + ) + .await } /// Adds a message to device chat. /// /// Optional `label` can be provided to ensure that message is added only once. -pub fn add_device_msg( +pub async fn add_device_msg( context: &Context, label: Option<&str>, msg: Option<&mut Message>, @@ -2422,19 +2523,21 @@ pub fn add_device_msg( let mut msg_id = MsgId::new_unset(); if let Some(label) = label { - if was_device_msg_ever_added(context, label)? { + if was_device_msg_ever_added(context, label).await? { info!(context, "device-message {} already added", label); return Ok(msg_id); } } if let Some(msg) = msg { - chat_id = create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not)?.0; + chat_id = create_or_lookup_by_contact_id(context, DC_CONTACT_ID_DEVICE, Blocked::Not) + .await? + .0; let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); msg.try_calc_and_set_dimensions(context).ok(); prepare_msg_blob(context, msg)?; - chat_id.unarchive(context)?; + chat_id.unarchive(context).await?; context.sql.execute( "INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt,param,rfc724_mid) \ @@ -2450,17 +2553,23 @@ pub fn add_device_msg( msg.param.to_string(), rfc724_mid, ], - )?; + ).await?; - let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid); + let row_id = context + .sql + .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) + .await?; msg_id = MsgId::new(row_id); } if let Some(label) = label { - context.sql.execute( - "INSERT INTO devmsglabels (label) VALUES (?);", - params![label], - )?; + context + .sql + .execute( + "INSERT INTO devmsglabels (label) VALUES (?);", + params![label], + ) + .await?; } if !msg_id.is_unset() { @@ -2470,13 +2579,17 @@ pub fn add_device_msg( Ok(msg_id) } -pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result { +pub async fn was_device_msg_ever_added(context: &Context, label: &str) -> Result { ensure!(!label.is_empty(), "empty label"); - if let Ok(()) = context.sql.query_row( - "SELECT label FROM devmsglabels WHERE label=?", - params![label], - |_| Ok(()), - ) { + if let Ok(()) = context + .sql + .query_row( + "SELECT label FROM devmsglabels WHERE label=?", + params![label], + |_| Ok(()), + ) + .await + { return Ok(true); } @@ -2488,21 +2601,25 @@ pub fn was_device_msg_ever_added(context: &Context, label: &str) -> Result Result<(), Error> { - context.sql.execute( - "DELETE FROM msgs WHERE from_id=?;", - params![DC_CONTACT_ID_DEVICE], - )?; +pub(crate) async fn delete_and_reset_all_device_msgs(context: &Context) -> Result<(), Error> { context .sql - .execute("DELETE FROM devmsglabels;", params![])?; + .execute( + "DELETE FROM msgs WHERE from_id=?;", + params![DC_CONTACT_ID_DEVICE], + ) + .await?; + context + .sql + .execute("DELETE FROM devmsglabels;", params![]) + .await?; Ok(()) } /// Adds an informational message to chat. /// /// For example, it can be a message showing that a member was added to a group. -pub(crate) fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { +pub(crate) async fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef) { let rfc724_mid = dc_create_outgoing_rfc724_mid(None, "@device"); if context.sql.execute( @@ -2517,11 +2634,15 @@ pub(crate) fn add_info_msg(context: &Context, chat_id: ChatId, text: impl AsRef< text.as_ref(), rfc724_mid, ] - ).is_err() { + ).await.is_err() { return; } - let row_id = sql::get_rowid(context, &context.sql, "msgs", "rfc724_mid", &rfc724_mid); + let row_id = context + .sql + .get_rowid(context, "msgs", "rfc724_mid", &rfc724_mid) + .await + .unwrap_or_default(); context.call_cb(Event::MsgsChanged { chat_id, msg_id: MsgId::new(row_id), @@ -2535,13 +2656,15 @@ mod tests { use crate::contact::Contact; use crate::test_utils::*; - #[test] - fn test_chat_info() { + #[async_std::test] + async fn test_chat_info() { let t = dummy_context(); - let bob = Contact::create(&t.ctx, "bob", "bob@example.com").unwrap(); - let chat_id = create_by_contact_id(&t.ctx, bob).unwrap(); - let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); - let info = chat.get_info(&t.ctx).unwrap(); + let bob = Contact::create(&t.ctx, "bob", "bob@example.com") + .await + .unwrap(); + let chat_id = create_by_contact_id(&t.ctx, bob).await.unwrap(); + let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); + let info = chat.get_info(&t.ctx).await.unwrap(); // Ensure we can serialize this. println!("{}", serde_json::to_string_pretty(&info).unwrap()); @@ -2568,40 +2691,45 @@ mod tests { assert_eq!(info, loaded); } - #[test] - fn test_get_draft_no_draft() { + #[async_std::test] + async fn test_get_draft_no_draft() { let t = dummy_context(); - let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); - let draft = chat_id.get_draft(&t.ctx).unwrap(); + let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); + let draft = chat_id.get_draft(&t.ctx).await.unwrap(); assert!(draft.is_none()); } - #[test] - fn test_get_draft_special_chat_id() { + #[async_std::test] + async fn test_get_draft_special_chat_id() { let t = dummy_context(); let draft = ChatId::new(DC_CHAT_ID_LAST_SPECIAL) .get_draft(&t.ctx) + .await .unwrap(); assert!(draft.is_none()); } - #[test] - fn test_get_draft_no_chat() { + #[async_std::test] + async fn test_get_draft_no_chat() { // This is a weird case, maybe this should be an error but we // do not get this info from the database currently. let t = dummy_context(); - let draft = ChatId::new(42).get_draft(&t.ctx).unwrap(); + let draft = ChatId::new(42).get_draft(&t.ctx).await.unwrap(); assert!(draft.is_none()); } - #[test] - fn test_get_draft() { + #[async_std::test] + async fn test_get_draft() { let t = dummy_context(); - let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("hello".to_string())); chat_id.set_draft(&t.ctx, Some(&mut msg)); - let draft = chat_id.get_draft(&t.ctx).unwrap().unwrap(); + let draft = chat_id.get_draft(&t.ctx).await.unwrap().unwrap(); let msg_text = msg.get_text(); let draft_text = draft.get_text(); assert_eq!(msg_text, draft_text); @@ -2611,33 +2739,39 @@ mod tests { async fn test_add_contact_to_chat_ex_add_self() { // Adding self to a contact should succeed, even though it's pointless. let t = test_context(Some(Box::new(logging_cb))); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false) .await .unwrap(); assert_eq!(added, false); } - #[test] - fn test_self_talk() { + #[async_std::test] + async fn test_self_talk() { let t = dummy_context(); - let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); assert_eq!(DC_CONTACT_ID_SELF, 1); assert!(!chat_id.is_special()); - let chat = Chat::load_from_db(&t.ctx, chat_id).unwrap(); + let chat = Chat::load_from_db(&t.ctx, chat_id).await.unwrap(); assert_eq!(chat.id, chat_id); assert!(chat.is_self_talk()); assert!(chat.visibility == ChatVisibility::Normal); assert!(!chat.is_device_talk()); assert!(chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::SavedMessages)); - assert!(chat.get_profile_image(&t.ctx).is_some()); + assert!(chat.get_profile_image(&t.ctx).await.is_some()); } - #[test] - fn test_deaddrop_chat() { + #[async_std::test] + async fn test_deaddrop_chat() { let t = dummy_context(); - let chat = Chat::load_from_db(&t.ctx, ChatId::new(DC_CHAT_ID_DEADDROP)).unwrap(); + let chat = Chat::load_from_db(&t.ctx, ChatId::new(DC_CHAT_ID_DEADDROP)) + .await + .unwrap(); assert_eq!(DC_CHAT_ID_DEADDROP, 1); assert!(chat.id.is_deaddrop()); assert!(!chat.is_self_talk()); @@ -2647,24 +2781,24 @@ mod tests { assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeadDrop)); } - #[test] - fn test_add_device_msg_unlabelled() { + #[async_std::test] + async fn test_add_device_msg_unlabelled() { let t = test_context(Some(Box::new(logging_cb))); // add two device-messages let mut msg1 = Message::new(Viewtype::Text); msg1.text = Some("first message".to_string()); - let msg1_id = add_device_msg(&t.ctx, None, Some(&mut msg1)); + let msg1_id = add_device_msg(&t.ctx, None, Some(&mut msg1)).await; assert!(msg1_id.is_ok()); let mut msg2 = Message::new(Viewtype::Text); msg2.text = Some("second message".to_string()); - let msg2_id = add_device_msg(&t.ctx, None, Some(&mut msg2)); + let msg2_id = add_device_msg(&t.ctx, None, Some(&mut msg2)).await; assert!(msg2_id.is_ok()); assert_ne!(msg1_id.as_ref().unwrap(), msg2_id.as_ref().unwrap()); // check added messages - let msg1 = message::Message::load_from_db(&t.ctx, msg1_id.unwrap()); + let msg1 = message::Message::load_from_db(&t.ctx, msg1_id.unwrap()).await; assert!(msg1.is_ok()); let msg1 = msg1.unwrap(); assert_eq!(msg1.text.as_ref().unwrap(), "first message"); @@ -2673,13 +2807,13 @@ mod tests { assert!(!msg1.is_info()); assert!(!msg1.is_setupmessage()); - let msg2 = message::Message::load_from_db(&t.ctx, msg2_id.unwrap()); + let msg2 = message::Message::load_from_db(&t.ctx, msg2_id.unwrap()).await; assert!(msg2.is_ok()); let msg2 = msg2.unwrap(); assert_eq!(msg2.text.as_ref().unwrap(), "second message"); // check device chat - assert_eq!(msg2.chat_id.get_msg_cnt(&t.ctx), 2); + assert_eq!(msg2.chat_id.get_msg_cnt(&t.ctx).await, 2); } #[async_std::test] @@ -2689,18 +2823,18 @@ mod tests { // add two device-messages with the same label (second attempt is not added) let mut msg1 = Message::new(Viewtype::Text); msg1.text = Some("first message".to_string()); - let msg1_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg1)); + let msg1_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg1)).await; assert!(msg1_id.is_ok()); assert!(!msg1_id.as_ref().unwrap().is_unset()); let mut msg2 = Message::new(Viewtype::Text); msg2.text = Some("second message".to_string()); - let msg2_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)); + let msg2_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)).await; assert!(msg2_id.is_ok()); assert!(msg2_id.as_ref().unwrap().is_unset()); // check added message - let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()); + let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()).await; assert!(msg1.is_ok()); let msg1 = msg1.unwrap(); assert_eq!(msg1_id.as_ref().unwrap(), &msg1.id); @@ -2712,9 +2846,9 @@ mod tests { // check device chat let chat_id = msg1.chat_id; - assert_eq!(chat_id.get_msg_cnt(&t.ctx), 1); + assert_eq!(chat_id.get_msg_cnt(&t.ctx).await, 1); assert!(!chat_id.is_special()); - let chat = Chat::load_from_db(&t.ctx, chat_id); + let chat = Chat::load_from_db(&t.ctx, chat_id).await; assert!(chat.is_ok()); let chat = chat.unwrap(); assert_eq!(chat.get_type(), Chattype::Single); @@ -2722,51 +2856,59 @@ mod tests { assert!(!chat.is_self_talk()); assert!(!chat.can_send()); assert_eq!(chat.name, t.ctx.stock_str(StockMessage::DeviceMessages)); - assert!(chat.get_profile_image(&t.ctx).is_some()); + assert!(chat.get_profile_image(&t.ctx).await.is_some()); // delete device message, make sure it is not added again message::delete_msgs(&t.ctx, &[*msg1_id.as_ref().unwrap()]).await; - let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()); + let msg1 = message::Message::load_from_db(&t.ctx, *msg1_id.as_ref().unwrap()).await; assert!(msg1.is_err() || msg1.unwrap().chat_id.is_trash()); - let msg3_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)); + let msg3_id = add_device_msg(&t.ctx, Some("any-label"), Some(&mut msg2)).await; assert!(msg3_id.is_ok()); assert!(msg2_id.as_ref().unwrap().is_unset()); } - #[test] - fn test_add_device_msg_label_only() { + #[async_std::test] + async fn test_add_device_msg_label_only() { let t = test_context(Some(Box::new(logging_cb))); - let res = add_device_msg(&t.ctx, Some(""), None); + let res = add_device_msg(&t.ctx, Some(""), None).await; assert!(res.is_err()); - let res = add_device_msg(&t.ctx, Some("some-label"), None); + let res = add_device_msg(&t.ctx, Some("some-label"), None).await; assert!(res.is_ok()); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - let msg_id = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)); + let msg_id = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).await; assert!(msg_id.is_ok()); assert!(msg_id.as_ref().unwrap().is_unset()); - let msg_id = add_device_msg(&t.ctx, Some("unused-label"), Some(&mut msg)); + let msg_id = add_device_msg(&t.ctx, Some("unused-label"), Some(&mut msg)).await; assert!(msg_id.is_ok()); assert!(!msg_id.as_ref().unwrap().is_unset()); } - #[test] - fn test_was_device_msg_ever_added() { + #[async_std::test] + async fn test_was_device_msg_ever_added() { let t = test_context(Some(Box::new(logging_cb))); - add_device_msg(&t.ctx, Some("some-label"), None).ok(); - assert!(was_device_msg_ever_added(&t.ctx, "some-label").unwrap()); + add_device_msg(&t.ctx, Some("some-label"), None).await.ok(); + assert!(was_device_msg_ever_added(&t.ctx, "some-label") + .await + .unwrap()); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - add_device_msg(&t.ctx, Some("another-label"), Some(&mut msg)).ok(); - assert!(was_device_msg_ever_added(&t.ctx, "another-label").unwrap()); + add_device_msg(&t.ctx, Some("another-label"), Some(&mut msg)) + .await + .ok(); + assert!(was_device_msg_ever_added(&t.ctx, "another-label") + .await + .unwrap()); - assert!(!was_device_msg_ever_added(&t.ctx, "unused-label").unwrap()); + assert!(!was_device_msg_ever_added(&t.ctx, "unused-label") + .await + .unwrap()); - assert!(was_device_msg_ever_added(&t.ctx, "").is_err()); + assert!(was_device_msg_ever_added(&t.ctx, "").await.is_err()); } #[async_std::test] @@ -2775,14 +2917,18 @@ mod tests { let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .ok(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 1); // after the device-chat and all messages are deleted, a re-adding should do nothing chats.get_chat_id(0).delete(&t.ctx).await.ok(); - add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).ok(); - assert_eq!(chatlist_len(&t.ctx, 0), 0) + add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .ok(); + assert_eq!(chatlist_len(&t.ctx, 0).await, 0) } #[async_std::test] @@ -2790,136 +2936,165 @@ mod tests { let t = test_context(Some(Box::new(logging_cb))); t.ctx.update_device_chats().unwrap(); let (device_chat_id, _) = - create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not).unwrap(); + create_or_lookup_by_contact_id(&t.ctx, DC_CONTACT_ID_DEVICE, Blocked::Not) + .await + .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); assert!(send_msg(&t.ctx, device_chat_id, &mut msg).await.is_err()); - assert!(prepare_msg(&t.ctx, device_chat_id, &mut msg).is_err()); + assert!(prepare_msg(&t.ctx, device_chat_id, &mut msg).await.is_err()); - let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); assert!(forward_msgs(&t.ctx, &[msg_id], device_chat_id) .await .is_err()); } - #[test] - fn test_delete_and_reset_all_device_msgs() { + #[async_std::test] + async fn test_delete_and_reset_all_device_msgs() { let t = test_context(Some(Box::new(logging_cb))); let mut msg = Message::new(Viewtype::Text); msg.text = Some("message text".to_string()); - let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap(); + let msg_id1 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .unwrap(); // adding a device message with the same label won't be executed again ... - assert!(was_device_msg_ever_added(&t.ctx, "some-label").unwrap()); - let msg_id2 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap(); + assert!(was_device_msg_ever_added(&t.ctx, "some-label") + .await + .unwrap()); + let msg_id2 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .unwrap(); assert!(msg_id2.is_unset()); // ... unless everything is deleted and resetted - as needed eg. on device switch - delete_and_reset_all_device_msgs(&t.ctx).unwrap(); - assert!(!was_device_msg_ever_added(&t.ctx, "some-label").unwrap()); - let msg_id3 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)).unwrap(); + delete_and_reset_all_device_msgs(&t.ctx).await.unwrap(); + assert!(!was_device_msg_ever_added(&t.ctx, "some-label") + .await + .unwrap()); + let msg_id3 = add_device_msg(&t.ctx, Some("some-label"), Some(&mut msg)) + .await + .unwrap(); assert_ne!(msg_id1, msg_id3); } - fn chatlist_len(ctx: &Context, listflags: usize) -> usize { + async fn chatlist_len(ctx: &Context, listflags: usize) -> usize { Chatlist::try_load(ctx, listflags, None, None) + .await .unwrap() .len() } - #[test] - fn test_archive() { + #[async_std::test] + async fn test_archive() { // create two chats let t = dummy_context(); let mut msg = Message::new(Viewtype::Text); msg.text = Some("foo".to_string()); - let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id) + .await .unwrap() .chat_id; - let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); assert!(!chat_id1.is_special()); assert!(!chat_id2.is_special()); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 2); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 2); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 0); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 2); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 2); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 0); assert_eq!(DC_GCL_ARCHIVED_ONLY, 0x01); assert_eq!(DC_GCL_NO_SPECIALS, 0x02); // archive first chat assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) + .await .unwrap() .get_visibility() == ChatVisibility::Normal ); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 2); // including DC_CHAT_ID_ARCHIVED_LINK now - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 2); // including DC_CHAT_ID_ARCHIVED_LINK now + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 1); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 1); // archive second chat assert!(chat_id2 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 1); // only DC_CHAT_ID_ARCHIVED_LINK now - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 0); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 2); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 1); // only DC_CHAT_ID_ARCHIVED_LINK now + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 0); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 2); // archive already archived first chat, unarchive second chat two times assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .is_ok()); assert!(chat_id2 .set_visibility(&t.ctx, ChatVisibility::Normal) + .await .is_ok()); assert!(chat_id2 .set_visibility(&t.ctx, ChatVisibility::Normal) + .await .is_ok()); assert!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility() == ChatVisibility::Archived ); assert!( Chat::load_from_db(&t.ctx, chat_id2) + .await .unwrap() .get_visibility() == ChatVisibility::Normal ); - assert_eq!(get_chat_cnt(&t.ctx), 2); - assert_eq!(chatlist_len(&t.ctx, 0), 2); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS), 1); - assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY), 1); + assert_eq!(get_chat_cnt(&t.ctx).await, 2); + assert_eq!(chatlist_len(&t.ctx, 0).await, 2); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_NO_SPECIALS).await, 1); + assert_eq!(chatlist_len(&t.ctx, DC_GCL_ARCHIVED_ONLY).await, 1); } - fn get_chats_from_chat_list(ctx: &Context, listflags: usize) -> Vec { - let chatlist = Chatlist::try_load(ctx, listflags, None, None).unwrap(); + async fn get_chats_from_chat_list(ctx: &Context, listflags: usize) -> Vec { + let chatlist = Chatlist::try_load(ctx, listflags, None, None) + .await + .unwrap(); let mut result = Vec::new(); for chatlist_index in 0..chatlist.len() { result.push(chatlist.get_chat_id(chatlist_index)) @@ -2927,85 +3102,104 @@ mod tests { result } - #[test] - fn test_pinned() { + #[async_std::test] + async fn test_pinned() { let t = dummy_context(); // create 3 chats, wait 1 second in between to get a reliable order (we order by time) let mut msg = Message::new(Viewtype::Text); msg.text = Some("foo".to_string()); - let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).unwrap(); + let msg_id = add_device_msg(&t.ctx, None, Some(&mut msg)).await.unwrap(); let chat_id1 = message::Message::load_from_db(&t.ctx, msg_id) + .await .unwrap() .chat_id; std::thread::sleep(std::time::Duration::from_millis(1000)); - let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF).unwrap(); + let chat_id2 = create_by_contact_id(&t.ctx, DC_CONTACT_ID_SELF) + .await + .unwrap(); std::thread::sleep(std::time::Duration::from_millis(1000)); - let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); - let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS).await; assert_eq!(chatlist, vec![chat_id3, chat_id2, chat_id1]); // pin assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Pinned) + .await .is_ok()); assert_eq!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility(), ChatVisibility::Pinned ); // check if chat order changed - let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS).await; assert_eq!(chatlist, vec![chat_id1, chat_id3, chat_id2]); // unpin assert!(chat_id1 .set_visibility(&t.ctx, ChatVisibility::Normal) + .await .is_ok()); assert_eq!( Chat::load_from_db(&t.ctx, chat_id1) + .await .unwrap() .get_visibility(), ChatVisibility::Normal ); // check if chat order changed back - let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS); + let chatlist = get_chats_from_chat_list(&t.ctx, DC_GCL_NO_SPECIALS).await; assert_eq!(chatlist, vec![chat_id3, chat_id2, chat_id1]); } #[async_std::test] async fn test_set_chat_name() { let t = dummy_context(); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .get_name(), "foo" ); set_chat_name(&t.ctx, chat_id, "bar").await.unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().get_name(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .get_name(), "bar" ); } - #[test] - fn test_create_same_chat_twice() { + #[async_std::test] + async fn test_create_same_chat_twice() { let context = dummy_context(); - let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de").unwrap(); + let contact1 = Contact::create(&context.ctx, "bob", "bob@mail.de") + .await + .unwrap(); assert_ne!(contact1, 0); - let chat_id = create_by_contact_id(&context.ctx, contact1).unwrap(); + let chat_id = create_by_contact_id(&context.ctx, contact1).await.unwrap(); assert!(!chat_id.is_special(), "chat_id too small {}", chat_id); - let chat = Chat::load_from_db(&context.ctx, chat_id).unwrap(); + let chat = Chat::load_from_db(&context.ctx, chat_id).await.unwrap(); - let chat2_id = create_by_contact_id(&context.ctx, contact1).unwrap(); + let chat2_id = create_by_contact_id(&context.ctx, contact1).await.unwrap(); assert_eq!(chat2_id, chat_id); - let chat2 = Chat::load_from_db(&context.ctx, chat2_id).unwrap(); + let chat2 = Chat::load_from_db(&context.ctx, chat2_id).await.unwrap(); assert_eq!(chat2.name, chat.name); } @@ -3013,39 +3207,61 @@ mod tests { #[async_std::test] async fn test_shall_attach_selfavatar() { let t = dummy_context(); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); - assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); + assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); let (contact_id, _) = - Contact::add_or_lookup(&t.ctx, "", "foo@bar.org", Origin::IncomingUnknownTo).unwrap(); + Contact::add_or_lookup(&t.ctx, "", "foo@bar.org", Origin::IncomingUnknownTo) + .await + .unwrap(); add_contact_to_chat(&t.ctx, chat_id, contact_id).await; - assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); t.ctx.set_config(Config::Selfavatar, None).await.unwrap(); // setting to None also forces re-sending - assert!(shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + assert!(shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); - assert!(chat_id.set_selfavatar_timestamp(&t.ctx, time()).is_ok()); - assert!(!shall_attach_selfavatar(&t.ctx, chat_id).unwrap()); + assert!(chat_id + .set_selfavatar_timestamp(&t.ctx, time()) + .await + .is_ok()); + assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); } - #[test] - fn test_set_mute_duration() { + #[async_std::test] + async fn test_set_mute_duration() { let t = dummy_context(); - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo").unwrap(); + let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + .await + .unwrap(); // Initial assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), false ); // Forever - set_muted(&t.ctx, chat_id, MuteDuration::Forever).unwrap(); + set_muted(&t.ctx, chat_id, MuteDuration::Forever) + .await + .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), true ); // unMute - set_muted(&t.ctx, chat_id, MuteDuration::NotMuted).unwrap(); + set_muted(&t.ctx, chat_id, MuteDuration::NotMuted) + .await + .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), false ); // Timed in the future @@ -3054,9 +3270,13 @@ mod tests { chat_id, MuteDuration::Until(SystemTime::now() + Duration::from_secs(3600)), ) + .await .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), true ); // Time in the past @@ -3065,9 +3285,13 @@ mod tests { chat_id, MuteDuration::Until(SystemTime::now() - Duration::from_secs(3600)), ) + .await .unwrap(); assert_eq!( - Chat::load_from_db(&t.ctx, chat_id).unwrap().is_muted(), + Chat::load_from_db(&t.ctx, chat_id) + .await + .unwrap() + .is_muted(), false ); } diff --git a/src/chatlist.rs b/src/chatlist.rs index 84491ce2d..fa8544654 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -85,7 +85,7 @@ impl Chatlist { /// are returned. /// `query_contact_id`: An optional contact ID for filtering the list. Only chats including this contact ID /// are returned. - pub fn try_load( + pub async fn try_load( context: &Context, listflags: usize, query: Option<&str>, @@ -139,11 +139,13 @@ impl Chatlist { params![MessageState::OutDraft, query_contact_id as i32, ChatVisibility::Pinned], process_row, process_rows, - )? + ).await? } else if 0 != listflags & DC_GCL_ARCHIVED_ONLY { // show archived chats - context.sql.query_map( - "SELECT c.id, m.id + context + .sql + .query_map( + "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id @@ -157,23 +159,26 @@ impl Chatlist { AND c.archived=1 GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft], - process_row, - process_rows, - )? + params![MessageState::OutDraft], + process_row, + process_rows, + ) + .await? } else if let Some(query) = query { let query = query.trim().to_string(); ensure!(!query.is_empty(), "missing query"); // allow searching over special names that may change at any time // when the ui calls set_stock_translation() - if let Err(err) = update_special_chat_names(context) { + if let Err(err) = update_special_chat_names(context).await { warn!(context, "cannot update special chat names: {:?}", err) } let str_like_cmd = format!("%{}%", query); - context.sql.query_map( - "SELECT c.id, m.id + context + .sql + .query_map( + "SELECT c.id, m.id FROM chats c LEFT JOIN msgs m ON c.id=m.chat_id @@ -187,10 +192,11 @@ impl Chatlist { AND c.name LIKE ? GROUP BY c.id ORDER BY IFNULL(m.timestamp,c.created_timestamp) DESC, m.id DESC;", - params![MessageState::OutDraft, str_like_cmd], - process_row, - process_rows, - )? + params![MessageState::OutDraft, str_like_cmd], + process_row, + process_rows, + ) + .await? } else { // show normal chatlist let sort_id_up = if 0 != listflags & DC_GCL_FOR_FORWARDING { @@ -218,9 +224,10 @@ impl Chatlist { params![MessageState::OutDraft, ChatVisibility::Archived, sort_id_up, ChatVisibility::Pinned], process_row, process_rows, - )?; + ).await?; if 0 == listflags & DC_GCL_NO_SPECIALS { - if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context) { + if let Some(last_deaddrop_fresh_msg_id) = get_last_deaddrop_fresh_msg(context).await + { if 0 == listflags & DC_GCL_FOR_FORWARDING { ids.insert( 0, @@ -233,7 +240,7 @@ impl Chatlist { ids }; - if add_archived_link_item && dc_get_archived_cnt(context) > 0 { + if add_archived_link_item && dc_get_archived_cnt(context).await > 0 { if ids.is_empty() && 0 != listflags & DC_GCL_ADD_ALLDONE_HINT { ids.push((ChatId::new(DC_CHAT_ID_ALLDONE_HINT), MsgId::new(0))); } @@ -285,7 +292,7 @@ impl Chatlist { /// - dc_lot_t::timestamp: the timestamp of the message. 0 if not applicable. /// - dc_lot_t::state: The state of the message as one of the DC_STATE_* constants (see #dc_msg_get_state()). // 0 if not applicable. - pub fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot { + pub async fn get_summary(&self, context: &Context, index: usize, chat: Option<&Chat>) -> Lot { // The summary is created by the chat, not by the last message. // This is because we may want to display drafts here or stuff as // "is typing". @@ -300,7 +307,7 @@ impl Chatlist { let chat_loaded: Chat; let chat = if let Some(chat) = chat { chat - } else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0) { + } else if let Ok(chat) = Chat::load_from_db(context, self.ids[index].0).await { chat_loaded = chat; &chat_loaded } else { @@ -310,11 +317,11 @@ impl Chatlist { let lastmsg_id = self.ids[index].1; let mut lastcontact = None; - let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id) { + let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id).await { if lastmsg.from_id != DC_CONTACT_ID_SELF && (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) { - lastcontact = Contact::load_from_db(context, lastmsg.from_id).ok(); + lastcontact = Contact::load_from_db(context, lastmsg.from_id).await.ok(); } Some(lastmsg) @@ -340,7 +347,7 @@ impl Chatlist { } /// Returns the number of archived chats -pub fn dc_get_archived_cnt(context: &Context) -> u32 { +pub async fn dc_get_archived_cnt(context: &Context) -> u32 { context .sql .query_get_value( @@ -348,26 +355,30 @@ pub fn dc_get_archived_cnt(context: &Context) -> u32 { "SELECT COUNT(*) FROM chats WHERE blocked=0 AND archived=1;", params![], ) + .await .unwrap_or_default() } -fn get_last_deaddrop_fresh_msg(context: &Context) -> Option { +async fn get_last_deaddrop_fresh_msg(context: &Context) -> Option { // We have an index over the state-column, this should be // sufficient as there are typically only few fresh messages. - context.sql.query_get_value( - context, - concat!( - "SELECT m.id", - " FROM msgs m", - " LEFT JOIN chats c", - " ON c.id=m.chat_id", - " WHERE m.state=10", - " AND m.hidden=0", - " AND c.blocked=2", - " ORDER BY m.timestamp DESC, m.id DESC;" - ), - params![], - ) + context + .sql + .query_get_value( + context, + concat!( + "SELECT m.id", + " FROM msgs m", + " LEFT JOIN chats c", + " ON c.id=m.chat_id", + " WHERE m.state=10", + " AND m.hidden=0", + " AND c.blocked=2", + " ORDER BY m.timestamp DESC, m.id DESC;" + ), + params![], + ) + .await } #[cfg(test)] @@ -376,15 +387,21 @@ mod tests { use crate::test_utils::*; - #[test] - fn test_try_load() { + #[async_std::test] + async fn test_try_load() { let t = dummy_context(); - let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap(); - let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat").unwrap(); - let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat").unwrap(); + let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") + .await + .unwrap(); + let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat") + .await + .unwrap(); + let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat") + .await + .unwrap(); // check that the chatlist starts with the most recent message - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 3); assert_eq!(chats.get_chat_id(0), chat_id3); assert_eq!(chats.get_chat_id(1), chat_id2); @@ -394,20 +411,27 @@ mod tests { let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("hello".to_string())); chat_id2.set_draft(&t.ctx, Some(&mut msg)); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.get_chat_id(0), chat_id2); // check chatlist query and archive functionality - let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("b"), None) + .await + .unwrap(); assert_eq!(chats.len(), 1); - let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None) + .await + .unwrap(); assert_eq!(chats.len(), 0); chat_id1 .set_visibility(&t.ctx, ChatVisibility::Archived) + .await .ok(); - let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, DC_GCL_ARCHIVED_ONLY, None, None) + .await + .unwrap(); assert_eq!(chats.len(), 1); } @@ -427,40 +451,50 @@ mod tests { .is_self_talk()); } - #[test] - fn test_search_special_chat_names() { + #[async_std::test] + async fn test_search_special_chat_names() { let t = dummy_context(); t.ctx.update_device_chats().unwrap(); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) + .await + .unwrap(); assert_eq!(chats.len(), 0); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None) + .await + .unwrap(); assert_eq!(chats.len(), 0); t.ctx .set_stock_translation(StockMessage::SavedMessages, "test-1234-save".to_string()) .unwrap(); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-1234-s"), None) + .await + .unwrap(); assert_eq!(chats.len(), 1); t.ctx .set_stock_translation(StockMessage::DeviceMessages, "test-5678-babbel".to_string()) .unwrap(); - let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, Some("t-5678-b"), None) + .await + .unwrap(); assert_eq!(chats.len(), 1); } - #[test] - fn test_get_summary_unwrap() { + #[async_std::test] + async fn test_get_summary_unwrap() { let t = dummy_context(); - let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat").unwrap(); + let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") + .await + .unwrap(); let mut msg = Message::new(Viewtype::Text); msg.set_text(Some("foo:\nbar \r\n test".to_string())); chat_id1.set_draft(&t.ctx, Some(&mut msg)); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); - let summary = chats.get_summary(&t.ctx, 0, None); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); + let summary = chats.get_summary(&t.ctx, 0, None).await; assert_eq!(summary.get_text2().unwrap(), "foo: bar test"); // the linebreak should be removed from summary } } diff --git a/src/config.rs b/src/config.rs index 9d437ef6b..62c02e510 100644 --- a/src/config.rs +++ b/src/config.rs @@ -95,16 +95,16 @@ pub enum Config { impl Context { /// Get a configuration key. Returns `None` if no value is set, and no default value found. - pub fn get_config(&self, key: Config) -> Option { + pub async fn get_config(&self, key: Config) -> Option { let value = match key { Config::Selfavatar => { - let rel_path = self.sql.get_raw_config(self, key); + let rel_path = self.sql.get_raw_config(self, key).await; rel_path.map(|p| dc_get_abs_path(self, &p).to_string_lossy().into_owned()) } Config::SysVersion => Some((&*DC_VERSION_STR).clone()), Config::SysMsgsizeMaxRecommended => Some(format!("{}", RECOMMENDED_FILE_SIZE)), Config::SysConfigKeys => Some(get_config_keys_string()), - _ => self.sql.get_raw_config(self, key), + _ => self.sql.get_raw_config(self, key).await, }; if value.is_some() { @@ -118,14 +118,15 @@ impl Context { } } - pub fn get_config_int(&self, key: Config) -> i32 { + pub async fn get_config_int(&self, key: Config) -> i32 { self.get_config(key) + .await .and_then(|s| s.parse().ok()) .unwrap_or_default() } - pub fn get_config_bool(&self, key: Config) -> bool { - self.get_config_int(key) != 0 + pub async fn get_config_bool(&self, key: Config) -> bool { + self.get_config_int(key).await != 0 } /// Set the given config key. @@ -134,30 +135,34 @@ impl Context { match key { Config::Selfavatar => { self.sql - .execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS)?; + .execute("UPDATE contacts SET selfavatar_sent=0;", NO_PARAMS) + .await?; self.sql - .set_raw_config_bool(self, "attach_selfavatar", true)?; + .set_raw_config_bool(self, "attach_selfavatar", true) + .await?; match value { Some(value) => { let blob = BlobObject::new_from_path(&self, value)?; blob.recode_to_avatar_size(self)?; - self.sql.set_raw_config(self, key, Some(blob.as_name())) + self.sql + .set_raw_config(self, key, Some(blob.as_name())) + .await } - None => self.sql.set_raw_config(self, key, None), + None => self.sql.set_raw_config(self, key, None).await, } } Config::InboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); + let ret = self.sql.set_raw_config(self, key, value).await; interrupt_inbox_idle(self).await; ret } Config::SentboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); + let ret = self.sql.set_raw_config(self, key, value).await; interrupt_sentbox_idle(self).await; ret } Config::MvboxWatch => { - let ret = self.sql.set_raw_config(self, key, value); + let ret = self.sql.set_raw_config(self, key, value).await; interrupt_mvbox_idle(self).await; ret } @@ -169,9 +174,9 @@ impl Context { value }; - self.sql.set_raw_config(self, key, val) + self.sql.set_raw_config(self, key, val).await } - _ => self.sql.set_raw_config(self, key, value), + _ => self.sql.set_raw_config(self, key, value).await, } } } @@ -234,7 +239,7 @@ mod tests { .unwrap(); assert!(avatar_blob.exists()); assert!(std::fs::metadata(&avatar_blob).unwrap().len() < avatar_bytes.len() as u64); - let avatar_cfg = t.ctx.get_config(Config::Selfavatar); + let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await; assert_eq!(avatar_cfg, avatar_blob.to_str().map(|s| s.to_string())); let img = image::open(avatar_src).unwrap(); @@ -264,7 +269,7 @@ mod tests { .set_config(Config::Selfavatar, Some(&avatar_src.to_str().unwrap())) .await .unwrap(); - let avatar_cfg = t.ctx.get_config(Config::Selfavatar); + let avatar_cfg = t.ctx.get_config(Config::Selfavatar).await; assert_eq!(avatar_cfg, avatar_src.to_str().map(|s| s.to_string())); let img = image::open(avatar_src).unwrap(); diff --git a/src/configure/mod.rs b/src/configure/mod.rs index 111f943d6..d069132d2 100644 --- a/src/configure/mod.rs +++ b/src/configure/mod.rs @@ -42,8 +42,8 @@ impl Context { } /// Checks if the context is already configured. - pub fn is_configured(&self) -> bool { - self.sql.get_raw_config_bool(self, "configured") + pub async fn is_configured(&self) -> bool { + self.sql.get_raw_config_bool(self, "configured").await } } @@ -52,7 +52,7 @@ impl Context { ******************************************************************************/ #[allow(clippy::cognitive_complexity)] pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { - if !context.sql.is_open() { + if !context.sql.is_open().await { error!(context, "Cannot configure, database not opened.",); progress!(context, 0); return job::Status::Finished(Err(format_err!("Database not opened"))); @@ -75,7 +75,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { info!(context, "Configure ...",); // Variables that are shared between steps: - let mut param = LoginParam::from_database(context, ""); + let mut param = LoginParam::from_database(context, "").await; // need all vars here to be mutable because rust thinks the same step could be called multiple times // and also initialize, because otherwise rust thinks it's used while unitilized, even if thats not the case as the loop goes only forward let mut param_domain = "undefined.undefined".to_owned(); @@ -115,6 +115,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { context .sql .set_raw_config(context, "addr", Some(param.addr.as_str())) + .await .ok(); } progress!(context, 20); @@ -352,8 +353,8 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { } 16 => { progress!(context, 900); - let create_mvbox = context.get_config_bool(Config::MvboxWatch) - || context.get_config_bool(Config::MvboxMove); + let create_mvbox = context.get_config_bool(Config::MvboxWatch).await + || context.get_config_bool(Config::MvboxMove).await; let imap = &context.inbox_thread.imap; if let Err(err) = imap.ensure_configured_folders(context, create_mvbox).await { warn!(context, "configuring folders failed: {:?}", err); @@ -376,11 +377,13 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { context, "configured_", /*the trailing underscore is correct*/ ) + .await .ok(); context .sql .set_raw_config_bool(context, "configured", true) + .await .ok(); true } @@ -389,7 +392,7 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { // we generate the keypair just now - we could also postpone this until the first message is sent, however, // this may result in a unexpected and annoying delay when the user sends his very first message // (~30 seconds on a Moto G4 play) and might looks as if message sending is always that slow. - success = e2ee::ensure_secret_key_exists(context).is_ok(); + success = e2ee::ensure_secret_key_exists(context).await.is_ok(); info!(context, "key generation completed"); progress!(context, 940); break; // We are done here @@ -416,11 +419,15 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { // this way, the parameters visible to the ui are always in-sync with the current configuration. if success { LoginParam::from_database(context, "") + .await .save_to_database(context, "configured_raw_") + .await .ok(); } else { LoginParam::from_database(context, "configured_raw_") + .await .save_to_database(context, "") + .await .ok(); } @@ -428,7 +435,10 @@ pub(crate) async fn job_configure_imap(context: &Context) -> job::Status { if !provider.after_login_hint.is_empty() { let mut msg = Message::new(Viewtype::Text); msg.text = Some(provider.after_login_hint.to_string()); - if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)).is_err() { + if chat::add_device_msg(context, Some("core-provider-info"), Some(&mut msg)) + .await + .is_err() + { warn!(context, "cannot add after_login_hint as core-provider-info"); } } diff --git a/src/contact.rs b/src/contact.rs index b3d6672b3..29a9bab9a 100644 --- a/src/contact.rs +++ b/src/contact.rs @@ -21,7 +21,6 @@ use crate::message::{MessageState, MsgId}; use crate::mimeparser::AvatarAction; use crate::param::*; use crate::peerstate::*; -use crate::sql; use crate::stock::StockMessage; /// Contacts with at least this origin value are shown in the contact list. @@ -164,29 +163,33 @@ pub enum VerifiedStatus { } impl Contact { - pub fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result { - let mut res = context.sql.query_row( - "SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param + pub async fn load_from_db(context: &Context, contact_id: u32) -> crate::sql::Result { + let mut res = context + .sql + .query_row( + "SELECT c.name, c.addr, c.origin, c.blocked, c.authname, c.param FROM contacts c WHERE c.id=?;", - params![contact_id as i32], - |row| { - let contact = Self { - id: contact_id, - name: row.get::<_, String>(0)?, - authname: row.get::<_, String>(4)?, - addr: row.get::<_, String>(1)?, - blocked: row.get::<_, Option>(3)?.unwrap_or_default() != 0, - origin: row.get(2)?, - param: row.get::<_, String>(5)?.parse().unwrap_or_default(), - }; - Ok(contact) - }, - )?; + params![contact_id as i32], + |row| { + let contact = Self { + id: contact_id, + name: row.get::<_, String>(0)?, + authname: row.get::<_, String>(4)?, + addr: row.get::<_, String>(1)?, + blocked: row.get::<_, Option>(3)?.unwrap_or_default() != 0, + origin: row.get(2)?, + param: row.get::<_, String>(5)?.parse().unwrap_or_default(), + }; + Ok(contact) + }, + ) + .await?; if contact_id == DC_CONTACT_ID_SELF { res.name = context.stock_str(StockMessage::SelfMsg).to_string(); res.addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); } else if contact_id == DC_CONTACT_ID_DEVICE { res.name = context.stock_str(StockMessage::DeviceMessages).to_string(); @@ -201,20 +204,21 @@ impl Contact { } /// Check if a contact is blocked. - pub fn is_blocked_load(context: &Context, id: u32) -> bool { + pub async fn is_blocked_load(context: &Context, id: u32) -> bool { Self::load_from_db(context, id) + .await .map(|contact| contact.blocked) .unwrap_or_default() } /// Block the given contact. - pub fn block(context: &Context, id: u32) { - set_block_contact(context, id, true); + pub async fn block(context: &Context, id: u32) { + set_block_contact(context, id, true).await; } /// Unblock the given contact. - pub fn unblock(context: &Context, id: u32) { - set_block_contact(context, id, false); + pub async fn unblock(context: &Context, id: u32) { + set_block_contact(context, id, false).await; } /// Add a single contact as a result of an _explicit_ user action. @@ -226,15 +230,19 @@ impl Contact { /// a bunch of addresses. /// /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. - pub fn create(context: &Context, name: impl AsRef, addr: impl AsRef) -> Result { + pub async fn create( + context: &Context, + name: impl AsRef, + addr: impl AsRef, + ) -> Result { ensure!( !addr.as_ref().is_empty(), "Cannot create contact with empty address" ); let (contact_id, sth_modified) = - Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated)?; - let blocked = Contact::is_blocked_load(context, contact_id); + Contact::add_or_lookup(context, name, addr, Origin::ManuallyCreated).await?; + let blocked = Contact::is_blocked_load(context, contact_id).await; context.call_cb(Event::ContactsChanged( if sth_modified == Modifier::Created { Some(contact_id) @@ -253,14 +261,15 @@ impl Contact { /// as *noticed*. See also dc_marknoticed_chat() and dc_markseen_msgs() /// /// Calling this function usually results in the event `#DC_EVENT_MSGS_CHANGED`. - pub fn mark_noticed(context: &Context, id: u32) { - if sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", - params![MessageState::InNoticed, id as i32, MessageState::InFresh], - ) - .is_ok() + pub async fn mark_noticed(context: &Context, id: u32) { + if context + .sql + .execute( + "UPDATE msgs SET state=? WHERE from_id=? AND state=?;", + params![MessageState::InNoticed, id as i32, MessageState::InFresh], + ) + .await + .is_ok() { context.call_cb(Event::MsgsChanged { chat_id: ChatId::new(0), @@ -274,7 +283,7 @@ impl Contact { /// /// To validate an e-mail address independently of the contact database /// use `dc_may_be_valid_addr()`. - pub fn lookup_id_by_addr(context: &Context, addr: impl AsRef) -> u32 { + pub async fn lookup_id_by_addr(context: &Context, addr: impl AsRef) -> u32 { if addr.as_ref().is_empty() { return 0; } @@ -282,6 +291,7 @@ impl Contact { let addr_normalized = addr_normalize(addr.as_ref()); let addr_self = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if addr_cmp(addr_normalized, addr_self) { @@ -296,7 +306,7 @@ impl Contact { DC_CONTACT_ID_LAST_SPECIAL as i32, DC_ORIGIN_MIN_CONTACT_LIST, ], - ).unwrap_or_default() + ).await.unwrap_or_default() } /// Lookup a contact and create it if it does not exist yet. @@ -324,7 +334,7 @@ impl Contact { /// Depending on the origin, both, "row_name" and "row_authname" are updated from "name". /// /// Returns the contact_id and a `Modifier` value indicating if a modification occured. - pub(crate) fn add_or_lookup( + pub(crate) async fn add_or_lookup( context: &Context, name: impl AsRef, addr: impl AsRef, @@ -341,6 +351,7 @@ impl Contact { let addr = addr_normalize(addr.as_ref()); let addr_self = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if addr_cmp(addr, addr_self) { @@ -396,7 +407,8 @@ impl Contact { Ok((row_id, row_name, row_addr, row_origin, row_authname)) }, - ) { + ) + .await { row_id = id; if origin as i32 >= row_origin as i32 && addr != row_addr { update_addr = true; @@ -412,37 +424,36 @@ impl Contact { &row_name }; - sql::execute( - context, - &context.sql, - "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", - params![ - new_name, - if update_addr { addr } else { &row_addr }, - if origin > row_origin { - origin - } else { - row_origin - }, - if update_authname { - name.as_ref() - } else { - &row_authname - }, - row_id - ], - ) - .ok(); + context + .sql + .execute( + "UPDATE contacts SET name=?, addr=?, origin=?, authname=? WHERE id=?;", + params![ + new_name, + if update_addr { addr } else { &row_addr }, + if origin > row_origin { + origin + } else { + row_origin + }, + if update_authname { + name.as_ref() + } else { + &row_authname + }, + row_id + ], + ) + .await + .ok(); if update_name { // Update the contact name also if it is used as a group name. // This is one of the few duplicated data, however, getting the chat list is easier this way. - sql::execute( - context, - &context.sql, + context.sql.execute( "UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);", params![new_name, Chattype::Single, row_id] - ).ok(); + ).await.ok(); } sth_modified = Modifier::Modified; } @@ -451,20 +462,24 @@ impl Contact { update_authname = true; } - if sql::execute( - context, - &context.sql, - "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", - params![ - name.as_ref(), - addr, - origin, - if update_authname { name.as_ref() } else { "" } - ], - ) - .is_ok() + if context + .sql + .execute( + "INSERT INTO contacts (name, addr, origin, authname) VALUES(?, ?, ?, ?);", + params![ + name.as_ref(), + addr, + origin, + if update_authname { name.as_ref() } else { "" } + ], + ) + .await + .is_ok() { - row_id = sql::get_rowid(context, &context.sql, "contacts", "addr", addr); + row_id = context + .sql + .get_rowid(context, "contacts", "addr", addr) + .await?; sth_modified = Modifier::Created; info!(context, "added contact id={} addr={}", row_id, addr); } else { @@ -492,12 +507,12 @@ impl Contact { /// The `addr_book` is a multiline string in the format `Name one\nAddress one\nName two\nAddress two`. /// /// Returns the number of modified contacts. - pub fn add_address_book(context: &Context, addr_book: impl AsRef) -> Result { + pub async fn add_address_book(context: &Context, addr_book: impl AsRef) -> Result { let mut modify_cnt = 0; for (name, addr) in split_address_book(addr_book.as_ref()).into_iter() { let name = normalize_name(name); - match Contact::add_or_lookup(context, name, addr, Origin::AddressBook) { + match Contact::add_or_lookup(context, name, addr, Origin::AddressBook).await { Err(err) => { warn!( context, @@ -527,13 +542,14 @@ impl Contact { /// - if the flag DC_GCL_VERIFIED_ONLY is set, only verified contacts are returned. /// if DC_GCL_VERIFIED_ONLY is not set, verified and unverified contacts are returned. /// `query` is a string to filter the list. - pub fn get_all( + pub async fn get_all( context: &Context, listflags: u32, query: Option>, ) -> Result> { let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); let mut add_self = false; @@ -549,8 +565,10 @@ impl Contact { .map(|s| s.as_ref().to_string()) .unwrap_or_default() ); - context.sql.query_map( - "SELECT c.id FROM contacts c \ + context + .sql + .query_map( + "SELECT c.id FROM contacts c \ LEFT JOIN acpeerstates ps ON c.addr=ps.addr \ WHERE c.addr!=?1 \ AND c.id>?2 \ @@ -559,24 +577,28 @@ impl Contact { AND (c.name LIKE ?4 OR c.addr LIKE ?5) \ AND (1=?6 OR LENGTH(ps.verified_key_fingerprint)!=0) \ ORDER BY LOWER(c.name||c.addr),c.id;", - params![ - self_addr, - DC_CONTACT_ID_LAST_SPECIAL as i32, - Origin::IncomingReplyTo, - &s3str_like_cmd, - &s3str_like_cmd, - if flag_verified_only { 0 } else { 1 }, - ], - |row| row.get::<_, i32>(0), - |ids| { - for id in ids { - ret.push(id? as u32); - } - Ok(()) - }, - )?; + params![ + self_addr, + DC_CONTACT_ID_LAST_SPECIAL as i32, + Origin::IncomingReplyTo, + &s3str_like_cmd, + &s3str_like_cmd, + if flag_verified_only { 0 } else { 1 }, + ], + |row| row.get::<_, i32>(0), + |ids| { + for id in ids { + ret.push(id? as u32); + } + Ok(()) + }, + ) + .await?; - let self_name = context.get_config(Config::Displayname).unwrap_or_default(); + let self_name = context + .get_config(Config::Displayname) + .await + .unwrap_or_default(); let self_name2 = context.stock_str(StockMessage::SelfMsg); if let Some(query) = query { @@ -602,7 +624,7 @@ impl Contact { } Ok(()) } - )?; + ).await?; } if flag_add_self && add_self { @@ -612,7 +634,7 @@ impl Contact { Ok(ret) } - pub fn get_blocked_cnt(context: &Context) -> usize { + pub async fn get_blocked_cnt(context: &Context) -> usize { context .sql .query_get_value::<_, isize>( @@ -620,11 +642,12 @@ impl Contact { "SELECT COUNT(*) FROM contacts WHERE id>? AND blocked!=0", params![DC_CONTACT_ID_LAST_SPECIAL as i32], ) + .await .unwrap_or_default() as usize } /// Get blocked contacts. - pub fn get_all_blocked(context: &Context) -> Vec { + pub async fn get_all_blocked(context: &Context) -> Vec { context .sql .query_map( @@ -636,6 +659,7 @@ impl Contact { .map_err(Into::into) }, ) + .await .unwrap_or_default() } @@ -644,14 +668,14 @@ impl Contact { /// This function returns a string explaining the encryption state /// of the contact and if the connection is encrypted the /// fingerprints of the keys involved. - pub fn get_encrinfo(context: &Context, contact_id: u32) -> Result { + pub async fn get_encrinfo(context: &Context, contact_id: u32) -> Result { let mut ret = String::new(); - if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if let Ok(contact) = Contact::load_from_db(context, contact_id).await { let peerstate = Peerstate::from_addr(context, &context.sql, &contact.addr); - let loginparam = LoginParam::from_database(context, "configured_"); + let loginparam = LoginParam::from_database(context, "configured_").await; - let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); + let mut self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; if peerstate.is_some() && peerstate @@ -668,8 +692,8 @@ impl Contact { }); ret += &p; if self_key.is_none() { - e2ee::ensure_secret_key_exists(context)?; - self_key = Key::from_self_public(context, &loginparam.addr, &context.sql); + e2ee::ensure_secret_key_exists(context).await?; + self_key = Key::from_self_public(context, &loginparam.addr, &context.sql).await; } let p = context.stock_str(StockMessage::FingerPrints); ret += &format!(" {}:", p); @@ -718,7 +742,7 @@ impl Contact { /// possible as the contact is in use. In this case, the contact can be blocked. /// /// May result in a `#DC_EVENT_CONTACTS_CHANGED` event. - pub fn delete(context: &Context, contact_id: u32) -> Result<()> { + pub async fn delete(context: &Context, contact_id: u32) -> Result<()> { ensure!( contact_id > DC_CONTACT_ID_LAST_SPECIAL, "Can not delete special contact" @@ -731,6 +755,7 @@ impl Contact { "SELECT COUNT(*) FROM chats_contacts WHERE contact_id=?;", params![contact_id as i32], ) + .await .unwrap_or_default(); let count_msgs: i32 = if count_contacts > 0 { @@ -741,18 +766,21 @@ impl Contact { "SELECT COUNT(*) FROM msgs WHERE from_id=? OR to_id=?;", params![contact_id as i32, contact_id as i32], ) + .await .unwrap_or_default() } else { 0 }; if count_msgs == 0 { - match sql::execute( - context, - &context.sql, - "DELETE FROM contacts WHERE id=?;", - params![contact_id as i32], - ) { + match context + .sql + .execute( + "DELETE FROM contacts WHERE id=?;", + params![contact_id as i32], + ) + .await + { Ok(_) => { context.call_cb(Event::ContactsChanged(None)); return Ok(()); @@ -776,17 +804,20 @@ impl Contact { /// For contact DC_CONTACT_ID_SELF (1), the function returns sth. /// like "Me" in the selected language and the email address /// defined by dc_set_config(). - pub fn get_by_id(context: &Context, contact_id: u32) -> Result { - Ok(Contact::load_from_db(context, contact_id)?) + pub async fn get_by_id(context: &Context, contact_id: u32) -> Result { + let contact = Contact::load_from_db(context, contact_id).await?; + + Ok(contact) } - pub fn update_param(&mut self, context: &Context) -> Result<()> { - sql::execute( - context, - &context.sql, - "UPDATE contacts SET param=? WHERE id=?", - params![self.param.to_string(), self.id as i32], - )?; + pub async fn update_param(&mut self, context: &Context) -> Result<()> { + context + .sql + .execute( + "UPDATE contacts SET param=? WHERE id=?", + params![self.param.to_string(), self.id as i32], + ) + .await?; Ok(()) } @@ -856,9 +887,9 @@ impl Contact { /// Get the contact's profile image. /// This is the image set by each remote user on their own /// using dc_set_config(context, "selfavatar", image). - pub fn get_profile_image(&self, context: &Context) -> Option { + pub async fn get_profile_image(&self, context: &Context) -> Option { if self.id == DC_CONTACT_ID_SELF { - if let Some(p) = context.get_config(Config::Selfavatar) { + if let Some(p) = context.get_config(Config::Selfavatar).await { return Some(PathBuf::from(p)); } } else if let Some(image_rel) = self.param.get(Param::ProfileImage) { @@ -916,12 +947,16 @@ impl Contact { VerifiedStatus::Unverified } - pub fn addr_equals_contact(context: &Context, addr: impl AsRef, contact_id: u32) -> bool { + pub async fn addr_equals_contact( + context: &Context, + addr: impl AsRef, + contact_id: u32, + ) -> bool { if addr.as_ref().is_empty() { return false; } - if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if let Ok(contact) = Contact::load_from_db(context, contact_id).await { if !contact.addr.is_empty() { let normalized_addr = addr_normalize(addr.as_ref()); if contact.addr == normalized_addr { @@ -933,8 +968,8 @@ impl Contact { false } - pub fn get_real_cnt(context: &Context) -> usize { - if !context.sql.is_open() { + pub async fn get_real_cnt(context: &Context) -> usize { + if !context.sql.is_open().await { return 0; } @@ -945,11 +980,12 @@ impl Contact { "SELECT COUNT(*) FROM contacts WHERE id>?;", params![DC_CONTACT_ID_LAST_SPECIAL as i32], ) + .await .unwrap_or_default() as usize } - pub fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { - if !context.sql.is_open() || contact_id <= DC_CONTACT_ID_LAST_SPECIAL { + pub async fn real_exists_by_id(context: &Context, contact_id: u32) -> bool { + if !context.sql.is_open().await || contact_id <= DC_CONTACT_ID_LAST_SPECIAL { return false; } @@ -959,16 +995,18 @@ impl Contact { "SELECT id FROM contacts WHERE id=?;", params![contact_id as i32], ) + .await .unwrap_or_default() } - pub fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool { + pub async fn scaleup_origin_by_id(context: &Context, contact_id: u32, origin: Origin) -> bool { context .sql .execute( "UPDATE contacts SET origin=? WHERE id=? AND origin &str { norm } -fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) { +async fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) { if contact_id <= DC_CONTACT_ID_LAST_SPECIAL { return; } - if let Ok(contact) = Contact::load_from_db(context, contact_id) { + if let Ok(contact) = Contact::load_from_db(context, contact_id).await { if contact.blocked != new_blocking - && sql::execute( - context, - &context.sql, - "UPDATE contacts SET blocked=? WHERE id=?;", - params![new_blocking as i32, contact_id as i32], - ) - .is_ok() + && context + .sql + .execute( + "UPDATE contacts SET blocked=? WHERE id=?;", + params![new_blocking as i32, contact_id as i32], + ) + .await + .is_ok() { // also (un)block all chats with _only_ this contact - we do not delete them to allow a // non-destructive blocking->unblocking. // (Maybe, beside normal chats (type=100) we should also block group chats with only this user. // However, I'm not sure about this point; it may be confusing if the user wants to add other people; // this would result in recreating the same group...) - if sql::execute( - context, - &context.sql, + if context.sql.execute( "UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);", params![new_blocking, 100, contact_id as i32], - ).is_ok() { + ).await.is_ok() { Contact::mark_noticed(context, contact_id); context.call_cb(Event::ContactsChanged(None)); } @@ -1028,14 +1065,14 @@ fn set_block_contact(context: &Context, contact_id: u32, new_blocking: bool) { } } -pub(crate) fn set_profile_image( +pub(crate) async fn set_profile_image( context: &Context, contact_id: u32, profile_image: &AvatarAction, ) -> Result<()> { // the given profile image is expected to be already in the blob directory // as profile images can be set only by receiving messages, this should be always the case, however. - let mut contact = Contact::load_from_db(context, contact_id)?; + let mut contact = Contact::load_from_db(context, contact_id).await?; let changed = match profile_image { AvatarAction::Change(profile_image) => { contact.param.set(Param::ProfileImage, profile_image); @@ -1047,7 +1084,7 @@ pub(crate) fn set_profile_image( } }; if changed { - contact.update_param(context)?; + contact.update_param(context).await?; context.call_cb(Event::ContactsChanged(Some(contact_id))); } Ok(()) @@ -1119,8 +1156,8 @@ fn cat_fingerprint( impl Context { /// determine whether the specified addr maps to the/a self addr - pub fn is_self_addr(&self, addr: &str) -> Result { - let self_addr = match self.get_config(Config::ConfiguredAddr) { + pub async fn is_self_addr(&self, addr: &str) -> Result { + let self_addr = match self.get_config(Config::ConfiguredAddr).await { Some(s) => s, None => return Err(Error::NotConfigured), }; @@ -1222,11 +1259,11 @@ mod tests { #[async_std::test] async fn test_is_self_addr() -> Result<()> { let t = test_context(None); - assert!(t.ctx.is_self_addr("me@me.org").is_err()); + assert!(t.ctx.is_self_addr("me@me.org").await.is_err()); let addr = configure_alice_keypair(&t.ctx).await; - assert_eq!(t.ctx.is_self_addr("me@me.org")?, false); - assert_eq!(t.ctx.is_self_addr(&addr)?, true); + assert_eq!(t.ctx.is_self_addr("me@me.org").await?, false); + assert_eq!(t.ctx.is_self_addr(&addr).await?, true); Ok(()) } diff --git a/src/context.rs b/src/context.rs index 3f711fedf..e9790079a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,7 @@ use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex, RwLock}; +use std::sync::{atomic::AtomicBool, Arc, Mutex, RwLock}; use crate::chat::*; use crate::config::Config; @@ -39,8 +39,8 @@ pub struct Context { /// Blob directory path blobdir: PathBuf, pub sql: Sql, - pub perform_inbox_jobs_needed: Arc>, - pub probe_imap_network: Arc>, + pub perform_inbox_jobs_needed: AtomicBool, + pub probe_imap_network: AtomicBool, pub inbox_thread: JobThread, pub sentbox_thread: JobThread, pub mvbox_thread: JobThread, @@ -81,7 +81,11 @@ pub fn get_info() -> HashMap<&'static str, String> { impl Context { /// Creates new context. - pub fn new(cb: Box, os_name: String, dbfile: PathBuf) -> Result { + pub async fn new( + cb: Box, + os_name: String, + dbfile: PathBuf, + ) -> Result { pretty_env_logger::try_init_timed().ok(); let mut blob_fname = OsString::new(); @@ -91,10 +95,10 @@ impl Context { if !blobdir.exists() { std::fs::create_dir_all(&blobdir)?; } - Context::with_blobdir(cb, os_name, dbfile, blobdir) + Context::with_blobdir(cb, os_name, dbfile, blobdir).await } - pub fn with_blobdir( + pub async fn with_blobdir( cb: Box, os_name: String, dbfile: PathBuf, @@ -120,14 +124,14 @@ impl Context { inbox_thread: JobThread::new("INBOX", "configured_inbox_folder", Imap::new()), sentbox_thread: JobThread::new("SENTBOX", "configured_sentbox_folder", Imap::new()), mvbox_thread: JobThread::new("MVBOX", "configured_mvbox_folder", Imap::new()), - probe_imap_network: Arc::new(RwLock::new(false)), - perform_inbox_jobs_needed: Arc::new(RwLock::new(false)), + probe_imap_network: Default::default(), + perform_inbox_jobs_needed: Default::default(), generating_key_mutex: Mutex::new(()), translated_stockstrings: RwLock::new(HashMap::new()), }; ensure!( - ctx.sql.open(&ctx, &ctx.dbfile, false), + ctx.sql.open(&ctx, &ctx.dbfile, false).await, "Failed opening sqlite database" ); @@ -208,57 +212,66 @@ impl Context { * UI chat/message related API ******************************************************************************/ - pub fn get_info(&self) -> HashMap<&'static str, String> { + pub async fn get_info(&self) -> HashMap<&'static str, String> { let unset = "0"; - let l = LoginParam::from_database(self, ""); - let l2 = LoginParam::from_database(self, "configured_"); - let displayname = self.get_config(Config::Displayname); - let chats = get_chat_cnt(self) as usize; + let l = LoginParam::from_database(self, "").await; + let l2 = LoginParam::from_database(self, "configured_").await; + let displayname = self.get_config(Config::Displayname).await; + let chats = get_chat_cnt(self).await as usize; let real_msgs = message::get_real_msg_cnt(self) as usize; let deaddrop_msgs = message::get_deaddrop_msg_cnt(self) as usize; - let contacts = Contact::get_real_cnt(self) as usize; - let is_configured = self.get_config_int(Config::Configured); + let contacts = Contact::get_real_cnt(self).await as usize; + let is_configured = self.get_config_int(Config::Configured).await; let dbversion = self .sql .get_raw_config_int(self, "dbversion") + .await .unwrap_or_default(); - let e2ee_enabled = self.get_config_int(Config::E2eeEnabled); - let mdns_enabled = self.get_config_int(Config::MdnsEnabled); - let bcc_self = self.get_config_int(Config::BccSelf); + let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await; + let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await; + let bcc_self = self.get_config_int(Config::BccSelf).await; - let prv_key_cnt: Option = - self.sql - .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS); + let prv_key_cnt: Option = self + .sql + .query_get_value(self, "SELECT COUNT(*) FROM keypairs;", rusqlite::NO_PARAMS) + .await; - let pub_key_cnt: Option = self.sql.query_get_value( - self, - "SELECT COUNT(*) FROM acpeerstates;", - rusqlite::NO_PARAMS, - ); + let pub_key_cnt: Option = self + .sql + .query_get_value( + self, + "SELECT COUNT(*) FROM acpeerstates;", + rusqlite::NO_PARAMS, + ) + .await; - let fingerprint_str = if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql) { - key.fingerprint() - } else { - "".into() - }; + let fingerprint_str = + if let Some(key) = Key::from_self_public(self, &l2.addr, &self.sql).await { + key.fingerprint() + } else { + "".into() + }; - let inbox_watch = self.get_config_int(Config::InboxWatch); - let sentbox_watch = self.get_config_int(Config::SentboxWatch); - let mvbox_watch = self.get_config_int(Config::MvboxWatch); - let mvbox_move = self.get_config_int(Config::MvboxMove); + let inbox_watch = self.get_config_int(Config::InboxWatch).await; + let sentbox_watch = self.get_config_int(Config::SentboxWatch).await; + let mvbox_watch = self.get_config_int(Config::MvboxWatch).await; + let mvbox_move = self.get_config_int(Config::MvboxMove).await; let folders_configured = self .sql .get_raw_config_int(self, "folders_configured") + .await .unwrap_or_default(); let configured_sentbox_folder = self .sql .get_raw_config(self, "configured_sentbox_folder") + .await .unwrap_or_else(|| "".to_string()); let configured_mvbox_folder = self .sql .get_raw_config(self, "configured_mvbox_folder") + .await .unwrap_or_else(|| "".to_string()); let mut res = get_info(); @@ -273,6 +286,7 @@ impl Context { res.insert( "selfavatar", self.get_config(Config::Selfavatar) + .await .unwrap_or_else(|| "".to_string()), ); res.insert("is_configured", is_configured.to_string()); @@ -301,7 +315,7 @@ impl Context { res } - pub fn get_fresh_msgs(&self) -> Vec { + pub async fn get_fresh_msgs(&self) -> Vec { let show_deaddrop = 0; self.sql .query_map( @@ -329,11 +343,12 @@ impl Context { Ok(ret) }, ) + .await .unwrap_or_default() } #[allow(non_snake_case)] - pub fn search_msgs(&self, chat_id: ChatId, query: impl AsRef) -> Vec { + pub async fn search_msgs(&self, chat_id: ChatId, query: impl AsRef) -> Vec { let real_query = query.as_ref().trim(); if real_query.is_empty() { return Vec::new(); @@ -383,6 +398,7 @@ impl Context { Ok(ret) }, ) + .await .unwrap_or_default() } @@ -390,8 +406,11 @@ impl Context { folder_name.as_ref() == "INBOX" } - pub fn is_sentbox(&self, folder_name: impl AsRef) -> bool { - let sentbox_name = self.sql.get_raw_config(self, "configured_sentbox_folder"); + pub async fn is_sentbox(&self, folder_name: impl AsRef) -> bool { + let sentbox_name = self + .sql + .get_raw_config(self, "configured_sentbox_folder") + .await; if let Some(name) = sentbox_name { name == folder_name.as_ref() } else { @@ -399,8 +418,11 @@ impl Context { } } - pub fn is_mvbox(&self, folder_name: impl AsRef) -> bool { - let mvbox_name = self.sql.get_raw_config(self, "configured_mvbox_folder"); + pub async fn is_mvbox(&self, folder_name: impl AsRef) -> bool { + let mvbox_name = self + .sql + .get_raw_config(self, "configured_mvbox_folder") + .await; if let Some(name) = mvbox_name { name == folder_name.as_ref() @@ -410,14 +432,14 @@ impl Context { } pub async fn do_heuristics_moves(&self, folder: &str, msg_id: MsgId) { - if !self.get_config_bool(Config::MvboxMove) { + if !self.get_config_bool(Config::MvboxMove).await { return; } - if self.is_mvbox(folder) { + if self.is_mvbox(folder).await { return; } - if let Ok(msg) = Message::load_from_db(self, msg_id) { + if let Ok(msg) = Message::load_from_db(self, msg_id).await { if msg.is_setupmessage() { // do not move setup messages; // there may be a non-delta device that wants to handle it @@ -567,11 +589,11 @@ mod tests { std::mem::drop(t.ctx); } - #[test] - fn test_get_info() { + #[async_std::test] + async fn test_get_info() { let t = dummy_context(); - let info = t.ctx.get_info(); + let info = t.ctx.get_info().await; assert!(info.get("database_dir").is_some()); } diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 095dc359f..d8c7b3cfd 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -17,7 +17,6 @@ use crate::mimeparser::*; use crate::param::*; use crate::peerstate::*; use crate::securejoin::{self, handle_securejoin_handshake}; -use crate::sql; use crate::stock::StockMessage; use crate::{contact, location}; @@ -54,7 +53,7 @@ pub async fn dc_receive_imf( println!("{}", String::from_utf8_lossy(imf_raw)); } - let mut mime_parser = MimeMessage::from_bytes(context, imf_raw)?; + let mut mime_parser = MimeMessage::from_bytes(context, imf_raw).await?; // we can not add even an empty record if we have no info whatsoever ensure!(mime_parser.has_headers(), "No Headers Found"); @@ -183,7 +182,7 @@ pub async fn dc_receive_imf( } if let Some(avatar_action) = &mime_parser.user_avatar { - match contact::set_profile_image(&context, from_id, avatar_action) { + match contact::set_profile_image(&context, from_id, avatar_action).await { Ok(()) => { context.call_cb(Event::ChatModified(chat_id)); } @@ -317,7 +316,7 @@ async fn add_parts( // incoming non-chat messages may be discarded let mut allow_creation = true; let show_emails = - ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); + ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await).unwrap_or_default(); if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage && msgrmsg == MessengerMessage::No { @@ -377,7 +376,9 @@ async fn add_parts( } let (test_normal_chat_id, test_normal_chat_id_blocked) = - chat::lookup_by_contact_id(context, from_id).unwrap_or_default(); + chat::lookup_by_contact_id(context, from_id) + .await + .unwrap_or_default(); // get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list, // it might also be blocked and displayed in the deaddrop as a result @@ -399,7 +400,8 @@ async fn add_parts( create_blocked, from_id, to_ids, - )?; + ) + .await?; *chat_id = new_chat_id; chat_id_blocked = new_chat_id_blocked; if !chat_id.is_unset() @@ -433,6 +435,7 @@ async fn add_parts( } else if allow_creation { let (id, bl) = chat::create_or_lookup_by_contact_id(context, from_id, create_blocked) + .await .unwrap_or_default(); *chat_id = id; chat_id_blocked = bl; @@ -487,7 +490,8 @@ async fn add_parts( Blocked::Not, from_id, to_ids, - )?; + ) + .await?; *chat_id = new_chat_id; chat_id_blocked = new_chat_id_blocked; // automatically unblock chat when the user sends a message @@ -498,13 +502,14 @@ async fn add_parts( } if chat_id.is_unset() && allow_creation { let create_blocked = if MessengerMessage::No != msgrmsg - && !Contact::is_blocked_load(context, to_id) + && !Contact::is_blocked_load(context, to_id).await { Blocked::Not } else { Blocked::Deaddrop }; let (id, bl) = chat::create_or_lookup_by_contact_id(context, to_id, create_blocked) + .await .unwrap_or_default(); *chat_id = id; chat_id_blocked = bl; @@ -527,6 +532,7 @@ async fn add_parts( // maybe an Autocrypt Setup Message let (id, bl) = chat::create_or_lookup_by_contact_id(context, DC_CONTACT_ID_SELF, Blocked::Not) + .await .unwrap_or_default(); *chat_id = id; chat_id_blocked = bl; @@ -554,11 +560,11 @@ async fn add_parts( ); // unarchive chat - chat_id.unarchive(context)?; + chat_id.unarchive(context).await?; // if the mime-headers should be saved, find out its size // (the mime-header ends with an empty line) - let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders); + let save_mime_headers = context.get_config_bool(Config::SaveMimeHeaders).await; if let Some(raw) = mime_parser.get(HeaderDef::InReplyTo) { mime_in_reply_to = raw.clone(); } @@ -573,18 +579,21 @@ async fn add_parts( // (eg. one per attachment)) let icnt = mime_parser.parts.len(); - let mut txt_raw = None; + context + .sql + .with_conn(|mut conn| { + let subject = mime_parser.get_subject().unwrap_or_default(); + let mut txt_raw = None; - context.sql.prepare( - "INSERT INTO msgs \ + for part in mime_parser.parts.iter_mut() { + let mut stmt = conn.prepare_cached( + "INSERT INTO msgs \ (rfc724_mid, server_folder, server_uid, chat_id, from_id, to_id, timestamp, \ timestamp_sent, timestamp_rcvd, type, state, msgrmsg, txt, txt_raw, param, \ bytes, hidden, mime_headers, mime_in_reply_to, mime_references) \ VALUES (?,?,?,?,?,?, ?,?,?,?,?,?, ?,?,?,?,?,?, ?,?);", - |mut stmt, conn| { - let subject = mime_parser.get_subject().unwrap_or_default(); + )?; - for part in mime_parser.parts.iter_mut() { if mime_parser.location_kml.is_some() && icnt == 1 && (part.msg == "-location-" || part.msg.is_empty()) @@ -633,14 +642,17 @@ async fn add_parts( ])?; txt_raw = None; + + // This is okay, as we use a cached prepared statement. + drop(stmt); let row_id = - sql::get_rowid_with_conn(context, conn, "msgs", "rfc724_mid", &rfc724_mid); + crate::sql::get_rowid(context, &mut conn, "msgs", "rfc724_mid", &rfc724_mid)?; *insert_msg_id = MsgId::new(row_id); created_db_entries.push((*chat_id, *insert_msg_id)); } Ok(()) - }, - )?; + }) + .await?; info!( context, @@ -763,7 +775,7 @@ fn calc_timestamps( /// /// on success the function returns the found/created (chat_id, chat_blocked) tuple . #[allow(non_snake_case, clippy::cognitive_complexity)] -fn create_or_lookup_group( +async fn create_or_lookup_group( context: &Context, mime_parser: &mut MimeMessage, allow_creation: bool, @@ -827,6 +839,7 @@ fn create_or_lookup_group( X_MrRemoveFromGrp = Some(optional_field); mime_parser.is_system_message = SystemMessage::MemberRemovedFromGroup; let left_group = Contact::lookup_id_by_addr(context, X_MrRemoveFromGrp.as_ref().unwrap()) + .await == from_id as u32; better_msg = context.stock_system_msg( if left_group { @@ -889,6 +902,7 @@ fn create_or_lookup_group( // check, if we have a chat with this group ID let (mut chat_id, chat_id_verified, _blocked) = chat::get_chat_id_by_grpid(context, &grpid) + .await .unwrap_or((ChatId::new(0), false, Blocked::Not)); if !chat_id.is_error() { if chat_id_verified { @@ -900,7 +914,7 @@ fn create_or_lookup_group( mime_parser.repl_msg_by_error(s); } } - if !chat::is_contact_in_chat(context, chat_id, from_id as u32) { + if !chat::is_contact_in_chat(context, chat_id, from_id as u32).await { // The From-address is not part of this group. // It could be a new user or a DSN from a mailer-daemon. // in any case we do not want to recreate the member list @@ -913,9 +927,12 @@ fn create_or_lookup_group( } // check if the group does not exist but should be created - let group_explicitly_left = chat::is_group_explicitly_left(context, &grpid).unwrap_or_default(); + let group_explicitly_left = chat::is_group_explicitly_left(context, &grpid) + .await + .unwrap_or_default(); let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); if chat_id.is_error() @@ -953,7 +970,8 @@ fn create_or_lookup_group( grpname.as_ref().unwrap(), create_blocked, create_verified, - ); + ) + .await; chat_id_blocked = create_blocked; recreate_member_list = true; } @@ -997,13 +1015,14 @@ fn create_or_lookup_group( if let Some(ref grpname) = grpname { if grpname.len() < 200 { info!(context, "updating grpname for chat {}", chat_id); - if sql::execute( - context, - &context.sql, - "UPDATE chats SET name=? WHERE id=?;", - params![grpname, chat_id], - ) - .is_ok() + if context + .sql + .execute( + "UPDATE chats SET name=? WHERE id=?;", + params![grpname, chat_id], + ) + .await + .is_ok() { context.call_cb(Event::ChatModified(chat_id)); } @@ -1012,7 +1031,7 @@ fn create_or_lookup_group( } if let Some(avatar_action) = &mime_parser.group_avatar { info!(context, "group-avatar change for {}", chat_id); - if let Ok(mut chat) = Chat::load_from_db(context, chat_id) { + if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { match avatar_action { AvatarAction::Change(profile_image) => { chat.param.set(Param::ProfileImage, profile_image); @@ -1021,33 +1040,33 @@ fn create_or_lookup_group( chat.param.remove(Param::ProfileImage); } }; - chat.update_param(context)?; + chat.update_param(context).await?; send_EVENT_CHAT_MODIFIED = true; } } // add members to group/check members if recreate_member_list { - if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF) { - chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF); + if !chat::is_contact_in_chat(context, chat_id, DC_CONTACT_ID_SELF).await { + chat::add_to_chat_contacts_table(context, chat_id, DC_CONTACT_ID_SELF).await; } if from_id > DC_CONTACT_ID_LAST_SPECIAL - && !Contact::addr_equals_contact(context, &self_addr, from_id as u32) - && !chat::is_contact_in_chat(context, chat_id, from_id) + && !Contact::addr_equals_contact(context, &self_addr, from_id as u32).await + && !chat::is_contact_in_chat(context, chat_id, from_id).await { - chat::add_to_chat_contacts_table(context, chat_id, from_id as u32); + chat::add_to_chat_contacts_table(context, chat_id, from_id as u32).await; } for &to_id in to_ids.iter() { info!(context, "adding to={:?} to chat id={}", to_id, chat_id); - if !Contact::addr_equals_contact(context, &self_addr, to_id) - && !chat::is_contact_in_chat(context, chat_id, to_id) + if !Contact::addr_equals_contact(context, &self_addr, to_id).await + && !chat::is_contact_in_chat(context, chat_id, to_id).await { chat::add_to_chat_contacts_table(context, chat_id, to_id); } } send_EVENT_CHAT_MODIFIED = true; } else if let Some(removed_addr) = X_MrRemoveFromGrp { - let contact_id = Contact::lookup_id_by_addr(context, removed_addr); + let contact_id = Contact::lookup_id_by_addr(context, removed_addr).await; if contact_id != 0 { info!(context, "remove {:?} from chat id={}", contact_id, chat_id); chat::remove_from_chat_contacts_table(context, chat_id, contact_id); @@ -1178,16 +1197,14 @@ fn create_or_lookup_adhoc_group( Ok((new_chat_id, create_blocked)) } -fn create_group_record( +async fn create_group_record( context: &Context, grpid: impl AsRef, grpname: impl AsRef, create_blocked: Blocked, create_verified: VerifiedStatus, ) -> ChatId { - if sql::execute( - context, - &context.sql, + if context.sql.execute( "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", params![ if VerifiedStatus::Unverified != create_verified { @@ -1200,7 +1217,7 @@ fn create_group_record( create_blocked, time(), ], - ) + ).await .is_err() { warn!( @@ -1211,7 +1228,12 @@ fn create_group_record( ); return ChatId::new(0); } - let row_id = sql::get_rowid(context, &context.sql, "chats", "grpid", grpid.as_ref()); + let row_id = context + .sql + .get_rowid(context, "chats", "grpid", grpid.as_ref()) + .await + .unwrap_or_default(); + let chat_id = ChatId::new(row_id); info!( context, diff --git a/src/e2ee.rs b/src/e2ee.rs index 940d0793b..317d1a542 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -27,18 +27,18 @@ pub struct EncryptHelper { } impl EncryptHelper { - pub fn new(context: &Context) -> Result { + pub async fn new(context: &Context) -> Result { let prefer_encrypt = - EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled)) + EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await) .unwrap_or_default(); - let addr = match context.get_config(Config::ConfiguredAddr) { + let addr = match context.get_config(Config::ConfiguredAddr).await { None => { bail!("addr not configured!"); } Some(addr) => addr, }; - let public_key = load_or_generate_self_public_key(context, &addr)?; + let public_key = load_or_generate_self_public_key(context, &addr).await?; Ok(EncryptHelper { prefer_encrypt, @@ -88,12 +88,12 @@ impl EncryptHelper { } /// Tries to encrypt the passed in `mail`. - pub fn encrypt( + pub async fn encrypt( &mut self, context: &Context, min_verified: PeerstateVerifiedStatus, mail_to_encrypt: lettre_email::PartBuilder, - peerstates: &[(Option, &str)], + peerstates: &[(Option>, &str)], ) -> Result { let mut keyring = Keyring::default(); @@ -109,6 +109,7 @@ impl EncryptHelper { let public_key = Key::from(self.public_key.clone()); keyring.add_ref(&public_key); let sign_key = Key::from_self_private(context, self.addr.clone(), &context.sql) + .await .ok_or_else(|| format_err!("missing own private key"))?; let raw_message = mail_to_encrypt.build().as_string().into_bytes(); @@ -119,7 +120,7 @@ impl EncryptHelper { } } -pub fn try_decrypt( +pub async fn try_decrypt( context: &Context, mail: &ParsedMail<'_>, message_time: i64, @@ -141,14 +142,14 @@ pub fn try_decrypt( if let Some(ref mut peerstate) = peerstate { if let Some(ref header) = autocryptheader { peerstate.apply_header(&header, message_time); - peerstate.save_to_db(&context.sql, false)?; + peerstate.save_to_db(&context.sql, false).await?; } else if message_time > peerstate.last_seen_autocrypt && !contains_report(mail) { peerstate.degrade_encryption(message_time); - peerstate.save_to_db(&context.sql, false)?; + peerstate.save_to_db(&context.sql, false).await?; } } else if let Some(ref header) = autocryptheader { let p = Peerstate::from_header(context, header, message_time); - p.save_to_db(&context.sql, true)?; + p.save_to_db(&context.sql, true).await?; peerstate = Some(p); } } @@ -158,16 +159,19 @@ pub fn try_decrypt( let mut public_keyring_for_validate = Keyring::default(); let mut out_mail = None; let mut signatures = HashSet::default(); - let self_addr = context.get_config(Config::ConfiguredAddr); + let self_addr = context.get_config(Config::ConfiguredAddr).await; if let Some(self_addr) = self_addr { - if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) { + if private_keyring + .load_self_private_for_decrypting(context, self_addr, &context.sql) + .await + { if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 { peerstate = Peerstate::from_addr(&context, &context.sql, &from); } if let Some(ref peerstate) = peerstate { if peerstate.degrade_event.is_some() { - handle_degrade_event(context, &peerstate)?; + handle_degrade_event(context, &peerstate).await?; } if let Some(ref key) = peerstate.gossip_key { public_keyring_for_validate.add_ref(key); @@ -195,18 +199,18 @@ pub fn try_decrypt( /// storing a new one when one doesn't exist yet. Care is taken to /// only generate one key per context even when multiple threads call /// this function concurrently. -fn load_or_generate_self_public_key( +async fn load_or_generate_self_public_key( context: &Context, self_addr: impl AsRef, ) -> Result { - 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 SignedPublicKey::try_from(key) .map_err(|_| Error::Message("Not a public key".into())); } let _guard = context.generating_key_mutex.lock().unwrap(); // 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) { + if let Some(key) = Key::from_self_public(context, &self_addr, &context.sql).await { return SignedPublicKey::try_from(key) .map_err(|_| Error::Message("Not a public key".into())); } @@ -214,10 +218,10 @@ fn load_or_generate_self_public_key( let start = std::time::Instant::now(); let keygen_type = - KeyGenType::from_i32(context.get_config_int(Config::KeyGenType)).unwrap_or_default(); + KeyGenType::from_i32(context.get_config_int(Config::KeyGenType).await).unwrap_or_default(); info!(context, "Generating keypair with type {}", keygen_type); let keypair = pgp::create_keypair(EmailAddress::new(self_addr.as_ref())?, keygen_type)?; - key::store_self_keypair(context, &keypair, KeyPairUse::Default)?; + key::store_self_keypair(context, &keypair, KeyPairUse::Default).await?; info!( context, "Keypair generated in {:.3}s.", @@ -347,14 +351,18 @@ fn contains_report(mail: &ParsedMail<'_>) -> bool { /// /// If this succeeds you are also guaranteed that the /// [Config::ConfiguredAddr] is configured, this address is returned. -pub fn ensure_secret_key_exists(context: &Context) -> Result { - let self_addr = context.get_config(Config::ConfiguredAddr).ok_or_else(|| { - format_err!(concat!( - "Failed to get self address, ", - "cannot ensure secret key if not configured." - )) - })?; - load_or_generate_self_public_key(context, &self_addr)?; +pub async fn ensure_secret_key_exists(context: &Context) -> Result { + let self_addr = context + .get_config(Config::ConfiguredAddr) + .await + .ok_or_else(|| { + format_err!(concat!( + "Failed to get self address, ", + "cannot ensure secret key if not configured." + )) + })?; + load_or_generate_self_public_key(context, &self_addr).await?; + Ok(self_addr) } @@ -371,7 +379,7 @@ mod tests { async fn test_prexisting() { let t = dummy_context(); let test_addr = configure_alice_keypair(&t.ctx).await; - assert_eq!(ensure_secret_key_exists(&t.ctx).unwrap(), test_addr); + assert_eq!(ensure_secret_key_exists(&t.ctx).await.unwrap(), test_addr); } #[test] @@ -412,7 +420,7 @@ Sent with my Delta Chat Messenger: https://delta.chat"; async fn test_existing() { let t = dummy_context(); let addr = configure_alice_keypair(&t.ctx).await; - let key = load_or_generate_self_public_key(&t.ctx, addr); + let key = load_or_generate_self_public_key(&t.ctx, addr).await; assert!(key.is_ok()); } diff --git a/src/imap/mod.rs b/src/imap/mod.rs index e520e55a7..9cb7018f8 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -367,11 +367,11 @@ impl Imap { if self.is_connected().await && !self.should_reconnect() { return Ok(()); } - if !context.sql.get_raw_config_bool(context, "configured") { + if !context.sql.get_raw_config_bool(context, "configured").await { return Err(Error::ConnectWithoutConfigure); } - let param = LoginParam::from_database(context, "configured_"); + let param = LoginParam::from_database(context, "configured_").await; // the trailing underscore is correct if self.connect(context, ¶m).await { @@ -415,7 +415,7 @@ impl Imap { let teardown = match &mut *self.session.lock().await { Some(ref mut session) => match session.capabilities().await { Ok(caps) => { - if !context.sql.is_open() { + if !context.sql.is_open().await { warn!(context, "IMAP-LOGIN as {} ok but ABORTING", lp.mail_user,); true } else { @@ -465,7 +465,7 @@ impl Imap { } pub async fn fetch(&self, context: &Context, watch_folder: &str) -> Result<()> { - if !context.sql.is_open() { + if !context.sql.is_open().await { // probably shutdown return Err(Error::InTeardown); } @@ -477,9 +477,13 @@ impl Imap { Ok(()) } - fn get_config_last_seen_uid>(&self, context: &Context, folder: S) -> (u32, u32) { + async fn get_config_last_seen_uid>( + &self, + context: &Context, + folder: S, + ) -> (u32, u32) { let key = format!("imap.mailbox.{}", folder.as_ref()); - if let Some(entry) = context.sql.get_raw_config(context, &key) { + if let Some(entry) = context.sql.get_raw_config(context, &key).await { // the entry has the format `imap.mailbox.=:` let mut parts = entry.split(':'); ( @@ -508,7 +512,7 @@ impl Imap { self.select_folder(context, Some(folder)).await?; // compare last seen UIDVALIDITY against the current one - let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder); + let (uid_validity, last_seen_uid) = self.get_config_last_seen_uid(context, &folder).await; let config = self.config.read().await; let mailbox = config @@ -590,8 +594,8 @@ impl Imap { context: &Context, folder: S, ) -> Result { - let show_emails = - ShowEmails::from_i32(context.get_config_int(Config::ShowEmails)).unwrap_or_default(); + let show_emails = ShowEmails::from_i32(context.get_config_int(Config::ShowEmails).await) + .unwrap_or_default(); let (uid_validity, last_seen_uid) = self .select_with_uidvalidity(context, folder.as_ref()) @@ -706,7 +710,7 @@ impl Imap { Ok(read_cnt > 0) } - fn set_config_last_seen_uid>( + async fn set_config_last_seen_uid>( &self, context: &Context, folder: S, @@ -716,7 +720,11 @@ impl Imap { let key = format!("imap.mailbox.{}", folder.as_ref()); let val = format!("{}:{}", uidvalidity, lastseenuid); - context.sql.set_raw_config(context, &key, Some(&val)).ok(); + context + .sql + .set_raw_config(context, &key, Some(&val)) + .await + .ok(); } /// Fetches a single message by server UID. @@ -1080,7 +1088,8 @@ impl Imap { ) -> Result<()> { let folders_configured = context .sql - .get_raw_config_int(context, "folders_configured"); + .get_raw_config_int(context, "folders_configured") + .await; if folders_configured.unwrap_or_default() >= 3 { // the "3" here we increase if we have future updates to // to folder configuration @@ -1168,24 +1177,28 @@ impl Imap { } context .sql - .set_raw_config(context, "configured_inbox_folder", Some("INBOX"))?; + .set_raw_config(context, "configured_inbox_folder", Some("INBOX")) + .await?; if let Some(ref mvbox_folder) = mvbox_folder { - context.sql.set_raw_config( - context, - "configured_mvbox_folder", - Some(mvbox_folder), - )?; + context + .sql + .set_raw_config(context, "configured_mvbox_folder", Some(mvbox_folder)) + .await?; } if let Some(ref sentbox_folder) = sentbox_folder { - context.sql.set_raw_config( - context, - "configured_sentbox_folder", - Some(sentbox_folder.name()), - )?; + context + .sql + .set_raw_config( + context, + "configured_sentbox_folder", + Some(sentbox_folder.name()), + ) + .await?; } context .sql - .set_raw_config_int(context, "folders_configured", 3)?; + .set_raw_config_int(context, "folders_configured", 3) + .await?; } info!(context, "FINISHED configuring IMAP-folders."); Ok(()) diff --git a/src/imex.rs b/src/imex.rs index 8dcf408db..0084038bb 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -81,7 +81,7 @@ pub async fn imex(context: &Context, what: ImexMode, param1: Option) -> Result { +pub async fn has_backup(context: &Context, dir_name: impl AsRef) -> Result { let dir_name = dir_name.as_ref(); let dir_iter = std::fs::read_dir(dir_name)?; let mut newest_backup_time = 0; @@ -93,16 +93,17 @@ pub fn has_backup(context: &Context, dir_name: impl AsRef) -> Result newest_backup_time { newest_backup_path = Some(path); newest_backup_time = curr_backup_time; } info!(context, "backup_time of {} is {}", name, curr_backup_time); - sql.close(&context); + sql.close(&context).await; } } } @@ -125,7 +126,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { let setup_code = create_setup_code(context); /* this may require a keypair to be created. this may take a second ... */ ensure!(!context.shall_stop_ongoing(), "canceled"); - let setup_file_content = render_setup_file(context, &setup_code)?; + let setup_file_content = render_setup_file(context, &setup_code).await?; /* encrypting may also take a while ... */ ensure!(!context.shall_stop_ongoing(), "canceled"); let setup_file_blob = BlobObject::create( @@ -134,7 +135,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { setup_file_content.as_bytes(), )?; - let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF)?; + let chat_id = chat::create_by_contact_id(context, DC_CONTACT_ID_SELF).await?; msg = Message::default(); msg.viewtype = Viewtype::File; msg.param.set(Param::File, setup_file_blob.as_name()); @@ -152,7 +153,7 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { info!(context, "Wait for setup message being sent ...",); while !context.shall_stop_ongoing() { std::thread::sleep(std::time::Duration::from_secs(1)); - if let Ok(msg) = Message::load_from_db(context, msg_id) { + if let Ok(msg) = Message::load_from_db(context, msg_id).await { if msg.is_sent() { info!(context, "... setup message sent.",); break; @@ -170,15 +171,16 @@ async fn do_initiate_key_transfer(context: &Context) -> Result { /// Renders HTML body of a setup file message. /// /// The `passphrase` must be at least 2 characters long. -pub fn render_setup_file(context: &Context, passphrase: &str) -> Result { +pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result { ensure!( passphrase.len() >= 2, "Passphrase must be at least 2 chars long." ); - let self_addr = e2ee::ensure_secret_key_exists(context)?; + let self_addr = e2ee::ensure_secret_key_exists(context).await?; let private_key = Key::from_self_private(context, self_addr, &context.sql) + .await .ok_or_else(|| format_err!("Failed to get private key."))?; - let ac_headers = match context.get_config_bool(Config::E2eeEnabled) { + let ac_headers = match context.get_config_bool(Config::E2eeEnabled).await { false => None, true => Some(("Autocrypt-Prefer-Encrypt", "mutual")), }; @@ -239,8 +241,8 @@ pub fn create_setup_code(_context: &Context) -> String { ret } -fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { - if !context.sql.get_raw_config_bool(context, "bcc_self") { +async fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { + if !context.sql.get_raw_config_bool(context, "bcc_self").await { let mut msg = Message::new(Viewtype::Text); // TODO: define this as a stockstring once the wording is settled. msg.text = Some( @@ -249,15 +251,19 @@ fn maybe_add_bcc_self_device_msg(context: &Context) -> Result<()> { go to the settings and enable \"Send copy to self\"." .to_string(), ); - chat::add_device_msg(context, Some("bcc-self-hint"), Some(&mut msg))?; + chat::add_device_msg(context, Some("bcc-self-hint"), Some(&mut msg)).await?; } Ok(()) } -pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) -> Result<()> { +pub async fn continue_key_transfer( + context: &Context, + msg_id: MsgId, + setup_code: &str, +) -> Result<()> { ensure!(!msg_id.is_special(), "wrong id"); - let msg = Message::load_from_db(context, msg_id)?; + let msg = Message::load_from_db(context, msg_id).await?; ensure!( msg.is_setupmessage(), "Message is no Autocrypt Setup Message." @@ -267,8 +273,8 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) let file = dc_open_file(context, filename)?; let sc = normalize_setup_code(setup_code); let armored_key = decrypt_setup_file(context, &sc, file)?; - set_self_key(context, &armored_key, true, true)?; - maybe_add_bcc_self_device_msg(context)?; + set_self_key(context, &armored_key, true, true).await?; + maybe_add_bcc_self_device_msg(context).await?; Ok(()) } else { @@ -276,7 +282,7 @@ pub fn continue_key_transfer(context: &Context, msg_id: MsgId, setup_code: &str) } } -fn set_self_key( +async fn set_self_key( context: &Context, armored: &str, set_default: bool, @@ -301,7 +307,8 @@ fn set_self_key( }; context .sql - .set_raw_config_int(context, "e2ee_enabled", e2ee_enabled)?; + .set_raw_config_int(context, "e2ee_enabled", e2ee_enabled) + .await?; } None => { if prefer_encrypt_required { @@ -310,7 +317,7 @@ fn set_self_key( } }; - let self_addr = context.get_config(Config::ConfiguredAddr); + let self_addr = context.get_config(Config::ConfiguredAddr).await; ensure!(self_addr.is_some(), "Missing self addr"); let addr = EmailAddress::new(&self_addr.unwrap_or_default())?; @@ -331,7 +338,8 @@ fn set_self_key( } else { key::KeyPairUse::ReadOnly }, - )?; + ) + .await?; Ok(()) } @@ -359,8 +367,7 @@ pub fn normalize_setup_code(s: &str) -> String { out } -#[allow(non_snake_case)] -pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { +pub async fn job_imex_imap(context: &Context, job: &Job) -> Result<()> { ensure!(context.alloc_ongoing(), "could not allocate ongoing"); let what: Option = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32); let param = job.param.get(Param::Arg).unwrap_or_default(); @@ -369,10 +376,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { info!(context, "Import/export process started."); context.call_cb(Event::ImexProgress(10)); - ensure!(context.sql.is_open(), "Database not opened."); + ensure!(context.sql.is_open().await, "Database not opened."); if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) { // before we export anything, make sure the private key exists - if e2ee::ensure_secret_key_exists(context).is_err() { + if e2ee::ensure_secret_key_exists(context).await.is_err() { context.free_ongoing(); bail!("Cannot create private key or private key not available."); } else { @@ -381,10 +388,10 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { } let path = Path::new(param); let success = match what { - Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path), - Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path), - Some(ImexMode::ExportBackup) => export_backup(context, path), - Some(ImexMode::ImportBackup) => import_backup(context, path), + Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path).await, + Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path).await, + Some(ImexMode::ExportBackup) => export_backup(context, path).await, + Some(ImexMode::ImportBackup) => import_backup(context, path).await, None => { bail!("unknown IMEX type"); } @@ -404,7 +411,7 @@ pub fn JobImexImap(context: &Context, job: &Job) -> Result<()> { } /// Import Backup -fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Result<()> { +async fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Result<()> { info!( context, "Import \"{}\" to \"{}\".", @@ -413,7 +420,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Resul ); ensure!( - !context.is_configured(), + !context.is_configured().await, "Cannot import backups to accounts in use." ); context.sql.close(&context); @@ -430,61 +437,71 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Resul /* error already logged */ /* re-open copied database file */ ensure!( - context.sql.open(&context, &context.get_dbfile(), false), + context + .sql + .open(&context, &context.get_dbfile(), false) + .await, "could not re-open db" ); - delete_and_reset_all_device_msgs(&context)?; + delete_and_reset_all_device_msgs(&context).await?; let total_files_cnt = context .sql .query_get_value::<_, isize>(context, "SELECT COUNT(*) FROM backup_blobs;", params![]) + .await .unwrap_or_default() as usize; info!( context, "***IMPORT-in-progress: total_files_cnt={:?}", total_files_cnt, ); - let res = context.sql.query_map( - "SELECT file_name, file_content FROM backup_blobs ORDER BY id;", - params![], - |row| { - let name: String = row.get(0)?; - let blob: Vec = row.get(1)?; + let res = context + .sql + .query_map( + "SELECT file_name, file_content FROM backup_blobs ORDER BY id;", + params![], + |row| { + let name: String = row.get(0)?; + let blob: Vec = row.get(1)?; - Ok((name, blob)) - }, - |files| { - for (processed_files_cnt, file) in files.enumerate() { - let (file_name, file_blob) = file?; - if context.shall_stop_ongoing() { - return Ok(false); - } - let mut permille = processed_files_cnt * 1000 / total_files_cnt; - if permille < 10 { - permille = 10 - } - if permille > 990 { - permille = 990 - } - context.call_cb(Event::ImexProgress(permille)); - if file_blob.is_empty() { - continue; - } + Ok((name, blob)) + }, + |files| { + for (processed_files_cnt, file) in files.enumerate() { + let (file_name, file_blob) = file?; + if context.shall_stop_ongoing() { + return Ok(false); + } + let mut permille = processed_files_cnt * 1000 / total_files_cnt; + if permille < 10 { + permille = 10 + } + if permille > 990 { + permille = 990 + } + context.call_cb(Event::ImexProgress(permille)); + if file_blob.is_empty() { + continue; + } - let path_filename = context.get_blobdir().join(file_name); - dc_write_file(context, &path_filename, &file_blob)?; - } - Ok(true) - }, - ); + let path_filename = context.get_blobdir().join(file_name); + dc_write_file(context, &path_filename, &file_blob)?; + } + Ok(true) + }, + ) + .await; match res { Ok(all_files_extracted) => { if all_files_extracted { // only delete backup_blobs if all files were successfully extracted - sql::execute(context, &context.sql, "DROP TABLE backup_blobs;", params![])?; - sql::try_execute(context, &context.sql, "VACUUM;").ok(); + context + .sql + .execute("DROP TABLE backup_blobs;", params![]) + .await?; + context.sql.execute("VACUUM;", params![]).await.ok(); Ok(()) } else { bail!("received stop signal"); @@ -499,7 +516,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef) -> Resul ******************************************************************************/ /* the FILE_PROGRESS macro calls the callback with the permille of files processed. The macro avoids weird values of 0% or 100% while still working. */ -fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { +async fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { // get a fine backup file name (the name includes the date so that multiple backup instances are possible) // FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. // let dest_path_filename = dc_get_next_backup_file(context, dir, res); @@ -507,9 +524,9 @@ fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { let dest_path_filename = dc_get_next_backup_path(dir, now)?; let dest_path_string = dest_path_filename.to_string_lossy().to_string(); - sql::housekeeping(context); + sql::housekeeping(context).await; - sql::try_execute(context, &context.sql, "VACUUM;").ok(); + context.sql.execute("VACUUM;", params![]).await.ok(); // we close the database during the copy of the dbfile context.sql.close(context); @@ -531,18 +548,20 @@ fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { } let dest_sql = Sql::new(); ensure!( - dest_sql.open(context, &dest_path_filename, false), + dest_sql.open(context, &dest_path_filename, false).await, "could not open exported database {}", dest_path_string ); - let res = match add_files_to_export(context, &dest_sql) { + let res = match add_files_to_export(context, &dest_sql).await { Err(err) => { dc_delete_file(context, &dest_path_filename); error!(context, "backup failed: {}", err); Err(err) } Ok(()) => { - dest_sql.set_raw_config_int(context, "backup_time", now as i32)?; + dest_sql + .set_raw_config_int(context, "backup_time", now as i32) + .await?; context.call_cb(Event::ImexFileWritten(dest_path_filename)); Ok(()) } @@ -552,16 +571,15 @@ fn export_backup(context: &Context, dir: impl AsRef) -> Result<()> { Ok(res?) } -fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { +async fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { // add all files as blobs to the database copy (this does not require // the source to be locked, neigher the destination as it is used only here) - if !sql.table_exists("backup_blobs") { - sql::execute( - context, - &sql, + if !sql.table_exists("backup_blobs").await? { + sql.execute( "CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);", params![], - )? + ) + .await?; } // copy all files from BLOBDIR into backup-db let mut total_files_cnt = 0; @@ -570,47 +588,50 @@ fn add_files_to_export(context: &Context, sql: &Sql) -> Result<()> { total_files_cnt += dir_handle.filter(|r| r.is_ok()).count(); info!(context, "EXPORT: total_files_cnt={}", total_files_cnt); - // scan directory, pass 2: copy files - let dir_handle = std::fs::read_dir(&dir)?; - let exported_all_files = sql.prepare( - "INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);", - |mut stmt, _| { - let mut processed_files_cnt = 0; - for entry in dir_handle { - let entry = entry?; - if context.shall_stop_ongoing() { - return Ok(false); - } - processed_files_cnt += 1; - let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); - context.call_cb(Event::ImexProgress(permille)); - let name_f = entry.file_name(); - let name = name_f.to_string_lossy(); - if name.starts_with("delta-chat") && name.ends_with(".bak") { + sql.with_conn(|conn| { + // scan directory, pass 2: copy files + let dir_handle = std::fs::read_dir(&dir)?; + + let mut stmt = conn + .prepare_cached("INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);")?; + + let mut processed_files_cnt = 0; + for entry in dir_handle { + let entry = entry?; + if context.shall_stop_ongoing() { + return Ok(()); + } + processed_files_cnt += 1; + let permille = max(min(processed_files_cnt * 1000 / total_files_cnt, 990), 10); + context.call_cb(Event::ImexProgress(permille)); + + let name_f = entry.file_name(); + let name = name_f.to_string_lossy(); + if name.starts_with("delta-chat") && name.ends_with(".bak") { + continue; + } + info!(context, "EXPORT: copying filename={}", name); + let curr_path_filename = context.get_blobdir().join(entry.file_name()); + if let Ok(buf) = dc_read_file(context, &curr_path_filename) { + if buf.is_empty() { continue; } - info!(context, "EXPORT: copying filename={}", name); - let curr_path_filename = context.get_blobdir().join(entry.file_name()); - if let Ok(buf) = dc_read_file(context, &curr_path_filename) { - if buf.is_empty() { - continue; - } - // bail out if we can't insert - stmt.execute(params![name, buf])?; - } + // bail out if we can't insert + stmt.execute(params![name, buf])?; } - Ok(true) - }, - )?; - ensure!(exported_all_files, "canceled during export-files"); + } + Ok(()) + }) + .await?; + Ok(()) } /******************************************************************************* * Classic key import ******************************************************************************/ -fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { +async fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { /* hint: even if we switch to import Autocrypt Setup Files, we should leave the possibility to import plain ASC keys, at least keys without a password, if we do not want to implement a password entry function. Importing ASC keys is useful to use keys in Delta Chat used by any other non-Autocrypt-PGP implementation. @@ -645,7 +666,7 @@ fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { match dc_read_file(context, &path_plus_name) { Ok(buf) => { let armored = std::string::String::from_utf8_lossy(&buf); - if let Err(err) = set_self_key(context, &armored, set_default, false) { + if let Err(err) = set_self_key(context, &armored, set_default, false).await { error!(context, "set_self_key: {}", err); continue; } @@ -662,45 +683,48 @@ fn import_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { Ok(()) } -fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { +async fn export_self_keys(context: &Context, dir: impl AsRef) -> Result<()> { let mut export_errors = 0; - context.sql.query_map( - "SELECT id, public_key, private_key, is_default FROM keypairs;", - params![], - |row| { - let id = row.get(0)?; - let public_key_blob: Vec = row.get(1)?; - let public_key = Key::from_slice(&public_key_blob, KeyType::Public); - let private_key_blob: Vec = row.get(2)?; - let private_key = Key::from_slice(&private_key_blob, KeyType::Private); - let is_default: i32 = row.get(3)?; + context + .sql + .query_map( + "SELECT id, public_key, private_key, is_default FROM keypairs;", + params![], + |row| { + let id = row.get(0)?; + let public_key_blob: Vec = row.get(1)?; + let public_key = Key::from_slice(&public_key_blob, KeyType::Public); + let private_key_blob: Vec = row.get(2)?; + let private_key = Key::from_slice(&private_key_blob, KeyType::Private); + let is_default: i32 = row.get(3)?; - Ok((id, public_key, private_key, is_default)) - }, - |keys| { - for key_pair in keys { - let (id, public_key, private_key, is_default) = key_pair?; - let id = Some(id).filter(|_| is_default != 0); - if let Some(key) = public_key { - if export_key_to_asc_file(context, &dir, id, &key).is_err() { + Ok((id, public_key, private_key, is_default)) + }, + |keys| { + for key_pair in keys { + let (id, public_key, private_key, is_default) = key_pair?; + let id = Some(id).filter(|_| is_default != 0); + if let Some(key) = public_key { + if export_key_to_asc_file(context, &dir, id, &key).is_err() { + export_errors += 1; + } + } else { export_errors += 1; } - } else { - export_errors += 1; - } - if let Some(key) = private_key { - if export_key_to_asc_file(context, &dir, id, &key).is_err() { + if let Some(key) = private_key { + if export_key_to_asc_file(context, &dir, id, &key).is_err() { + export_errors += 1; + } + } else { export_errors += 1; } - } else { - export_errors += 1; } - } - Ok(()) - }, - )?; + Ok(()) + }, + ) + .await?; ensure!(export_errors == 0, "errors while exporting keys"); Ok(()) @@ -745,7 +769,7 @@ mod tests { let t = test_context(Some(Box::new(logging_cb))); configure_alice_keypair(&t.ctx).await; - let msg = render_setup_file(&t.ctx, "hello").unwrap(); + let msg = render_setup_file(&t.ctx, "hello").await.unwrap(); println!("{}", &msg); // Check some substrings, indicating things got substituted. // In particular note the mixing of `\r\n` and `\n` depending @@ -767,7 +791,7 @@ mod tests { .set_stock_translation(StockMessage::AcSetupMsgBody, "hello\r\nthere".to_string()) .unwrap(); configure_alice_keypair(&t.ctx).await; - let msg = render_setup_file(&t.ctx, "pw").unwrap(); + let msg = render_setup_file(&t.ctx, "pw").await.unwrap(); println!("{}", &msg); assert!(msg.contains("

hello
there

")); } diff --git a/src/job.rs b/src/job.rs index f7b13557f..19ebc75e0 100644 --- a/src/job.rs +++ b/src/job.rs @@ -150,6 +150,7 @@ impl Job { context .sql .execute("DELETE FROM jobs WHERE id=?;", params![self.job_id as i32]) + .await .is_ok() } @@ -157,18 +158,19 @@ impl Job { /// /// To add a new job, use [job_add]. async fn update(&self, context: &Context) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", - params![ - self.desired_timestamp, - self.tries as i64, - self.param.to_string(), - self.job_id as i32, - ], - ) - .is_ok() + context + .sql + .execute( + "UPDATE jobs SET desired_timestamp=?, tries=?, param=? WHERE id=?;", + params![ + self.desired_timestamp, + self.tries as i64, + self.param.to_string(), + self.job_id as i32, + ], + ) + .await + .is_ok() } async fn smtp_send( @@ -247,7 +249,7 @@ impl Job { async fn send_msg_to_smtp(&mut self, context: &Context) -> Status { // connect to SMTP server, if not yet done if !context.smtp.is_connected().await { - let loginparam = LoginParam::from_database(context, "configured_"); + let loginparam = LoginParam::from_database(context, "configured_").await; if let Err(err) = context.smtp.connect(context, &loginparam).await { warn!(context, "SMTP connection failure: {:?}", err); return Status::RetryLater; @@ -310,32 +312,35 @@ impl Job { contact_id: u32, ) -> sql::Result<(Vec, Vec)> { // Extract message IDs from job parameters - let res: Vec<(u32, MsgId)> = context.sql.query_map( - "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", - params![contact_id, self.job_id], - |row| { - let job_id: u32 = row.get(0)?; - let params_str: String = row.get(1)?; - let params: Params = params_str.parse().unwrap_or_default(); - Ok((job_id, params)) - }, - |jobs| { - let res = jobs - .filter_map(|row| { - let (job_id, params) = row.ok()?; - let msg_id = params.get_msg_id()?; - Some((job_id, msg_id)) - }) - .collect(); - Ok(res) - }, - )?; + let res: Vec<(u32, MsgId)> = context + .sql + .query_map( + "SELECT id, param FROM jobs WHERE foreign_id=? AND id!=?", + params![contact_id, self.job_id], + |row| { + let job_id: u32 = row.get(0)?; + let params_str: String = row.get(1)?; + let params: Params = params_str.parse().unwrap_or_default(); + Ok((job_id, params)) + }, + |jobs| { + let res = jobs + .filter_map(|row| { + let (job_id, params) = row.ok()?; + let msg_id = params.get_msg_id()?; + Some((job_id, msg_id)) + }) + .collect(); + Ok(res) + }, + ) + .await?; // Load corresponding RFC724 message IDs let mut job_ids = Vec::new(); let mut rfc724_mids = Vec::new(); for (job_id, msg_id) in res { - if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id) { + if let Ok(Message { rfc724_mid, .. }) = Message::load_from_db(context, msg_id).await { job_ids.push(job_id); rfc724_mids.push(rfc724_mid); } @@ -344,14 +349,14 @@ impl Job { } async fn send_mdn(&mut self, context: &Context) -> Status { - if !context.get_config_bool(Config::MdnsEnabled) { + if !context.get_config_bool(Config::MdnsEnabled).await { // User has disabled MDNs after job scheduling but before // execution. return Status::Finished(Err(format_err!("MDNs are disabled"))); } let contact_id = self.foreign_id; - let contact = job_try!(Contact::load_from_db(context, contact_id)); + let contact = job_try!(Contact::load_from_db(context, contact_id).await); if contact.is_blocked() { return Status::Finished(Err(format_err!("Contact is blocked"))); } @@ -379,7 +384,7 @@ impl Job { ) } - let msg = job_try!(Message::load_from_db(context, msg_id)); + let msg = job_try!(Message::load_from_db(context, msg_id).await); let mimefactory = job_try!(MimeFactory::from_mdn(context, &msg, additional_rfc724_mids)); let rendered_msg = job_try!(mimefactory.render()); let body = rendered_msg.message; @@ -391,7 +396,7 @@ impl Job { // connect to SMTP server, if not yet done if !context.smtp.is_connected().await { - let loginparam = LoginParam::from_database(context, "configured_"); + let loginparam = LoginParam::from_database(context, "configured_").await; if let Err(err) = context.smtp.connect(context, &loginparam).await { warn!(context, "SMTP connection failure: {:?}", err); return Status::RetryLater; @@ -411,7 +416,7 @@ impl Job { async fn move_msg(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.imap; - let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); + let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); if let Err(err) = imap_inbox.ensure_configured_folders(context, true).await { warn!(context, "could not configure folders: {:?}", err); @@ -419,7 +424,8 @@ impl Job { } let dest_folder = context .sql - .get_raw_config(context, "configured_mvbox_folder"); + .get_raw_config(context, "configured_mvbox_folder") + .await; if let Some(dest_folder) = dest_folder { let server_folder = msg.server_folder.as_ref().unwrap(); @@ -453,7 +459,7 @@ impl Job { async fn delete_msg_on_imap(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.imap; - let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); + let mut msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); if !msg.rfc724_mid.is_empty() { if message::rfc724_mid_cnt(context, &msg.rfc724_mid) > 1 { @@ -488,6 +494,7 @@ impl Job { if let Some(mvbox_folder) = context .sql .get_raw_config(context, "configured_mvbox_folder") + .await { imap_inbox.empty_folder(context, &mvbox_folder).await; } @@ -501,7 +508,7 @@ impl Job { async fn markseen_msg_on_imap(&mut self, context: &Context) -> Status { let imap_inbox = &context.inbox_thread.imap; - let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id))); + let msg = job_try!(Message::load_from_db(context, MsgId::new(self.foreign_id)).await); let folder = msg.server_folder.as_ref().unwrap(); match imap_inbox.set_seen(context, folder, msg.server_uid).await { @@ -513,7 +520,7 @@ impl Job { // The job will not be retried so locally // there is no risk of double-sending MDNs. if msg.param.get_bool(Param::WantsMdn).unwrap_or_default() - && context.get_config_bool(Config::MdnsEnabled) + && context.get_config_bool(Config::MdnsEnabled).await { if let Err(err) = send_mdn(context, &msg).await { warn!(context, "could not send out mdn for {}: {}", msg.id, err); @@ -543,7 +550,8 @@ impl Job { } let dest_folder = context .sql - .get_raw_config(context, "configured_mvbox_folder"); + .get_raw_config(context, "configured_mvbox_folder") + .await; if let Some(dest_folder) = dest_folder { let mut dest_uid = 0; if ImapActionResult::RetryLater @@ -566,67 +574,70 @@ impl Job { /// Delete all pending jobs with the given action. pub async fn kill_action(context: &Context, action: Action) -> bool { - sql::execute( - context, - &context.sql, - "DELETE FROM jobs WHERE action=?;", - params![action], - ) - .is_ok() + context + .sql + .execute("DELETE FROM jobs WHERE action=?;", params![action]) + .await + .is_ok() } /// Remove jobs with specified IDs. pub async fn kill_ids(context: &Context, job_ids: &[u32]) -> sql::Result<()> { - sql::execute( - context, - &context.sql, - format!( - "DELETE FROM jobs WHERE id IN({})", - job_ids.iter().map(|_| "?").join(",") - ), - job_ids, - ) + context + .sql + .execute( + format!( + "DELETE FROM jobs WHERE id IN({})", + job_ids.iter().map(|_| "?").join(",") + ), + job_ids, + ) + .await?; + Ok(()) } pub async fn perform_inbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::InboxWatch); + let use_network = context.get_config_bool(Config::InboxWatch).await; context.inbox_thread.fetch(context, use_network).await; } pub async fn perform_mvbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::MvboxWatch); + let use_network = context.get_config_bool(Config::MvboxWatch).await; context.mvbox_thread.fetch(context, use_network).await; } pub async fn perform_sentbox_fetch(context: &Context) { - let use_network = context.get_config_bool(Config::SentboxWatch); + let use_network = context.get_config_bool(Config::SentboxWatch).await; context.sentbox_thread.fetch(context, use_network).await; } pub async fn perform_inbox_idle(context: &Context) { - if *context.perform_inbox_jobs_needed.clone().read().unwrap() { + if context + .perform_inbox_jobs_needed + .load(std::sync::atomic::Ordering::Relaxed) + { info!( context, "INBOX-IDLE will not be started because of waiting jobs." ); return; } - let use_network = context.get_config_bool(Config::InboxWatch); + let use_network = context.get_config_bool(Config::InboxWatch).await; context.inbox_thread.idle(context, use_network).await; } pub async fn perform_mvbox_idle(context: &Context) { - let use_network = context.get_config_bool(Config::MvboxWatch); + let use_network = context.get_config_bool(Config::MvboxWatch).await; context.mvbox_thread.idle(context, use_network).await; } pub async fn perform_sentbox_idle(context: &Context) { - let use_network = context.get_config_bool(Config::SentboxWatch); + let use_network = context.get_config_bool(Config::SentboxWatch).await; context.sentbox_thread.idle(context, use_network).await; } @@ -638,7 +649,9 @@ pub async fn interrupt_inbox_idle(context: &Context) { // If it's currently fetching then we can not get the lock // but we flag it for checking jobs so that idle will be skipped. if !context.inbox_thread.try_interrupt_idle(context).await { - *context.perform_inbox_jobs_needed.write().unwrap() = true; + context + .perform_inbox_jobs_needed + .store(true, std::sync::atomic::Ordering::Relaxed); warn!(context, "could not interrupt idle"); } } @@ -687,7 +700,7 @@ pub async fn perform_smtp_idle(context: &Context) { ); } PerformJobsNeeded::Not | PerformJobsNeeded::AvoidDos => { - let dur = get_next_wakeup_time(context, Thread::Smtp); + let dur = get_next_wakeup_time(context, Thread::Smtp).await; context.smtp.notify_receiver.recv().timeout(dur).await.ok(); } @@ -696,7 +709,7 @@ pub async fn perform_smtp_idle(context: &Context) { info!(context, "SMTP-idle ended.",); } -fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { +async fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { let t: i64 = context .sql .query_get_value( @@ -704,6 +717,7 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { "SELECT MIN(desired_timestamp) FROM jobs WHERE thread=?;", params![thread], ) + .await .unwrap_or_default(); let mut wakeup_time = time::Duration::new(10 * 60, 0); @@ -722,7 +736,9 @@ fn get_next_wakeup_time(context: &Context, thread: Thread) -> time::Duration { pub async fn maybe_network(context: &Context) { { context.smtp.state.write().await.probe_network = true; - *context.probe_imap_network.write().unwrap() = true; + context + .probe_imap_network + .store(true, std::sync::atomic::Ordering::Relaxed); } interrupt_smtp_idle(context).await; @@ -731,14 +747,15 @@ pub async fn maybe_network(context: &Context) { interrupt_sentbox_idle(context).await; } -pub fn action_exists(context: &Context, action: Action) -> bool { +pub async fn action_exists(context: &Context, action: Action) -> bool { context .sql .exists("SELECT id FROM jobs WHERE action=?;", params![action]) + .await .unwrap_or_default() } -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); let chat_id: ChatId = context .sql @@ -747,19 +764,20 @@ fn set_delivered(context: &Context, msg_id: MsgId) { "SELECT chat_id FROM msgs WHERE id=?", params![msg_id], ) + .await .unwrap_or_default(); context.call_cb(Event::MsgDelivered { chat_id, msg_id }); } // special case for DC_JOB_SEND_MSG_TO_SMTP pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { - let mut msg = Message::load_from_db(context, msg_id)?; + let mut msg = Message::load_from_db(context, msg_id).await?; msg.try_calc_and_set_dimensions(context).ok(); /* create message */ let needs_encryption = msg.param.get_bool(Param::GuaranteeE2ee).unwrap_or_default(); - let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id) { + let attach_selfavatar = match chat::shall_attach_selfavatar(context, msg.chat_id).await { Ok(attach_selfavatar) => attach_selfavatar, Err(err) => { warn!(context, "job: cannot get selfavatar-state: {}", err); @@ -773,9 +791,10 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { let from = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); let lowercase_from = from.to_lowercase(); - if context.get_config_bool(Config::BccSelf) + if context.get_config_bool(Config::BccSelf).await && !recipients .iter() .any(|x| x.to_lowercase() == lowercase_from) @@ -813,16 +832,17 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { } if rendered_msg.is_gossiped { - chat::set_gossiped_timestamp(context, msg.chat_id, time())?; + chat::set_gossiped_timestamp(context, msg.chat_id, time()).await?; } if 0 != rendered_msg.last_added_location_id { - if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()) { + if let Err(err) = location::set_kml_sent_timestamp(context, msg.chat_id, time()).await { error!(context, "Failed to set kml sent_timestamp: {:?}", err); } if !msg.hidden { if let Err(err) = location::set_msg_location_id(context, msg.id, rendered_msg.last_added_location_id) + .await { error!(context, "Failed to set msg_location_id: {:?}", err); } @@ -830,7 +850,7 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { } if attach_selfavatar { - if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()) { + if let Err(err) = msg.chat_id.set_selfavatar_timestamp(context, time()).await { error!(context, "Failed to set selfavatar timestamp: {:?}", err); } } @@ -855,9 +875,15 @@ pub async fn send_msg(context: &Context, msg_id: MsgId) -> Result<()> { pub async fn perform_inbox_jobs(context: &Context) { info!(context, "dc_perform_inbox_jobs starting.",); - let probe_imap_network = *context.probe_imap_network.clone().read().unwrap(); - *context.probe_imap_network.write().unwrap() = false; - *context.perform_inbox_jobs_needed.write().unwrap() = false; + let probe_imap_network = context + .probe_imap_network + .load(std::sync::atomic::Ordering::Relaxed); + context + .probe_imap_network + .store(false, std::sync::atomic::Ordering::Relaxed); + context + .perform_inbox_jobs_needed + .store(false, std::sync::atomic::Ordering::Relaxed); job_perform(context, Thread::Imap, probe_imap_network).await; info!(context, "dc_perform_inbox_jobs ended.",); @@ -872,7 +898,7 @@ pub async fn perform_sentbox_jobs(context: &Context) { } async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { - while let Some(mut job) = load_next_job(context, thread, probe_network) { + while let Some(mut job) = load_next_job(context, thread, probe_network).await { info!(context, "{}-job {} started...", thread, job); // some configuration jobs are "exclusive": @@ -891,77 +917,77 @@ async fn job_perform(context: &Context, thread: Thread, probe_network: bool) { x => x, }; - if Action::ConfigureImap == job.action || Action::ImexImap == job.action { - context.sentbox_thread.unsuspend(context).await; - context.mvbox_thread.unsuspend(context).await; - suspend_smtp_thread(context, false).await; - break; - } + // if Action::ConfigureImap == job.action || Action::ImexImap == job.action { + // context.sentbox_thread.unsuspend(context).await; + // context.mvbox_thread.unsuspend(context).await; + // suspend_smtp_thread(context, false).await; + // break; + // } - match try_res { - Status::RetryNow | Status::RetryLater => { - let tries = job.tries + 1; + // match try_res { + // Status::RetryNow | Status::RetryLater => { + // let tries = job.tries + 1; - if tries < JOB_RETRIES { - info!( - context, - "{} thread increases job {} tries to {}", thread, job, tries - ); - job.tries = tries; - let time_offset = get_backoff_time_offset(tries); - job.desired_timestamp = time() + time_offset; - job.update(context).await; - info!( - context, - "{}-job #{} not succeeded on try #{}, retry in {} seconds.", - thread, - job.job_id as u32, - tries, - time_offset - ); - if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { - context.smtp.state.write().await.perform_jobs_needed = - PerformJobsNeeded::AvoidDos; - } - } else { - info!( - context, - "{} thread removes job {} as it exhausted {} retries", - thread, - job, - JOB_RETRIES - ); - if job.action == Action::SendMsgToSmtp { - message::set_msg_failed( - context, - MsgId::new(job.foreign_id), - job.pending_error.as_ref(), - ); - } - job.delete(context).await; - } - if !probe_network { - continue; - } - // on dc_maybe_network() we stop trying here; - // these jobs are already tried once. - // otherwise, we just continue with the next job - // to give other jobs a chance being tried at least once. - break; - } - Status::Finished(res) => { - if let Err(err) = res { - warn!( - context, - "{} removes job {} as it failed with error {:?}", thread, job, err - ); - } else { - info!(context, "{} removes job {} as it succeeded", thread, job); - } + // if tries < JOB_RETRIES { + // info!( + // context, + // "{} thread increases job {} tries to {}", thread, job, tries + // ); + // job.tries = tries; + // let time_offset = get_backoff_time_offset(tries); + // job.desired_timestamp = time() + time_offset; + // job.update(context).await; + // info!( + // context, + // "{}-job #{} not succeeded on try #{}, retry in {} seconds.", + // thread, + // job.job_id as u32, + // tries, + // time_offset + // ); + // if thread == Thread::Smtp && tries < JOB_RETRIES - 1 { + // context.smtp.state.write().await.perform_jobs_needed = + // PerformJobsNeeded::AvoidDos; + // } + // } else { + // info!( + // context, + // "{} thread removes job {} as it exhausted {} retries", + // thread, + // job, + // JOB_RETRIES + // ); + // if job.action == Action::SendMsgToSmtp { + // message::set_msg_failed( + // context, + // MsgId::new(job.foreign_id), + // job.pending_error.as_ref(), + // ); + // } + // job.delete(context).await; + // } + // if !probe_network { + // continue; + // } + // // on dc_maybe_network() we stop trying here; + // // these jobs are already tried once. + // // otherwise, we just continue with the next job + // // to give other jobs a chance being tried at least once. + // break; + // } + // Status::Finished(res) => { + // if let Err(err) = res { + // warn!( + // context, + // "{} removes job {} as it failed with error {:?}", thread, job, err + // ); + // } else { + // info!(context, "{} removes job {} as it succeeded", thread, job); + // } - job.delete(context).await; - } - } + // job.delete(context).await; + // } + // } } } @@ -986,7 +1012,7 @@ async fn perform_job_action( Action::MoveMsg => job.move_msg(context).await, Action::SendMdn => job.send_mdn(context).await, Action::ConfigureImap => job_configure_imap(context).await, - Action::ImexImap => match JobImexImap(context, &job) { + Action::ImexImap => match job_imex_imap(context, &job).await { Ok(()) => Status::Finished(Ok(())), Err(err) => { error!(context, "{}", err); @@ -994,7 +1020,9 @@ async fn perform_job_action( } }, Action::MaybeSendLocations => location::job_maybe_send_locations(context, &job).await, - Action::MaybeSendLocationsEnded => location::JobMaybeSendLocationsEnded(context, &mut job), + Action::MaybeSendLocationsEnded => { + location::job_maybe_send_locations_ended(context, &mut job).await + } Action::Housekeeping => { sql::housekeeping(context); Status::Finished(Ok(())) @@ -1078,9 +1106,7 @@ pub async fn add( let timestamp = time(); let thread: Thread = action.into(); - sql::execute( - context, - &context.sql, + context.sql.execute( "INSERT INTO jobs (added_timestamp, thread, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?,?);", params![ timestamp, @@ -1090,7 +1116,7 @@ pub async fn add( param.to_string(), (timestamp + delay_seconds as i64) ] - ).ok(); + ).await.ok(); match thread { Thread::Imap => interrupt_inbox_idle(context).await, @@ -1114,7 +1140,7 @@ pub async fn interrupt_smtp_idle(context: &Context) { /// IMAP jobs. The `probe_network` parameter decides how to query /// jobs, this is tricky and probably wrong currently. Look at the /// SQL queries for details. -fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option { +async fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Option { let query = if !probe_network { // processing for first-try and after backoff-timeouts: // process jobs in the order they were added. @@ -1165,6 +1191,7 @@ fn load_next_job(context: &Context, thread: Thread, probe_network: bool) -> Opti Ok(None) }, ) + .await .unwrap_or_default() } diff --git a/src/job_thread.rs b/src/job_thread.rs index a39c34f44..2d3f843f9 100644 --- a/src/job_thread.rs +++ b/src/job_thread.rs @@ -115,7 +115,7 @@ impl JobThread { let prefix = format!("{}-fetch", self.name); match self.imap.connect_configured(context).await { Ok(()) => { - if let Some(watch_folder) = self.get_watch_folder(context) { + if let Some(watch_folder) = self.get_watch_folder(context).await { let start = std::time::Instant::now(); info!(context, "{} started...", prefix); let res = self @@ -135,8 +135,12 @@ impl JobThread { } } - fn get_watch_folder(&self, context: &Context) -> Option { - match context.sql.get_raw_config(context, self.folder_config_name) { + async fn get_watch_folder(&self, context: &Context) -> Option { + match context + .sql + .get_raw_config(context, self.folder_config_name) + .await + { Some(name) => Some(name), None => { if self.folder_config_name == "configured_inbox_folder" { @@ -184,7 +188,7 @@ impl JobThread { if !self.imap.can_idle().await { true // we have to do fake_idle } else { - let watch_folder = self.get_watch_folder(context); + let watch_folder = self.get_watch_folder(context).await; info!(context, "{} started...", prefix); let res = self.imap.idle(context, watch_folder).await; info!(context, "{} ended...", prefix); @@ -205,7 +209,7 @@ impl JobThread { } }; if do_fake_idle { - let watch_folder = self.get_watch_folder(context); + let watch_folder = self.get_watch_folder(context).await; self.imap.fake_idle(context, watch_folder).await; } diff --git a/src/key.rs b/src/key.rs index 243f09e2c..0429f3a58 100644 --- a/src/key.rs +++ b/src/key.rs @@ -197,7 +197,7 @@ impl Key { } } - pub fn from_self_public( + pub async fn from_self_public( context: &Context, self_addr: impl AsRef, sql: &Sql, @@ -209,10 +209,11 @@ impl Key { "SELECT public_key FROM keypairs WHERE addr=? AND is_default=1;", &[addr], ) + .await .and_then(|blob: Vec| Self::from_slice(&blob, KeyType::Public)) } - pub fn from_self_private( + pub async fn from_self_private( context: &Context, self_addr: impl AsRef, sql: &Sql, @@ -222,6 +223,7 @@ impl Key { "SELECT private_key FROM keypairs WHERE addr=? AND is_default=1;", &[self_addr.as_ref()], ) + .await .and_then(|blob: Vec| Self::from_slice(&blob, KeyType::Private)) } @@ -347,7 +349,7 @@ impl SaveKeyError { /// same key again overwrites it. /// /// [Config::ConfiguredAddr]: crate::config::Config::ConfiguredAddr -pub fn store_self_keypair( +pub async fn store_self_keypair( context: &Context, keypair: &KeyPair, default: KeyPairUse, @@ -368,11 +370,13 @@ pub fn store_self_keypair( "DELETE FROM keypairs WHERE public_key=? OR private_key=?;", params![public_key, secret_key], ) + .await .map_err(|err| SaveKeyError::new("failed to remove old use of key", err))?; if default == KeyPairUse::Default { context .sql .execute("UPDATE keypairs SET is_default=0;", params![]) + .await .map_err(|err| SaveKeyError::new("failed to clear default", err))?; } let is_default = match default { @@ -392,6 +396,7 @@ pub fn store_self_keypair( time() ], ) + .await .map(|_| ()) .map_err(|err| SaveKeyError::new("failed to insert keypair", err)) } diff --git a/src/keyring.rs b/src/keyring.rs index 25d197cd3..4009a0591 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -27,7 +27,7 @@ impl<'a> Keyring<'a> { &self.keys } - pub fn load_self_private_for_decrypting( + pub async fn load_self_private_for_decrypting( &mut self, context: &Context, self_addr: impl AsRef, @@ -38,6 +38,7 @@ impl<'a> Keyring<'a> { "SELECT private_key FROM keypairs ORDER BY addr=? DESC, is_default DESC;", &[self_addr.as_ref()], ) + .await .and_then(|blob: Vec| Key::from_slice(&blob, KeyType::Private)) .map(|key| self.add_owned(key)) .is_some() diff --git a/src/location.rs b/src/location.rs index b494957ae..1ca5cd3f4 100644 --- a/src/location.rs +++ b/src/location.rs @@ -15,7 +15,6 @@ use crate::job::{self, Job}; use crate::message::{Message, MsgId}; use crate::mimeparser::SystemMessage; use crate::param::*; -use crate::sql; use crate::stock::StockMessage; /// Location record @@ -195,21 +194,22 @@ impl Kml { pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: i64) { let now = time(); if !(seconds < 0 || chat_id.is_special()) { - let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id); - if sql::execute( - context, - &context.sql, - "UPDATE chats \ + let is_sending_locations_before = is_sending_locations_to_chat(context, chat_id).await; + if context + .sql + .execute( + "UPDATE chats \ SET locations_send_begin=?, \ locations_send_until=? \ WHERE id=?", - params![ - if 0 != seconds { now } else { 0 }, - if 0 != seconds { now + seconds } else { 0 }, - chat_id, - ], - ) - .is_ok() + params![ + if 0 != seconds { now } else { 0 }, + if 0 != seconds { now + seconds } else { 0 }, + chat_id, + ], + ) + .await + .is_ok() { if 0 != seconds && !is_sending_locations_before { let mut msg = Message::new(Viewtype::Text); @@ -241,7 +241,7 @@ pub async fn send_locations_to_chat(context: &Context, chat_id: ChatId, seconds: } async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) { - if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations) { + if force_schedule || !job::action_exists(context, job::Action::MaybeSendLocations).await { job::add( context, job::Action::MaybeSendLocations, @@ -253,13 +253,14 @@ async fn schedule_maybe_send_locations(context: &Context, force_schedule: bool) }; } -pub fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool { +pub async fn is_sending_locations_to_chat(context: &Context, chat_id: ChatId) -> bool { context .sql .exists( "SELECT id FROM chats WHERE (? OR id=?) AND locations_send_until>?;", params![if chat_id.is_unset() { 1 } else { 0 }, chat_id, time()], ) + .await .unwrap_or_default() } @@ -269,12 +270,16 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 } let mut continue_streaming = false; - if let Ok(chats) = context.sql.query_map( - "SELECT id FROM chats WHERE locations_send_until>?;", - params![time()], - |row| row.get::<_, i32>(0), - |chats| chats.collect::, _>>().map_err(Into::into), - ) { + if let Ok(chats) = context + .sql + .query_map( + "SELECT id FROM chats WHERE locations_send_until>?;", + params![time()], + |row| row.get::<_, i32>(0), + |chats| chats.collect::, _>>().map_err(Into::into), + ) + .await + { for chat_id in chats { if let Err(err) = context.sql.execute( "INSERT INTO locations \ @@ -287,7 +292,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 chat_id, DC_CONTACT_ID_SELF, ] - ) { + ).await { warn!(context, "failed to store location {:?}", err); } else { continue_streaming = true; @@ -302,7 +307,7 @@ pub async fn set(context: &Context, latitude: f64, longitude: f64, accuracy: f64 continue_streaming } -pub fn get_range( +pub async fn get_range( context: &Context, chat_id: ChatId, contact_id: u32, @@ -360,6 +365,7 @@ pub fn get_range( Ok(ret) }, ) + .await .unwrap_or_default() } @@ -368,17 +374,21 @@ fn is_marker(txt: &str) -> bool { } /// Deletes all locations from the database. -pub fn delete_all(context: &Context) -> Result<(), Error> { - sql::execute(context, &context.sql, "DELETE FROM locations;", params![])?; +pub async fn delete_all(context: &Context) -> Result<(), Error> { + context + .sql + .execute("DELETE FROM locations;", params![]) + .await?; context.call_cb(Event::LocationChanged(None)); Ok(()) } -pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> { +pub async fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Error> { let mut last_added_location_id = 0; let self_addr = context .get_config(Config::ConfiguredAddr) + .await .unwrap_or_default(); let (locations_send_begin, locations_send_until, locations_last_sent) = context.sql.query_row( @@ -389,7 +399,8 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro let last_sent: i64 = row.get(2)?; Ok((send_begin, send_until, last_sent)) - })?; + }) + .await?; let now = time(); let mut location_count = 0; @@ -433,7 +444,7 @@ pub fn get_kml(context: &Context, chat_id: ChatId) -> Result<(String, u32), Erro } Ok(()) } - )?; + ).await?; ret += "\n"; } @@ -466,37 +477,38 @@ pub fn get_message_kml(timestamp: i64, latitude: f64, longitude: f64) -> String ) } -pub fn set_kml_sent_timestamp( +pub async fn set_kml_sent_timestamp( context: &Context, chat_id: ChatId, timestamp: i64, ) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE chats SET locations_last_sent=? WHERE id=?;", - params![timestamp, chat_id], - )?; - + context + .sql + .execute( + "UPDATE chats SET locations_last_sent=? WHERE id=?;", + params![timestamp, chat_id], + ) + .await?; Ok(()) } -pub fn set_msg_location_id( +pub async fn set_msg_location_id( context: &Context, msg_id: MsgId, location_id: u32, ) -> Result<(), Error> { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET location_id=? WHERE id=?;", - params![location_id, msg_id], - )?; + context + .sql + .execute( + "UPDATE msgs SET location_id=? WHERE id=?;", + params![location_id, msg_id], + ) + .await?; Ok(()) } -pub fn save( +pub async fn save( context: &Context, chat_id: ChatId, contact_id: u32, @@ -504,50 +516,56 @@ pub fn save( independent: bool, ) -> Result { ensure!(!chat_id.is_special(), "Invalid chat id"); - context + + let newest_location_id = context .sql - .prepare2( - "SELECT id FROM locations WHERE timestamp=? AND from_id=?", - "INSERT INTO locations\ + .with_conn(|mut conn| { + let mut newest_timestamp = 0; + let mut newest_location_id = 0; + + for location in locations { + let mut stmt_test = conn + .prepare_cached("SELECT id FROM locations WHERE timestamp=? AND from_id=?")?; + let mut stmt_insert = conn.prepare_cached( + "INSERT INTO locations\ (timestamp, from_id, chat_id, latitude, longitude, accuracy, independent) \ VALUES (?,?,?,?,?,?,?);", - |mut stmt_test, mut stmt_insert, conn| { - let mut newest_timestamp = 0; - let mut newest_location_id = 0; + )?; - for location in locations { - let exists = - stmt_test.exists(params![location.timestamp, contact_id as i32])?; + let exists = stmt_test.exists(params![location.timestamp, contact_id as i32])?; - if independent || !exists { - stmt_insert.execute(params![ + if independent || !exists { + stmt_insert.execute(params![ + location.timestamp, + contact_id as i32, + chat_id, + location.latitude, + location.longitude, + location.accuracy, + independent, + ])?; + + if location.timestamp > newest_timestamp { + // okay to drop, as we use cached prepared statements + drop(stmt_test); + drop(stmt_insert); + newest_timestamp = location.timestamp; + newest_location_id = crate::sql::get_rowid2( + context, + &mut conn, + "locations", + "timestamp", location.timestamp, + "from_id", contact_id as i32, - chat_id, - location.latitude, - location.longitude, - location.accuracy, - independent, - ])?; - - if location.timestamp > newest_timestamp { - newest_timestamp = location.timestamp; - newest_location_id = sql::get_rowid2_with_conn( - context, - conn, - "locations", - "timestamp", - location.timestamp, - "from_id", - contact_id as i32, - ); - } + )?; } } - Ok(newest_location_id) - }, - ) - .map_err(Into::into) + } + Ok(newest_location_id) + }) + .await?; + Ok(newest_location_id) } pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> job::Status { @@ -558,80 +576,87 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j " ----------------- MAYBE_SEND_LOCATIONS -------------- ", ); - if let Ok(rows) = context.sql.query_map( - "SELECT id, locations_send_begin, locations_last_sent \ + if let Ok(ref rows) = context + .sql + .query_map( + "SELECT id, locations_send_begin, locations_last_sent \ FROM chats \ WHERE locations_send_until>?;", - params![now], - |row| { - let chat_id: ChatId = row.get(0)?; - let locations_send_begin: i64 = row.get(1)?; - let locations_last_sent: i64 = row.get(2)?; - continue_streaming = true; + params![now], + |row| { + let chat_id: ChatId = row.get(0)?; + let locations_send_begin: i64 = row.get(1)?; + let locations_last_sent: i64 = row.get(2)?; + continue_streaming = true; - // be a bit tolerant as the timer may not align exactly with time(NULL) - if now - locations_last_sent < (60 - 3) { - Ok(None) - } else { - Ok(Some((chat_id, locations_send_begin, locations_last_sent))) - } - }, - |rows| { - rows.filter_map(|v| v.transpose()) - .collect::, _>>() - .map_err(Into::into) - }, - ) { + // be a bit tolerant as the timer may not align exactly with time(NULL) + if now - locations_last_sent < (60 - 3) { + Ok(None) + } else { + Ok(Some((chat_id, locations_send_begin, locations_last_sent))) + } + }, + |rows| { + rows.filter_map(|v| v.transpose()) + .collect::, _>>() + .map_err(Into::into) + }, + ) + .await + { let msgs = context .sql - .prepare( - "SELECT id \ + .with_conn(|conn| { + let mut stmt_locations = conn.prepare_cached( + "SELECT id \ FROM locations \ WHERE from_id=? \ AND timestamp>=? \ AND timestamp>? \ AND independent=0 \ ORDER BY timestamp;", - |mut stmt_locations, _| { - let msgs = rows - .into_iter() - .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { - if !stmt_locations - .exists(params![ - DC_CONTACT_ID_SELF, - locations_send_begin, - locations_last_sent, - ]) - .unwrap_or_default() - { - // if there is no new location, there's nothing to send. - // however, maybe we want to bypass this test eg. 15 minutes - None - } else { - // pending locations are attached automatically to every message, - // so also to this empty text message. - // DC_CMD_LOCATION is only needed to create a nicer subject. - // - // for optimisation and to avoid flooding the sending queue, - // we could sending these messages only if we're really online. - // the easiest way to determine this, is to check for an empty message queue. - // (might not be 100%, however, as positions are sent combined later - // and dc_set_location() is typically called periodically, this is ok) - let mut msg = Message::new(Viewtype::Text); - msg.hidden = true; - msg.param.set_cmd(SystemMessage::LocationOnly); - Some((chat_id, msg)) - } - }) - .collect::>(); - Ok(msgs) - }, - ) - .unwrap_or_default(); // TODO: Better error handling + )?; + + let msgs = rows + .iter() + .filter_map(|(chat_id, locations_send_begin, locations_last_sent)| { + if !stmt_locations + .exists(params![ + DC_CONTACT_ID_SELF, + locations_send_begin, + locations_last_sent, + ]) + .unwrap_or_default() + { + // if there is no new location, there's nothing to send. + // however, maybe we want to bypass this test eg. 15 minutes + None + } else { + // pending locations are attached automatically to every message, + // so also to this empty text message. + // DC_CMD_LOCATION is only needed to create a nicer subject. + // + // for optimisation and to avoid flooding the sending queue, + // we could sending these messages only if we're really online. + // the easiest way to determine this, is to check for an empty message queue. + // (might not be 100%, however, as positions are sent combined later + // and dc_set_location() is typically called periodically, this is ok) + let mut msg = Message::new(Viewtype::Text); + msg.hidden = true; + msg.param.set_cmd(SystemMessage::LocationOnly); + Some((chat_id, msg)) + } + }) + .collect::>(); + + Ok(msgs) + }) + .await + .unwrap_or_default(); for (chat_id, mut msg) in msgs.into_iter() { // TODO: better error handling - chat::send_msg(context, chat_id, &mut msg) + chat::send_msg(context, *chat_id, &mut msg) .await .unwrap_or_default(); } @@ -642,19 +667,26 @@ pub(crate) async fn job_maybe_send_locations(context: &Context, _job: &Job) -> j job::Status::Finished(Ok(())) } -#[allow(non_snake_case)] -pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> job::Status { +pub(crate) async fn job_maybe_send_locations_ended( + context: &Context, + job: &mut Job, +) -> job::Status { // this function is called when location-streaming _might_ have ended for a chat. // the function checks, if location-streaming is really ended; // if so, a device-message is added if not yet done. let chat_id = ChatId::new(job.foreign_id); - let (send_begin, send_until) = job_try!(context.sql.query_row( - "SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", - params![chat_id], - |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), - )); + let (send_begin, send_until) = job_try!( + context + .sql + .query_row( + "SELECT locations_send_begin, locations_send_until FROM chats WHERE id=?", + params![chat_id], + |row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?)), + ) + .await + ); if !(send_begin != 0 && time() <= send_until) { // still streaming - @@ -665,7 +697,7 @@ pub(crate) fn JobMaybeSendLocationsEnded(context: &Context, job: &mut Job) -> jo job_try!(context.sql.execute( "UPDATE chats SET locations_send_begin=0, locations_send_until=0 WHERE id=?", params![chat_id], - )); + ).await); let stock_str = context.stock_system_msg(StockMessage::MsgLocationDisabled, "", "", 0); chat::add_info_msg(context, chat_id, stock_str); diff --git a/src/login_param.rs b/src/login_param.rs index be55feacd..7309a90ad 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -50,59 +50,69 @@ impl LoginParam { } /// Read the login parameters from the database. - pub fn from_database(context: &Context, prefix: impl AsRef) -> Self { + pub async fn from_database(context: &Context, prefix: impl AsRef) -> Self { let prefix = prefix.as_ref(); let sql = &context.sql; let key = format!("{}addr", prefix); let addr = sql .get_raw_config(context, key) + .await .unwrap_or_default() .trim() .to_string(); let key = format!("{}mail_server", prefix); - let mail_server = sql.get_raw_config(context, key).unwrap_or_default(); + let mail_server = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}mail_port", prefix); - let mail_port = sql.get_raw_config_int(context, key).unwrap_or_default(); + let mail_port = sql + .get_raw_config_int(context, key) + .await + .unwrap_or_default(); let key = format!("{}mail_user", prefix); - let mail_user = sql.get_raw_config(context, key).unwrap_or_default(); + let mail_user = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}mail_pw", prefix); - let mail_pw = sql.get_raw_config(context, key).unwrap_or_default(); + let mail_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}imap_certificate_checks", prefix); let imap_certificate_checks = - if let Some(certificate_checks) = sql.get_raw_config_int(context, key) { + if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() } else { Default::default() }; let key = format!("{}send_server", prefix); - let send_server = sql.get_raw_config(context, key).unwrap_or_default(); + let send_server = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}send_port", prefix); - let send_port = sql.get_raw_config_int(context, key).unwrap_or_default(); + let send_port = sql + .get_raw_config_int(context, key) + .await + .unwrap_or_default(); let key = format!("{}send_user", prefix); - let send_user = sql.get_raw_config(context, key).unwrap_or_default(); + let send_user = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}send_pw", prefix); - let send_pw = sql.get_raw_config(context, key).unwrap_or_default(); + let send_pw = sql.get_raw_config(context, key).await.unwrap_or_default(); let key = format!("{}smtp_certificate_checks", prefix); let smtp_certificate_checks = - if let Some(certificate_checks) = sql.get_raw_config_int(context, key) { + if let Some(certificate_checks) = sql.get_raw_config_int(context, key).await { num_traits::FromPrimitive::from_i32(certificate_checks).unwrap() } else { Default::default() }; let key = format!("{}server_flags", prefix); - let server_flags = sql.get_raw_config_int(context, key).unwrap_or_default(); + let server_flags = sql + .get_raw_config_int(context, key) + .await + .unwrap_or_default(); LoginParam { addr, @@ -125,7 +135,7 @@ impl LoginParam { } /// Save this loginparam to the database. - pub fn save_to_database( + pub async fn save_to_database( &self, context: &Context, prefix: impl AsRef, @@ -134,40 +144,49 @@ impl LoginParam { let sql = &context.sql; let key = format!("{}addr", prefix); - sql.set_raw_config(context, key, Some(&self.addr))?; + sql.set_raw_config(context, key, Some(&self.addr)).await?; let key = format!("{}mail_server", prefix); - sql.set_raw_config(context, key, Some(&self.mail_server))?; + sql.set_raw_config(context, key, Some(&self.mail_server)) + .await?; let key = format!("{}mail_port", prefix); - sql.set_raw_config_int(context, key, self.mail_port)?; + sql.set_raw_config_int(context, key, self.mail_port).await?; let key = format!("{}mail_user", prefix); - sql.set_raw_config(context, key, Some(&self.mail_user))?; + sql.set_raw_config(context, key, Some(&self.mail_user)) + .await?; let key = format!("{}mail_pw", prefix); - sql.set_raw_config(context, key, Some(&self.mail_pw))?; + sql.set_raw_config(context, key, Some(&self.mail_pw)) + .await?; let key = format!("{}imap_certificate_checks", prefix); - sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32)?; + sql.set_raw_config_int(context, key, self.imap_certificate_checks as i32) + .await?; let key = format!("{}send_server", prefix); - sql.set_raw_config(context, key, Some(&self.send_server))?; + sql.set_raw_config(context, key, Some(&self.send_server)) + .await?; let key = format!("{}send_port", prefix); - sql.set_raw_config_int(context, key, self.send_port)?; + sql.set_raw_config_int(context, key, self.send_port).await?; let key = format!("{}send_user", prefix); - sql.set_raw_config(context, key, Some(&self.send_user))?; + sql.set_raw_config(context, key, Some(&self.send_user)) + .await?; let key = format!("{}send_pw", prefix); - sql.set_raw_config(context, key, Some(&self.send_pw))?; + sql.set_raw_config(context, key, Some(&self.send_pw)) + .await?; let key = format!("{}smtp_certificate_checks", prefix); - sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32)?; + sql.set_raw_config_int(context, key, self.smtp_certificate_checks as i32) + .await?; let key = format!("{}server_flags", prefix); - sql.set_raw_config_int(context, key, self.server_flags)?; + sql.set_raw_config_int(context, key, self.server_flags) + .await?; Ok(()) } diff --git a/src/message.rs b/src/message.rs index b2a7c7e21..401f93be7 100644 --- a/src/message.rs +++ b/src/message.rs @@ -19,7 +19,6 @@ use crate::lot::{Lot, LotState, Meaning}; use crate::mimeparser::SystemMessage; use crate::param::*; use crate::pgp::*; -use crate::sql; use crate::stock::StockMessage; lazy_static! { @@ -219,12 +218,12 @@ impl Message { msg } - pub fn load_from_db(context: &Context, id: MsgId) -> Result { + pub async fn load_from_db(context: &Context, id: MsgId) -> Result { ensure!( !id.is_special(), "Can not load special message IDs from DB." ); - context + let msg = context .sql .query_row( concat!( @@ -302,25 +301,23 @@ impl Message { Ok(msg) }, ) - .map_err(Into::into) + .await?; + + Ok(msg) } - pub fn delete_from_db(context: &Context, msg_id: MsgId) { - if let Ok(msg) = Message::load_from_db(context, msg_id) { - sql::execute( - context, - &context.sql, - "DELETE FROM msgs WHERE id=?;", - params![msg.id], - ) - .ok(); - sql::execute( - context, - &context.sql, - "DELETE FROM msgs_mdns WHERE msg_id=?;", - params![msg.id], - ) - .ok(); + pub async fn delete_from_db(context: &Context, msg_id: MsgId) { + if let Ok(msg) = Message::load_from_db(context, msg_id).await { + context + .sql + .execute("DELETE FROM msgs WHERE id=?;", params![msg.id]) + .await + .ok(); + context + .sql + .execute("DELETE FROM msgs_mdns WHERE msg_id=?;", params![msg.id]) + .await + .ok(); } } @@ -477,13 +474,13 @@ impl Message { self.param.get_int(Param::GuaranteeE2ee).unwrap_or_default() != 0 } - pub fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot { + pub async fn get_summary(&mut self, context: &Context, chat: Option<&Chat>) -> Lot { let mut ret = Lot::new(); let chat_loaded: Chat; let chat = if let Some(chat) = chat { chat - } else if let Ok(chat) = Chat::load_from_db(context, self.chat_id) { + } else if let Ok(chat) = Chat::load_from_db(context, self.chat_id).await { chat_loaded = chat; &chat_loaded } else { @@ -493,7 +490,7 @@ impl Message { let contact = if self.from_id != DC_CONTACT_ID_SELF as u32 && (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) { - Contact::get_by_id(context, self.from_id).ok() + Contact::get_by_id(context, self.from_id).await.ok() } else { None }; @@ -615,14 +612,15 @@ impl Message { self.save_param_to_disk(context); } - pub fn save_param_to_disk(&mut self, context: &Context) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET param=? WHERE id=?;", - params![self.param.to_string(), self.id], - ) - .is_ok() + pub async fn save_param_to_disk(&mut self, context: &Context) -> bool { + context + .sql + .execute( + "UPDATE msgs SET param=? WHERE id=?;", + params![self.param.to_string(), self.id], + ) + .await + .is_ok() } } @@ -793,21 +791,24 @@ impl Lot { } } -pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String { +pub async fn get_msg_info(context: &Context, msg_id: MsgId) -> String { let mut ret = String::new(); - let msg = Message::load_from_db(context, msg_id); + let msg = Message::load_from_db(context, msg_id).await; if msg.is_err() { return ret; } let msg = msg.unwrap_or_default(); - let rawtxt: Option = context.sql.query_get_value( - context, - "SELECT txt_raw FROM msgs WHERE id=?;", - params![msg_id], - ); + let rawtxt: Option = context + .sql + .query_get_value( + context, + "SELECT txt_raw FROM msgs WHERE id=?;", + params![msg_id], + ) + .await; if rawtxt.is_none() { ret += &format!("Cannot load message {}.", msg_id); @@ -820,6 +821,7 @@ pub fn get_msg_info(context: &Context, msg_id: MsgId) -> String { ret += &format!("Sent: {}", fts); let name = Contact::load_from_db(context, msg.from_id) + .await .map(|contact| contact.get_name_n_addr()) .unwrap_or_default(); @@ -952,7 +954,7 @@ pub fn get_mime_headers(context: &Context, msg_id: MsgId) -> Option { pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { for msg_id in msg_ids.iter() { - if let Ok(msg) = Message::load_from_db(context, *msg_id) { + if let Ok(msg) = Message::load_from_db(context, *msg_id).await { if msg.location_id > 0 { delete_poi_location(context, msg.location_id); } @@ -978,24 +980,26 @@ pub async fn delete_msgs(context: &Context, msg_ids: &[MsgId]) { } } -fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET chat_id=? WHERE id=?;", - params![chat_id, msg_id], - ) - .is_ok() +async fn update_msg_chat_id(context: &Context, msg_id: MsgId, chat_id: ChatId) -> bool { + context + .sql + .execute( + "UPDATE msgs SET chat_id=? WHERE id=?;", + params![chat_id, msg_id], + ) + .await + .is_ok() } -fn delete_poi_location(context: &Context, location_id: u32) -> bool { - sql::execute( - context, - &context.sql, - "DELETE FROM locations WHERE independent = 1 AND id=?;", - params![location_id as i32], - ) - .is_ok() +async fn delete_poi_location(context: &Context, location_id: u32) -> bool { + context + .sql + .execute( + "DELETE FROM locations WHERE independent = 1 AND id=?;", + params![location_id as i32], + ) + .await + .is_ok() } pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { @@ -1003,16 +1007,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { return false; } - let msgs = context.sql.prepare( - concat!( - "SELECT", - " m.state AS state,", - " c.blocked AS blocked", - " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", - " WHERE m.id=? AND m.chat_id>9" - ), - |mut stmt, _| { - let mut res = Vec::with_capacity(msg_ids.len()); + let msgs = context + .sql + .with_conn(|conn| { + let mut stmt = conn.prepare_cached(concat!( + "SELECT", + " m.state AS state,", + " c.blocked AS blocked", + " FROM msgs m LEFT JOIN chats c ON c.id=m.chat_id", + " WHERE m.id=? AND m.chat_id>9" + ))?; + + let mut msgs = Vec::with_capacity(msg_ids.len()); for id in msg_ids.iter() { let query_res = stmt.query_row(params![*id], |row| { Ok(( @@ -1024,20 +1030,18 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { if let Err(rusqlite::Error::QueryReturnedNoRows) = query_res { continue; } - let (state, blocked) = query_res?; - res.push((id, state, blocked)); + let (state, blocked) = query_res + .map_err(|err| Error::SqlError(err.into())) + .expect("query fail"); + msgs.push((id, state, blocked)); } - Ok(res) - }, - ); + Ok(msgs) + }) + .await + .unwrap_or_default(); - if msgs.is_err() { - warn!(context, "markseen_msgs failed: {:?}", msgs); - return false; - } let mut send_event = false; - let msgs = msgs.unwrap_or_default(); for (id, curr_state, curr_blocked) in msgs.into_iter() { if curr_blocked == Blocked::Not { @@ -1071,14 +1075,15 @@ pub async fn markseen_msgs(context: &Context, msg_ids: &[MsgId]) -> bool { true } -pub fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool { - sql::execute( - context, - &context.sql, - "UPDATE msgs SET state=? WHERE id=?;", - params![state, msg_id], - ) - .is_ok() +pub async fn update_msg_state(context: &Context, msg_id: MsgId, state: MessageState) -> bool { + context + .sql + .execute( + "UPDATE msgs SET state=? WHERE id=?;", + params![state, msg_id], + ) + .await + .is_ok() } pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool { @@ -1189,8 +1194,8 @@ pub fn exists(context: &Context, msg_id: MsgId) -> bool { } } -pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option>) { - if let Ok(mut msg) = Message::load_from_db(context, msg_id) { +pub async fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option>) { + if let Ok(mut msg) = Message::load_from_db(context, msg_id).await { if msg.state.can_fail() { msg.state = MessageState::OutFailed; } @@ -1199,13 +1204,14 @@ pub fn set_msg_failed(context: &Context, msg_id: MsgId, error: Option MimeFactory<'a, 'b> { }) } - fn peerstates_for_recipients(&self) -> Result, &str)>, Error> { + async fn peerstates_for_recipients(&self) -> Result>, &str)>, Error> { let self_addr = self .context .get_config(Config::ConfiguredAddr) + .await .ok_or_else(|| format_err!("Not configured"))?; Ok(self diff --git a/src/mimeparser.rs b/src/mimeparser.rs index c95863f69..51f20bdef 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -80,7 +80,7 @@ impl Default for SystemMessage { const MIME_AC_SETUP_FILE: &str = "application/autocrypt-setup"; impl MimeMessage { - pub fn from_bytes(context: &Context, body: &[u8]) -> Result { + pub async fn from_bytes(context: &Context, body: &[u8]) -> Result { let mail = mailparse::parse_mail(body)?; let message_time = mail @@ -98,7 +98,7 @@ impl MimeMessage { let mail_raw; let mut gossipped_addr = Default::default(); - let (mail, signatures) = match e2ee::try_decrypt(context, &mail, message_time) { + let (mail, signatures) = match e2ee::try_decrypt(context, &mail, message_time).await { Ok((raw, signatures)) => { if let Some(raw) = raw { // Valid autocrypt message, encrypted @@ -114,7 +114,8 @@ impl MimeMessage { let gossip_headers = decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?; gossipped_addr = - update_gossip_peerstates(context, message_time, &mail, gossip_headers)?; + update_gossip_peerstates(context, message_time, &mail, gossip_headers) + .await?; // let known protected headers from the decrypted // part override the unencrypted top-level @@ -837,7 +838,7 @@ impl MimeMessage { let mut param = Params::new(); param.set(Param::ServerFolder, server_folder.as_ref()); param.set_int(Param::ServerUid, server_uid as i32); - if self.has_chat_version() && context.get_config_bool(Config::MvboxMove) { + if self.has_chat_version() && context.get_config_bool(Config::MvboxMove).await { param.set_int(Param::AlsoMove, 1); } job::add(context, Action::MarkseenMdnOnImap, 0, param, 0).await; @@ -845,7 +846,7 @@ impl MimeMessage { } } -fn update_gossip_peerstates( +async fn update_gossip_peerstates( context: &Context, message_time: i64, mail: &mailparse::ParsedMail<'_>, @@ -878,15 +879,15 @@ fn update_gossip_peerstates( let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr); if let Some(ref mut peerstate) = peerstate { peerstate.apply_gossip(header, message_time); - peerstate.save_to_db(&context.sql, false)?; + peerstate.save_to_db(&context.sql, false).await?; } else { let p = Peerstate::from_gossip(context, header, message_time); - p.save_to_db(&context.sql, true)?; + p.save_to_db(&context.sql, true).await?; peerstate = Some(p); } if let Some(peerstate) = peerstate { if peerstate.degrade_event.is_some() { - handle_degrade_event(context, &peerstate)?; + handle_degrade_event(context, &peerstate).await?; } } diff --git a/src/peerstate.rs b/src/peerstate.rs index e2a17e0ce..52a591dcb 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -9,7 +9,7 @@ use crate::aheader::*; use crate::constants::*; use crate::context::Context; use crate::key::{Key, SignedPublicKey}; -use crate::sql::{self, Sql}; +use crate::sql::Sql; #[derive(Debug)] pub enum PeerstateKeyType { @@ -409,20 +409,17 @@ impl<'a> Peerstate<'a> { } } - pub fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> { + pub async fn save_to_db(&self, sql: &Sql, create: bool) -> crate::sql::Result<()> { if create { - sql::execute( - self.context, - sql, + sql.execute( "INSERT INTO acpeerstates (addr) VALUES(?);", params![self.addr], - )?; + ) + .await?; } if self.to_save == Some(ToSave::All) || create { - sql::execute( - self.context, - sql, + sql.execute( "UPDATE acpeerstates \ SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, \ public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, \ @@ -441,11 +438,9 @@ impl<'a> Peerstate<'a> { &self.verified_key_fingerprint, &self.addr, ], - )?; + ).await?; } else if self.to_save == Some(ToSave::Timestamps) { - sql::execute( - self.context, - sql, + sql.execute( "UPDATE acpeerstates SET last_seen=?, last_seen_autocrypt=?, gossip_timestamp=? \ WHERE addr=?;", params![ @@ -454,7 +449,8 @@ impl<'a> Peerstate<'a> { self.gossip_timestamp, &self.addr ], - )?; + ) + .await?; } Ok(()) diff --git a/src/qr.rs b/src/qr.rs index 1dc35b50f..8c69655df 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -40,13 +40,13 @@ impl Into for Error { /// Check a scanned QR code. /// The function should be called after a QR code is scanned. /// The function takes the raw text scanned and checks what can be done with it. -pub fn check_qr(context: &Context, qr: impl AsRef) -> Lot { +pub async fn check_qr(context: &Context, qr: impl AsRef) -> Lot { let qr = qr.as_ref(); info!(context, "Scanned QR code: {}", qr); if qr.starts_with(OPENPGP4FPR_SCHEME) { - decode_openpgp(context, qr) + decode_openpgp(context, qr).await } else if qr.starts_with(DCACCOUNT_SCHEME) { decode_account(context, qr) } else if qr.starts_with(MAILTO_SCHEME) { @@ -66,7 +66,7 @@ pub fn check_qr(context: &Context, qr: impl AsRef) -> Lot { /// scheme: `OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH` /// or: `OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH` -fn decode_openpgp(context: &Context, qr: &str) -> Lot { +async fn decode_openpgp(context: &Context, qr: &str) -> Lot { let payload = &qr[OPENPGP4FPR_SCHEME.len()..]; let (fingerprint, fragment) = match payload.find('#').map(|offset| { @@ -152,10 +152,12 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot { peerstate.addr.clone(), Origin::UnhandledQrScan, ) + .await .map(|(id, _)| id) .unwrap_or_default(); let (id, _) = chat::create_or_lookup_by_contact_id(context, lot.id, Blocked::Deaddrop) + .await .unwrap_or_default(); chat::add_info_msg(context, id, format!("{} verified.", peerstate.addr)); @@ -172,6 +174,7 @@ fn decode_openpgp(context: &Context, qr: &str) -> Lot { lot.state = LotState::QrAskVerifyContact; } lot.id = Contact::add_or_lookup(context, &name, &addr, Origin::UnhandledQrScan) + .await .map(|(id, _)| id) .unwrap_or_default(); diff --git a/src/securejoin.rs b/src/securejoin.rs index 44f045842..feda5657f 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -143,35 +143,40 @@ fn get_self_fingerprint(context: &Context) -> Option { None } +async fn cleanup( + context: &Context, + contact_chat_id: ChatId, + ongoing_allocated: bool, + join_vg: bool, +) -> ChatId { + let mut bob = context.bob.write().unwrap(); + bob.expects = 0; + let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { + if join_vg { + chat::get_chat_id_by_grpid( + context, + bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), + ) + .await + .unwrap_or((ChatId::new(0), false, Blocked::Not)) + .0 + } else { + contact_chat_id + } + } else { + ChatId::new(0) + }; + bob.qr_scan = None; + + if ongoing_allocated { + context.free_ongoing(); + } + ret_chat_id +} + /// Take a scanned QR-code and do the setup-contact/join-group handshake. /// See the ffi-documentation for more details. pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { - let cleanup = - |context: &Context, contact_chat_id: ChatId, ongoing_allocated: bool, join_vg: bool| { - let mut bob = context.bob.write().unwrap(); - bob.expects = 0; - let ret_chat_id: ChatId = if bob.status == DC_BOB_SUCCESS { - if join_vg { - chat::get_chat_id_by_grpid( - context, - bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(), - ) - .unwrap_or((ChatId::new(0), false, Blocked::Not)) - .0 - } else { - contact_chat_id - } - } else { - ChatId::new(0) - }; - bob.qr_scan = None; - - if ongoing_allocated { - context.free_ongoing(); - } - ret_chat_id - }; - /*======================================================== ==== Bob - the joiner's side ===== ==== Step 2 in "Setup verified contact" protocol ===== @@ -181,25 +186,25 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { let mut join_vg: bool = false; info!(context, "Requesting secure-join ...",); - ensure_secret_key_exists(context).ok(); + ensure_secret_key_exists(context).await.ok(); if !context.alloc_ongoing() { - return cleanup(&context, contact_chat_id, false, join_vg); + return cleanup(&context, contact_chat_id, false, join_vg).await; } - let qr_scan = check_qr(context, &qr); + let qr_scan = check_qr(context, &qr).await; if qr_scan.state != LotState::QrAskVerifyContact && qr_scan.state != LotState::QrAskVerifyGroup { error!(context, "Unknown QR code.",); - return cleanup(&context, contact_chat_id, true, join_vg); + return cleanup(&context, contact_chat_id, true, join_vg).await; } - contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id) { + contact_chat_id = match chat::create_by_contact_id(context, qr_scan.id).await { Ok(chat_id) => chat_id, Err(_) => { error!(context, "Unknown contact."); - return cleanup(&context, contact_chat_id, true, join_vg); + return cleanup(&context, contact_chat_id, true, join_vg).await; } }; if context.shall_stop_ongoing() { - return cleanup(&context, contact_chat_id, true, join_vg); + return cleanup(&context, contact_chat_id, true, join_vg).await; } join_vg = qr_scan.get_state() == LotState::QrAskVerifyGroup; { @@ -266,7 +271,7 @@ pub async fn dc_join_securejoin(context: &Context, qr: &str) -> ChatId { while !context.shall_stop_ongoing() { std::thread::sleep(std::time::Duration::from_millis(200)); } - cleanup(&context, contact_chat_id, true, join_vg) + cleanup(&context, contact_chat_id, true, join_vg).await } else { // for a one-to-one-chat, the chat is already known, return the chat-id, // the verification runs in background @@ -412,7 +417,7 @@ pub(crate) async fn handle_securejoin_handshake( ); let contact_chat_id = - match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not) { + match chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).await { Ok((chat_id, blocked)) => { if blocked != Blocked::Not { chat_id.unblock(context); @@ -447,7 +452,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(HandshakeMessage::Ignore); } }; - if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) { + if !token::exists(context, token::Namespace::InviteNumber, &invitenumber).await { warn!(context, "Secure-join denied (bad invitenumber)."); return Ok(HandshakeMessage::Ignore); } @@ -583,7 +588,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(HandshakeMessage::Ignore); } }; - if !token::exists(context, token::Namespace::Auth, &auth_0) { + if !token::exists(context, token::Namespace::Auth, &auth_0).await { could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid."); return Ok(HandshakeMessage::Ignore); } @@ -611,7 +616,7 @@ pub(crate) async fn handle_securejoin_handshake( return Ok(HandshakeMessage::Ignore); } }; - match chat::get_chat_id_by_grpid(context, field_grpid) { + match chat::get_chat_id_by_grpid(context, field_grpid).await { Ok((group_chat_id, _, _)) => { if let Err(err) = chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true) @@ -672,6 +677,7 @@ pub(crate) async fn handle_securejoin_handshake( // only after we have returned. It does not impact // the security invariants of secure-join however. let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id) + .await .unwrap_or((ChatId::new(0), false, Blocked::Not)); // when joining a non-verified group // the vg-member-added message may be unencrypted @@ -711,6 +717,7 @@ pub(crate) async fn handle_securejoin_handshake( if join_vg && !context .is_self_addr(cg_member_added) + .await .map_err(|_| HandshakeError::NoSelfAddr)? { info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); @@ -744,7 +751,7 @@ pub(crate) async fn handle_securejoin_handshake( ==== Step 8 in "Out-of-band verified groups" protocol ==== ==========================================================*/ - if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if let Ok(contact) = Contact::get_by_id(context, contact_id).await { if contact.is_verified(context) == VerifiedStatus::Unverified { warn!(context, "vg-member-added-received invalid.",); return Ok(HandshakeMessage::Ignore); @@ -756,6 +763,7 @@ pub(crate) async fn handle_securejoin_handshake( .map(|s| s.as_str()) .unwrap_or_else(|| ""); let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid) + .await .map_err(|err| { warn!(context, "Failed to lookup chat_id from grpid: {}", err); HandshakeError::ChatNotFound { @@ -868,18 +876,25 @@ fn encrypted_and_signed( } } -pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result<(), Error> { +pub async fn handle_degrade_event( + context: &Context, + peerstate: &Peerstate<'_>, +) -> Result<(), Error> { // - we do not issue an warning for DC_DE_ENCRYPTION_PAUSED as this is quite normal // - currently, we do not issue an extra warning for DC_DE_VERIFICATION_LOST - this always comes // together with DC_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother // with things they cannot fix, so the user is just kicked from the verified group // (and he will know this and can fix this) if Some(DegradeEvent::FingerprintChanged) == peerstate.degrade_event { - let contact_id: i32 = match context.sql.query_get_value( - context, - "SELECT id FROM contacts WHERE addr=?;", - params![&peerstate.addr], - ) { + let contact_id: i32 = match context + .sql + .query_get_value( + context, + "SELECT id FROM contacts WHERE addr=?;", + params![&peerstate.addr], + ) + .await + { None => bail!( "contact with peerstate.addr {:?} not found", &peerstate.addr @@ -889,6 +904,7 @@ pub fn handle_degrade_event(context: &Context, peerstate: &Peerstate) -> Result< if contact_id > 0 { let (contact_chat_id, _) = chat::create_or_lookup_by_contact_id(context, contact_id as u32, Blocked::Deaddrop) + .await .unwrap_or_default(); let msg = context diff --git a/src/sql.rs b/src/sql.rs index 8e15d1fdd..f5a08f1d3 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1,10 +1,13 @@ //! # SQLite wrapper +use async_std::prelude::*; +use async_std::sync::{Arc, RwLock}; + use std::collections::HashSet; -use std::sync::{Arc, RwLock}; +use std::path::Path; use std::time::Duration; -use rusqlite::{Connection, OpenFlags, Statement, NO_PARAMS}; +use rusqlite::{Connection, Error as SqlError, OpenFlags, NO_PARAMS}; use thread_local_object::ThreadLocal; use crate::chat::{update_device_icon, update_saved_messages_icon}; @@ -80,12 +83,12 @@ impl Sql { Self::default() } - pub fn is_open(&self) -> bool { - self.pool.read().unwrap().is_some() + pub async fn is_open(&self) -> bool { + self.pool.read().await.is_some() } - pub fn close(&self, context: &Context) { - let _ = self.pool.write().unwrap().take(); + pub async fn close(&self, context: &Context) { + let _ = self.pool.write().await.take(); self.in_use.remove(); // drop closes the connection @@ -93,79 +96,36 @@ impl Sql { } // return true on success, false on failure - pub fn open(&self, context: &Context, dbfile: &std::path::Path, readonly: bool) -> bool { - match open(context, self, dbfile, readonly) { + pub async fn open(&self, context: &Context, dbfile: &Path, readonly: bool) -> bool { + match open(context, self, dbfile, readonly).await { Ok(_) => true, Err(crate::error::Error::SqlError(Error::SqlAlreadyOpen)) => false, Err(_) => { - self.close(context); + self.close(context).await; false } } } - pub fn execute

(&self, sql: &str, params: P) -> Result + pub async fn execute>(&self, sql: S, params: P) -> Result where P: IntoIterator, P::Item: rusqlite::ToSql, { - self.start_stmt(sql.to_string()); - self.with_conn(|conn| conn.execute(sql, params).map_err(Into::into)) - } + self.start_stmt(sql.as_ref()); - pub fn with_conn(&self, g: G) -> Result - where - G: FnOnce(&mut Connection) -> Result, - { - let res = match &*self.pool.read().unwrap() { - Some(pool) => { - let mut conn = pool.get()?; - - // Only one process can make changes to the database at one time. - // busy_timeout defines, that if a second process wants write access, - // this second process will wait some milliseconds - // and try over until it gets write access or the given timeout is elapsed. - // If the second process does not get write access within the given timeout, - // sqlite3_step() will return the error SQLITE_BUSY. - // (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_) - conn.busy_timeout(Duration::from_secs(10))?; - - g(&mut conn) - } - None => Err(Error::SqlNoConnection), + let res = { + let conn = self.get_conn().await?; + conn.execute(sql.as_ref(), params)? }; - self.in_use.remove(); - res - } - pub fn prepare(&self, sql: &str, g: G) -> Result - where - G: FnOnce(Statement<'_>, &Connection) -> Result, - { - self.start_stmt(sql.to_string()); - self.with_conn(|conn| { - let stmt = conn.prepare(sql)?; - g(stmt, conn) - }) - } - - pub fn prepare2(&self, sql1: &str, sql2: &str, g: G) -> Result - where - G: FnOnce(Statement<'_>, Statement<'_>, &Connection) -> Result, - { - self.start_stmt(format!("{} - {}", sql1, sql2)); - self.with_conn(|conn| { - let stmt1 = conn.prepare(sql1)?; - let stmt2 = conn.prepare(sql2)?; - - g(stmt1, stmt2, conn) - }) + Ok(res) } /// Prepares and executes the statement and maps a function over the resulting rows. /// Then executes the second function over the returned iterator and returns the /// result of that function. - pub fn query_map( + pub async fn query_map( &self, sql: impl AsRef, params: P, @@ -179,54 +139,153 @@ impl Sql { G: FnMut(rusqlite::MappedRows) -> Result, { self.start_stmt(sql.as_ref().to_string()); - self.with_conn(|conn| { - let mut stmt = conn.prepare(sql.as_ref())?; + let sql = sql.as_ref(); + + let res = { + let conn = self.get_conn().await?; + let mut stmt = conn.prepare(sql)?; let res = stmt.query_map(params, f)?; - g(res) - }) + g(res)? + }; + + Ok(res) + } + + /// Prepares and executes the statement and maps a function over the resulting rows. + /// Then executes the second function over the returned iterator and returns the + /// result of that function. + pub async fn query_map_async<'a, T, P, F, G, H, Fut>( + &self, + sql: impl AsRef, + params: P, + f: F, + g: G, + ) -> Result + where + P: IntoIterator, + P::Item: rusqlite::ToSql, + F: FnMut(&rusqlite::Row) -> rusqlite::Result, + G: FnMut(rusqlite::MappedRows<'a, F>) -> Fut, + Fut: Future> + '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( + &self, + ) -> Result> { + let lock = self.pool.read().await; + let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + let conn = pool.get()?; + + Ok(conn) + } + + pub async fn with_conn(&self, mut g: G) -> Result + where + G: FnMut(r2d2::PooledConnection) -> Result, + { + let lock = self.pool.read().await; + let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + + let res = { + let conn = pool.get()?; + let res = g(conn); + self.in_use.remove(); + res + }; + res + } + + pub async fn with_conn_async(&self, mut g: G) -> Result + where + G: FnMut(r2d2::PooledConnection) -> Fut, + Fut: Future> + Send, + { + let lock = self.pool.read().await; + let pool = lock.as_ref().ok_or_else(|| Error::SqlNoConnection)?; + + let res = { + let conn = pool.get()?; + let res = g(conn).await; + self.in_use.remove(); + res + }; + res } /// Return `true` if a query in the SQL statement it executes returns one or more /// rows and false if the SQL returns an empty set. - pub fn exists

(&self, sql: &str, params: P) -> Result + pub async fn exists

(&self, sql: &str, params: P) -> Result where P: IntoIterator, P::Item: rusqlite::ToSql, { self.start_stmt(sql.to_string()); - self.with_conn(|conn| { + let res = { + let conn = self.get_conn().await?; let mut stmt = conn.prepare(sql)?; - let res = stmt.exists(params)?; - Ok(res) - }) + stmt.exists(params)? + }; + + Ok(res) } /// Execute a query which is expected to return one row. - pub fn query_row(&self, sql: impl AsRef, params: P, f: F) -> Result + pub async fn query_row(&self, sql: impl AsRef, params: P, f: F) -> Result where - P: IntoIterator, + P: IntoIterator + Copy, P::Item: rusqlite::ToSql, F: FnOnce(&rusqlite::Row) -> rusqlite::Result, { self.start_stmt(sql.as_ref().to_string()); - self.with_conn(|conn| conn.query_row(sql.as_ref(), params, f).map_err(Into::into)) + let sql = sql.as_ref(); + let res = { + let conn = self.get_conn().await?; + conn.query_row(sql, params, f)? + }; + + Ok(res) } - pub fn table_exists(&self, name: impl AsRef) -> bool { - self.with_conn(|conn| table_exists(conn, name)) - .unwrap_or_default() + pub async fn table_exists(&self, name: impl AsRef) -> Result { + self.start_stmt("table_exists"); + self.with_conn(|conn| { + let mut exists = false; + conn.pragma(None, "table_info", &name.as_ref().to_string(), |_row| { + // will only be executed if the info was found + exists = true; + Ok(()) + })?; + + Ok(exists) + }) + .await } /// Executes a query which is expected to return one row and one /// column. If the query does not return a value or returns SQL /// `NULL`, returns `Ok(None)`. - pub fn query_get_value_result(&self, query: &str, params: P) -> Result> + pub async fn query_get_value_result(&self, query: &str, params: P) -> Result> where - P: IntoIterator, + P: IntoIterator + Copy, P::Item: rusqlite::ToSql, T: rusqlite::types::FromSql, { - match self.query_row(query, params, |row| row.get::<_, T>(0)) { + match self + .query_row(query, params, |row| row.get::<_, T>(0)) + .await + { Ok(res) => Ok(Some(res)), Err(Error::Sql(rusqlite::Error::QueryReturnedNoRows)) => Ok(None), Err(Error::Sql(rusqlite::Error::InvalidColumnType( @@ -240,13 +299,18 @@ impl Sql { /// Not resultified version of `query_get_value_result`. Returns /// `None` on error. - pub fn query_get_value(&self, context: &Context, query: &str, params: P) -> Option + pub async fn query_get_value( + &self, + context: &Context, + query: &str, + params: P, + ) -> Option where - P: IntoIterator, + P: IntoIterator + Copy, P::Item: rusqlite::ToSql, T: rusqlite::types::FromSql, { - match self.query_get_value_result(query, params) { + match self.query_get_value_result(query, params).await { Ok(res) => res, Err(err) => { error!(context, "sql: Failed query_row: {}", err); @@ -259,42 +323,38 @@ impl Sql { /// /// Setting `None` deletes the value. On failure an error message /// will already have been logged. - pub fn set_raw_config( + pub async fn set_raw_config( &self, context: &Context, key: impl AsRef, value: Option<&str>, ) -> Result<()> { - if !self.is_open() { + if !self.is_open().await { error!(context, "set_raw_config(): Database not ready."); return Err(Error::SqlNoConnection); } let key = key.as_ref(); let res = if let Some(ref value) = value { - let exists = self.exists("SELECT value FROM config WHERE keyname=?;", params![key])?; + let exists = self + .exists("SELECT value FROM config WHERE keyname=?;", params![key]) + .await?; if exists { - execute( - context, - self, + self.execute( "UPDATE config SET value=? WHERE keyname=?;", params![value, key], ) + .await } else { - execute( - context, - self, + self.execute( "INSERT INTO config (keyname, value) VALUES (?, ?);", params![key, value], ) + .await } } else { - execute( - context, - self, - "DELETE FROM config WHERE keyname=?;", - params![key], - ) + self.execute("DELETE FROM config WHERE keyname=?;", params![key]) + .await }; match res { @@ -307,8 +367,8 @@ impl Sql { } /// Get configuration options from the database. - pub fn get_raw_config(&self, context: &Context, key: impl AsRef) -> Option { - if !self.is_open() || key.as_ref().is_empty() { + pub async fn get_raw_config(&self, context: &Context, key: impl AsRef) -> Option { + if !self.is_open().await || key.as_ref().is_empty() { return None; } self.query_get_value( @@ -316,47 +376,59 @@ impl Sql { "SELECT value FROM config WHERE keyname=?;", params![key.as_ref()], ) + .await } - pub fn set_raw_config_int( + pub async fn set_raw_config_int( &self, context: &Context, key: impl AsRef, value: i32, ) -> Result<()> { self.set_raw_config(context, key, Some(&format!("{}", value))) + .await } - pub fn get_raw_config_int(&self, context: &Context, key: impl AsRef) -> Option { + pub async fn get_raw_config_int(&self, context: &Context, key: impl AsRef) -> Option { self.get_raw_config(context, key) + .await .and_then(|s| s.parse().ok()) } - pub fn get_raw_config_bool(&self, context: &Context, key: impl AsRef) -> bool { + pub async fn get_raw_config_bool(&self, context: &Context, key: impl AsRef) -> bool { // Not the most obvious way to encode bool as string, but it is matter // of backward compatibility. - self.get_raw_config_int(context, key).unwrap_or_default() > 0 + self.get_raw_config_int(context, key) + .await + .unwrap_or_default() + > 0 } - pub fn set_raw_config_bool(&self, context: &Context, key: T, value: bool) -> Result<()> + pub async fn set_raw_config_bool(&self, context: &Context, key: T, value: bool) -> Result<()> where T: AsRef, { let value = if value { Some("1") } else { None }; - self.set_raw_config(context, key, value) + self.set_raw_config(context, key, value).await } - pub fn set_raw_config_int64( + pub async fn set_raw_config_int64( &self, context: &Context, key: impl AsRef, value: i64, ) -> Result<()> { self.set_raw_config(context, key, Some(&format!("{}", value))) + .await } - pub fn get_raw_config_int64(&self, context: &Context, key: impl AsRef) -> Option { + pub async fn get_raw_config_int64( + &self, + context: &Context, + key: impl AsRef, + ) -> Option { self.get_raw_config(context, key) + .await .and_then(|r| r.parse().ok()) } @@ -370,622 +442,60 @@ impl Sql { self.in_use.set(stmt.as_ref().to_string()); } -} -fn table_exists(conn: &Connection, name: impl AsRef) -> Result { - let mut exists = false; - conn.pragma(None, "table_info", &name.as_ref().to_string(), |_row| { - // will only be executed if the info was found - exists = true; - Ok(()) - })?; - Ok(exists) -} + /// Alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. + /// the ORDER BY ensures, this function always returns the most recent id, + /// eg. if a Message-ID is split into different messages. + pub async fn get_rowid( + &self, + context: &Context, + table: impl AsRef, + field: impl AsRef, + value: impl AsRef, + ) -> Result { + self.start_stmt("get rowid".to_string()); -#[allow(clippy::cognitive_complexity)] -fn open( - context: &Context, - sql: &Sql, - dbfile: impl AsRef, - readonly: bool, -) -> crate::error::Result<()> { - if sql.is_open() { - error!( - context, - "Cannot open, database \"{:?}\" already opened.", - dbfile.as_ref(), + let query = format!( + "SELECT id FROM {} WHERE {}=? ORDER BY id DESC", + table.as_ref(), + field.as_ref(), ); - return Err(Error::SqlAlreadyOpen.into()); + + let res = { + let mut conn = self.get_conn().await?; + get_rowid(context, &mut conn, table, field, value)? + }; + + Ok(res) } - let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX; - if readonly { - open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY); - } else { - open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE); - open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE); - } - let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref()) - .with_flags(open_flags) - .with_init(|c| c.execute_batch("PRAGMA secure_delete=on;")); - let pool = r2d2::Pool::builder() - .min_idle(Some(2)) - .max_size(10) - .connection_timeout(std::time::Duration::new(60, 0)) - .build(mgr) - .map_err(Error::ConnectionPool)?; + pub async fn get_rowid2( + &self, + context: &Context, + table: impl AsRef, + field: impl AsRef, + value: i64, + field2: impl AsRef, + value2: i32, + ) -> Result { + self.start_stmt("get rowid2".to_string()); - { - *sql.pool.write().unwrap() = Some(pool); - } + let res = { + let mut conn = self.get_conn().await?; + get_rowid2(context, &mut conn, table, field, value, field2, value2)? + }; - if !readonly { - let mut exists_before_update = false; - let mut dbversion_before_update = 0; - /* Init tables to dbversion=0 */ - if !sql.table_exists("config") { - info!( - context, - "First time init: creating tables in {:?}.", - dbfile.as_ref(), - ); - sql.execute( - "CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);", - NO_PARAMS, - )?; - sql.execute("CREATE INDEX config_index1 ON config (keyname);", NO_PARAMS)?; - sql.execute( - "CREATE TABLE contacts (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - name TEXT DEFAULT '', \ - addr TEXT DEFAULT '' COLLATE NOCASE, \ - origin INTEGER DEFAULT 0, \ - blocked INTEGER DEFAULT 0, \ - last_seen INTEGER DEFAULT 0, \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute( - "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);", - params![], - )?; - sql.execute( - "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);", - params![], - )?; - sql.execute( - "INSERT INTO contacts (id,name,origin) VALUES \ - (1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \ - (4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \ - (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);", - params![], - )?; - sql.execute( - "CREATE TABLE chats (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - type INTEGER DEFAULT 0, \ - name TEXT DEFAULT '', \ - draft_timestamp INTEGER DEFAULT 0, \ - draft_txt TEXT DEFAULT '', \ - blocked INTEGER DEFAULT 0, \ - grpid TEXT DEFAULT '', \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", params![])?; - sql.execute( - "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);", - params![], - )?; - sql.execute( - "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);", - params![], - )?; - sql.execute( - "INSERT INTO chats (id,type,name) VALUES \ - (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \ - (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \ - (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');", - params![], - )?; - sql.execute( - "CREATE TABLE msgs (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - rfc724_mid TEXT DEFAULT '', \ - server_folder TEXT DEFAULT '', \ - server_uid INTEGER DEFAULT 0, \ - chat_id INTEGER DEFAULT 0, \ - from_id INTEGER DEFAULT 0, \ - to_id INTEGER DEFAULT 0, \ - timestamp INTEGER DEFAULT 0, \ - type INTEGER DEFAULT 0, \ - state INTEGER DEFAULT 0, \ - msgrmsg INTEGER DEFAULT 1, \ - bytes INTEGER DEFAULT 0, \ - txt TEXT DEFAULT '', \ - txt_raw TEXT DEFAULT '', \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", params![])?; - sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", params![])?; - sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", params![])?; - sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", params![])?; - sql.execute( - "INSERT INTO msgs (id,msgrmsg,txt) VALUES \ - (1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \ - (4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \ - (8,0,'rsvd'), (9,0,'daymarker');", - params![], - )?; - sql.execute( - "CREATE TABLE jobs (\ - id INTEGER PRIMARY KEY AUTOINCREMENT, \ - added_timestamp INTEGER, \ - desired_timestamp INTEGER DEFAULT 0, \ - action INTEGER, \ - foreign_id INTEGER, \ - param TEXT DEFAULT '');", - params![], - )?; - sql.execute( - "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);", - params![], - )?; - if !sql.table_exists("config") - || !sql.table_exists("contacts") - || !sql.table_exists("chats") - || !sql.table_exists("chats_contacts") - || !sql.table_exists("msgs") - || !sql.table_exists("jobs") - { - error!( - context, - "Cannot create tables in new database \"{:?}\".", - dbfile.as_ref(), - ); - // cannot create the tables - maybe we cannot write? - return Err(Error::SqlFailedToOpen.into()); - } else { - sql.set_raw_config_int(context, "dbversion", 0)?; - } - } else { - exists_before_update = true; - dbversion_before_update = sql - .get_raw_config_int(context, "dbversion") - .unwrap_or_default(); - } - - // (1) update low-level database structure. - // this should be done before updates that use high-level objects that - // rely themselves on the low-level structure. - // -------------------------------------------------------------------- - - let mut dbversion = dbversion_before_update; - let mut recalc_fingerprints = false; - let mut update_icons = false; - - if dbversion < 1 { - info!(context, "[migration] v1"); - sql.execute( - "CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');", - params![], - )?; - sql.execute( - "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);", - params![], - )?; - dbversion = 1; - sql.set_raw_config_int(context, "dbversion", 1)?; - } - if dbversion < 2 { - info!(context, "[migration] v2"); - sql.execute( - "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';", - params![], - )?; - dbversion = 2; - sql.set_raw_config_int(context, "dbversion", 2)?; - } - if dbversion < 7 { - info!(context, "[migration] v7"); - sql.execute( - "CREATE TABLE keypairs (\ - id INTEGER PRIMARY KEY, \ - addr TEXT DEFAULT '' COLLATE NOCASE, \ - is_default INTEGER DEFAULT 0, \ - private_key, \ - public_key, \ - created INTEGER DEFAULT 0);", - params![], - )?; - dbversion = 7; - sql.set_raw_config_int(context, "dbversion", 7)?; - } - if dbversion < 10 { - info!(context, "[migration] v10"); - sql.execute( - "CREATE TABLE acpeerstates (\ - id INTEGER PRIMARY KEY, \ - addr TEXT DEFAULT '' COLLATE NOCASE, \ - last_seen INTEGER DEFAULT 0, \ - last_seen_autocrypt INTEGER DEFAULT 0, \ - public_key, \ - prefer_encrypted INTEGER DEFAULT 0);", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);", - params![], - )?; - dbversion = 10; - sql.set_raw_config_int(context, "dbversion", 10)?; - } - if dbversion < 12 { - info!(context, "[migration] v12"); - sql.execute( - "CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);", - params![], - )?; - sql.execute( - "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);", - params![], - )?; - dbversion = 12; - sql.set_raw_config_int(context, "dbversion", 12)?; - } - if dbversion < 17 { - info!(context, "[migration] v17"); - sql.execute( - "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("CREATE INDEX chats_index2 ON chats (archived);", params![])?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![])?; - dbversion = 17; - sql.set_raw_config_int(context, "dbversion", 17)?; - } - if dbversion < 18 { - info!(context, "[migration] v18"); - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![])?; - dbversion = 18; - sql.set_raw_config_int(context, "dbversion", 18)?; - } - if dbversion < 27 { - info!(context, "[migration] v27"); - // chat.id=1 and chat.id=2 are the old deaddrops, - // the current ones are defined by chats.blocked=2 - sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![])?; - sql.execute( - "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);", - params![], - )?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 27; - sql.set_raw_config_int(context, "dbversion", 27)?; - } - if dbversion < 34 { - info!(context, "[migration] v34"); - sql.execute( - "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';", - params![], - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);", - params![], - )?; - recalc_fingerprints = true; - dbversion = 34; - sql.set_raw_config_int(context, "dbversion", 34)?; - } - if dbversion < 39 { - info!(context, "[migration] v39"); - sql.execute( - "CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);", - params![] - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN verified_key;", - params![], - )?; - sql.execute( - "ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';", - params![], - )?; - sql.execute( - "CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);", - params![], - )?; - dbversion = 39; - sql.set_raw_config_int(context, "dbversion", 39)?; - } - if dbversion < 40 { - info!(context, "[migration] v40"); - sql.execute( - "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 40; - sql.set_raw_config_int(context, "dbversion", 40)?; - } - if dbversion < 44 { - info!(context, "[migration] v44"); - sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![])?; - dbversion = 44; - sql.set_raw_config_int(context, "dbversion", 44)?; - } - if dbversion < 46 { - info!(context, "[migration] v46"); - sql.execute( - "ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;", - params![], - )?; - sql.execute( - "ALTER TABLE msgs ADD COLUMN mime_references TEXT;", - params![], - )?; - dbversion = 46; - sql.set_raw_config_int(context, "dbversion", 46)?; - } - if dbversion < 47 { - info!(context, "[migration] v47"); - sql.execute( - "ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 47; - sql.set_raw_config_int(context, "dbversion", 47)?; - } - if dbversion < 48 { - info!(context, "[migration] v48"); - // NOTE: move_state is not used anymore - sql.execute( - "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;", - params![], - )?; - - dbversion = 48; - sql.set_raw_config_int(context, "dbversion", 48)?; - } - if dbversion < 49 { - info!(context, "[migration] v49"); - sql.execute( - "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;", - params![], - )?; - dbversion = 49; - sql.set_raw_config_int(context, "dbversion", 49)?; - } - if dbversion < 50 { - info!(context, "[migration] v50"); - // installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly; - // keep this default and use DC_SHOW_EMAILS_NO - // only for new installations - if exists_before_update { - sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32)?; - } - dbversion = 50; - sql.set_raw_config_int(context, "dbversion", 50)?; - } - if dbversion < 53 { - info!(context, "[migration] v53"); - // the messages containing _only_ locations - // are also added to the database as _hidden_. - sql.execute( - "CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);", - params![] - )?; - sql.execute( - "CREATE INDEX locations_index1 ON locations (from_id);", - params![], - )?; - sql.execute( - "CREATE INDEX locations_index2 ON locations (timestamp);", - params![], - )?; - sql.execute( - "ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;", - params![], - )?; - sql.execute( - "CREATE INDEX chats_index3 ON chats (locations_send_until);", - params![], - )?; - dbversion = 53; - sql.set_raw_config_int(context, "dbversion", 53)?; - } - if dbversion < 54 { - info!(context, "[migration] v54"); - sql.execute( - "ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;", - params![], - )?; - sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![])?; - dbversion = 54; - sql.set_raw_config_int(context, "dbversion", 54)?; - } - if dbversion < 55 { - info!(context, "[migration] v55"); - sql.execute( - "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;", - params![], - )?; - sql.set_raw_config_int(context, "dbversion", 55)?; - } - if dbversion < 59 { - info!(context, "[migration] v59"); - // records in the devmsglabels are kept when the message is deleted. - // so, msg_id may or may not exist. - sql.execute( - "CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);", - NO_PARAMS, - )?; - sql.execute( - "CREATE INDEX devmsglabels_index1 ON devmsglabels (label);", - NO_PARAMS, - )?; - if exists_before_update && sql.get_raw_config_int(context, "bcc_self").is_none() { - sql.set_raw_config_int(context, "bcc_self", 1)?; - } - sql.set_raw_config_int(context, "dbversion", 59)?; - } - if dbversion < 60 { - info!(context, "[migration] v60"); - sql.execute( - "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;", - NO_PARAMS, - )?; - sql.set_raw_config_int(context, "dbversion", 60)?; - } - if dbversion < 61 { - info!(context, "[migration] v61"); - sql.execute( - "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;", - NO_PARAMS, - )?; - update_icons = true; - sql.set_raw_config_int(context, "dbversion", 61)?; - } - if dbversion < 62 { - info!(context, "[migration] v62"); - sql.execute( - "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", - NO_PARAMS, - )?; - sql.set_raw_config_int(context, "dbversion", 62)?; - } - if dbversion < 63 { - info!(context, "[migration] v63"); - sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS)?; - sql.set_raw_config_int(context, "dbversion", 63)?; - } - - // (2) updates that require high-level objects - // (the structure is complete now and all objects are usable) - // -------------------------------------------------------------------- - - if recalc_fingerprints { - info!(context, "[migration] recalc fingerprints"); - sql.query_map( - "SELECT addr FROM acpeerstates;", - params![], - |row| row.get::<_, String>(0), - |addrs| { - for addr in addrs { - if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?) - { - peerstate.recalc_fingerprint(); - peerstate.save_to_db(sql, false)?; - } - } - Ok(()) - }, - )?; - } - if update_icons { - update_saved_messages_icon(context)?; - update_device_icon(context)?; - } - } - - info!(context, "Opened {:?}.", dbfile.as_ref(),); - - Ok(()) -} - -pub fn execute

(context: &Context, sql: &Sql, querystr: impl AsRef, params: P) -> Result<()> -where - P: IntoIterator, - P::Item: rusqlite::ToSql, -{ - match sql.execute(querystr.as_ref(), params) { - Ok(_) => Ok(()), - Err(err) => { - error!( - context, - "execute failed: {:?} for {}", - &err, - querystr.as_ref() - ); - Err(err) - } - } -} - -pub fn try_execute(context: &Context, sql: &Sql, querystr: impl AsRef) -> Result<()> { - // same as execute() but does not pass error to ui - match sql.execute(querystr.as_ref(), params![]) { - Ok(_) => Ok(()), - Err(err) => { - warn!( - context, - "Try-execute for \"{}\" failed: {}", - querystr.as_ref(), - &err, - ); - Err(err) - } + Ok(res) } } pub fn get_rowid( context: &Context, - sql: &Sql, + conn: &mut Connection, table: impl AsRef, field: impl AsRef, value: impl AsRef, -) -> u32 { - sql.start_stmt("get rowid".to_string()); - sql.with_conn(|conn| Ok(get_rowid_with_conn(context, conn, table, field, value))) - .unwrap_or_else(|_| 0) -} - -pub fn get_rowid_with_conn( - context: &Context, - conn: &Connection, - table: impl AsRef, - field: impl AsRef, - value: impl AsRef, -) -> u32 { +) -> std::result::Result { // alternative to sqlite3_last_insert_rowid() which MUST NOT be used due to race conditions, see comment above. // the ORDER BY ensures, this function always returns the most recent id, // eg. if a Message-ID is split into different messages. @@ -995,45 +505,19 @@ pub fn get_rowid_with_conn( field.as_ref(), ); - match conn.query_row(&query, params![value.as_ref()], |row| row.get::<_, u32>(0)) { - Ok(id) => id, - Err(err) => { - error!( - context, - "sql: Failed to retrieve rowid: {} in {}", err, query - ); - 0 - } - } -} -pub fn get_rowid2( - context: &Context, - sql: &Sql, - table: impl AsRef, - field: impl AsRef, - value: i64, - field2: impl AsRef, - value2: i32, -) -> u32 { - sql.start_stmt("get rowid2".to_string()); - sql.with_conn(|conn| { - Ok(get_rowid2_with_conn( - context, conn, table, field, value, field2, value2, - )) - }) - .unwrap_or_else(|_| 0) + conn.query_row(&query, params![value.as_ref()], |row| row.get::<_, u32>(0)) } -pub fn get_rowid2_with_conn( +pub fn get_rowid2( context: &Context, - conn: &Connection, + conn: &mut Connection, table: impl AsRef, field: impl AsRef, value: i64, field2: impl AsRef, value2: i32, -) -> u32 { - match conn.query_row( +) -> std::result::Result { + conn.query_row( &format!( "SELECT id FROM {} WHERE {}={} AND {}={} ORDER BY id DESC", table.as_ref(), @@ -1044,16 +528,10 @@ pub fn get_rowid2_with_conn( ), NO_PARAMS, |row| row.get::<_, u32>(0), - ) { - Ok(id) => id, - Err(err) => { - error!(context, "sql: Failed to retrieve rowid2: {}", err); - 0 - } - } + ) } -pub fn housekeeping(context: &Context) { +pub async fn housekeeping(context: &Context) { let mut files_in_use = HashSet::new(); let mut unreferenced_count = 0; @@ -1063,25 +541,29 @@ pub fn housekeeping(context: &Context) { &mut files_in_use, "SELECT param FROM msgs WHERE chat_id!=3 AND type!=10;", Param::File, - ); + ) + .await; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM jobs;", Param::File, - ); + ) + .await; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM chats;", Param::ProfileImage, - ); + ) + .await; maybe_add_from_param( context, &mut files_in_use, "SELECT param FROM contacts;", Param::ProfileImage, - ); + ) + .await; context .sql @@ -1096,6 +578,7 @@ pub fn housekeeping(context: &Context) { Ok(()) }, ) + .await .unwrap_or_else(|err| { warn!(context, "sql: failed query: {}", err); }); @@ -1103,13 +586,13 @@ pub fn housekeeping(context: &Context) { info!(context, "{} files in use.", files_in_use.len(),); /* go through directory and delete unused files */ let p = context.get_blobdir(); - match std::fs::read_dir(p) { - Ok(dir_handle) => { + match async_std::fs::read_dir(p).await { + Ok(mut dir_handle) => { /* avoid deletion of files that are just created to build a message object */ let diff = std::time::Duration::from_secs(60 * 60); let keep_files_newer_than = std::time::SystemTime::now().checked_sub(diff).unwrap(); - for entry in dir_handle { + while let Some(entry) = dir_handle.next().await { if entry.is_err() { break; } @@ -1127,7 +610,7 @@ pub fn housekeeping(context: &Context) { unreferenced_count += 1; - if let Ok(stats) = std::fs::metadata(entry.path()) { + if let Ok(stats) = async_std::fs::metadata(entry.path()).await { let recently_created = stats.created().is_ok() && stats.created().unwrap() > keep_files_newer_than; let recently_modified = stats.modified().is_ok() @@ -1190,7 +673,7 @@ fn maybe_add_file(files_in_use: &mut HashSet, file: impl AsRef) { files_in_use.insert(file.as_ref()[9..].into()); } -fn maybe_add_from_param( +async fn maybe_add_from_param( context: &Context, files_in_use: &mut HashSet, query: &str, @@ -1212,11 +695,644 @@ fn maybe_add_from_param( Ok(()) }, ) + .await .unwrap_or_else(|err| { warn!(context, "sql: failed to add_from_param: {}", err); }); } +#[allow(clippy::cognitive_complexity)] +async fn open( + context: &Context, + sql: &Sql, + dbfile: impl AsRef, + readonly: bool, +) -> crate::error::Result<()> { + if sql.is_open().await { + error!( + context, + "Cannot open, database \"{:?}\" already opened.", + dbfile.as_ref(), + ); + return Err(Error::SqlAlreadyOpen.into()); + } + + let mut open_flags = OpenFlags::SQLITE_OPEN_NO_MUTEX; + if readonly { + open_flags.insert(OpenFlags::SQLITE_OPEN_READ_ONLY); + } else { + open_flags.insert(OpenFlags::SQLITE_OPEN_READ_WRITE); + open_flags.insert(OpenFlags::SQLITE_OPEN_CREATE); + } + let mgr = r2d2_sqlite::SqliteConnectionManager::file(dbfile.as_ref()) + .with_flags(open_flags) + .with_init(|c| { + // Only one process can make changes to the database at one time. + // busy_timeout defines, that if a second process wants write access, + // this second process will wait some milliseconds + // and try over until it gets write access or the given timeout is elapsed. + // If the second process does not get write access within the given timeout, + // sqlite3_step() will return the error SQLITE_BUSY. + // (without a busy_timeout, sqlite3_step() would return SQLITE_BUSY _at once_) + c.busy_timeout(Duration::from_secs(10))?; + + c.execute_batch("PRAGMA secure_delete=on;")?; + Ok(()) + }); + let pool = r2d2::Pool::builder() + .min_idle(Some(2)) + .max_size(10) + .connection_timeout(std::time::Duration::new(60, 0)) + .build(mgr) + .map_err(Error::ConnectionPool)?; + + { + *sql.pool.write().await = Some(pool); + } + + if !readonly { + let mut exists_before_update = false; + let mut dbversion_before_update: i32 = 0; + /* Init tables to dbversion=0 */ + if !sql.table_exists("config").await? { + info!( + context, + "First time init: creating tables in {:?}.", + dbfile.as_ref(), + ); + sql.execute( + "CREATE TABLE config (id INTEGER PRIMARY KEY, keyname TEXT, value TEXT);", + NO_PARAMS, + ) + .await?; + sql.execute("CREATE INDEX config_index1 ON config (keyname);", NO_PARAMS) + .await?; + sql.execute( + "CREATE TABLE contacts (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + name TEXT DEFAULT '', \ + addr TEXT DEFAULT '' COLLATE NOCASE, \ + origin INTEGER DEFAULT 0, \ + blocked INTEGER DEFAULT 0, \ + last_seen INTEGER DEFAULT 0, \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);", + params![], + ) + .await?; + sql.execute( + "INSERT INTO contacts (id,name,origin) VALUES \ + (1,'self',262144), (2,'info',262144), (3,'rsvd',262144), \ + (4,'rsvd',262144), (5,'device',262144), (6,'rsvd',262144), \ + (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);", + params![], + ) + .await?; + sql.execute( + "CREATE TABLE chats (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + type INTEGER DEFAULT 0, \ + name TEXT DEFAULT '', \ + draft_timestamp INTEGER DEFAULT 0, \ + draft_txt TEXT DEFAULT '', \ + blocked INTEGER DEFAULT 0, \ + grpid TEXT DEFAULT '', \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute("CREATE INDEX chats_index1 ON chats (grpid);", params![]) + .await?; + sql.execute( + "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);", + params![], + ) + .await?; + sql.execute( + "INSERT INTO chats (id,type,name) VALUES \ + (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), \ + (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), \ + (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');", + params![], + ) + .await?; + sql.execute( + "CREATE TABLE msgs (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + rfc724_mid TEXT DEFAULT '', \ + server_folder TEXT DEFAULT '', \ + server_uid INTEGER DEFAULT 0, \ + chat_id INTEGER DEFAULT 0, \ + from_id INTEGER DEFAULT 0, \ + to_id INTEGER DEFAULT 0, \ + timestamp INTEGER DEFAULT 0, \ + type INTEGER DEFAULT 0, \ + state INTEGER DEFAULT 0, \ + msgrmsg INTEGER DEFAULT 1, \ + bytes INTEGER DEFAULT 0, \ + txt TEXT DEFAULT '', \ + txt_raw TEXT DEFAULT '', \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute("CREATE INDEX msgs_index1 ON msgs (rfc724_mid);", params![]) + .await?; + sql.execute("CREATE INDEX msgs_index2 ON msgs (chat_id);", params![]) + .await?; + sql.execute("CREATE INDEX msgs_index3 ON msgs (timestamp);", params![]) + .await?; + sql.execute("CREATE INDEX msgs_index4 ON msgs (state);", params![]) + .await?; + sql.execute( + "INSERT INTO msgs (id,msgrmsg,txt) VALUES \ + (1,0,'marker1'), (2,0,'rsvd'), (3,0,'rsvd'), \ + (4,0,'rsvd'), (5,0,'rsvd'), (6,0,'rsvd'), (7,0,'rsvd'), \ + (8,0,'rsvd'), (9,0,'daymarker');", + params![], + ) + .await?; + sql.execute( + "CREATE TABLE jobs (\ + id INTEGER PRIMARY KEY AUTOINCREMENT, \ + added_timestamp INTEGER, \ + desired_timestamp INTEGER DEFAULT 0, \ + action INTEGER, \ + foreign_id INTEGER, \ + param TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX jobs_index1 ON jobs (desired_timestamp);", + params![], + ) + .await?; + if !sql.table_exists("config").await? + || !sql.table_exists("contacts").await? + || !sql.table_exists("chats").await? + || !sql.table_exists("chats_contacts").await? + || !sql.table_exists("msgs").await? + || !sql.table_exists("jobs").await? + { + error!( + context, + "Cannot create tables in new database \"{:?}\".", + dbfile.as_ref(), + ); + // cannot create the tables - maybe we cannot write? + return Err(Error::SqlFailedToOpen.into()); + } else { + sql.set_raw_config_int(context, "dbversion", 0).await?; + } + } else { + exists_before_update = true; + dbversion_before_update = sql + .get_raw_config_int(context, "dbversion") + .await + .unwrap_or_default(); + } + + // (1) update low-level database structure. + // this should be done before updates that use high-level objects that + // rely themselves on the low-level structure. + // -------------------------------------------------------------------- + + let mut dbversion = dbversion_before_update; + let mut recalc_fingerprints = false; + let mut update_icons = false; + + if dbversion < 1 { + info!(context, "[migration] v1"); + sql.execute( + "CREATE TABLE leftgrps ( id INTEGER PRIMARY KEY, grpid TEXT DEFAULT '');", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX leftgrps_index1 ON leftgrps (grpid);", + params![], + ) + .await?; + dbversion = 1; + sql.set_raw_config_int(context, "dbversion", 1).await?; + } + if dbversion < 2 { + info!(context, "[migration] v2"); + sql.execute( + "ALTER TABLE contacts ADD COLUMN authname TEXT DEFAULT '';", + params![], + ) + .await?; + dbversion = 2; + sql.set_raw_config_int(context, "dbversion", 2).await?; + } + if dbversion < 7 { + info!(context, "[migration] v7"); + sql.execute( + "CREATE TABLE keypairs (\ + id INTEGER PRIMARY KEY, \ + addr TEXT DEFAULT '' COLLATE NOCASE, \ + is_default INTEGER DEFAULT 0, \ + private_key, \ + public_key, \ + created INTEGER DEFAULT 0);", + params![], + ) + .await?; + dbversion = 7; + sql.set_raw_config_int(context, "dbversion", 7).await?; + } + if dbversion < 10 { + info!(context, "[migration] v10"); + sql.execute( + "CREATE TABLE acpeerstates (\ + id INTEGER PRIMARY KEY, \ + addr TEXT DEFAULT '' COLLATE NOCASE, \ + last_seen INTEGER DEFAULT 0, \ + last_seen_autocrypt INTEGER DEFAULT 0, \ + public_key, \ + prefer_encrypted INTEGER DEFAULT 0);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index1 ON acpeerstates (addr);", + params![], + ) + .await?; + dbversion = 10; + sql.set_raw_config_int(context, "dbversion", 10).await?; + } + if dbversion < 12 { + info!(context, "[migration] v12"); + sql.execute( + "CREATE TABLE msgs_mdns ( msg_id INTEGER, contact_id INTEGER);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX msgs_mdns_index1 ON msgs_mdns (msg_id);", + params![], + ) + .await?; + dbversion = 12; + sql.set_raw_config_int(context, "dbversion", 12).await?; + } + if dbversion < 17 { + info!(context, "[migration] v17"); + sql.execute( + "ALTER TABLE chats ADD COLUMN archived INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("CREATE INDEX chats_index2 ON chats (archived);", params![]) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN starred INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("CREATE INDEX msgs_index5 ON msgs (starred);", params![]) + .await?; + dbversion = 17; + sql.set_raw_config_int(context, "dbversion", 17).await?; + } + if dbversion < 18 { + info!(context, "[migration] v18"); + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN gossip_timestamp INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("ALTER TABLE acpeerstates ADD COLUMN gossip_key;", params![]) + .await?; + dbversion = 18; + sql.set_raw_config_int(context, "dbversion", 18).await?; + } + if dbversion < 27 { + info!(context, "[migration] v27"); + // chat.id=1 and chat.id=2 are the old deaddrops, + // the current ones are defined by chats.blocked=2 + sql.execute("DELETE FROM msgs WHERE chat_id=1 OR chat_id=2;", params![]) + .await?; + sql.execute( + "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 27; + sql.set_raw_config_int(context, "dbversion", 27).await?; + } + if dbversion < 34 { + info!(context, "[migration] v34"); + sql.execute( + "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);", + params![], + ) + .await?; + recalc_fingerprints = true; + dbversion = 34; + sql.set_raw_config_int(context, "dbversion", 34).await?; + } + if dbversion < 39 { + info!(context, "[migration] v39"); + sql.execute( + "CREATE TABLE tokens ( id INTEGER PRIMARY KEY, namespc INTEGER DEFAULT 0, foreign_id INTEGER DEFAULT 0, token TEXT DEFAULT '', timestamp INTEGER DEFAULT 0);", + params![] + ).await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN verified_key;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE acpeerstates ADD COLUMN verified_key_fingerprint TEXT DEFAULT '';", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX acpeerstates_index5 ON acpeerstates (verified_key_fingerprint);", + params![], + ) + .await?; + dbversion = 39; + sql.set_raw_config_int(context, "dbversion", 39).await?; + } + if dbversion < 40 { + info!(context, "[migration] v40"); + sql.execute( + "ALTER TABLE jobs ADD COLUMN thread INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 40; + sql.set_raw_config_int(context, "dbversion", 40).await?; + } + if dbversion < 44 { + info!(context, "[migration] v44"); + sql.execute("ALTER TABLE msgs ADD COLUMN mime_headers TEXT;", params![]) + .await?; + dbversion = 44; + sql.set_raw_config_int(context, "dbversion", 44).await?; + } + if dbversion < 46 { + info!(context, "[migration] v46"); + sql.execute( + "ALTER TABLE msgs ADD COLUMN mime_in_reply_to TEXT;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE msgs ADD COLUMN mime_references TEXT;", + params![], + ) + .await?; + dbversion = 46; + sql.set_raw_config_int(context, "dbversion", 46).await?; + } + if dbversion < 47 { + info!(context, "[migration] v47"); + sql.execute( + "ALTER TABLE jobs ADD COLUMN tries INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 47; + sql.set_raw_config_int(context, "dbversion", 47).await?; + } + if dbversion < 48 { + info!(context, "[migration] v48"); + // NOTE: move_state is not used anymore + sql.execute( + "ALTER TABLE msgs ADD COLUMN move_state INTEGER DEFAULT 1;", + params![], + ) + .await?; + + dbversion = 48; + sql.set_raw_config_int(context, "dbversion", 48).await?; + } + if dbversion < 49 { + info!(context, "[migration] v49"); + sql.execute( + "ALTER TABLE chats ADD COLUMN gossiped_timestamp INTEGER DEFAULT 0;", + params![], + ) + .await?; + dbversion = 49; + sql.set_raw_config_int(context, "dbversion", 49).await?; + } + if dbversion < 50 { + info!(context, "[migration] v50"); + // installations <= 0.100.1 used DC_SHOW_EMAILS_ALL implicitly; + // keep this default and use DC_SHOW_EMAILS_NO + // only for new installations + if exists_before_update { + sql.set_raw_config_int(context, "show_emails", ShowEmails::All as i32) + .await?; + } + dbversion = 50; + sql.set_raw_config_int(context, "dbversion", 50).await?; + } + if dbversion < 53 { + info!(context, "[migration] v53"); + // the messages containing _only_ locations + // are also added to the database as _hidden_. + sql.execute( + "CREATE TABLE locations ( id INTEGER PRIMARY KEY AUTOINCREMENT, latitude REAL DEFAULT 0.0, longitude REAL DEFAULT 0.0, accuracy REAL DEFAULT 0.0, timestamp INTEGER DEFAULT 0, chat_id INTEGER DEFAULT 0, from_id INTEGER DEFAULT 0);", + params![] + ).await?; + sql.execute( + "CREATE INDEX locations_index1 ON locations (from_id);", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX locations_index2 ON locations (timestamp);", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE chats ADD COLUMN locations_send_begin INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE chats ADD COLUMN locations_send_until INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "ALTER TABLE chats ADD COLUMN locations_last_sent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute( + "CREATE INDEX chats_index3 ON chats (locations_send_until);", + params![], + ) + .await?; + dbversion = 53; + sql.set_raw_config_int(context, "dbversion", 53).await?; + } + if dbversion < 54 { + info!(context, "[migration] v54"); + sql.execute( + "ALTER TABLE msgs ADD COLUMN location_id INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.execute("CREATE INDEX msgs_index6 ON msgs (location_id);", params![]) + .await?; + dbversion = 54; + sql.set_raw_config_int(context, "dbversion", 54).await?; + } + if dbversion < 55 { + info!(context, "[migration] v55"); + sql.execute( + "ALTER TABLE locations ADD COLUMN independent INTEGER DEFAULT 0;", + params![], + ) + .await?; + sql.set_raw_config_int(context, "dbversion", 55).await?; + } + if dbversion < 59 { + info!(context, "[migration] v59"); + // records in the devmsglabels are kept when the message is deleted. + // so, msg_id may or may not exist. + sql.execute( + "CREATE TABLE devmsglabels (id INTEGER PRIMARY KEY AUTOINCREMENT, label TEXT, msg_id INTEGER DEFAULT 0);", + NO_PARAMS, + ).await?; + sql.execute( + "CREATE INDEX devmsglabels_index1 ON devmsglabels (label);", + NO_PARAMS, + ) + .await?; + if exists_before_update && sql.get_raw_config_int(context, "bcc_self").await.is_none() { + sql.set_raw_config_int(context, "bcc_self", 1).await?; + } + sql.set_raw_config_int(context, "dbversion", 59).await?; + } + if dbversion < 60 { + info!(context, "[migration] v60"); + sql.execute( + "ALTER TABLE chats ADD COLUMN created_timestamp INTEGER DEFAULT 0;", + NO_PARAMS, + ) + .await?; + sql.set_raw_config_int(context, "dbversion", 60).await?; + } + if dbversion < 61 { + info!(context, "[migration] v61"); + sql.execute( + "ALTER TABLE contacts ADD COLUMN selfavatar_sent INTEGER DEFAULT 0;", + NO_PARAMS, + ) + .await?; + update_icons = true; + sql.set_raw_config_int(context, "dbversion", 61).await?; + } + if dbversion < 62 { + info!(context, "[migration] v62"); + sql.execute( + "ALTER TABLE chats ADD COLUMN muted_until INTEGER DEFAULT 0;", + NO_PARAMS, + ) + .await?; + sql.set_raw_config_int(context, "dbversion", 62).await?; + } + if dbversion < 63 { + info!(context, "[migration] v63"); + sql.execute("UPDATE chats SET grpid='' WHERE type=100", NO_PARAMS) + .await?; + sql.set_raw_config_int(context, "dbversion", 63).await?; + } + + // (2) updates that require high-level objects + // (the structure is complete now and all objects are usable) + // -------------------------------------------------------------------- + + if recalc_fingerprints { + info!(context, "[migration] recalc fingerprints"); + sql.query_map_async( + "SELECT addr FROM acpeerstates;", + params![], + |row| row.get::<_, String>(0), + |addrs| async move { + for addr in addrs { + if let Some(ref mut peerstate) = Peerstate::from_addr(context, sql, &addr?) + { + peerstate.recalc_fingerprint(); + peerstate.save_to_db(sql, false).await?; + } + } + Ok(()) + }, + ) + .await?; + } + if update_icons { + update_saved_messages_icon(context).await?; + update_device_icon(context).await?; + } + } + + info!(context, "Opened {:?}.", dbfile.as_ref(),); + + Ok(()) +} + #[cfg(test)] mod test { use super::*; diff --git a/src/stock.rs b/src/stock.rs index b67e8b220..8ce11cc33 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -544,17 +544,17 @@ mod tests { async fn test_update_device_chats() { let t = dummy_context(); t.ctx.update_device_chats().ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 2); chats.get_chat_id(0).delete(&t.ctx).await.ok(); chats.get_chat_id(1).delete(&t.ctx).await.ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); // a subsequent call to update_device_chats() must not re-add manally deleted messages or chats t.ctx.update_device_chats().ok(); - let chats = Chatlist::try_load(&t.ctx, 0, None, None).unwrap(); + let chats = Chatlist::try_load(&t.ctx, 0, None, None).await.unwrap(); assert_eq!(chats.len(), 0); } } diff --git a/src/test_utils.rs b/src/test_utils.rs index 2c134cc28..681f57fa1 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -83,6 +83,7 @@ pub(crate) async fn configure_alice_keypair(ctx: &Context) -> String { .await .unwrap(); key::store_self_keypair(&ctx, &keypair, key::KeyPairUse::Default) + .await .expect("Failed to save Alice's key"); keypair.addr.to_string() } diff --git a/src/token.rs b/src/token.rs index 11ae53f0c..ac2b4af02 100644 --- a/src/token.rs +++ b/src/token.rs @@ -9,7 +9,6 @@ use deltachat_derive::*; use crate::chat::ChatId; use crate::context::Context; use crate::dc_tools::*; -use crate::sql; /// Token namespace #[derive(Debug, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql)] @@ -28,37 +27,46 @@ impl Default for Namespace { /// Creates a new token and saves it into the database. /// Returns created token. -pub fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { +pub async fn save(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { // foreign_id may be 0 let token = dc_create_id(); - sql::execute( - context, - &context.sql, - "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", - params![namespace, foreign_id, &token, time()], - ) - .ok(); + context + .sql + .execute( + "INSERT INTO tokens (namespc, foreign_id, token, timestamp) VALUES (?, ?, ?, ?);", + params![namespace, foreign_id, &token, time()], + ) + .await + .ok(); token } -pub fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option { - context.sql.query_get_value::<_, String>( - context, - "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", - params![namespace, foreign_id], - ) +pub async fn lookup(context: &Context, namespace: Namespace, foreign_id: ChatId) -> Option { + context + .sql + .query_get_value::<_, String>( + context, + "SELECT token FROM tokens WHERE namespc=? AND foreign_id=?;", + params![namespace, foreign_id], + ) + .await } -pub fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { - lookup(context, namespace, foreign_id).unwrap_or_else(|| save(context, namespace, foreign_id)) +pub async fn lookup_or_new(context: &Context, namespace: Namespace, foreign_id: ChatId) -> String { + if let Some(token) = lookup(context, namespace, foreign_id).await { + return token; + } + + save(context, namespace, foreign_id).await } -pub fn exists(context: &Context, namespace: Namespace, token: &str) -> bool { +pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> bool { context .sql .exists( "SELECT id FROM tokens WHERE namespc=? AND token=?;", params![namespace, token], ) + .await .unwrap_or_default() }